local hookInterval = 10000
local function calcHookInterval()
	local bogomipsDivider = 0.05
	local bogomipsDeadline = computer.realTime() + bogomipsDivider
	local ipsCount = 0
	local bogomipsBusy = true
	local function calcBogoMips()
		ipsCount = ipsCount + hookInterval
		if computer.realTime() > bogomipsDeadline then
			bogomipsBusy = false
		end
	end
	-- The following is a bit of nonsensical-seeming code attempting
	-- to cover Lua's VM sufficiently for the IPS calculation.
	local bogomipsTmpA = {{["b"]=3, ["d"]=9}}
	local function c(k)
		if k <= 2 then
			bogomipsTmpA[1].d = k / 2.0
		end
	end
	debug.sethook(calcBogoMips, "", hookInterval)
	while bogomipsBusy do
		local st = ""
		for k=2,4 do
			st = st .. "a" .. k
			c(k)
			if k >= 3 then
				bogomipsTmpA[1].b = bogomipsTmpA[1].b * (k ^ k)
			end
		end
	end
	debug.sethook()
	return ipsCount / bogomipsDivider
end

local ipsCount = calcHookInterval()
-- Since our IPS might still be too generous (hookInterval needs to run at most
-- every 0.05 seconds), we divide it further by 10 relative to that.
hookInterval = (ipsCount * 0.005)
if hookInterval < 1000 then hookInterval = 1000 end

local deadline = math.huge
local hitDeadline = false
local tooLongWithoutYielding = setmetatable({},  { __tostring = function() return "too long without yielding" end})
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(tooLongWithoutYielding)
  end
end
local function pcallTimeoutCheck(...)
  local ok, timeout = ...
  if rawequal(timeout, tooLongWithoutYielding) then
    return ok, tostring(tooLongWithoutYielding)
  end
  return ...
end

-------------------------------------------------------------------------------

local isLuaOver54 = _VERSION:match("5.4")
local isLuaOver53 = isLuaOver54 or _VERSION:match("5.3")

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

-------------------------------------------------------------------------------

--[[ This is pretty much a straight port of Lua's pattern matching code from
     the standard PUC-Rio C implementation. We want to have this in plain Lua
     for the sandbox, so that timeouts also apply while matching stuff, which
     can take a looong time for certain "evil" patterns.
     It passes the pattern matching unit tests from Lua 5.2's test suite, so
     that should be good enough. ]]
