Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local hookInterval = 100
- local deadline = math.huge
- local hitDeadline = false
- local function checkDeadline()
- if computer.realTime() > deadline then
- debug.sethook(coroutine.running(), checkDeadline, "", 1)
- if not hitDeadline then
- deadline = deadline + 0.5
- end
- hitDeadline = true
- error("too long without yielding", 0)
- end
- end
- -------------------------------------------------------------------------------
- local function checkArg(n, have, ...)
- have = type(have)
- local function check(want, ...)
- if not want then
- return false
- else
- return have == want or check(...)
- end
- end
- if not check(...) then
- local msg = string.format("bad argument #%d (%s expected, got %s)",
- n, table.concat({...}, " or "), have)
- error(msg, 3)
- end
- end
- -------------------------------------------------------------------------------
- local function smatch(f, s, ...)
- if #s > system.maxPatternInputLength() then
- return nil, "input too long, see maxPatternInputLength in the config"
- end
- return f(s, ...)
- end
- -- Replacing methods in actual string table to also take care of OO
- -- calls (i.e. ("asd"):match(...)).
- local string_find, string_gmatch, string_gsub, string_match =
- string.find, string.gmatch, string.gsub, string.match
- local function installPatternSandbox()
- string.find = function(...)
- return smatch(string_find, ...)
- end
- string.gmatch = function(...)
- return smatch(string_gmatch, ...)
- end
- string.gsub = function(...)
- return smatch(string_gsub, ...)
- end
- string.match = function(...)
- return smatch(string_match, ...)
- end
- end
- installPatternSandbox()
- -------------------------------------------------------------------------------
- local loadHookMetatable
- loadHookMetatable = {
- [persistKey and persistKey() or "LuaJ"] = function()
- return function()
- -- Has to be re-applied after loading saved state because global
- -- metatables (such as the string one) aren't persisted automatically.
- installPatternSandbox()
- return setmetatable({}, loadHookMetatable)
- end
- end
- }
- local loadHook = setmetatable({}, loadHookMetatable)
- -------------------------------------------------------------------------------
- local function spcall(...)
- local result = table.pack(pcall(...))
- if not result[1] then
- error(tostring(result[2]), 0)
- else
- return table.unpack(result, 2, result.n)
- end
- end
- local function sgc(self)
- local oldDeadline, oldHitDeadline = deadline, hitDeadline
- local mt = debug.getmetatable(self)
- mt = rawget(mt, "mt")
- local gc = rawget(mt, "__gc")
- if type(gc) ~= "function" then
- return
- end
- local co = coroutine.create(gc)
- debug.sethook(co, checkDeadline, "", hookInterval)
- deadline, hitDeadline = math.min(oldDeadline, computer.realTime() + 0.5), true
- local result, reason = coroutine.resume(co, self)
- debug.sethook(co)
- deadline, hitDeadline = oldDeadline, oldHitDeadline
- if not result then
- error(reason, 0)
- end
- end
- --[[ This is the global environment we make available to userland programs. ]]
- -- You'll notice that we do a lot of wrapping of native functions and adding
- -- parameter checks in those wrappers. This is to avoid errors from the host
- -- side that would push error objects - which are userdata and cannot be
- -- persisted.
- local sandbox, libprocess
- sandbox = {
- assert = assert,
- dofile = nil, -- in boot/*_base.lua
- error = error,
- _G = nil, -- see below
- getmetatable = function(t)
- if type(t) == "string" then -- don't allow messing with the string mt
- return nil
- end
- local result = getmetatable(t)
- -- check if we have a wrapped __gc using mt
- if type(result) == "table" and rawget(result, "__gc") == sgc then
- result = rawget(result, "mt")
- end
- return result
- end,
- ipairs = ipairs,
- load = function(ld, source, mode, env)
- if not system.allowBytecode() then
- mode = "t"
- end
- return load(ld, source, mode, env or sandbox)
- end,
- loadfile = nil, -- in boot/*_base.lua
- next = next,
- pairs = pairs,
- pcall = function(...)
- local result = table.pack(pcall(...))
- checkDeadline()
- return table.unpack(result, 1, result.n)
- end,
- print = nil, -- in boot/*_base.lua
- rawequal = rawequal,
- rawget = rawget,
- rawlen = rawlen,
- rawset = rawset,
- select = select,
- setmetatable = function(t, mt)
- if type(mt) ~= "table" then
- return setmetatable(t, mt)
- end
- if type(rawget(mt, "__gc")) == "function" then
- -- For all user __gc functions we enforce a much tighter deadline.
- -- This is because these functions may be called from the main
- -- thread under certain circumstanced (such as when saving the world),
- -- which can lead to noticeable lag if the __gc function behaves badly.
- local sbmt = {} -- sandboxed metatable. only for __gc stuff, so it's
- -- kinda ok to have a shallow copy instead... meh.
- for k, v in pairs(mt) do
- sbmt[k] = v
- end
- sbmt.mt = mt
- sbmt.__gc = sgc
- mt = sbmt
- end
- return setmetatable(t, mt)
- end,
- tonumber = tonumber,
- tostring = tostring,
- type = type,
- _VERSION = "Lua 5.2",
- xpcall = function(f, msgh, ...)
- local handled = false
- local result = table.pack(xpcall(f, function(...)
- if handled then
- return ...
- else
- handled = true
- return msgh(...)
- end
- end, ...))
- checkDeadline()
- return table.unpack(result, 1, result.n)
- end,
- coroutine = {
- create = coroutine.create,
- resume = function(co, ...) -- custom resume part for bubbling sysyields
- checkArg(1, co, "thread")
- local args = table.pack(...)
- while true do -- for consecutive sysyields
- debug.sethook(co, checkDeadline, "", hookInterval)
- local result = table.pack(
- coroutine.resume(co, table.unpack(args, 1, args.n)))
- debug.sethook(co) -- avoid gc issues
- checkDeadline()
- if result[1] then -- success: (true, sysval?, ...?)
- if coroutine.status(co) == "dead" then -- return: (true, ...)
- return true, table.unpack(result, 2, result.n)
- elseif result[2] ~= nil then -- yield: (true, sysval)
- args = table.pack(coroutine.yield(result[2]))
- else -- yield: (true, nil, ...)
- return true, table.unpack(result, 3, result.n)
- end
- else -- error: result = (false, string)
- return false, result[2]
- end
- end
- end,
- running = coroutine.running,
- status = coroutine.status,
- wrap = function(f) -- for bubbling coroutine.resume
- local co = coroutine.create(f)
- return function(...)
- local result = table.pack(sandbox.coroutine.resume(co, ...))
- if result[1] then
- return table.unpack(result, 2, result.n)
- else
- error(result[2], 0)
- end
- end
- end,
- yield = function(...) -- custom yield part for bubbling sysyields
- return coroutine.yield(nil, ...)
- end
- },
- string = {
- byte = string.byte,
- char = string.char,
- dump = string.dump,
- find = string.find,
- format = string.format,
- gmatch = string.gmatch,
- gsub = string.gsub,
- len = string.len,
- lower = string.lower,
- match = string.match,
- rep = string.rep,
- reverse = string.reverse,
- sub = string.sub,
- upper = string.upper
- },
- table = {
- concat = table.concat,
- insert = table.insert,
- pack = table.pack,
- remove = table.remove,
- sort = table.sort,
- unpack = table.unpack
- },
- math = {
- abs = math.abs,
- acos = math.acos,
- asin = math.asin,
- atan = math.atan,
- atan2 = math.atan2,
- ceil = math.ceil,
- cos = math.cos,
- cosh = math.cosh,
- deg = math.deg,
- exp = math.exp,
- floor = math.floor,
- fmod = math.fmod,
- frexp = math.frexp,
- huge = math.huge,
- ldexp = math.ldexp,
- log = math.log,
- max = math.max,
- min = math.min,
- modf = math.modf,
- pi = math.pi,
- pow = math.pow,
- rad = math.rad,
- random = function(...)
- return spcall(math.random, ...)
- end,
- randomseed = function(seed)
- spcall(math.randomseed, seed)
- end,
- sin = math.sin,
- sinh = math.sinh,
- sqrt = math.sqrt,
- tan = math.tan,
- tanh = math.tanh
- },
- bit32 = {
- arshift = bit32.arshift,
- band = bit32.band,
- bnot = bit32.bnot,
- bor = bit32.bor,
- btest = bit32.btest,
- bxor = bit32.bxor,
- extract = bit32.extract,
- replace = bit32.replace,
- lrotate = bit32.lrotate,
- lshift = bit32.lshift,
- rrotate = bit32.rrotate,
- rshift = bit32.rshift
- },
- io = nil, -- in lib/io.lua
- os = {
- clock = os.clock,
- date = function(format, time)
- return spcall(os.date, format, time)
- end,
- difftime = function(t2, t1)
- return t2 - t1
- end,
- execute = nil, -- in boot/*_os.lua
- exit = nil, -- in boot/*_os.lua
- remove = nil, -- in boot/*_os.lua
- rename = nil, -- in boot/*_os.lua
- time = function(table)
- checkArg(1, table, "table", "nil")
- return os.time(table)
- end,
- tmpname = nil, -- in boot/*_os.lua
- },
- debug = {
- traceback = debug.traceback
- },
- checkArg = checkArg
- }
- sandbox._G = sandbox
- -------------------------------------------------------------------------------
- -- Start of non-standard stuff.
- -- JNLua derps when the metatable of userdata is changed, so we have to
- -- wrap and isolate it, to make sure it can't be touched by user code.
- -- These functions provide the logic for wrapping and unwrapping (when
- -- pushed to user code and when pushed back to the host, respectively).
- local wrapUserdata, wrapSingleUserdata, unwrapUserdata, wrappedUserdataMeta
- wrappedUserdataMeta = {
- -- Weak keys, clean up once a proxy is no longer referenced anywhere.
- __mode="k",
- -- We need custom persist logic here to avoid ERIS trying to save the
- -- userdata referenced in this table directly. It will be repopulated
- -- in the load methods of the persisted userdata wrappers (see below).
- [persistKey and persistKey() or "LuaJ"] = function()
- return function()
- -- When using special persistence we have to manually reassign the
- -- metatable of the persisted value.
- return setmetatable({}, wrappedUserdataMeta)
- end
- end
- }
- local wrappedUserdata = setmetatable({}, wrappedUserdataMeta)
- local function processResult(result)
- wrapUserdata(result) -- needed for metamethods.
- if not result[1] then -- error that should be re-thrown.
- error(result[2], 0)
- else -- success or already processed error.
- return table.unpack(result, 2, result.n)
- end
- end
- local function invoke(target, direct, ...)
- local result
- if direct then
- local args = table.pack(...) -- for unwrapping
- unwrapUserdata(args)
- result = table.pack(target.invoke(table.unpack(args, 1, args.n)))
- if result.n == 0 then -- limit for direct calls reached
- result = nil
- end
- -- no need to wrap here, will be wrapped in processResult
- end
- if not result then
- local args = table.pack(...) -- for access in closure
- result = select(1, coroutine.yield(function()
- unwrapUserdata(args)
- local result = table.pack(target.invoke(table.unpack(args, 1, args.n)))
- wrapUserdata(result)
- return result
- end))
- end
- return processResult(result)
- end
- local function udinvoke(f, data, ...)
- local args = table.pack(...)
- unwrapUserdata(args)
- local result = table.pack(f(data, table.unpack(args)))
- return processResult(result)
- end
- -- Metatable for additional functionality on userdata.
- local userdataWrapper = {
- __index = function(self, ...)
- return udinvoke(userdata.apply, wrappedUserdata[self], ...)
- end,
- __newindex = function(self, ...)
- return udinvoke(userdata.unapply, wrappedUserdata[self], ...)
- end,
- __call = function(self, ...)
- return udinvoke(userdata.call, wrappedUserdata[self], ...)
- end,
- __gc = function(self)
- local data = wrappedUserdata[self]
- wrappedUserdata[self] = nil
- userdata.dispose(data)
- end,
- -- This is the persistence protocol for userdata. Userdata is considered
- -- to be 'owned' by Lua, and is saved to an NBT tag. We also get the name
- -- of the actual class when saving, so we can create a new instance via
- -- reflection when loading again (and then immediately wrap it again).
- -- Collect wrapped callback methods.
- [persistKey and persistKey() or "LuaJ"] = function(self)
- local className, nbt = userdata.save(wrappedUserdata[self])
- -- The returned closure is what actually gets persisted, including the
- -- upvalues, that being the classname and a byte array representing the
- -- nbt data of the userdata value.
- return function()
- return wrapSingleUserdata(userdata.load(className, nbt))
- end
- end,
- -- Do not allow changing the metatable to avoid the gc callback being
- -- unset, leading to potential resource leakage on the host side.
- __metatable = "userdata",
- __tostring = function(self)
- local data = wrappedUserdata[self]
- return tostring(select(2, pcall(data.toString, data)))
- end
- }
- local userdataCallback = {
- __call = function(self, ...)
- local methods = spcall(userdata.methods, wrappedUserdata[self.proxy])
- for name, direct in pairs(methods) do
- if name == self.name then
- return invoke(userdata, direct, self.proxy, name, ...)
- end
- end
- error("no such method", 1)
- end,
- __tostring = function(self)
- return userdata.doc(wrappedUserdata[self.proxy], self.name) or "function"
- end
- }
- function wrapSingleUserdata(data)
- -- Reuse proxies for lower memory consumption and more logical behavior
- -- without the need of metamethods like __eq, as well as proper reference
- -- behavior after saving and loading again.
- for k, v in pairs(wrappedUserdata) do
- -- We need a custom 'equals' check for userdata because metamethods on
- -- userdata introduced by JNLua tend to crash the game for some reason.
- if v == data then
- return k
- end
- end
- local proxy = {type = "userdata"}
- local methods = spcall(userdata.methods, data)
- for method in pairs(methods) do
- proxy[method] = setmetatable({name=method, proxy=proxy}, userdataCallback)
- end
- wrappedUserdata[proxy] = data
- return setmetatable(proxy, userdataWrapper)
- end
- function wrapUserdata(values)
- local processed = {}
- local function wrapRecursively(value)
- if type(value) == "table" then
- if not processed[value] then
- processed[value] = true
- for k, v in pairs(value) do
- value[k] = wrapRecursively(v)
- end
- end
- elseif type(value) == "userdata" then
- return wrapSingleUserdata(value)
- end
- return value
- end
- wrapRecursively(values)
- end
- function unwrapUserdata(values)
- local processed = {}
- local function unwrapRecursively(value)
- if wrappedUserdata[value] then
- return wrappedUserdata[value]
- end
- if type(value) == "table" then
- if not processed[value] then
- processed[value] = true
- for k, v in pairs(value) do
- value[k] = unwrapRecursively(v)
- end
- end
- end
- return value
- end
- unwrapRecursively(values)
- end
- -------------------------------------------------------------------------------
- local libcomponent
- -- Caching proxy objects for lower memory use.
- local proxyCache = setmetatable({}, {__mode="v"})
- -- Short-term caching of callback directness for improved performance.
- local directCache = setmetatable({}, {__mode="k"})
- local function isDirect(address, method)
- local cacheKey = address..":"..method
- local cachedValue = directCache[cacheKey]
- if cachedValue ~= nil then
- return cachedValue
- end
- local methods, reason = spcall(component.methods, address)
- if not methods then
- return false
- end
- for name, info in pairs(methods) do
- if name == method then
- directCache[cacheKey] = info.direct
- return info.direct
- end
- end
- error("no such method", 1)
- end
- local componentProxy = {
- __index = function(self, key)
- if self.fields[key] and self.fields[key].getter then
- return libcomponent.invoke(self.address, key)
- else
- rawget(self, key)
- end
- end,
- __newindex = function(self, key, value)
- if self.fields[key] and self.fields[key].setter then
- return libcomponent.invoke(self.address, key, value)
- elseif self.fields[key] and self.fields[key].getter then
- error("field is read-only")
- else
- rawset(self, key, value)
- end
- end,
- __pairs = function(self)
- local keyProxy, keyField, value
- return function()
- if not keyField then
- repeat
- keyProxy, value = next(self, keyProxy)
- until not keyProxy or keyProxy ~= "fields"
- end
- if not keyProxy then
- keyField, value = next(self.fields, keyField)
- end
- return keyProxy or keyField, value
- end
- end
- }
- local componentCallback = {
- __call = function(self, ...)
- return libcomponent.invoke(self.address, self.name, ...)
- end,
- __tostring = function(self)
- return libcomponent.doc(self.address, self.name) or "function"
- end
- }
- libcomponent = {
- doc = function(address, method)
- checkArg(1, address, "string")
- checkArg(2, method, "string")
- local result, reason = spcall(component.doc, address, method)
- if not result and reason then
- error(reason, 2)
- end
- return result
- end,
- invoke = function(address, method, ...)
- checkArg(1, address, "string")
- checkArg(2, method, "string")
- return invoke(component, isDirect(address, method), address, method, ...)
- end,
- list = function(filter, exact)
- checkArg(1, filter, "string", "nil")
- local list = spcall(component.list, filter, not not exact)
- local key = nil
- return setmetatable(list, {__call=function()
- key = next(list, key)
- if key then
- return key, list[key]
- end
- end})
- end,
- methods = function(address)
- local result, reason = spcall(component.methods, address)
- -- Transform to pre 1.4 format to avoid breaking scripts.
- if type(result) == "table" then
- for k, v in pairs(result) do
- if not v.getter and not v.setter then
- result[k] = v.direct
- else
- result[k] = nil
- end
- end
- return result
- end
- return result, reason
- end,
- fields = function(address)
- local result, reason = spcall(component.methods, address)
- if type(result) == "table" then
- for k, v in pairs(result) do
- if not v.getter and not v.setter then
- result[k] = nil
- end
- end
- return result
- end
- return result, reason
- end,
- proxy = function(address)
- local type, reason = spcall(component.type, address)
- if not type then
- return nil, reason
- end
- local slot, reason = spcall(component.slot, address)
- if not slot then
- return nil, reason
- end
- if proxyCache[address] then
- return proxyCache[address]
- end
- local proxy = {address = address, type = type, slot = slot, fields = {}}
- local methods, reason = spcall(component.methods, address)
- if not methods then
- return nil, reason
- end
- for method, info in pairs(methods) do
- if not info.getter and not info.setter then
- proxy[method] = setmetatable({address=address,name=method}, componentCallback)
- else
- proxy.fields[method] = info
- end
- end
- setmetatable(proxy, componentProxy)
- proxyCache[address] = proxy
- return proxy
- end,
- type = function(address)
- return spcall(component.type, address)
- end,
- slot = function(address)
- return spcall(component.slot, address)
- end
- }
- sandbox.component = libcomponent
- local libcomputer = {
- isRobot = computer.isRobot,
- address = computer.address,
- tmpAddress = computer.tmpAddress,
- freeMemory = computer.freeMemory,
- totalMemory = computer.totalMemory,
- uptime = computer.uptime,
- energy = computer.energy,
- maxEnergy = computer.maxEnergy,
- getBootAddress = computer.getBootAddress,
- setBootAddress = function(...)
- return spcall(computer.setBootAddress, ...)
- end,
- users = computer.users,
- addUser = function(...)
- return spcall(computer.addUser, ...)
- end,
- removeUser = function(...)
- return spcall(computer.removeUser, ...)
- end,
- shutdown = function(reboot)
- coroutine.yield(reboot ~= nil and reboot ~= false)
- end,
- pushSignal = function(...)
- return spcall(computer.pushSignal, ...)
- end,
- pullSignal = function(timeout)
- local deadline = computer.uptime() +
- (type(timeout) == "number" and timeout or math.huge)
- repeat
- local signal = table.pack(coroutine.yield(deadline - computer.uptime()))
- if signal.n > 0 then
- return table.unpack(signal, 1, signal.n)
- end
- until computer.uptime() >= deadline
- end,
- beep = function(...)
- libcomponent.invoke(computer.address(), "beep", ...)
- end
- }
- sandbox.computer = libcomputer
- local libunicode = {
- char = function(...)
- return spcall(unicode.char, ...)
- end,
- len = function(s)
- return spcall(unicode.len, s)
- end,
- lower = function(s)
- return spcall(unicode.lower, s)
- end,
- reverse = function(s)
- return spcall(unicode.reverse, s)
- end,
- sub = function(s, i, j)
- if j then
- return spcall(unicode.sub, s, i, j)
- end
- return spcall(unicode.sub, s, i)
- end,
- upper = function(s)
- return spcall(unicode.upper, s)
- end,
- isWide = function(s)
- return spcall(unicode.isWide, s)
- end,
- charWidth = function(s)
- return spcall(unicode.charWidth, s)
- end,
- wlen = function(s)
- return spcall(unicode.wlen, s)
- end,
- wtrunc = function(s, n)
- return spcall(unicode.wtrunc, s, n)
- end
- }
- sandbox.unicode = libunicode
- -------------------------------------------------------------------------------
- local function bootstrap()
- function boot_invoke(address, method, ...)
- local result = table.pack(pcall(libcomponent.invoke, address, method, ...))
- if not result[1] then
- return nil, result[2]
- else
- return table.unpack(result, 2, result.n)
- end
- end
- do
- local screen = libcomponent.list("screen")()
- local gpu = libcomponent.list("gpu")()
- if gpu and screen then
- boot_invoke(gpu, "bind", screen)
- end
- end
- local function tryLoadFrom(address)
- local handle, reason = boot_invoke(address, "open", "/init.lua")
- if not handle then
- return nil, reason
- end
- local buffer = ""
- repeat
- local data, reason = boot_invoke(address, "read", handle, math.huge)
- if not data and reason then
- return nil, reason
- end
- buffer = buffer .. (data or "")
- until not data
- boot_invoke(address, "close", handle)
- return load(buffer, "=init", "t", sandbox)
- end
- local init, reason
- if computer.getBootAddress() then
- init, reason = tryLoadFrom(computer.getBootAddress())
- end
- if not init then
- computer.setBootAddress()
- for address in libcomponent.list("filesystem") do
- init, reason = tryLoadFrom(address)
- if init then
- computer.setBootAddress(address)
- break
- end
- end
- end
- if not init then
- error("no bootable medium found" .. (reason and (": " .. tostring(reason)) or ""), 0)
- end
- return coroutine.create(init), {n=0}
- end
- -------------------------------------------------------------------------------
- local function main()
- -- Yield once to get a memory baseline.
- coroutine.yield()
- -- After memory footprint to avoid init.lua bumping the baseline.
- local co, args = bootstrap()
- local forceGC = 10
- while true do
- deadline = computer.realTime() + system.timeout()
- hitDeadline = false
- -- NOTE: since this is run in an executor thread and we enforce timeouts
- -- in user-defined garbage collector callbacks this should be safe.
- if persistKey then -- otherwise we're in LuaJ
- forceGC = forceGC - 1
- if forceGC < 1 then
- collectgarbage("collect")
- forceGC = 10
- end
- end
- debug.sethook(co, checkDeadline, "", hookInterval)
- local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
- if not result[1] then
- error(tostring(result[2]), 0)
- elseif coroutine.status(co) == "dead" then
- error("computer stopped unexpectedly", 0)
- else
- args = table.pack(coroutine.yield(result[2])) -- system yielded value
- wrapUserdata(args)
- end
- end
- end
- -- JNLua converts the coroutine to a string immediately, so we can't get the
- -- traceback later. Because of that we have to do the error handling here.
- return pcall(main)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement