initial commit of vaguely working ish build

This commit is contained in:
2020-08-22 11:39:15 +01:00
commit ff85c35873
34 changed files with 11067 additions and 0 deletions

View File

@@ -0,0 +1,330 @@
-- by "Yevano", tweaked due to issues with signed integer handling
--[[
BLODS - Binary Lua Object (De)Serialization
]]
--[[
Save on table access.
]]
local pairs = pairs
local type = type
local loadstring = loadstring
local mathabs = math.abs
local mathfloor = math.floor
local mathfrexp = math.frexp
local mathmodf = math.modf
local mathpow = math.pow
local stringbyte = string.byte
local stringchar = string.char
local stringdump = string.dump
local stringsub = string.sub
local tableconcat = table.concat
--[[
Float conversions. Modified from http://snippets.luacode.org/snippets/IEEE_float_conversion_144.
]]
local function double2str(value)
local s=value<0 and 1 or 0
if mathabs(value)==1/0 then
return (s==1 and "\0\0\0\0\0\0\240\255" or "\0\0\0\0\0\0\240\127")
end
if value~=value then
return "\170\170\170\170\170\170\250\255"
end
local fr,exp=mathfrexp(mathabs(value))
fr,exp=fr*2,exp-1
exp=exp+1023
return tableconcat({stringchar(mathfloor(fr*2^52)%256),
stringchar(mathfloor(fr*2^44)%256),
stringchar(mathfloor(fr*2^36)%256),
stringchar(mathfloor(fr*2^28)%256),
stringchar(mathfloor(fr*2^20)%256),
stringchar(mathfloor(fr*2^12)%256),
stringchar(mathfloor(fr*2^4)%16+mathfloor(exp)%16*16),
stringchar(mathfloor(exp/2^4)%128+128*s)})
end
local function str2double(str)
local fr=stringbyte(str, 1)/2^52+stringbyte(str, 2)/2^44+stringbyte(str, 3)/2^36+stringbyte(str, 4)/2^28+stringbyte(str, 5)/2^20+stringbyte(str, 6)/2^12+(stringbyte(str, 7)%16)/2^4+1
local exp=(stringbyte(str, 8)%128)*16+mathfloor(str:byte(7)/16)-1023
local s=mathfloor(stringbyte(str, 8)/128)
if exp==1024 then
return fr==1 and (1-2*s)/0 or 0/0
end
return (1-2*s)*fr*2^exp
end
--[[
Integer conversions. Taken from http://lua-users.org/wiki/ReadWriteFormat.
Modified to support signed ints.
]]
local function signedstringtonumber(str)
local function _b2n(exp, num, digit, ...)
if not digit then return num end
return _b2n(exp*256, num + digit*exp, ...)
end
return _b2n(256, stringbyte(str, 1, -1)) - mathpow(2, #str * 8 - 1)
end
local function stringtonumber(str)
local function _b2n(exp, num, digit, ...)
if not digit then return num end
return _b2n(exp*256, num + digit*exp, ...)
end
return _b2n(256, stringbyte(str, 1, -1))
end
local function numbertobytes(num, width)
local function _n2b(width, num, rem)
rem = rem * 256
if width == 0 then return rem end
return rem, _n2b(width-1, mathmodf(num/256))
end
return stringchar(_n2b(width-1, mathmodf((num)/256)))
end
local function log2(x)
return math.log10(x) / math.log10(2)
end
--[[
(De)Serialization for Lua types.
]]
local function intWidth(int)
local out = math.ceil((log2(int) + 1) / 8)
if out == math.huge or out == -math.huge then return 1 end
return out
end
local types = {
boolean = "b",
double = "d",
posinteger = "p",
neginteger = "n",
string = "s",
table = "t",
["function"] = "f",
["nil"] = "_"
}
local serialization = { }
local deserialization = { }
function serialization.boolean(obj)
return obj and "\1" or "\0"
end
function serialization.double(obj)
return double2str(obj)
end
function serialization.integer(obj)
local width = intWidth(obj)
return stringchar(width) .. numbertobytes(obj, width)
end
function serialization.string(obj)
local len = #obj
local width = intWidth(len)
return tableconcat({ stringchar(width), numbertobytes(len, width), obj })
end
serialization["function"] = function(obj)
local ok, s = pcall(stringdump, obj)
if not ok then return "_" end
return numbertobytes(#s, 4) .. s
end
function deserialization.b(idx, ser)
local ret = stringsub(ser[1], idx, idx) == "\1"
return ret, idx + 1
end
function deserialization.d(idx, ser)
local ret = str2double(stringsub(ser[1], idx, idx + 8))
return ret, idx + 8
end
function deserialization.p(idx, ser)
local width = stringtonumber(stringsub(ser[1], idx, idx))
local ret = stringtonumber(stringsub(ser[1], idx + 1, idx + width))
return ret, idx + width + 1
end
function deserialization.n(idx, ser)
local width = stringtonumber(stringsub(ser[1], idx, idx))
local ret = stringtonumber(stringsub(ser[1], idx + 1, idx + width))
return -ret, idx + width + 1
end
function deserialization.s(idx, ser)
local width = stringtonumber(stringsub(ser[1], idx, idx))
local len = stringtonumber(stringsub(ser[1], idx + 1, idx + width))
local ret = stringsub(ser[1], idx + width + 1, idx + width + len)
return ret, idx + width + len + 1
end
function deserialization.f(idx, ser)
local len = stringtonumber(stringsub(ser[1], idx, idx + 3))
local ret = loadstring(stringsub(ser[1], idx + 4, idx + len + 3))
return ret, idx + len + 4
end
function deserialization._(idx, ser)
return nil, idx
end
local function yield()
os.queueEvent ""
os.pullEvent ""
end
if not os.queueEvent then yield = function() end end
function serialize(obj)
-- State vars.
local ntables = 1
local tables = { }
local tableIDs = { }
local tableSerial = { }
-- Internal recursive function.
local function serialize(obj)
yield()
local t = type(obj)
if t == "table" then
local len = #obj
if tables[obj] then
-- We already serialized this table. Just return the id.
return tableIDs[obj]
end
-- Insert table info.
local id = ntables
tables[obj] = true
local width = intWidth(ntables)
local ser = "t" .. numbertobytes(width, 1) .. numbertobytes(ntables, width)
tableIDs[obj] = ser
-- Important to increment here so tables inside this one don't use the same id.
ntables = ntables + 1
-- Serialize the table.
local serialConcat = { }
-- Array part.
for i = 1, len do
if obj[i] == nil then
len = i - 1
break
end
serialConcat[#serialConcat + 1] = serialize(obj[i])
end
serialConcat[#serialConcat + 1] = "\0"
-- Table part.
for k, v in pairs(obj) do
if type(k) ~= "number" or ((k > len or k < 1) or mathfloor(k) ~= k) then
-- For each pair, serialize both the key and the value.
local idx = #serialConcat
serialConcat[idx + 1] = serialize(k)
serialConcat[idx + 2] = serialize(v)
end
end
serialConcat[#serialConcat + 1] = "\0"
-- tableconcat is way faster than normal concatenation using .. when dealing with lots of strings.
-- Add this serialization to the table of serialized tables for quick access and later more concatenation.
tableSerial[id] = tableconcat(serialConcat)
return ser
else
-- Do serialization on a non-recursive type.
if t == "number" then
-- Space optimization can be done for ints, so serialize them differently from doubles.
-- OSMARKS EDIT: handle sign in type and not actual serialization
if mathfloor(obj) == obj then
local intval = serialization.integer(math.abs(obj))
local typespec = "p"
if obj < 0 then typespec = "n" end
return typespec .. intval
end
return "d" .. serialization.double(obj)
end
local ser = types[t]
return obj == nil and ser or ser .. serialization[t](obj)
end
end
-- Either serialize for a table or for a non-recursive type.
local ser = serialize(obj)
if type(obj) == "table" then
return tableconcat({ "t", tableconcat(tableSerial) })
end
return ser
end
function deserialize(ser)
local idx = 1
local tables = { { } }
local serref = { ser }
local function getchar()
local ret = stringsub(serref[1], idx, idx)
return ret ~= "" and ret or nil
end
local function deserializeValue()
yield()
local t = getchar()
idx = idx + 1
if t == "t" then
-- Get table id.
local width = stringtonumber(getchar())
idx = idx + 1
local id = stringtonumber(stringsub(serref[1], idx, idx + width - 1))
idx = idx + width
-- Create an empty table as a placeholder.
if not tables[id] then
tables[id] = { }
end
return tables[id]
else
local ret
ret, idx = deserialization[t](idx, serref)
return ret
end
end
-- Either deserialize for a table or for a non-recursive type.
local i = 1
if getchar() == "t" then
idx = idx + 1
while getchar() do
if not tables[i] then tables[i] = { } end
local curtbl = tables[i]
-- Array part.
while getchar() ~= "\0" do
curtbl[#curtbl + 1] = deserializeValue()
end
-- Table part.
idx = idx + 1
while getchar() ~= "\0" do
curtbl[deserializeValue()] = deserializeValue()
end
i = i + 1
idx = idx + 1
end
return tables[1]
end
return deserializeValue()
end
return { serialize = serialize, deserialize = deserialize }

1311
src/lib/ecc-168.lua Normal file

File diff suppressed because it is too large Load Diff

2135
src/lib/ecc.lua Normal file

File diff suppressed because it is too large Load Diff

213
src/lib/gps.lua Normal file
View File

@@ -0,0 +1,213 @@
local expect
if _G["~expect"] then
expect = _G["~expect"]
else
local native_select, native_type = select, type
expect = function(index, value, ...)
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return true end
end
local types = table.pack(...)
for i = types.n, 1, -1 do
if types[i] == "nil" then table.remove(types, i) end
end
local type_names
if #types <= 1 then
type_names = tostring(...)
else
type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
end
-- If we can determine the function name with a high level of confidence, try to include it.
local name
if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
local ok, info = pcall(debug.getinfo, 3, "nS")
if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
end
if name then
error( ("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3 )
else
error( ("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3 )
end
end
end
CHANNEL_GPS = 65534
local function trilaterate( A, B, C )
local a2b = B.vPosition - A.vPosition
local a2c = C.vPosition - A.vPosition
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
return nil
end
local d = a2b:length()
local ex = a2b:normalize( )
local i = ex:dot( a2c )
local ey = (a2c - ex * i):normalize()
local j = ey:dot( a2c )
local ez = ex:cross( ey )
local r1 = A.nDistance
local r2 = B.nDistance
local r3 = C.nDistance
local x = (r1 * r1 - r2 * r2 + d * d) / (2 * d)
local y = (r1 * r1 - r3 * r3 - x * x + (x - i) * (x - i) + j * j) / (2 * j)
local result = A.vPosition + ex * x + ey * y
local zSquared = r1 * r1 - x * x - y * y
if zSquared > 0 then
local z = math.sqrt( zSquared )
local result1 = result + ez * z
local result2 = result - ez * z
local rounded1, rounded2 = result1:round( 0.01 ), result2:round( 0.01 )
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
return rounded1, rounded2
else
return rounded1
end
end
return result:round( 0.01 )
end
local function narrow( p1, p2, fix )
local dist1 = math.abs( (p1 - fix.vPosition):length() - fix.nDistance )
local dist2 = math.abs( (p2 - fix.vPosition):length() - fix.nDistance )
if math.abs(dist1 - dist2) < 0.01 then
return p1, p2
elseif dist1 < dist2 then
return p1:round( 0.01 )
else
return p2:round( 0.01 )
end
end
function locate( _nTimeout, _bDebug )
expect(1, _nTimeout, "number", "nil")
expect(2, _bDebug, "boolean", "nil")
-- Let command computers use their magic fourth-wall-breaking special abilities
if commands then
return commands.getBlockPosition()
end
-- Find a modem
local sModemSide = nil
for _, sSide in ipairs( rs.getSides() ) do
if peripheral.getType( sSide ) == "modem" and peripheral.call( sSide, "isWireless" ) then
sModemSide = sSide
break
end
end
if sModemSide == nil then
if _bDebug then
print( "No wireless modem attached" )
end
return nil
end
if _bDebug then
print( "Finding position..." )
end
-- Open it
local modem = peripheral.wrap( sModemSide )
local bCloseChannel = false
if not modem.isOpen( CHANNEL_GPS ) then
modem.open( CHANNEL_GPS )
bCloseChannel = true
end
-- Send a ping to listening GPS hosts
modem.transmit( CHANNEL_GPS, CHANNEL_GPS, "PING" )
-- Wait for the responses
local tFixes = {}
local pos1, pos2, dimension
local timeout = os.startTimer( _nTimeout or 2 )
while true do
local e, p1, p2, p3, p4, p5 = os.pullEvent()
if e == "modem_message" then
-- We received a reply from a modem
local sSide, sChannel, sReplyChannel, tMessage, nDistance = p1, p2, p3, p4, p5
if sSide == sModemSide and sChannel == CHANNEL_GPS and sReplyChannel == CHANNEL_GPS and nDistance then
-- Received the correct message from the correct modem: use it to determine position
if type(tMessage) == "table" and #tMessage == 3 and tonumber(tMessage[1]) and tonumber(tMessage[2]) and tonumber(tMessage[3]) then
local tFix = { vPosition = vector.new( tMessage[1], tMessage[2], tMessage[3] ), nDistance = nDistance, dimension = tMessage.dimension }
if _bDebug then
local text = tFix.nDistance .. " metres from " .. tostring(tFix.vPosition)
if type(tFix.dimension) == "number" or type(tFix.dimension) == "string" then text = text .. " " .. tFix.dimension end
print(text)
end
if tFix.nDistance == 0 then
pos1, pos2 = tFix.vPosition, nil
else
table.insert( tFixes, tFix )
if #tFixes >= 3 then
if not pos1 then
pos1, pos2 = trilaterate( tFixes[1], tFixes[2], tFixes[#tFixes] )
else
pos1, pos2 = narrow( pos1, pos2, tFixes[#tFixes] )
end
end
end
if pos1 and not pos2 then
local dimension_claims = {}
for _, fix in pairs(tFixes) do
if type(fix.dimension) == "string" or type(fix.dimension) == "number" then
dimension_claims[fix.dimension] = (dimension_claims[fix.dimension] or 0) + 1
end
end
local highest_count = 0
for dim, count in pairs(dimension_claims) do
if count > highest_count then
highest_count = count
dimension = dim
end
end
break
end
end
end
elseif e == "timer" then
-- We received a timeout
local timer = p1
if timer == timeout then
break
end
end
end
-- Close the channel, if we opened one
if bCloseChannel then
modem.close( CHANNEL_GPS )
end
-- Return the response
if pos1 and pos2 then
if _bDebug then
print( "Ambiguous position" )
print( "Could be " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z .. " or " .. pos2.x .. "," .. pos2.y .. "," .. pos2.z )
end
return nil
elseif pos1 then
if _bDebug then
print( "Position is " .. pos1.x .. "," .. pos1.y .. "," .. pos1.z )
if dimension then print("Dimension is " .. dimension) end
end
return pos1.x, pos1.y, pos1.z, dimension
else
if _bDebug then
print( "Could not determine position" )
end
return nil
end
end

400
src/lib/json.lua Normal file
View File

@@ -0,0 +1,400 @@
--
-- json.lua
--
-- Copyright (c) 2018 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = {}
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if val[1] ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

28
src/lib/meta.lua Normal file
View File

@@ -0,0 +1,28 @@
local function new()
local x = { level = 1, new = new }
local m = {}
setmetatable(x, m)
m.__eq = function(p1,p2)
if getmetatable(p1) == getmetatable(p2) then
return true
end
end
m.__index = function(inst, key)
local lvl = rawget(inst, "level")
if key == "level" then
return lvl
else
return setmetatable({ level = lvl + 1 }, m)
end
end
m.__tostring = function(inst)
return ("RECURSION "):rep(rawget(inst, "level"))
end
return x
end
return new()

49
src/lib/persistence.lua Normal file
View File

@@ -0,0 +1,49 @@
--[[
Fixes PS#DE7E9F99
Out-of-sandbox code/data files could be overwritten using this due to environment weirdness.
Now persistence data is in its own folder so this will not happen.
]]
local persist_dir = "persistence_data"
local function load_data(name)
local file = fs.combine(persist_dir, name)
if not fs.exists(file) then error "does not exist" end
local f = fs.open(file, "r")
local x = f.readAll()
f.close()
return textutils.unserialise(x)
end
local function save_data(name, value)
if not fs.isDir(persist_dir) then fs.delete(persist_dir) fs.makeDir(persist_dir) end
local f = fs.open(fs.combine(persist_dir, name), "w")
f.write(textutils.serialise(value))
f.close()
end
return function(name)
if type(name) ~= "string" then error "Name of persistence volume must be a string." end
local ok, data = pcall(load_data, name)
if not ok or not data then data = {} end
local mt = {}
setmetatable(data, mt)
mt.__index = {
save = function()
save_data(name, data)
end,
reload = function()
-- swap table in place to keep references valid
for k in pairs(data) do data[k] = nil end
for k, v in pairs(load_data(name)) do
data[k] = v
end
end
}
function mt.__tostring(_)
return ("[PersistenceStore %s]"):format(name)
end
return data
end

72
src/lib/registry.lua Normal file
View File

@@ -0,0 +1,72 @@
local ser = require "binary-serialization"
function split(self, separator, max)
local out = {}
if self:len() > 0 then
max = max or -1
local field, start = 1, 1
local first, last = self:find(separator, start, true)
while first and max ~= 0 do
out[field] = self:sub(start, first - 1)
field = field + 1
start = last + 1
first , last = self:find(separator, start, true)
max = max - 1
end
out[field] = self:sub(start)
end
return out
end
local function fwrite(n, c)
local f = fs.open(n, "wb")
f.write(c)
f.close()
end
local function fread(n)
local f = fs.open(n, "rb")
local out = f.readAll()
f.close()
return out
end
local fsopen = fs.open
local registry_path = ".registry"
local registry = {}
function registry.set(key, value)
local path = split(key, ".")
local ok, orig_data
if fs.exists(registry_path) then
ok, orig_data = pcall(ser.deserialize, fread(registry_path))
if not ok then orig_data = {} end
else
orig_data = {}
end
local last_bit = table.remove(path)
local data = orig_data
for _, seg in ipairs(path) do
local new_bit = data[seg]
if type(new_bit) ~= "table" then data[seg] = {} end
data = data[seg]
end
data[last_bit] = value
fwrite(registry_path, ser.serialize(orig_data))
end
function registry.get(key)
if not fs.exists(registry_path) then return nil end
local path = split(key, ".")
local ok, data = pcall(ser.deserialize, fread(registry_path))
if not ok then data = {} end
for _, seg in ipairs(path) do
data = data[seg]
if not data then return nil end
end
return data
end
return registry

172
src/lib/sha256.lua Normal file
View File

@@ -0,0 +1,172 @@
-- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
-- By Anavrins
-- MIT License
-- Pastebin: https://pastebin.com/6UV4qfNF
-- Usage: https://pastebin.com/q2SQ7eRg
-- Last updated: March 27 2020
-- HMAC/PBKFD2 removed
local mod32 = 2^32
local band = bit32 and bit32.band or bit.band
local bnot = bit32 and bit32.bnot or bit.bnot
local bxor = bit32 and bit32.bxor or bit.bxor
local blshift = bit32 and bit32.lshift or bit.blshift
local upack = unpack
local function rrotate(n, b)
local s = n/(2^b)
local f = s%1
return (s-f) + f*mod32
end
local function brshift(int, by)
local s = int / (2^by)
return s - s%1
end
local H = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
}
local K = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function counter(incr)
local t1, t2 = 0, 0
if 0xFFFFFFFF - t1 < incr then
t2 = t2 + 1
t1 = incr - (0xFFFFFFFF - t1) - 1
else t1 = t1 + incr
end
return t2, t1
end
local function BE_toInt(bs, i)
return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0)
end
local function preprocess(data)
local len = #data
local proc = {}
data[#data+1] = 0x80
while #data%64~=56 do data[#data+1] = 0 end
local blocks = math.ceil(#data/64)
for i = 1, blocks do
proc[i] = {}
for j = 1, 16 do
proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
end
end
proc[blocks][15], proc[blocks][16] = counter(len*8)
return proc
end
local function digestblock(w, C)
for j = 17, 64 do
local v = w[j-15]
local s0 = bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18), brshift(w[j-15], 3))
local s1 = bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19),brshift(w[j-2], 10))
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
end
local a, b, c, d, e, f, g, h = upack(C)
for j = 1, 64 do
local S1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
local S0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (S0 + maj)%mod32
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
end
C[1] = (C[1] + a)%mod32
C[2] = (C[2] + b)%mod32
C[3] = (C[3] + c)%mod32
C[4] = (C[4] + d)%mod32
C[5] = (C[5] + e)%mod32
C[6] = (C[6] + f)%mod32
C[7] = (C[7] + g)%mod32
C[8] = (C[8] + h)%mod32
return C
end
local mt = {
__tostring = function(a) return string.char(unpack(a)) end,
__index = {
toHex = function(self, s) return ("%02x"):rep(#self):format(unpack(self)) end,
isEqual = function(self, t)
if type(t) ~= "table" then return false end
if #self ~= #t then return false end
local ret = 0
for i = 1, #self do
ret = bit32.bor(ret, bxor(self[i], t[i]))
end
return ret == 0
end
}
}
local function toBytes(t, n)
local b = {}
for i = 1, n do
b[(i-1)*4+1] = band(brshift(t[i], 24), 0xFF)
b[(i-1)*4+2] = band(brshift(t[i], 16), 0xFF)
b[(i-1)*4+3] = band(brshift(t[i], 8), 0xFF)
b[(i-1)*4+4] = band(t[i], 0xFF)
end
return setmetatable(b, mt)
end
local function digest(data)
local data = data or ""
data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
data = preprocess(data)
local C = {upack(H)}
for i = 1, #data do C = digestblock(data[i], C) end
return toBytes(C, 8)
end
local function hmac(data, key)
local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
local blocksize = 64
key = #key > blocksize and digest(key) or key
local ipad = {}
local opad = {}
local padded_key = {}
for i = 1, blocksize do
ipad[i] = bxor(0x36, key[i] or 0)
opad[i] = bxor(0x5C, key[i] or 0)
end
for i = 1, #data do
ipad[blocksize+i] = data[i]
end
ipad = digest(ipad)
for i = 1, blocksize do
padded_key[i] = opad[i]
padded_key[blocksize+i] = ipad[i]
end
return digest(padded_key)
end
return {
hmac = hmac,
digest = digest
}

94
src/lib/stack_trace.lua Normal file
View File

@@ -0,0 +1,94 @@
-- see pastebin.com/NdUKJ07j for license info
-- mostly written by SquidDev, bodged by gollark/osmarks
local type = type
local debug_traceback = type(debug) == "table" and type(debug.traceback) == "function" and debug.traceback
local _pcall, _xpcall = pcall, xpcall
local function traceback(x)
-- Attempt to detect error() and error("xyz", 0).
-- This probably means they're erroring the program intentionally and so we
-- shouldn't display anything.
if x == nil or (type(x) == "string" and not x:find(":%d+:")) or type(x) ~= "string" then
return x
end
if debug_traceback then
-- The parens are important, as they prevent a tail call occuring, meaning
-- the stack level is preserved. This ensures the code behaves identically
-- on LuaJ and PUC Lua.
return (debug_traceback(tostring(x), 2))
else
local level = 3
local out = { tostring(x), "stack traceback:" }
while true do
local _, msg = _pcall(error, "", level)
if msg == "" then break end
out[#out + 1] = " " .. msg
level = level + 1
end
return table.concat(out, "\n")
end
end
local function trim_traceback(target, marker)
local target = tostring(target)
local ttarget, tmarker = {}, {}
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
-- Trim identical suffixes
local t_len, m_len = #ttarget, #tmarker
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
table.remove(ttarget, t_len)
t_len, m_len = t_len - 1, m_len - 1
end
-- Trim elements from this file and xpcall invocations
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
table.remove(ttarget, t_len)
t_len = t_len - 1
end
return ttarget
end
--- Run a function with
local function xpcall_with(fn, ...)
local args = {...}
-- So this is rather grim: we need to get the full traceback and current one and remove
-- the common prefix
local trace
local res = table.pack(_xpcall(function() return fn(unpack(args)) end, traceback)) if not res[1] then trace = traceback("stack_trace.lua:1:") end
local ok, err = res[1], res[2]
if not ok and err ~= nil then
trace = trim_traceback(err, trace)
-- Find the position where the stack traceback actually starts
local trace_starts
for i = #trace, 1, -1 do
if trace[i] == "stack traceback:" then trace_starts = i; break end
end
-- If this traceback is more than 15 elements long, keep the first 9, last 5
-- and put an ellipsis between the rest
local max = 15
if trace_starts and #trace - trace_starts > max then
local keep_starts = trace_starts + 10
for i = #trace - trace_starts - max, 0, -1 do table.remove(trace, keep_starts + i) end
table.insert(trace, keep_starts, " ...")
end
return false, table.concat(trace, "\n")
end
return table.unpack(res, 1, res.n)
end
_G.pcall = xpcall_with
return xpcall_with

40
src/lib/urandom.lua Normal file
View File

@@ -0,0 +1,40 @@
-- ComputerCraft Event Entropy Extractor
local sha256 = require("sha256")
local entropy = tostring(math.random()) .. tostring(os.epoch("utc"))
local maxEntropySize = 1024
local state = ""
if process then
process.spawn(function()
while true do
local event = {coroutine.yield()}
entropy = {
entropy,
event[1],
tostring(event[2]),
tostring(event[3]),
tostring(event[4]),
tostring(os.epoch("utc")),
tostring({}),
}
entropy = table.concat(entropy, "|")
if #entropy > maxEntropySize then
state = sha256.digest(entropy)
entropy = tostring(state)
end
end
end, "entropy")
end
os.urandom = function()
os.queueEvent("random")
os.pullEvent()
local result = sha256.hmac("out", state)
state = sha256.digest(state)
return result
end

469
src/lib/yafss.lua Normal file
View File

@@ -0,0 +1,469 @@
-- Deep-copy a table
local function copy(tabl)
local new = {}
for k, v in pairs(tabl) do
if type(v) == "table" and tabl ~= v then
new[k] = copy(v)
else
new[k] = v
end
end
return new
end
-- Deep-map all values in a table
local function deepmap(table, f, path)
local path = path or ""
local new = {}
for k, v in pairs(table) do
local thisp = path .. "." .. k
if type(v) == "table" and v ~= table then -- bodge it to not stackoverflow
new[k] = deepmap(v, f, thisp)
else
new[k] = f(v, k, thisp)
end
end
return new
end
-- Takes a list of keys to copy, returns a function which takes a table and copies the given keys to a new table
local function copy_some_keys(keys)
return function(from)
local new = {}
for _, key_to_copy in pairs(keys) do
local x = from[key_to_copy]
if type(x) == "table" then
x = copy(x)
end
new[key_to_copy] = x
end
return new
end
end
-- Simple string operations
local function starts_with(s, with)
return string.sub(s, 1, #with) == with
end
local function ends_with(s, with)
return string.sub(s, -#with, -1) == with
end
local function contains(s, subs)
return string.find(s, subs) ~= nil
end
-- Maps function f over table t. f is passed the value and key and can return a new value and key.
local function map(f, t)
local mapper = function(t)
local new = {}
for k, v in pairs(t) do
local new_v, new_k = f(v, k)
new[new_k or k] = new_v
end
return new
end
if t then return mapper(t) else return mapper end
end
-- Copies stuff from t2 into t1
local function add_to_table(t1, t2)
for k, v in pairs(t2) do
if type(v) == "table" and v ~= t2 and v ~= t1 then
if not t1[k] then t1[k] = {} end
add_to_table(t1[k], v)
else
t1[k] = v
end
end
end
-- Convert path to canonical form
local function canonicalize(path)
return fs.combine(path, "")
end
-- Checks whether a path is in a directory
local function path_in(p, dir)
return starts_with(canonicalize(p), canonicalize(dir))
end
local function make_mappings(root)
return {
["/disk"] = "/disk",
["/rom"] = "/rom",
default = root
}
end
local function get_root(path, mappings)
for mapfrom, mapto in pairs(mappings) do
if path_in(path, mapfrom) then
return mapto, mapfrom
end
end
return mappings.default, "/"
end
-- Escapes lua patterns in a string. Should not be needed, but lua is stupid so the only string.replace thing is gsub
local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])'
local function escape(str)
return str:gsub(quotepattern, "%%%1")
end
local function strip(p, root)
return p:gsub("^" .. escape(canonicalize(root)), "")
end
local function resolve_path(path, mappings)
local root, to_strip = get_root(path, mappings)
local newpath = strip(fs.combine(root, path), to_strip)
if path_in(newpath, root) then return newpath end
return resolve_path(newpath, mappings)
end
local function segments(path)
local segs, rest = {}, ""
repeat
table.insert(segs, 1, fs.getName(rest))
rest = fs.getDir(rest)
until rest == ""
return segs
end
local function combine(segs)
local out = ""
for _, p in pairs(segs) do
out = fs.combine(out, p)
end
return out
end
local function difference(p1, p2)
local s1, s2 = segments(p1), segments(p2)
if #s2 == 0 then return combine(s1) end
local segs = {}
for _, p in pairs(s1) do
local item = table.remove(s1, 1)
table.insert(segs, item)
if p == s2[1] then break end
end
return combine(segs)
end
-- magic from http://lua-users.org/wiki/SplitJoin
-- split string into lines
local function lines(str)
local t = {}
local function helper(line)
table.insert(t, line)
return ""
end
helper((str:gsub("(.-)\r?\n", helper)))
return t
end
-- Fetch the contents of URL "u"
local function fetch(u)
local h = http.get(u)
local c = h.readAll()
h.close()
return c
end
-- Make a read handle for a string
local function make_handle(text)
local lines = lines(text)
local h = {line = 0}
function h.close() end
function h.readLine() h.line = h.line + 1 return lines[h.line] end
function h.readAll() return text end
return h
end
-- Get a path from a filesystem overlay
local function path_in_overlay(overlay, path)
return overlay[canonicalize(path)]
end
local this_level_env = _G
-- Create a modified FS table which confines you to root and has some extra read-only pseudofiles.
local function create_FS(root, overlay)
local mappings = make_mappings(root)
local new_overlay = {}
for k, v in pairs(overlay) do
new_overlay[canonicalize(k)] = v
end
local function lift_to_sandbox(f, n)
return function(...)
local args = map(function(x) return resolve_path(x, mappings) end, {...})
return f(table.unpack(args))
end
end
local new = copy_some_keys {"getDir", "getName", "combine"} (fs)
function new.isReadOnly(path)
return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom")
end
function new.open(path, mode)
if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then
error "Access denied"
else
local overlay_data = path_in_overlay(new_overlay, path)
if overlay_data then
if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end
return make_handle(overlay_data), "YAFSS overlay"
end
return fs.open(resolve_path(path, mappings), mode)
end
end
function new.exists(path)
if path_in_overlay(new_overlay, path) ~= nil then return true end
return fs.exists(resolve_path(path, mappings))
end
function new.overlay()
return map(function(x)
if type(x) == "function" then return x(this_level_env)
else return x end
end, new_overlay)
end
function new.list(dir)
local sdir = canonicalize(resolve_path(dir, mappings))
local contents = fs.list(sdir)
for opath in pairs(new_overlay) do
if fs.getDir(opath) == sdir then
table.insert(contents, fs.getName(opath))
end
end
return contents
end
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs)))
function new.find(wildcard)
local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua
local segment = spec:match('([^/]*)'):gsub('/', '')
local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.'):gsub("-", "%%-") .. '$'
if new.isDir(path) then
for _, file in ipairs(new.list(path)) do
if file:match(pattern) then
local f = new.combine(path, file)
if new.isDir(f) then
recurse_spec(results, f, spec:sub(#segment + 2))
end
if spec == segment then
table.insert(results, f)
end
end
end
end
end
local results = {}
recurse_spec(results, '', wildcard)
return results
end
function new.dump(dir)
local dir = dir or "/"
local out = {}
for _, f in pairs(new.list(dir)) do
local path = fs.combine(dir, f)
local to_add = {
n = f,
t = "f"
}
if new.isDir(path) then
to_add.c = new.dump(path)
to_add.t = "d"
else
local fh = new.open(path, "r")
to_add.c = fh.readAll()
fh.close()
end
table.insert(out, to_add)
end
return out
end
function new.load(dump, root)
local root = root or "/"
for _, f in pairs(dump) do
local path = fs.combine(root, f.n)
if f.t == "d" then
new.makeDir(path)
new.load(f.c, path)
else
local fh = new.open(path, "w")
fh.write(f.c)
fh.close()
end
end
end
return new
end
local allowed_APIs = {
"term",
"http",
"pairs",
"ipairs",
-- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment
"peripheral",
"table",
"string",
"type",
"setmetatable",
"getmetatable",
"os",
"sleep",
"pcall",
"xpcall",
"select",
"tostring",
"tonumber",
"coroutine",
"next",
"error",
"math",
"redstone",
"rs",
"assert",
"unpack",
"bit",
"bit32",
"turtle",
"pocket",
"ccemux",
"config",
"commands",
"rawget",
"rawset",
"rawequal",
"~expect",
"__inext",
"periphemu",
}
local gf, sf = getfenv, setfenv
-- Takes the root directory to allow access to,
-- a map of paths to either strings containing their contents or functions returning them
-- and a table of extra APIs and partial overrides for existing APIs
local function make_environment(root_directory, overlay, API_overrides)
local environment = copy_some_keys(allowed_APIs)(_G)
environment.fs = create_FS(root_directory, overlay)
-- if function is not from within the VM, return env from within sandbox
function environment.getfenv(arg)
local env
if type(arg) == "number" then return gf() end
if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then
return gf()
else
return env
end
end
--[[
Fix PS#AD2A532C
Allowing `setfenv` to operate on any function meant that privileged code could in some cases be manipulated to leak information or operate undesirably. Due to this, we restrict it, similarly to getfenv.
]]
function environment.setfenv(fn, env)
local nenv = gf(fn)
if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then
return false
end
return sf(fn, env)
end
function environment.load(code, file, mode, env)
return load(code, file or "@<input>", mode or "t", env or environment)
end
if debug then
environment.debug = copy_some_keys {
"getmetatable",
"setmetatable",
"traceback",
"getinfo",
"getregistry"
}(debug)
end
environment._G = environment
environment._ENV = environment
environment._HOST = string.format("YAFSS on %s", _HOST)
local upper = _G.hypercalls
environment.hypercalls = {}
function environment.hypercalls.upper()
return upper
end
function environment.hypercalls.layers()
if upper then return upper.layers() + 1
else return 1 end
end
function environment.os.shutdown()
os.queueEvent("power_state", "shutdown")
while true do coroutine.yield() end
end
function environment.os.reboot()
os.queueEvent("power_state", "reboot")
while true do coroutine.yield() end
end
add_to_table(environment, copy(API_overrides))
return environment
end
local function run(root_directory, overlay, API_overrides, init)
if type(init) == "table" and init.URL then init = fetch(init.URL) end
init = init or fetch "https://pastebin.com/raw/wKdMTPwQ"
local running = true
while running do
parallel.waitForAny(function()
local env = make_environment(root_directory, overlay, API_overrides)
env.init_code = init
local out, err = load(init, "@[init]", "t", env)
env.hypercalls.run = out
if not out then error(err) end
local ok, err = pcall(out)
if not ok then printError(err) end
end,
function()
while true do
local event, state = coroutine.yield "power_state"
if event == "power_state" then -- coroutine.yield behaves weirdly with terminate
if process then
local this_process = process.running.ID
for _, p in pairs(process.list()) do
if p.parent and p.parent.ID == this_process then
process.signal(p.ID, process.signals.KILL)
end
end
end
if state == "shutdown" then running = false return
elseif state == "reboot" then return end
end
end
end)
end
end
return run