do
  local CAP_UNFINISHED = -1
  local CAP_POSITION = -2
  local L_ESC = '%'
  local SPECIALS = "^$*+?.([%-"
  local SHORT_STRING = 500 -- use native implementations for short strings

  local string_find, string_lower, string_match, string_gmatch, string_gsub =
        string.find, string.lower, string.match, string.gmatch, string.gsub

  local match -- forward declaration

  local strptr
  local strptr_mt = {__index={
    step = function(self, count)
      self.pos = self.pos + (count or 1)
      return self
    end,
    head = function(self, len)
      return string.sub(self.data, self.pos, self.pos + (len or self:len()) - 1)
    end,
    len = function(self)
      return #self.data - (self.pos - 1)
    end,
    char = function(self, offset)
      local pos = self.pos + (offset or 0)
      if pos == #self.data + 1 then
        return "\0"
      end
      return string.sub(self.data, pos, pos)
    end,
    copy = function(self, offset)
      return strptr(self.data, self.pos + (offset or 0))
    end
    },
    __add = function(a, b)
      if type(b) == "table" then
        return a.pos + b.pos
      else
        return a:copy(b)
      end
    end,
    __sub = function(a, b)
      if type(b) == "table" then
        return a.pos - b.pos
      else
        return a:copy(-b)
      end
    end,
    __eq = function(a, b)
      return a.data == b.data and a.pos == b.pos
    end,
    __lt = function(a, b)
      assert(a.data == b.data)
      return a.pos < b.pos
    end,
    __le = function(a, b)
      assert(a.data == b.data)
      return a.pos <= b.pos
    end
  }
  function strptr(s, pos)
    return setmetatable({
      data = s,
      pos = pos or 1
    }, strptr_mt)
  end

  local function islower(b) return b >= 'a' and b <= 'z' end
  local function isupper(b) return b >= 'A' and b <= 'Z' end
  local function isalpha(b) return islower(b) or isupper(b) end
  local function iscntrl(b) return b <= '\007' or (b >= '\010' and b <= '\017') or (b >= '\020' and b <= '\027') or (b >= '\030' and b <= '\037' and b ~= ' ') or b == '\177' end
  local function isdigit(b) return b >= '0' and b <= '9' end
  local function ispunct(b) return (b >= '{' and b <= '~') or (b == '`') or (b >= '[' and b <= '_') or (b == '@') or (b >= ':' and b <= '?') or (b >= '(' and b <= '/') or (b >= '!' and b <= '\'') end
  local function isspace(b) return b == '\t' or b == '\n' or b == '\v' or b == '\f' or b == '\r' or b == ' ' end
  local function isalnum(b) return isalpha(b) or isdigit(b) end
  local function isxdigit(b) return isdigit(b) or (b >= 'a' and b <= 'f') or (b >= 'A' and b <= 'F') end
  local function isgraph(b) return not iscntrl(b) and not isspace(b) end

  -- translate a relative string position: negative means back from end
  local function posrelat(pos, len)
    if pos >= 0 then return pos
    elseif -pos > len then return 0
    else return len + pos + 1
    end
  end

  local function check_capture(ms, l)
    l = l - '1'
    if l < 0 or l >= ms.level or ms.capture[l].len == CAP_UNFINISHED then
      error("invalid capture index %" .. (l + 1))
    end
    return l
  end

  local function capture_to_close(ms)
    local level = ms.level
    while level > 0 do
      level = level - 1
      if ms.capture[level].len == CAP_UNFINISHED then
        return level
      end
    end
    return error("invalid pattern capture")
  end

  local function classend(ms, p)
    local p0 = p:char() p = p:copy(1)
    if p0 == L_ESC then
      if p == ms.p_end then
        error("malformed pattern (ends with %)")
      end
      return p:step(1)
    elseif p0 == '[' then
      if p:char() == '^' then
        p:step()
      end
      repeat  -- look for a `]'
        if p == ms.p_end then
          error("malformed pattern (missing ])")
        end
        p:step()
        if p:char(-1) == L_ESC then
          if p < ms.p_end then
            p:step()  -- skip escapes (e.g. `%]')
          end
        end
      until p:char() == ']'
      return p:step()
    else
      return p
    end
  end

  local function match_class(c, cl)
    local res
    local cll = string_lower(cl)
    if cll == 'a' then res = isalpha(c)
    elseif cll == 'c' then res = iscntrl(c)
    elseif cll == 'd' then res = isdigit(c)
    elseif cll == 'g' then res = isgraph(c)
    elseif cll == 'l' then res = islower(c)
    elseif cll == 'p' then res = ispunct(c)
    elseif cll == 's' then res = isspace(c)
    elseif cll == 'u' then res = isupper(c)
    elseif cll == 'w' then res = isalnum(c)
    elseif cll == 'x' then res = isxdigit(c)
    elseif cll == 'z' then res = c == '\0'  -- deprecated option
    else return cl == c
    end
    if islower(cl) then return res
    else return not res
    end
  end

  local function matchbracketclass(c, p, ec)
    local sig = true
    p = p:copy(1)
    if p:char() == '^' then
      sig = false
      p:step()  -- skip the `^'
    end
    while p < ec do
      if p:char() == L_ESC then
        p:step()
        if match_class(c, p:char()) then
          return sig
        end
      elseif p:char(1) == '-' and p + 2 < ec then
        p:step(2)
        if p:char(-2) <= c and c <= p:char() then
          return sig
        end
      elseif p:char() == c then
        return sig
      end
      p:step()
    end
    return not sig
  end

  local function singlematch(ms, s, p, ep)
    if s >= ms.src_end then
      return false
    end
    local p0 = p:char()
    if p0 == '.' then return true -- matches any char
    elseif p0 == L_ESC then return match_class(s:char(), p:char(1))
    elseif p0 == '[' then return matchbracketclass(s:char(), p, ep:copy(-1))
    else return p:char() == s:char()
    end
  end

  local function matchbalance(ms, s, p)
    if p >= ms.p_end - 1 then
      error("malformed pattern (missing arguments to %b)")
    end
    if s:char() ~= p:char() then return nil end
    local b = p:char()
    local e = p:char(1)
    local cont = 1
    s = s:copy()
    while s:step() < ms.src_end do
      if s:char() == e then
        cont = cont - 1
        if cont == 0 then return s:step() end
      elseif s:char() == b then
        cont = cont + 1
      end
    end
    return nil  -- string ends out of balance
  end

  local function max_expand(ms, s, p, ep)
    local i = 0  -- counts maximum expand for item
    while singlematch(ms, s:copy(i), p, ep) do
      i = i + 1
    end
    -- keeps trying to match with the maximum repetitions
    while i >= 0 do
      local res = match(ms, s:copy(i), ep:copy(1))
      if res then return res end
      i = i - 1  -- else didn't match; reduce 1 repetition to try again
    end
    return nil
  end

  local function min_expand(ms, s, p, ep)
    s = s:copy()
    while true do
      local res = match(ms, s, ep:copy(1))
      if res ~= nil then
        return res
      elseif singlematch(ms, s, p, ep) then
        s:step()  -- try with one more repetition
      else return nil
      end
    end
  end

  local function start_capture(ms, s, p, what)
    local level = ms.level
    ms.capture[level] = ms.capture[level] or {}
    ms.capture[level].init = s:copy()
    ms.capture[level].len = what
    ms.level = level + 1
    local res = match(ms, s, p)
    if res == nil then  -- match failed?
      ms.level = ms.level - 1  -- undo capture
    end
    return res
  end

  local function end_capture(ms, s, p)
    local l = capture_to_close(ms)
    ms.capture[l].len = s - ms.capture[l].init  -- close capture
    local res = match(ms, s, p)
    if res == nil then  -- match failed?
      ms.capture[l].len = CAP_UNFINISHED  -- undo capture
    end
    return res
  end

  local function match_capture(ms, s, l)
    l = check_capture(ms, l)
    local len = ms.capture[l].len
    if ms.src_end - s >= len and
       ms.capture[l].init:head(len) == s:head(len)
    then
      return s:copy(len)
    else return nil
    end
  end

  function match(ms, s, p)
    s = s:copy()
    p = p:copy()
    ::init:: -- using goto's to optimize tail recursion
    if p ~= ms.p_end then
      local p0 = p:char()
      if p0 == '(' then  -- start capture
        if p:char(1) == ')' then  -- position capture?
          s = start_capture(ms, s, p:copy(2), CAP_POSITION)
        else
          s = start_capture(ms, s, p:copy(1), CAP_UNFINISHED)
        end
        goto brk
      elseif p0 == ')' then  -- end capture
        s = end_capture(ms, s, p:copy(1))
        goto brk
      elseif p0 == '$' then
        if p + 1 ~= ms.p_end then  -- is the `$' the last char in pattern?
          goto dflt  -- no; go to default
        end
        s = (s == ms.src_end) and s or nil  -- check end of string
        goto brk
      elseif p0 == L_ESC then  -- escaped sequences not in the format class[*+?-]?
        local p1 = p:char(1)
        if p1 == 'b' then  -- balanced string?
          s = matchbalance(ms, s, p:copy(2))
          if s ~= nil then
            p:step(4)
            goto init  -- return match(ms, s, p + 4)
          end
          -- else fail (s == nil)
        elseif p1 == 'f' then  -- frontier?
          p:step(2)
          if p:char() ~= '[' then
            error("missing [ after %f in pattern")
          end
          local ep = classend(ms, p)  -- points to what is next
          local previous = (s == ms.src_init) and '\0' or s:char(-1)
          if not matchbracketclass(previous, p, ep:copy(-1)) and
             matchbracketclass(s:char(), p, ep:copy(-1))
          then
            p = ep
            goto init  -- return match(ms, s, ep)
          end
          s = nil  -- match failed
        elseif isdigit(p:char(1)) then  -- capture results (%0-%9)?
          s = match_capture(ms, s, p:char(1))
          if s ~= nil then
            p:step(2)
            goto init  -- return match(ms, s, p + 2)
          end
        else
          goto dflt
        end
        goto brk
      end
      ::dflt:: do
        local ep = classend(ms, p)  -- points to what is next
        local ep0 = ep:char()
        if not singlematch(ms, s, p, ep) then
          if ep0 == '*' or ep0 == '?' or ep0 == '-' then  -- accept empty?
            p = ep:copy(1)
            goto init  -- return match(ms, s, ep + 1)
          else  -- '+' or no suffix
            s = nil  -- fail
          end
        else  -- matched once
          if ep0 == '?' then  -- optional
            local res = match(ms, s:copy(1), ep:copy(1))
            if res ~= nil then
              s = res
            else
              p = ep:copy(1)
              goto init  -- else return match(ms, s, ep + 1)
            end
          elseif ep0 == '+' then  -- 1 or more repetitions
            s = max_expand(ms, s:copy(1), p, ep)  -- 1 match already done
          elseif ep0 == '*' then  -- 0 or more repetitions
            s = max_expand(ms, s, p, ep)
          elseif ep0 == '-' then  -- 0 or more repetitions (minimum)
            s = min_expand(ms, s, p, ep)
          else
            s:step()
            p = ep
            goto init  -- else return match(ms, s+1, ep);
          end
        end
      end
      ::brk::
    end
    return s
  end

  local function push_onecapture(ms, i, s, e)
    if i >= ms.level then
      if i == 0 then  -- ms->level == 0, too
        return s:head(e - s)  -- add whole match
      else
        error("invalid capture index")
      end
    else
      local l = ms.capture[i].len;
      if l == CAP_UNFINISHED then error("unfinished capture") end
      if l == CAP_POSITION then
        return ms.capture[i].init - ms.src_init + 1
      else
        return ms.capture[i].init:head(l)
      end
    end
  end

  local function push_captures(ms, s, e)
    local nlevels = (ms.level == 0 and s) and 1 or ms.level
    local captures = {}
    for i = 0, nlevels - 1 do
      table.insert(captures, push_onecapture(ms, i, s, e))
    end
    return table.unpack(captures)
  end

  -- check whether pattern has no special characters
  local function nospecials(p)
    for i = 1, #p do
      for j = 1, #SPECIALS do
        if p:sub(i, i) == SPECIALS:sub(j, j) then
          return false
        end
      end
    end
    return true
  end

  local function str_find_aux(str, pattern, init, plain, find)
    checkArg(1, str, "string")
    checkArg(2, pattern, "string")
    checkArg(3, init, "number", "nil")

    if #str < SHORT_STRING then
      return (find and string_find or string_match)(str, pattern, init, plain)
    end

    local s = strptr(str)
    local p = strptr(pattern)
    local init = posrelat(init or 1, #str)
    if init < 1 then init = 1
    elseif init > #str + 1 then  -- start after string's end?
      return nil  -- cannot find anything
    end
    -- explicit request or no special characters?
    if find and (plain or nospecials(pattern)) then
      -- do a plain search
      local s2 = string_find(str, pattern, init, true)
      if s2 then
        return s2-s.pos + 1, s2 - s.pos + p:len()
      end
    else
      local s1 = s:copy(init - 1)
      local anchor = p:char() == '^'
      if anchor then p:step() end
      local ms = {
        src_init = s,
        src_end = s:copy(s:len()),
        p_end = p:copy(p:len()),
        capture = {}
      }
      repeat
        ms.level = 0
        local res = match(ms, s1, p)
        if res ~= nil then
          if find then
            return s1.pos - s.pos + 1, res.pos - s.pos, push_captures(ms, nil, nil)
          else
            return push_captures(ms, s1, res)
          end
        end
      until s1:step() > ms.src_end or anchor
    end
    return nil  -- not found
  end

  local function str_find(s, pattern, init, plain)
    return str_find_aux(s, pattern, init, plain, true)
  end

  local function str_match(s, pattern, init)
    return str_find_aux(s, pattern, init, false, false)
  end

  local function str_gmatch(s, pattern, init)
    checkArg(1, s, "string")
    checkArg(2, pattern, "string")

    if #s < SHORT_STRING then
      return string_gmatch(s, pattern, init)
    end

    local start = 0
    if isLuaOver54 then
      checkArg(3, init, "number", "nil")
      if init ~= nil then
        start = posrelat(init, #s)
        if start < 1 then start = 0
        elseif start > #s + 1 then start = #s + 1
        else start = start - 1 end
      end
    end

    local s = strptr(s)
    local p = strptr(pattern)
    return function()
      ms = {
        src_init = s,
        src_end = s:copy(s:len()),
        p_end = p:copy(p:len()),
        capture = {}
      }
      for offset = start, ms.src_end.pos - 1 do
        local src = s:copy(offset)
        ms.level = 0
        local e = match(ms, src, p)
        if e ~= nil then
          local newstart = e - s
          if e == src then newstart = newstart + 1 end -- empty match? go at least one position
          start = newstart
          return push_captures(ms, src, e)
        end
      end
      return nil  -- not found
    end
  end

  local function add_s(ms, b, s, e, r)
    local news = tostring(r)
    local i = 1
    while i <= #news do
      if news:sub(i, i) ~= L_ESC then
        b = b .. news:sub(i, i)
      else
        i = i + 1  -- skip ESC
        if not isdigit(news:sub(i, i)) then
          b = b .. news:sub(i, i)
        elseif news:sub(i, i) == '0' then
          b = b .. s:head(e - s)
        else
          b = b .. push_onecapture(ms, news:sub(i, i) - '1', s, e)  -- add capture to accumulated result
        end
      end
      i = i + 1
    end
    return b
  end

  local function add_value(ms, b, s, e, r, tr)
    local res
    if tr == "function" then
      res = r(push_captures(ms, s, e))
    elseif tr == "table" then
      res = r[push_onecapture(ms, 0, s, e)]
    else  -- LUA_TNUMBER or LUA_TSTRING
      return add_s(ms, b, s, e, r)
    end
    if not res then  -- nil or false?
      res = s:head(e - s)  -- keep original text
    elseif type(res) ~= "string" and type(res) ~= "number" then
      error("invalid replacement value (a "..type(res)..")")
    end
    return b .. res  -- add result to accumulator
  end

  local function str_gsub(s, pattern, repl, n)
    checkArg(1, s, "string")
    checkArg(2, pattern, "string", "number")
    checkArg(3, repl, "number", "string", "function", "table")
    checkArg(4, n, "number", "nil")

    if #s < SHORT_STRING then
      return string_gsub(s, pattern, repl, n)
    end

    pattern = tostring(pattern)
    local src = strptr(s);
    local p = strptr(pattern)
    local tr = type(repl)
    local max_s = n or (#s + 1)
    local anchor = p:char() == '^'
    if anchor then
      p:step()  -- skip anchor character
    end
    n = 0
    local b = ""
    local ms = {
      src_init = src:copy(),
      src_end = src:copy(src:len()),
      p_end = p:copy(p:len()),
      capture = {}
    }
    while n < max_s do
      ms.level = 0
      local e = match(ms, src, p)
      if e then
        n = n + 1
        b = add_value(ms, b, src, e, repl, tr)
      end
      if e and e > src then  -- non empty match?
        src = e  -- skip it
      elseif src < ms.src_end then
        b = b .. src:char()
        src:step()
      else break
      end
      if anchor then break end
    end
    b = b .. src:head()
    return b, n  -- number of substitutions
  end

  string.find = str_find
  string.match = str_match
  string.gmatch = str_gmatch
  string.gsub = str_gsub
end

-------------------------------------------------------------------------------

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 sgcco

local function sgcf(self, gc)
  while true do
    self, gc = coroutine.yield(pcall(gc, self))
  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
  if not sgcco then
    sgcco = coroutine.create(sgcf)
  end
  debug.sethook(sgcco, checkDeadline, "", hookInterval)
  deadline, hitDeadline = math.min(oldDeadline, computer.realTime() + 0.5), true
  local _, result, reason = coroutine.resume(sgcco, self, gc)
  debug.sethook(sgcco)
  if coroutine.status(sgcco) == "dead" then
    sgcco = nil
  end
  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 system.allowGC() 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(...)
    -- prevent infinite pcall() loops by checking deadline before pcall()
    local status, err = pcall(checkDeadline)
    if not status then return false, err end

    return pcallTimeoutCheck(pcall(...))
  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 rawget(mt, "__gc") ~= nil then -- If __gc is set to ANYTHING not `nil`, we're gonna have issues
      -- Garbage collector callbacks apparently can't be sandboxed after
      -- all, because hooks are disabled while they're running. So we just
      -- disable them altogether by default.
      if system.allowGC() 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 next, mt do
          sbmt[k] = v
        end
        sbmt.__gc = sgc
        sbmt.mt = mt
        mt = sbmt
      else
        -- Don't allow marking for finalization, but use the raw metatable.
        local gc = rawget(mt, "__gc")
        rawset(mt, "__gc", nil) -- remove __gc
        local ret = table.pack(pcall(setmetatable, t, mt))
        rawset(mt, "__gc", gc) -- restore __gc
        if not ret[1] then error(ret[2], 0) end
        return table.unpack(ret, 2, ret.n)
      end
    end
    return setmetatable(t, mt)
  end,
  tonumber = tonumber,
  tostring = tostring,
  type = type,
  _VERSION = _VERSION:match("Luaj") and "Luaj" or _VERSION:match("5.4") and "Lua 5.4" or _VERSION:match("5.3") and "Lua 5.3" or "Lua 5.2",
  xpcall = function(f, msgh, ...)
    -- allow xpcall() to call the message handler recursively, per manual 2.3
    -- Lua itself promises to break the infinite loop; failing that, the timeout
    -- check will take care of this.
    local errorCapture
    errorCapture = function(ff, ...)
      -- prevent infinite xpcall() loops by checking deadline before xpcall()
      local status, err = pcall(checkDeadline)
      if not status then return false, err end

      return xpcall(ff, function(...)
        if rawequal((...), tooLongWithoutYielding) then
          return tooLongWithoutYielding
        else
          return select(2, errorCapture(msgh, ...))
        end
      end, ...)
    end

    checkArg(2, msgh, "function")
    local result = table.pack(errorCapture(f, ...))
    -- if the final returned error is due to timeout, run handler one last time
    if rawequal(result[2], tooLongWithoutYielding) then
      result = table.pack(result[1], select(2, pcallTimeoutCheck(pcall(msgh, tostring(tooLongWithoutYielding)))))
    end
    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,
    -- Lua 5.3.
    isyieldable = coroutine.isyieldable
  },

  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,
    -- Lua 5.3.
    pack = string.pack,
    unpack = string.unpack,
    packsize = string.packsize
  },

  table = {
    concat = table.concat,
    insert = table.insert,
    pack = table.pack,
    remove = table.remove,
    sort = table.sort,
    unpack = table.unpack,
    -- Lua 5.3.
    move = table.move
  },

  math = {
    abs = math.abs,
    acos = math.acos,
    asin = math.asin,
    atan = math.atan,
    atan2 = math.atan2 or math.atan, -- Deprecated in Lua 5.3
    ceil = math.ceil,
    cos = math.cos,
    cosh = math.cosh, -- Deprecated in Lua 5.3
    deg = math.deg,
    exp = math.exp,
    floor = math.floor,
    fmod = math.fmod,
    frexp = math.frexp, -- Deprecated in Lua 5.3
    huge = math.huge,
    ldexp = math.ldexp or function(a, e) -- Deprecated in Lua 5.3
        return a*(2.0^e)
    end,
    log = math.log,
    max = math.max,
    min = math.min,
    modf = math.modf,
    pi = math.pi,
    pow = math.pow or function(a, b) -- Deprecated in Lua 5.3
      return a^b
    end,
    rad = math.rad,
    random = function(...)
      return spcall(math.random, ...)
    end,
    randomseed = function(seed)
      -- math.floor(seed) emulates pre-OC 1.8.0 behaviour
      spcall(math.randomseed, math.floor(seed))
    end,
    sin = math.sin,
    sinh = math.sinh, -- Deprecated in Lua 5.3
    sqrt = math.sqrt,
    tan = math.tan,
    tanh = math.tanh, -- Deprecated in Lua 5.3
    -- Lua 5.3.
    maxinteger = math.maxinteger,
    mininteger = math.mininteger,
    tointeger = math.tointeger,
    type = math.type,
    ult = math.ult
  },

  -- Deprecated in Lua 5.3.
  bit32 = bit32 and {
    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 = {
    getinfo = function(...)
      local result = debug.getinfo(...)
      if result then
        -- Only make primitive information available in the sandbox.
        return {
          source = result.source,
          short_src = result.short_src,
          linedefined = result.linedefined,
          lastlinedefined = result.lastlinedefined,
          what = result.what,
          currentline = result.currentline,
          nups = result.nups,
          nparams = result.nparams,
          isvararg = result.isvararg,
          name = result.name,
          namewhat = result.namewhat,
          istailcall = result.istailcall
        }
      end
    end,
    traceback = debug.traceback,
    -- using () to wrap the return of debug methods because in Lua doing this
    -- causes only the first return value to be selected
    -- e.g. (1, 2) is only (1), the 2 is not returned
    -- this is critically important here because the 2nd return value from these
    -- debug methods is the value itself, which opens a door to exploit the sandbox
    getlocal = function(...) return (debug.getlocal(...)) end,
    getupvalue = function(...) return (debug.getupvalue(...)) end,
  },

  -- Lua 5.3.
  utf8 = utf8 and {
    char = utf8.char,
    charpattern = utf8.charpattern,
    codes = utf8.codes,
    codepoint = utf8.codepoint,
    len = utf8.len,
    offset = utf8.offset
  },

  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)
  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
    args = unwrapUserdata(args)
    result = table.pack(target.invoke(table.unpack(args, 1, args.n)))
    args = nil -- clear upvalue, avoids trying to persist it
    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()
      args = unwrapUserdata(args)
      local result = table.pack(target.invoke(table.unpack(args, 1, args.n)))
      args = nil -- clear upvalue, avoids trying to persist it
      result = wrapUserdata(result)
      return result
    end))
  end
  return processResult(result)
end

local function udinvoke(f, data, ...)
  local args = table.pack(...)
  args = unwrapUserdata(args)
  local result = table.pack(f(data, table.unpack(args)))
  args = nil -- clear upvalue, avoids trying to persist it
  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(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
  return 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
  return 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(not not reboot)
  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(...)
    return libcomponent.invoke(computer.address(), "beep", ...)
  end,
  getDeviceInfo = function()
    return libcomponent.invoke(computer.address(), "getDeviceInfo")
  end,
  getProgramLocations = function()
    return libcomponent.invoke(computer.address(), "getProgramLocations")
  end,

  getArchitectures = function(...)
    return spcall(computer.getArchitectures, ...)
  end,
  getArchitecture = function(...)
    return spcall(computer.getArchitecture, ...)
  end,
  setArchitecture = function(...)
    local result, reason = spcall(computer.setArchitecture, ...)
    if not result then
      if reason then
        return result, reason
      end
    else
      coroutine.yield(true) -- reboot
    end
  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()
  local eeprom = libcomponent.list("eeprom")()
  if eeprom then
    local code = libcomponent.invoke(eeprom, "get")
    if code and #code > 0 then
      local bios, reason = load(code, "=bios", "t", sandbox)
      if bios then
        return coroutine.create(bios), {n=0}
      end
      error("failed loading bios: " .. reason, 0)
    end
  end
  error("no bios found; install a configured EEPROM", 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)))
    args = nil -- clear upvalue, avoids trying to persist it
    if not result[1] then
      error(tostring(result[2]), 0)
    elseif coroutine.status(co) == "dead" then
      error("computer halted", 0)
    else
      args = table.pack(coroutine.yield(result[2])) -- system yielded value
      args = 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 pcallTimeoutCheck(pcall(main))