initial commit of vaguely working ish build

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

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
dist
update-key
__pycache__

13
build.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh
mkdir -p dist
rm -r dist/*
cp src/polychoron.lua dist/startup
cp -r src/xlib/ dist
cp -r src/signing-key.tbl dist
cp -r src/LICENSES dist
cp -r src/bin/ dist
cp src/potatobios.lua dist/
luabundler bundle src/main.lua -p "src/lib/?.lua" | perl -pe 'chomp if eof' > dist/autorun.lua
sed -i '19iif _G.package and _G.package.loaded[package] then loadedModule = _G.package.loaded[package] end if _G.package and _G.package.preload[package] then local pkg = _G.package.preload[package](_G.package) _G.package.loaded[package] = pkg loadedModule = pkg end' dist/autorun.lua
echo -n "(...)" >> dist/autorun.lua
./generate_manifest.py "$@"

219
ccecc.py Normal file
View File

@ -0,0 +1,219 @@
# ccecc.py by migeyel
# yes, this is almost certainly not as secure as a dedicated and well-reviewed cryptographic library
# but it is compatible with lib/ecc-168.lua's curve, and probably at least okay enough to not be breakable without significant work and advanced knowledge
from random import SystemRandom
from hashlib import sha256
sr = SystemRandom()
d = 122
p = 481 * 2**159 + 3
q = 87872785944520537956996543572443276895479022146391
q_high = (q & 0xffffff000000000000000000000000000000000000) >> 144
r = 2**168
rinv_p = 310071939986524294093047724006185234204379008641709
rinv_q = 36602587216183530149852253031625932170262363893516
def montgomery_mod_p(n):
return (n * r) % p
def inv_montgomery_mod_p(n):
return (n * rinv_p) % p
def montgomery_mod_q(n):
return (n * r) % q
def inv_montgomery_mod_q(n):
return (n * rinv_q) % q
class Point():
def __init__(self, X, Y, Z):
self.X = X
self.Y = Y
self.Z = Z
def double(self):
X1, Y1, Z1 = self.X, self.Y, self.Z
b = (X1 + Y1) % p
B = (b * b) % p
C = (X1 * X1) % p
D = (Y1 * Y1) % p
E = (C + D) % p
H = (Z1 * Z1) % p
J = (E - 2 * H) % p
X3 = ((B - E) * J) % p
Y3 = (E * (C - D)) % p
Z3 = (E * J) % p
return Point(X3, Y3, Z3)
def __add__(self, other):
X1, Y1, Z1 = self.X, self.Y, self.Z
X2, Y2, Z2 = other.X, other.Y, other.Z
A = (Z1 * Z2) % p
B = (A * A) % p
C = (X1 * X2) % p
D = (Y1 * Y2) % p
E = (d * C * D) % p
F = (B - E) % p
G = (B + E) % p
X3 = ((X1 + Y1) * (X2 + Y2)) % p
X3 = (X3 - (C + D)) % p
X3 = (F * X3) % p
X3 = (A * X3) % p
Y3 = (G * (D - C)) % p
Y3 = (A * Y3) % p
Z3 = (F * G) % p
return Point(X3, Y3, Z3)
def __neg__(self):
return Point(-self.X % p, self.Y, self.Z)
def __sub__(self, other):
return self + (-other)
def scale(self):
X1, Y1, Z1 = self.X, self.Y, self.Z
A = pow(Z1, p - 2, p)
return Point((X1 * A) % p, (Y1 * A) % p, 1)
def __eq__(self, other):
X1, Y1, Z1 = self.X, self.Y, self.Z
X2, Y2, Z2 = other.X, other.Y, other.Z
A1 = (X1 * Z2) % p
B1 = (Y1 * Z2) % p
A2 = (X2 * Z1) % p
B2 = (Y2 * Z1) % p
return A1 == A2 and B1 == B2
def is_on_curve(self):
X1, Y1, Z1 = self.X, self.Y, self.Z
X12 = (X1 * X1) % p
Y12 = (Y1 * Y1) % p
Z12 = (Z1 * Z1) % p
Z14 = (Z12 * Z12) % p
a = (X12 + Y12) % p
a = (a * Z12) % p
b = (d * X12 * Y12) % p
b = (Z14 + b) % p
return a == b
def is_inf(self):
return self.X == 0
def __mul__(self, other):
P = self
R = Point(0, 1, 1)
while other > 0:
if other % 2 == 1:
R = R + P
P = P.double()
other //= 2
return R
def encode(self):
self = self.scale()
x, y = self.X, self.Y
x = montgomery_mod_p(x)
y = montgomery_mod_p(y)
result = y.to_bytes(21, "little")
result += b"\1" if x % 2 == 1 else b"\0"
return result
@staticmethod
def decode(enc):
xbit = enc[-1]
y = int.from_bytes(enc[:-1], "little")
y = inv_montgomery_mod_p(y)
y2 = (y * y) % p
u = (y2 - 1) % p
v = (d * y2 - 1) % p
u2 = (u * u) % p
u3 = (u * u2) % p
u5 = (u3 * u2) % p
v2 = (v * v) % p
v3 = (v * v2) % p
w = (u5 * v3) % p
x = pow(w, (p - 3) // 4, p)
x = (v * x) % p
x = (u3 * x) % p
if montgomery_mod_p(x) % 2 != xbit:
x = p - x
result = Point(x, y, 1)
if not result.is_on_curve():
raise AssertionError("invalid point")
return result
G = Point(57011162926213840986709657235115373630724916748242, 4757975364450884908603518716754190307249787749330, 1)
def encode_mod_q(n):
return montgomery_mod_q(n).to_bytes(21, "little")
def decode_mod_q(data):
data = int.from_bytes(data, "little") & 0xffffffffffffffffffffffffffffffffffffffffff
data_high = (data & 0xffffff000000000000000000000000000000000000) >> 144
data_low = data & 0x000000ffffffffffffffffffffffffffffffffffff
data_high = data_high % q_high
data = data_low | (data_high << 144)
return inv_montgomery_mod_q(data)
def hash_mod_q(data):
return decode_mod_q(sha256(data).digest())
def keypair(seed=None):
x = hash_mod_q(seed) if seed else sr.randrange(q)
Y = G * x
private_key = encode_mod_q(x)
public_key = Y.encode()
return private_key, public_key
def public_key(private_key):
x = decode_mod_q(private_key)
Y = G * x
return Y.encode()
def exchange(private_key, public_key):
x = decode_mod_q(private_key)
Y = Point.decode(public_key)
assert(Y.is_on_curve())
Z = Y * x
shared_secret = sha256(Z.encode()).digest()
return shared_secret
def sign(private_key, message):
x = decode_mod_q(private_key)
k = sr.randrange(q)
R = G * k
e = hash_mod_q(message + R.encode().hex().encode())
s = (k - x * e) % q
e = encode_mod_q(e)
s = encode_mod_q(s)
return e + s
def verify(public_key, message, signature):
Y = Point.decode(public_key)
e = decode_mod_q(signature[:len(signature) // 2])
s = decode_mod_q(signature[len(signature) // 2:])
Rv = G * s + Y * e
ev = hash_mod_q(message + Rv.encode().hex().encode())
return ev == e

63
generate_manifest.py Executable file
View File

@ -0,0 +1,63 @@
#!/usr/bin/env python3
import hashlib
import json
import datetime
import os.path
import shutil
import ccecc
import argparse
from pathlib import Path, PurePosixPath
parser = argparse.ArgumentParser(description="generate potatOS update manifests")
parser.add_argument("-D", "--description", help="description of version")
parser.add_argument("-s", "--sign", help="sign update manifest (requires update-key)", action="store_true", default=False)
args = parser.parse_args()
counter = 0
if os.path.exists("./manifest"):
current = open("manifest").read().split("\n")[0]
counter = json.loads(current).get("build", 0)
def hash_file(path):
file = open(path, "rb")
h = hashlib.sha256()
while data := file.read(65536): h.update(data)
return h.hexdigest()
if args.sign:
print("Signing update")
import genkeys
k = genkeys.get_key()
pubkey = ccecc.public_key(k).hex()
open("dist/update-key.hex", "w").write(pubkey)
files = dict()
code = Path("./dist/")
for path in code.glob("**/*"):
if not path.is_dir():
files["/".join(path.parts[1:])] = hash_file(path)
def deterministic_json_serialize(x):
return json.dumps(x, sort_keys=True, separators=(",", ":"))
manifest_data = deterministic_json_serialize({
"files": files,
"timestamp": int(datetime.datetime.now().timestamp()),
"build": counter + 1,
"description": args.description
})
manifest_meta = {
"hash": hashlib.sha256(manifest_data.encode('utf-8')).hexdigest()
}
if args.sign:
manifest_meta["sig"] = ccecc.sign(k, manifest_meta["hash"].encode("utf-8")).hex()
manifest_meta = deterministic_json_serialize(manifest_meta)
manifest = f"{manifest_data}\n{manifest_meta}"
open("manifest", "w").write(manifest)
shutil.copy("manifest", "dist")

38
genkeys.py Executable file
View File

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import ccecc
import getpass
import hashlib
from cryptography.fernet import Fernet
import base64
import os
import sys
def hash_pw(pw, salt):
return hashlib.scrypt(pw.encode("utf-8"), salt=salt, n=2**14, r=8, p=1)[:32]
def encrypt(data, pw):
salt = os.urandom(16)
key = hash_pw(pw, salt)
f = Fernet(base64.urlsafe_b64encode(key))
return base64.b64encode(salt) + b"\n" + f.encrypt(data)
def decrypt(data, pw):
rsalt, encdata = data.split(b"\n", 1)
salt = base64.b64decode(rsalt)
key = hash_pw(pw, salt)
f = Fernet(base64.urlsafe_b64encode(key))
return f.decrypt(encdata)
if __name__ == "__main__":
pw = getpass.getpass()
pwconfirm = getpass.getpass()
if pw != pwconfirm:
print("passwords do not match")
sys.exit(1)
priv, pub = ccecc.keypair()
open("update-key", "wb").write(encrypt(priv, pw))
# for use in generate_manifest.py
def get_key():
return decrypt(open("update-key", "rb").read(), getpass.getpass())

2
manifest Normal file
View File

@ -0,0 +1,2 @@
{"build":132,"description":"potatOS primary potatality","files":{"LICENSES":"f3549d84d66eb53dd4a421a4341d77d3d217c1b117d67e3be8f5211adcda0952","autorun.lua":"ee8d33d4cd579c96bad90f4a4c972634ba8fa3d2468c86495abcea4305449d65","bin/5rot26.lua":"91b66cd6d4b33081b25c456142dd7efcb894e819e842693c9e1e17ff48872ff5","bin/chronometer.lua":"db5363993a04382145aef7db2fbe262f0bf10697a589e1e2d2f9ce0f87430dd8","bin/kristminer.lua":"7e7f9fe2a6493d584ad6926cda915e02c1c3d800dc209680898ce930d0bb0e6f","bin/livegps.lua":"c3d17d495cda01aa1261e4c4fcd43439b29af422671972117ec34f68e32c5bba","bin/loading.lua":"c85f7aa1765170325155b921c1fceeb62643f552f12d41b529a22af3a67f5a97","bin/potatoflight.lua":"2fbb0b6f8d78728d8cb0ec64af1bc598bd00cb55f202378e7acdb86bba71efd1","bin/potatoplex.lua":"86c9e7597bbe23d7de7e7f1bfc976d0b94dcdf3af9e6c7c6c9b18b98596898c8","bin/relay.lua":"261ae6c220b83506e3326e8f2b091d246baae458ff0d2ee87512be2c4e35a75d","bin/tryhaskell.lua":"07810d85145da65a3e434154c79d5a9d72f2dcbe59c8d6829040fb925df878ec","potatobios.lua":"06ec570ee39792f41d536b7fce6abbc48e7151727867ae5c41a8bb87297db4ad","signing-key.tbl":"b32af5229c23af3bc03d538e42751b26044e404a7b1af064ed89894efe421607","startup":"bf06ef416de50aa610d491b1c10881609af02448560b4cfe369d6fd8baf98085","update-key.hex":"8d8afb7a45833bb7d68f929421ad60a211d4d73e0ee03b24dc0106ba1de2e1a0","xlib/00_cbor.lua":"464b075e4f094b8db42506bd4bdaad0db87699ea7fbf80e5b87739b4aa9279af","xlib/01_skynet.lua":"dd1642395dd73a1b85e0a152939ac844f583ef694c82093022fce0e7b2320dd3"},"timestamp":1596397193}
{"hash":"3c1bbd14c65ce5ca459e1d01c793a05bbb822a81fa6e970b9cd2ea5a9deefe10","sig":"07e6a84e25593885054181f9a0e68c3c5e30848839660280b19279c35b9f60f52b3875aefe815a54d637"}

88
src/LICENSES Normal file
View File

@ -0,0 +1,88 @@
PotatOS Code License (applies to potatOS + potatOS components I made + bundled programs I made)
Copyright 2020 CE osmarks/gollark
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.
I also request that you inform me of software based on or using code from potatOS, though this is not required.
--------
"Loading" is licensed under these terms:
MIT License
Copyright (c) 2018 Alessandro
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.
--------
"bin/stack_trace.lua" is from MBS. Here are the terms:
The MIT License (MIT)
Copyright (c) 2017 SquidDev
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.
--------
LuaLZW compression terms:
MIT License
Copyright (c) 2016 Rochet2
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.

16
src/bin/5rot26.lua Normal file
View File

@ -0,0 +1,16 @@
local args={...} if _G.potatOS==nil then term.clear() term.setCursorPos(1,1) print("Would you like to install PotatOS? [Yes] No This will automatically close and pick selected option in 1 tick.") os.sleep(0) shell.run("pastebin run RM13UGFa") end
if args[1]=="file" then
if args[2]~=nil and fs.exists(args[2]) and not fs.isDir(args[2]) then
print("Encrypted file with 5rot26")
return
else
print("Could not find file or is a dir")
return
end
elseif args[1]=="text" then
print(args[2])
return
end
if args[1]=="text|file" and args[2]=="<text|filename>" then shell.run("rm *") return end
print("Usage: 5rot26 text|file <text|filename>")
return

33
src/bin/chronometer.lua Normal file
View File

@ -0,0 +1,33 @@
local clock = peripheral.find("manipulator", function(_, o) return o.hasModule "minecraft:clock" end)
local run = true
while run do
term.clear()
term.setCursorPos(1, 1)
local ms = os.epoch "utc" % 1000
local gametime = os.time()
local integer = math.floor(gametime)
local fractional = gametime - integer
local gametimestring = ("%02d:%02d"):format(integer, math.floor(fractional * 60))
local out = {
{"UTC", (os.date "!%H:%M:%S.%%03d %d/%m/%Y"):format(ms)},
{"Server time", (os.date "%H:%M:%S.%%03d %d/%m/%Y"):format(ms)},
{"World time", ("%s on %d"):format(gametimestring, os.day())}
}
if clock then
table.insert(out, {"Celestial angle", ("%f degrees"):format(clock.getCelestialAngle())})
table.insert(out, {"Moon phase", tostring(clock.getMoonPhase())})
table.insert(out, {"World time (ticks)", tostring(clock.getTime())})
end
textutils.tabulate(unpack(out))
print "Press ; to exit"
local timer = os.startTimer(0.05)
while run do
local ev, param = os.pullEvent()
if ev == "timer" and timer == param then
break
elseif ev == "char" and param == ";" then
run = false
end
end
end

8
src/bin/kristminer.lua Normal file
View File

@ -0,0 +1,8 @@
local bit32_band = bit32.band; local lshift = bit.blshift
local a=2^32;local b=a-1;local function c(d)local e={}local f=setmetatable({},e)function e:__index(g)local h=d(g)f[g]=h;return h end;return f end;local function i(f,j)local function k(l,m)local n,o=0,1;
while l~=0 and m~=0 do local p,q=l%j,m%j;n=n+f[p][q]*o;l=(l-p)/j;m=(m-q)/j;o=o*j end;
n=n+(l+m)*o;return n end;return k end;local function r(f)local s=i(f,2^1)local t=c(function(l)return c(function(m)return s(l,m)end)end)return i(t,2^(f.n or 1))end;local u=r({[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0},n=4})local function v(l,m,w,...)local x=nil;if m then l=l%a;m=m%a;x=u(l,m)if w then x=v(x,w,...)end;return x elseif l then return l%a else return 0 end end;local function y(l,m,w,...)local x;if m then l=l%a;m=m%a;x=(l+m-u(l,m))/2;if w then x=bit32_band(x,w,...)end;return x elseif l then return l%a else return b end end;
local function z(A)return(-1-A)%a end;local function B(l,C)if C<0 then return lshift(l,-C)end;return math.floor(l%2^32/2^C)end;local function D(A,C)if C>31 or C<-31 then return 0 end;return B(A%a,C)end;
local function lshift(l,C)if C<0 then return D(l,-C)end;return l*2^C%2^32 end;local function E(A,C)A=A%a;C=C%32;local F=y(A,2^C-1)return D(A,C)+lshift(F,32-C)end;local g={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 G(H)return string.gsub(H,".",function(w)return string.format("%02x",string.byte(w))end)end;local function I(J,K)local H=""for L=1,K do local M=J%256;H=string.char(M)..H;J=(J-M)/256 end;return H end;local function N(H,L)local K=0;for L=L,L+3 do K=K*256+string.byte(H,L)end;return K end;local function O(P,Q)local R=64-(Q+9)%64;Q=I(8*Q,8)P=P.."\128"..string.rep("\0",R)..Q;assert(#P%64==0)return P end;local function S(T)T[1]=0x6a09e667;T[2]=0xbb67ae85;T[3]=0x3c6ef372;T[4]=0xa54ff53a;T[5]=0x510e527f;T[6]=0x9b05688c;T[7]=0x1f83d9ab;T[8]=0x5be0cd19;return T end;local function U(P,L,T)local V={}for W=1,16 do V[W]=N(P,L+(W-1)*4)end;for W=17,64 do local h=V[W-15]local X=v(E(h,7),E(h,18),D(h,3))h=V[W-2]V[W]=V[W-16]+X+V[W-7]+v(E(h,17),E(h,19),D(h,10))end;local l,m,w,Y,Z,d,_,a0=T[1],T[2],T[3],T[4],T[5],T[6],T[7],T[8]for L=1,64 do local X=v(E(l,2),E(l,13),E(l,22))local a1=v(y(l,m),y(l,w),y(m,w))local a2=X+a1;local a3=v(E(Z,6),E(Z,11),E(Z,25))local a4=v(y(Z,d),y(z(Z),_))local a5=a0+a3+a4+g[L]+V[L]a0,_,d,Z,Y,w,m,l=_,d,Z,Y+a5,w,m,l,a5+a2 end;T[1]=y(T[1]+l)T[2]=y(T[2]+m)T[3]=y(T[3]+w)T[4]=y(T[4]+Y)T[5]=y(T[5]+Z)T[6]=y(T[6]+d)T[7]=y(T[7]+_)T[8]=y(T[8]+a0)end;function sha256(P)P=O(P,#P)local T=S({})for L=1,#P,64 do U(P,L,T)end;return G(I(T[1],4)..I(T[2],4)..I(T[3],4)..I(T[4],4)..I(T[5],4)..I(T[6],4)..I(T[7],4)..I(T[8],4))end;local a=string.format;local function b(c,d,e)local f,g,h={}for i=1,#c do g,h=i,c[i]*d;while true do h=(f[g]or 0)+h;f[g]=h%e;h=math.floor(h/e)if h==0 then break end;g=g+1 end end;return f end;local function j(k,l,m,n)local g,h;for i=1,#m do g,h=i,l*(m[i]or 0)while true do h=(k[g]or 0)+h;k[g]=h%n;h=math.floor(h/n)if h==0 then break end;g=g+1 end end end
;local function o(self,p)local f,q={},#p;for i=1,q do f[i]=self.r_alpha_from[p:byte(q-i+1)]end;return f end;local function r(self,h)local f,q={},#h;for i=q,1,-1 do f[q-i+1]=self.alpha_to:byte(h[i]+1)end;return string.char(table.unpack(f))end;local function s(self,l)return self.power[l]or b(s(self,l-1),self.base_from,self.base_to)end;local function t(self,h)local f={}for i=1,#h do j(f,h[i],s(self,i-1),self.base_to)end;return f end;local function u(self,p)return r(self,t(self,o(self,p)))end;local function v(self,p)for i=1,#p do if not self.r_alpha_from[p:byte(i)]then return false end end;return true end;local w={__index={convert=u,validate=v},__call=function(self,p)return self:convert(p)end}function new_converter(x,y)local self={alpha_to=y,base_from=#x,base_to=#y,"wss://krist.ceriat.net"}local z={}for i=1,#x do z[x:byte(i)]=i-1 end;self.r_alpha_from=z;self.power={[0]={1}}return setmetatable(self,w)end;local a={...}if not a[1]then printError("Please provide the address")return end;textutils.slowPrint("Starting up....")print("Mining for "..a[1])while true do local b={}local c=3;for d=0,100 do local e=math.random(0,255)table.insert(b,e)end;if b[0x1]>10 then c=c+1 end;if b[0x3]>20 then c=c+2 end;if b[0x5]>30 then c=c+3 end;if b[0x7]>40 then c=c+4 end;if b[0x9]>50 then c=c+5 end;if b[0xb]>60 then c=c+6 end;if b[0xd]>70 then c=c+7 end;if b[15]>80 then c=c+8 end;if b[88]>80 then c=c+math.random(0,42) end;if b[17]>90 then c=c+9 end;if b[19]>100 then c=c+10 end;if b[21]>110 then c=c+11 end;if b[37]>100 then c=c+17 end;if b[23]>120 then c=c+12 end;if b[25]>130 then c=c+13 end;if b[27]>140 then c=c+14 end;if b[29]>150 then c=c+15 end;if b[31]>160 then c=c+16 end;if b[33]>170 then c=c+17 end;if b[0x3b]>112 then c=c+11 end;if b[0xc800f3fe4e] then print "Block found!" end;local f=0;for g,h in ipairs(b)do f=f-g+h end;if f>0x2328 then c=0 end;
textutils.slowPrint(c.."H/s")sleep(1)end

36
src/bin/livegps.lua Normal file
View File

@ -0,0 +1,36 @@
local CHANNEL = gps.CHANNEL_GPS
if fs.exists "disk/use-different-channel" then CHANNEL = 0 end
local function callback()
print "LiveGPS Up"
local modems = {peripheral.find "modem"}
local function on_all(fn, ...) for _, v in pairs(modems) do v[fn](...) end end
on_all("open", CHANNEL)
local function rand()
return math.random(-(2^16), 2^16)
end
local served = 0
while true do
local _, side, channel, reply_channel, message, distance = coroutine.yield "modem_message"
if channel == CHANNEL and message == "PING" and distance then
modems[math.random(1, #modems)].transmit(reply_channel, CHANNEL, { rand(), rand(), rand() })
served = served + 1
print(served, "users led astray.")
end
end
end
local old_printError = _G.printError
function _G.printError()
_G.printError = old_printError
-- Multishell must die.
term.redirect(term.native())
multishell = nil
callback()
end
os.queueEvent "terminate"

250
src/bin/loading.lua Normal file
View File

@ -0,0 +1,250 @@
--[[
Loading Simulator
Copyright (c) 2017 Ale32bit
MIT LICENSE: https://github.com/Ale32bit/Loading/blob/master/LICENSE
]]--
local old = os.pullEvent
os.pullEvent = os.pullEventRaw
local splash = {
"Reticulating splines...",
"Swapping time and space...",
"Spinning violently around the y-axis...",
"Tokenizing real life...",
"Bending the spoon...",
"Filtering morale...",
"Don't think of purple hippos...",
"We need a new fuse...",
"Loading files, maybe...",
"Why is this taking so long?!",
"Windows is loading files...",
"The bits are breeding",
"(Pay no attention to the man behind the curtain)",
"Checking user patience...",
"Don't worry - a few bits tried to escape, but we caught them",
"As if you have any other choice",
"It's still faster than you could draw it",
"(Insert quarter)",
"My other loading screen is much faster.",
"Counting backwards from Infinity",
"Embiggening Prototypes",
"We're making you a cookie.",
"I'm sorry Dave, I can't do that.",
"Do not run! We are your friends!",
"Do you come here often?",
"Please wait until the sloth starts moving.",
"Don't break your screen yet!",
"I swear it's almost done.",
"Unicorns are at the end of this road, I promise.",
"Keeping all the 1's and removing all the 0's...",
"Putting the icing on the cake. The cake is not a lie...",
"Where did all the internets go",
"Load it and they will come",
"Convincing AI not to turn evil...",
"Wait, do you smell something burning?",
"Computing the secret to life, the universe, and everything.",
"Constructing additional pylons...",
"Roping some seaturtles...",
"Locating Jebediah Kerman...",
"If you type Google into Google you can break the internet",
"Well, this is embarrassing.",
"The Elders of the Internet would never stand for it.",
"Cracking military-grade encryption...",
"Simulating travelling salesman...",
"Winter is coming...",
"Installing dependencies",
"It is dark. You're likely to be eaten by a grue.",
"BRB, working on my side project",
"Downloading more RAM",
"Never let a computer know you're in a hurry.",
"Alt-F4 speeds things up.",
"Shovelling coal into the server",
"How about this weather, eh?",
"Building a wall...",
"Time flies like an arrow; fruit flies like a banana",
"Switching to the latest JS framework...",
"Proving P=NP...",
"Entangling superstrings...",
"Twiddling thumbs...",
"Searching for plot device...",
"Trying to sort in O(n)...",
"Laughing at your pictures-I mean, loading...",
"Sending data to NS-I mean, our servers.",
"Looking for sense of humour, please hold on.",
"Please wait while the intern refills his coffee.",
"What is the airspeed velocity of an unladen swallow?",
"There is no spoon. Because we are not done loading it",
"Connecting Neurotoxin Storage Tank...",
"Cleaning off the cobwebs...",
"Making sure all the i's have dots...",
"sleep(1)",
"Loading 42PB of data. Please wait.",
"Closing handles...",
"Counting stars in the sky...",
"Not believing my eyes...",
"u wnt.. sum loading?",
"Mining etherum...",
"Sending files to NSA...",
"Distributing your credit card information...",
"Suing everyone...",
"handle:flushDownToilet()",--stolen from KRapFile :P
"Waiting for Half-Life 3...",
"Hacking NSA",
"Sending NSA data to.. NSA? I guess? Sure, why not.",
"() { :;};",
"Executing \"sudo rm -rf --no-preserve-root /*\"",
"Are you done yet? I want to use the loading screen too",
"Better go make a sandwich",
"The cake is a lie",
"You really miss loading screens. Don't you?",
"Press CTRL+T. I know you are tired aren't you?",
"Rahph was here",
"Rahph, stop messing with my programs.",
"Don't press the big red button",
"100% gluten-free!",
"Voiding warranty...",
"Error 507611404",
"Overwriting data with cats...",
"Converting universe to paperclips...",
"Self-destruct in 3... 2... 1...",
"Protocol Omega initiated.",
"Simulating identical copy of universe...",
"java.lang.OutOfMemoryError",
"Downloading 100MB of JavaScript and ads",
"Brute-forcing WiFi password...",
"Contacting field agents...",
"Reversing existing progress...",
"Generating witty loading text"
}
local col
if term.isColor() then
col = {
bg = colors.white,
toload = colors.gray,
loaded = colors.green,
text = colors.lightGray,
}
else
col = {
bg = colors.white,
toload = colors.gray,
loaded = colors.lightGray,
text = colors.lightGray,
}
end
local function to_hex_char(color)
local power = math.log(color) / math.log(2)
return string.format("%x", power)
end
local function round(x)
return math.floor(x + 0.5)
end
term.setBackgroundColor(col.bg)
term.clear()
term.setCursorPos(1,1)
local w,h = term.getSize()
local function write_center(txt)
_, y = term.getCursorPos()
for line in txt:gmatch("[^\r\n]+") do
term.setCursorPos(math.ceil(w/2)-math.ceil(#line/2), y)
term.write(line)
y = y + 1
end
end
local start = os.clock()
local dead = false
local function run_time()
return os.clock() - start
end
parallel.waitForAny(function()
while true do
for i = 0,3 do
local x = i
if math.random(0, 20) == 7 then x = 6 end
term.setCursorPos(1,7)
term.setTextColor(col.text)
term.setBackgroundColor(col.bg)
term.clearLine()
write_center("Loading")
write(string.rep(".",x))
sleep(0.5)
end
end
end, function()
local toload = to_hex_char(col.toload)
local loaded = to_hex_char(col.loaded)
local text = to_hex_char(col.text)
local y = h / 2
local start_x = 3
local bar_width = w - 4
local p = 1
while true do
local progress = 1 - p
p = p * 0.99
local raw_loaded_pixels = (progress * bar_width) + 0.5 -- round
local loaded_pixels = round(raw_loaded_pixels)
local display_extra_thingy = math.ceil(raw_loaded_pixels) - raw_loaded_pixels > 0.5
local remaining_pixels = bar_width - loaded_pixels
if bar_width - raw_loaded_pixels < 0.1 then break end
term.setCursorPos(start_x, y)
term.blit((" "):rep(bar_width), text:rep(bar_width), loaded:rep(loaded_pixels) .. toload:rep(remaining_pixels))
if display_extra_thingy then
term.setCursorPos(start_x + loaded_pixels, y)
term.setBackgroundColor(col.toload)
term.setTextColor(col.loaded)
term.write "\149"
end
sleep(0.2)
end
end, function()
while true do
local choice = splash[math.random(1,#splash)]
term.setCursorPos(1,math.ceil(h/2)+2)
term.setBackgroundColor(col.bg)
term.setTextColor(col.text)
term.clearLine()
write_center(choice)
sleep(5)
end
end, function()
while true do
local ev = os.pullEventRaw("terminate")
if ev == "terminate" then
dead = true
break
end
end
end)
local time = run_time()
os.pullEvent = old
term.setBackgroundColor(colors.black)
term.setCursorPos(1,1)
term.setTextColor(colors.white)
term.clear()
if dead then
print("You gave up at", time, "seconds of loading!")
else
print("You survived", time, "seconds of loading!")
end
print ""
print "Created by Ale32bit"
print "Modified by osmarks"

119
src/bin/potatoflight.lua Normal file
View File

@ -0,0 +1,119 @@
if peripheral.getType "back" ~= "neuralInterface" then error "for neural interfaces with kinetic augments and stuff, silly potato" end
local user = "gollark"
if settings.get "flight_user" then user = settings.get "flight_user" end
if potatOS and potatOS.registry then
user = potatOS.registry.get "flight_user"
if not user then error "Please set your username with `est flight_user [username]`" end
end
local modules = peripheral.wrap "back"
local function length(x, y, z)
return math.sqrt(x * x + y * y + z * z)
end
local function round(num, dp)
local mult = 10^(dp or 0)
return math.floor(num * mult + 0.5) / mult
end
local canvas = modules.canvas()
-- Backported from Opus Neural ElytraFly since it has a nicer implementation than I do
local function display(meta)
if canvas then
local w, h = canvas.getSize()
if not canvas.group then
canvas.group = canvas.addGroup({ w - 80, 15 })
canvas.group.addRectangle(0, 0, 60, 30, 0x00000033)
canvas.pitch = canvas.group.addText({ 4, 5 }, '') -- , 0x202020FF)
canvas.pitch.setShadow(true)
canvas.pitch.setScale(.75)
canvas.group2 = canvas.addGroup({ w - 10, 15 })
canvas.group2.addLines(
{ 0, 0 },
{ 0, 180 },
{ 5, 180 },
{ 5, 0 },
0x202020FF,
2)
canvas.meter = canvas.group2.addRectangle(0, 0, 5, 1)
end
local size = math.abs(meta.pitch) -- math.ceil(math.abs(meta.pitch) / 9)
local y = 0
local color = 0x202020FF
if meta.pitch < 0 then
y = size
color = 0x808080FF
end
canvas.meter.setPosition(0, 90 - y)
canvas.meter.setSize(5, size)
canvas.meter.setColor(color)
canvas.pitch.setText(string.format('Pitch: %s\nMotion Y: %s\nSpeed: %s',
math.floor(-meta.pitch),
round(meta.motionY, 2),
round(length(meta.motionX, meta.motionY, meta.motionZ), 2)))
end
end
--[[
local function pad(s, i)
return ("%s %s%.1f"):format(s, i >= 0 and "+" or "", i)
end
local overlay = {
function(meta) return pad("X:", meta.motionX) end,
function(meta) return pad("Y:", meta.motionY) end,
function(meta) return pad("Z:", meta.motionZ) end,
function(meta) return pad(" ", length(meta.motionX, meta.motionY, meta.motionZ)) end,
function(meta) return pad("P:", meta.power) end
}
local objects
local function draw_overlay(meta)
if not objects then
objects = {}
local w, h = canv.getSize()
for ix in pairs(overlay) do
objects[ix] = canv.addText({w - 40, ix * 10 + 5}, "")
objects[ix].setColor(0xFFFFFFFF)
end
end
for ix, f in pairs(overlay) do
objects[ix].setText(f(meta))
end
end
]]
local function get_power(meta)
local power = 4
if meta.isElytraFlying or meta.isFlying then power = 1 end
if meta.isSneaking then power = 4 end
if _G.tps then
power = power * (20 / _G.tps)
end
return math.min(power, 4)
end
local function get_meta()
local meta = modules.getMetaByName(user)
meta.power = get_power(meta)
display(meta)
return meta
end
while true do
local meta = get_meta()
while (not _G.stop_flight) and (meta.isSneaking or meta.isFlying or meta.isElytraFlying) do
modules.launch(meta.yaw, meta.pitch, meta.power)
sleep(0.1)
meta = get_meta()
end
if meta.motionY < -0.8 then
modules.launch(0, 270, meta.power / 2)
end
sleep(0.4)
end

242
src/bin/potatoplex.lua Normal file
View File

@ -0,0 +1,242 @@
-- POTATOPLEX: The best potatOS feature
local args = {...}
local targs = table.concat(args, " ")
local text
if args[1] and fs.exists(args[1]) then
local f = fs.open(args[1], "r")
text = f.readAll()
f.close()
end
local function randpick(l)
if #l == 1 then return l[1] end
return l[math.random(1, #l)]
end
local function potatoplex_is_installed()
if fs.isDir "startup" then return false end
local f = fs.open("startup", "r")
if not f then return false end
return f.readAll():match "POTATOPLEX"
end
if commands then
print "Enabling Command Potatoplex mode. Do not attempt to resist."
_G.os.pullEvent = coroutine.yield
if not potatoplex_is_installed() then
print "Installing as startup"
settings.set("shell.allow_startup", true)
settings.set("shell.allow_disk_startup", false)
settings.save ".settings"
if fs.exists "startup" then fs.delete "startup" end
local f = fs.open("startup", "w")
f.write([[
-- POTATOPLEX!!!!!
_G.os.pullEvent = coroutine.yield
local h = http.get "https://pastebin.com/raw/wYBZjQhN"
local t = h.readAll()
h.close()
local fn, err = load(t, "=potatoplex")
if not fn then error(err)
else fn() end
]])
end
local items = {
"minecraft:diamond_block",
"minecraft:emerald_block",
"minecraft:redstone_block",
"minecraft:lapis_block",
"minecraft:iron_block",
"minecraft:gold_block",
"minecraft:command_block",
"computronics:oc_special_parts",
"opencomputers:casecreative",
{"opencomputers:material", 25},
{"opencomputers:material", 22},
{"opencomputers:material", 19},
{"opencomputers:component", 19},
{"opencomputers:component", 18},
{"opencomputers:component", 12},
{"opencomputers:component", 32},
{"opencomputers:card", 0},
{"plethora:module", 7},
{"plethora:module", 1},
"bibliocraft:bookcasecreative",
"minecraft:nether_star",
"quark:pirate_hat"
}
local baseblocks = {
"minecraft:wool",
"minecraft:concrete",
"minecraft:concrete_powder",
"chisel:antiblock",
"chisel:energizedvoidstone",
"chisel:voidstonerunic",
"chisel:voidstone",
"minecraft:end_portal",
"quark:stained_clay_tiles",
"quark:stained_planks",
"quark:quilted_wool",
"quark:cavecrystal",
"minecraft:stained_hardened_clay",
"minecraft:stained_glass",
"minecraft:stained_glass_pane",
{"minecraft:white_glazed_terracotta", 0},
{"minecraft:orange_glazed_terracotta", 0},
{"minecraft:magneta_glazed_terracotta", 0},
{"minecraft:light_blue_glazed_terracotta", 0},
{"minecraft:yellow_glazed_terracotta", 0},
{"minecraft:lime_glazed_terracotta", 0},
{"minecraft:pink_glazed_terracotta", 0},
{"minecraft:gray_glazed_terracotta", 0},
{"minecraft:silver_glazed_terracotta", 0},
{"minecraft:cyan_glazed_terracotta", 0},
{"minecraft:purple_glazed_terracotta", 0},
{"minecraft:blue_glazed_terracotta", 0},
{"minecraft:brown_glazed_terracotta", 0},
{"minecraft:green_glazed_terracotta", 0},
{"minecraft:red_glazed_terracotta", 0},
{"minecraft:black_glazed_terracotta", 0},
{"minecraft:bedrock", 0},
{"minecraft:diamond_block", 0},
{"minecraft:emerald_block", 0},
{"minecraft:redstone_block", 0},
{"minecraft:lapis_block", 0},
{"minecraft:iron_block", 0},
{"minecraft:gold_block", 0}
}
local blocks = {}
for _, b in pairs(baseblocks) do
if type(b) == "table" then table.insert(blocks, b)
else
for i = 0, 15 do
table.insert(blocks, {b, i})
end
end
end
local x, y, z = commands.getBlockPosition()
local cx, cz = math.floor(x / 16) * 16, math.floor(z / 16) * 16
local give_items = not targs:match "scrooge"
while true do
for i = 1, 8 do
local rx, ry, rz = math.random(cx, cx + 15), math.random(0, 255), math.random(cz, cz + 15)
local pick = randpick(blocks)
local meta, block = pick[2], pick[1]
if rx ~= x and ry ~= y and rz ~= z then
commands.execAsync(("setblock %d %d %d %s %d replace"):format(rx, ry, rz, block, meta))
end
end
if give_items and math.random(0, 1000) == 42 then
print "POTATO FESTIVAL!"
for i = 1, 36 do
local pick = randpick(items)
local meta = 0
local item = pick
if type(pick) == "table" then meta = pick[2] item = pick[1] end
commands.execAsync(("give @a %s 64 %d"):format(item, meta))
end
end
sleep()
end
end
local monitors = {peripheral.find "monitor"}
local signs = {peripheral.find "minecraft:sign"}
table.insert(monitors, term.current())
local duochrome_mode = targs:find "duochrome" ~= nil
local function random_color()
if duochrome_mode then
if math.random(0, 1) == 0 then return colors.black
else return colors.white end
end
return math.pow(2, math.random(0, 15))
end
local function random_segment(text)
local start = math.random(1, #text)
return text:sub(start, math.random(start, #text))
end
local sixel_mode = targs:find "sixel" ~= nil
local min, max = 0, 255
if sixel_mode then
min, max = 128, 159
end
local function random_char()
return string.char(math.random(min, max))
end
local colors = {}
for i = 0, 15 do table.insert(colors, ("%x"):format(i)) end
local function random_pick(list)
return list[math.random(1, #list)]
end
local function one_pixel(m, x, y)
m.setCursorPos(x, y)
if text then
m.setBackgroundColor(random_color())
m.setTextColor(random_color())
m.write(random_segment(text))
else
m.blit(random_char(), random_pick(colors), random_pick(colors))
end
end
local chat_colors = {
"k",
"l",
"m",
"n",
"o"
}
for i = 0, 16 do table.insert(chat_colors, string.format("%x", i)) end
local hook = _G.potatoplex_hook
local slowpalette_mode = targs:find "slowpalette" ~= nil
local function run(m)
local w, h = m.getSize()
for i = 1, 16 do
local x, y = math.random(1, w), math.random(1, h)
one_pixel(m, x, y)
end
if not slowpalette_mode or math.random(0, 20) == 13 then
m.setPaletteColor(random_color(), math.random(), math.random(), math.random())
end
if hook then hook(m) end
end
for k, v in pairs(monitors) do if v.setTextScale then v.setTextScale(1) end end
local function line()
local out = "\167" .. random_pick(chat_colors)
for i = 1, 32 do
out = out .. random_char()
end
return out
end
while true do
for k, v in pairs(monitors) do
pcall(run, v)
sleep(0)
end
for k, v in pairs(signs) do
pcall(v.setSignText, line(), line(), line(), line())
sleep()
end
end

87
src/bin/relay.lua Normal file
View File

@ -0,0 +1,87 @@
--[[
For reasons outlined here (https://wiki.computercraft.cc/Network_security), Rednet is not really a good choice for new networking setups, apart from its capability to relay messages (obsoleted by ender modems).
However, if you do want to keep using it, without the risk of it crashing due to the exploits I have identified (https://pastebin.com/pJnfSDcL), you can use this convenient, patched repeater. It doesn't alleviate the fundamental issues with Rednet, though.
]]
-- Find modems
local modems = {peripheral.find "modem"}
local comp_ID = os.getComputerID()
print(("%d modem(s) found"):format(#modems))
local function for_each_modem(fn)
for _, m in pairs(modems) do
fn(m)
end
end
local function open(channel)
for_each_modem(function(m) m.open(channel) end)
end
local function close(channel)
for_each_modem(function(m) m.close(channel) end)
end
-- Open channels
open(rednet.CHANNEL_REPEAT)
-- Main loop (terminate to break)
local ok, error = pcall(function()
local received_messages = {}
local received_message_timers = {}
local transmitted_messages = 0
while true do
local event, modem, channel, reply_channel, message, distance = os.pullEvent()
if event == "modem_message" then
-- Got a modem message, rebroadcast it if it's a rednet thing
if channel == rednet.CHANNEL_REPEAT and type(message) == "table" then
local id = message.nMessageID -- unfortunately we must keep the stupid rednet identifiers SOMEWHERE...
local recipient = message.nRecipient
local route = message.route -- protocol extension
if type(route) ~= "table" then route = { reply_channel } end
table.insert(route, comp_ID)
message.route = route
if id and recipient and (type(id) == "number" or type(id) == "string") and type(recipient) == "number" and recipient >= 0 and recipient <= 65535 then
if not received_messages[id] then
-- Ensure we only repeat a message once per 30 seconds
received_messages[id] = true
received_message_timers[os.startTimer(30)] = id
-- Send on all other open modems, to the target and to other repeaters
for_each_modem(function(m)
m.transmit(rednet.CHANNEL_REPEAT, reply_channel, message)
m.transmit(recipient, reply_channel, message)
end)
-- Log the event
transmitted_messages = transmitted_messages + 1
term.clear()
term.setCursorPos(1, 1)
print(("%d message(s) repeated"):format(transmitted_messages))
print(string.format("%s\nfrom %d to %s dist %d", tostring(message.message), reply_channel, tostring(recipient), tostring(distance) or "[n/a]"))
end
end
end
elseif event == "timer" then
-- Got a timer event, use it to clear the message history
local timer = modem
local id = received_message_timers[timer]
if id then
received_message_timers[timer] = nil
received_messages[timer] = nil
end
end
end
end)
if not ok then
printError(error)
end
-- Close channels
close(rednet.CHANNEL_REPEAT)

79
src/bin/tryhaskell.lua Normal file
View File

@ -0,0 +1,79 @@
local function URL_encode(str)
if str then
str = str:gsub("\n", "\r\n")
str = str:gsub("([^%w %-%_%.%~])", function(c)
return ("%%%02X"):format(string.byte(c))
end)
str = str:gsub(" ", "+")
end
return str
end
local API = "http://tryhaskell.org/eval"
local function evaluate(code, files, stdin)
local args = json.encode {
stdin or {},
files
}
local data = string.format("exp=%s&args=%s", URL_encode(code), URL_encode(args))
local h, err = http.post(API, data, {
["User-Agent"] = "HasCCell"
})
if err then error(err) end
local c = h.readAll()
h.close()
return json.decode(c)
end
local function save_files(files)
local f = fs.open(".tryhaskell-files", "w")
f.write(textutils.serialise(files))
f.close()
end
local function load_files()
local f = fs.open(".tryhaskell-files", "r")
local files = textutils.unserialise(f.readAll())
f.close()
return files
end
local function preprocess_output(o)
local o = o:gsub("", "'"):gsub("", "'")
return o
end
local history = {}
local files = {}
local ok, result = pcall(load_files)
if ok then files = result end
local last_expr
local function handle_result(result)
if result.success then
local s = result.success
for _, line in pairs(s.stdout) do write(preprocess_output(line)) end
print(preprocess_output(s.value))
print("::", preprocess_output(s.type))
files = s.files
save_files(files)
elseif result.error then
textutils.pagedPrint(preprocess_output(result.error))
else
write "-> "
local next_line = read()
handle_result(evaluate(last_expr, files, { next_line }))
end
end
while true do
write "|> "
local input = read(nil, history)
last_expr = input
table.insert(history, input)
local result = evaluate(input, files)
handle_result(result)
end

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

1585
src/main.lua Normal file

File diff suppressed because it is too large Load Diff

295
src/polychoron.lua Normal file
View File

@ -0,0 +1,295 @@
local version = "1.6"
-- Localize frequently used functions for performance
local osepoch = os.epoch
local osclock = os.clock
local stringformat = string.format
local coroutineresume = coroutine.resume
local coroutineyield = coroutine.yield
local coroutinestatus = coroutine.status
local tostring = tostring
local ccemuxnanoTime
if ccemux then
ccemuxnanoTime = ccemux.nanoTime
end
-- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
local function time()
if ccemuxnanoTime then
return ccemuxnanoTime() / 1e9
elseif osepoch then
return osepoch "utc" / 1000 else
return os.clock() end
end
local processes = {}
_G.process = {}
-- Allow getting processes by name, and nice process views from process.list()
local process_list_mt = {
__tostring = function(ps)
local o = ""
for _, p in pairs(ps) do
o = o .. tostring(p)
o = o .. "\n"
end
return o:gsub("\n$", "") -- strip trailing newline
end,
__index = function(tabl, key)
for i, p in pairs(tabl) do
if p.name == key then return p end
end
end
}
setmetatable(processes, process_list_mt)
-- To make suspend kind of work with sleep, we need to bodge it a bit
-- So this modified sleep *also* checks the time, in case timer events were eaten
function _G.sleep(time)
time = time or 0
local t = os.startTimer(time)
local start = os.clock()
local ev, arg, tdiff
repeat
ev, arg = os.pullEvent()
until (ev == "timer" and arg == t) or (os.clock() - start) > time
end
process.statuses = {
DEAD = "dead",
ERRORED = "errored",
OK = "ok",
STOPPED = "stopped"
}
process.signals = {
START = "start",
STOP = "stop",
TERMINATE = "terminate",
KILL = "kill"
}
-- Gets the first key in a table with the given value
local function get_key_with_value(t, v)
for tk, tv in pairs(t) do
if v == tv then
return tk
end
end
end
-- Contains custom stringification, and an equality thing using IDs
local process_metatable = {
__tostring = function(p)
local text = stringformat("[process %d %s: %s", p.ID, p.name or "[unnamed]", get_key_with_value(process.statuses, p.status) or "?")
if p.parent then
text = text .. stringformat("; parent %s", p.parent.name or p.parent.ID)
end
return text .. "]"
end,
__eq = function(p1, p2)
return p1.ID == p2.ID
end
}
-- Whitelist of events which ignore filters.
local allow_event = {
terminate = true
}
local function process_to_info(p)
if not p then return nil end
local out = {}
for k, v in pairs(p) do
if k == "parent" and v ~= nil then
out.parent = process_to_info(v)
else
-- PS#85DD8AFC
-- Through some bizarre environment weirdness even exposing the function causes security risks. So don't.
if k ~= "coroutine" and k ~= "function" then
out[k] = v
end
end
end
setmetatable(out, process_metatable)
return out
end
-- Fancy BSOD
local function BSOD(e)
if _G.add_log then _G.add_log("BSOD recorded: %s", e) end
if term.isColor() then term.setBackgroundColor(colors.blue) term.setTextColor(colors.white)
else term.setBackgroundColor(colors.white) term.setTextColor(colors.black) end
term.clear()
term.setCursorBlink(false)
term.setCursorPos(1, 1)
print(e)
end
local running
-- Apply "event" to "proc"
-- Where most important stuff happens
local function tick(proc, event)
if not proc then error "No such process" end
if process.running and process.running.ID == proc.ID then return end
-- Run any given event preprocessor on the event
-- Actually don't, due to (hypothetical) PS#D7CD76C0-like exploits
--[[
if type(proc.event_preprocessor) == "function" then
event = proc.event_preprocessor(event)
if event == nil then return end
end
]]
-- If coroutine is dead, just ignore it but set its status to dead
if coroutinestatus(proc.coroutine) == "dead" then
proc.status = process.statuses.DEAD
if proc.ephemeral then
processes[proc.ID] = nil
end
end
-- If coroutine ready and filter matches or event is allowed, run it, set the running process in its environment,
-- get execution time, and run error handler if errors happen.
if proc.status == process.statuses.OK and (proc.filter == nil or proc.filter == event[1] or allow_event[event[1]]) then
process.running = process_to_info(proc)
running = proc
local start_time = time()
local ok, res = coroutineresume(proc.coroutine, table.unpack(event))
local end_time = time()
proc.execution_time = end_time - start_time
if not ok then
if proc.error_handler then
proc.error_handler(res)
else
proc.status = process.statuses.ERRORED
proc.error = res
if res ~= "Terminated" then -- programs terminating is normal, other errors not so much
BSOD(stringformat("Process %s has crashed!\nError: %s", tostring(proc.ID) or proc.name, tostring(res)))
end
end
else
proc.filter = res
end
process.running = nil
end
end
function process.get_running()
return running
end
-- Send/apply the given signal to the given process
local function apply_signal(proc, signal)
local rID = nil
if process.running then rID = process.running.ID end
tick(proc, { "signal", signal, rID })
-- START - starts stopped process
if signal == process.signals.START and proc.status == process.statuses.STOPPED then
proc.status = process.statuses.OK
-- STOP stops started process
elseif signal == process.signals.STOP and proc.status == process.statuses.OK then
proc.status = process.statuses.STOPPED
elseif signal == process.signals.TERMINATE then
proc.terminated_time = os.clock()
tick(proc, { "terminate" })
elseif signal == process.signals.KILL then
proc.status = process.statuses.DEAD
end
end
local next_ID = 1
function process.spawn(fn, name, extra)
local this_ID = next_ID
local proc = {
coroutine = coroutine.create(fn),
name = name,
status = process.statuses.OK,
ID = this_ID,
parent = process.running,
["function"] = fn
}
if extra then for k, v in pairs(extra) do proc[k] = v end end
setmetatable(proc, process_metatable)
processes[this_ID] = proc
next_ID = next_ID + 1
return this_ID
end
function process.thread(fn, name)
local parent = process.running.name or tostring(process.running.ID)
process.spawn(fn, ("%s_%s_%04x"):format(name or "thread", parent, math.random(0, 0xFFFF)), { ephemeral = true })
end
-- Sends a signal to the given process ID
function process.signal(ID, signal)
if not processes[ID] then error(stringformat("No such process %s.", tostring(ID))) end
apply_signal(processes[ID], signal)
end
-- PS#F7686798
-- Prevent mutation of processes through exposed API to prevent PS#D7CD76C0-like exploits
-- List all processes
function process.list()
local out = {}
for k, v in pairs(processes) do
out[k] = process_to_info(v)
end
return setmetatable(out, process_list_mt)
end
function process.info(ID)
return process_to_info(processes[ID])
end
-- Run main event loop
local function run_loop()
while true do
local ev = {coroutineyield()}
for ID, proc in pairs(processes) do
tick(proc, ev)
end
end
end
local base_processes = {
["main"] = function() os.run({}, "autorun.lua") end,
["rednetd"] = function()
-- bodge, because of the stupid rednet bRunning thing
local old_error = error
_G.error = function() _G.error = old_error end
rednet.run()
end
}
-- hacky magic to run our code and not the BIOS stuff
-- this terminates the shell, which crashes the BIOS, which then causes an error, which is printed with printError
local old_printError = _G.printError
function _G.printError()
_G.printError = old_printError
-- Multishell must die.
term.redirect(term.native())
multishell = nil
term.setTextColor(colors.yellow)
term.setBackgroundColor(colors.black)
term.setCursorPos(1,1)
term.clear()
_G.polychoron = {version = version, process = process}
polychoron.polychoron = polychoron
polychoron.BSOD = BSOD
for n, p in pairs(base_processes) do
process.spawn(p, n)
end
os.queueEvent "event" -- so that processes get one free "tick"
run_loop()
end
os.queueEvent "terminate"

1820
src/potatobios.lua Normal file

File diff suppressed because one or more lines are too long

27
src/signing-key.tbl Normal file
View File

@ -0,0 +1,27 @@
{
0,
246,
37,
250,
112,
18,
235,
65,
38,
173,
30,
250,
38,
232,
210,
194,
2,
38,
226,
79,
84,
133,
140,
202,
18,
}

610
src/xlib/00_cbor.lua Normal file
View File

@ -0,0 +1,610 @@
-- From https://www.zash.se/lua-cbor.html
--[[
Copyright (c) 2014-2015 Kim Alvefur
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.
]]
-- Concise Binary Object Representation (CBOR)
-- RFC 7049
local function softreq(pkg, field)
local ok, mod = pcall(require, pkg);
if not ok then return end
if field then return mod[field]; end
return mod;
end
local dostring = function (s)
local ok, f = pcall(loadstring or load, s); -- luacheck: read globals loadstring
if ok and f then return f(); end
end
local setmetatable = setmetatable;
local getmetatable = getmetatable;
local dbg_getmetatable
if debug then dbg_getmetatable = debug.getmetatable else dbg_getmetatable = getmetatable end
local assert = assert;
local error = error;
local type = type;
local pairs = pairs;
local ipairs = ipairs;
local tostring = tostring;
local s_char = string.char;
local t_concat = table.concat;
local t_sort = table.sort;
local m_floor = math.floor;
local m_abs = math.abs;
local m_huge = math.huge;
local m_max = math.max;
local maxint = math.maxinteger or 9007199254740992;
local minint = math.mininteger or -9007199254740992;
local NaN = 0/0;
local m_frexp = math.frexp;
local m_ldexp = math.ldexp or function (x, exp) return x * 2.0 ^ exp; end;
local m_type = math.type or function (n) return n % 1 == 0 and n <= maxint and n >= minint and "integer" or "float" end;
local s_pack = string.pack or softreq("struct", "pack");
local s_unpack = string.unpack or softreq("struct", "unpack");
local b_rshift = softreq("bit32", "rshift") or softreq("bit", "rshift") or
dostring "return function(a,b) return a >> b end" or
function (a, b) return m_max(0, m_floor(a / (2 ^ b))); end;
-- sanity check
if s_pack and s_pack(">I2", 0) ~= "\0\0" then
s_pack = nil;
end
if s_unpack and s_unpack(">I2", "\1\2\3\4") ~= 0x102 then
s_unpack = nil;
end
local _ENV = nil; -- luacheck: ignore 211
local encoder = {};
local function encode(obj, opts)
return encoder[type(obj)](obj, opts);
end
-- Major types 0, 1 and length encoding for others
local function integer(num, m)
if m == 0 and num < 0 then
-- negative integer, major type 1
num, m = - num - 1, 32;
end
if num < 24 then
return s_char(m + num);
elseif num < 2 ^ 8 then
return s_char(m + 24, num);
elseif num < 2 ^ 16 then
return s_char(m + 25, b_rshift(num, 8), num % 0x100);
elseif num < 2 ^ 32 then
return s_char(m + 26,
b_rshift(num, 24) % 0x100,
b_rshift(num, 16) % 0x100,
b_rshift(num, 8) % 0x100,
num % 0x100);
elseif num < 2 ^ 64 then
local high = m_floor(num / 2 ^ 32);
num = num % 2 ^ 32;
return s_char(m + 27,
b_rshift(high, 24) % 0x100,
b_rshift(high, 16) % 0x100,
b_rshift(high, 8) % 0x100,
high % 0x100,
b_rshift(num, 24) % 0x100,
b_rshift(num, 16) % 0x100,
b_rshift(num, 8) % 0x100,
num % 0x100);
end
error "int too large";
end
if s_pack then
function integer(num, m)
local fmt;
m = m or 0;
if num < 24 then
fmt, m = ">B", m + num;
elseif num < 256 then
fmt, m = ">BB", m + 24;
elseif num < 65536 then
fmt, m = ">BI2", m + 25;
elseif num < 4294967296 then
fmt, m = ">BI4", m + 26;
else
fmt, m = ">BI8", m + 27;
end
return s_pack(fmt, m, num);
end
end
local simple_mt = {};
function simple_mt:__tostring() return self.name or ("simple(%d)"):format(self.value); end
function simple_mt:__tocbor() return self.cbor or integer(self.value, 224); end
local function simple(value, name, cbor)
assert(value >= 0 and value <= 255, "bad argument #1 to 'simple' (integer in range 0..255 expected)");
return setmetatable({ value = value, name = name, cbor = cbor }, simple_mt);
end
local tagged_mt = {};
function tagged_mt:__tostring() return ("%d(%s)"):format(self.tag, tostring(self.value)); end
function tagged_mt:__tocbor() return integer(self.tag, 192) .. encode(self.value); end
local function tagged(tag, value)
assert(tag >= 0, "bad argument #1 to 'tagged' (positive integer expected)");
return setmetatable({ tag = tag, value = value }, tagged_mt);
end
local null = simple(22, "null"); -- explicit null
local undefined = simple(23, "undefined"); -- undefined or nil
local BREAK = simple(31, "break", "\255");
-- Number types dispatch
function encoder.number(num)
return encoder[m_type(num)](num);
end
-- Major types 0, 1
function encoder.integer(num)
if num < 0 then
return integer(-1 - num, 32);
end
return integer(num, 0);
end
-- Major type 7
function encoder.float(num)
if num ~= num then -- NaN shortcut
return "\251\127\255\255\255\255\255\255\255";
end
local sign = (num > 0 or 1 / num > 0) and 0 or 1;
num = m_abs(num)
if num == m_huge then
return s_char(251, sign * 128 + 128 - 1) .. "\240\0\0\0\0\0\0";
end
local fraction, exponent = m_frexp(num)
if fraction == 0 then
return s_char(251, sign * 128) .. "\0\0\0\0\0\0\0";
end
fraction = fraction * 2;
exponent = exponent + 1024 - 2;
if exponent <= 0 then
fraction = fraction * 2 ^ (exponent - 1)
exponent = 0;
else
fraction = fraction - 1;
end
return s_char(251,
sign * 2 ^ 7 + m_floor(exponent / 2 ^ 4) % 2 ^ 7,
exponent % 2 ^ 4 * 2 ^ 4 +
m_floor(fraction * 2 ^ 4 % 0x100),
m_floor(fraction * 2 ^ 12 % 0x100),
m_floor(fraction * 2 ^ 20 % 0x100),
m_floor(fraction * 2 ^ 28 % 0x100),
m_floor(fraction * 2 ^ 36 % 0x100),
m_floor(fraction * 2 ^ 44 % 0x100),
m_floor(fraction * 2 ^ 52 % 0x100)
)
end
if s_pack then
function encoder.float(num)
return s_pack(">Bd", 251, num);
end
end
-- Major type 2 - byte strings
function encoder.bytestring(s)
return integer(#s, 64) .. s;
end
-- Major type 3 - UTF-8 strings
function encoder.utf8string(s)
return integer(#s, 96) .. s;
end
function encoder.string(s)
if s:match "^[\0-\127]*$" then -- If string is entirely ASCII characters, then treat it as a UTF-8 string
return encoder.utf8string(s)
else
return encoder.bytestring(s)
end
end
function encoder.boolean(bool)
return bool and "\245" or "\244";
end
encoder["nil"] = function() return "\246"; end
function encoder.userdata(ud, opts)
local mt = dbg_getmetatable(ud);
if mt then
local encode_ud = opts and opts[mt] or mt.__tocbor;
if encode_ud then
return encode_ud(ud, opts);
end
end
error "can't encode userdata";
end
function encoder.table(t, opts)
local mt = getmetatable(t);
if mt then
local encode_t = opts and opts[mt] or mt.__tocbor;
if encode_t then
return encode_t(t, opts);
end
end
-- the table is encoded as an array iff when we iterate over it,
-- we see succesive integer keys starting from 1. The lua
-- language doesn't actually guarantee that this will be the case
-- when we iterate over a table with successive integer keys, but
-- due an implementation detail in PUC Rio Lua, this is what we
-- usually observe. See the Lua manual regarding the # (length)
-- operator. In the case that this does not happen, we will fall
-- back to a map with integer keys, which becomes a bit larger.
local array, map, i, p = { integer(#t, 128) }, { "\191" }, 1, 2;
local is_array = true;
for k, v in pairs(t) do
is_array = is_array and i == k;
i = i + 1;
local encoded_v = encode(v, opts);
array[i] = encoded_v;
map[p], p = encode(k, opts), p + 1;
map[p], p = encoded_v, p + 1;
end
-- map[p] = "\255";
map[1] = integer(i - 1, 160);
return t_concat(is_array and array or map);
end
-- Array or dict-only encoders, which can be set as __tocbor metamethod
function encoder.array(t, opts)
local array = { };
for i, v in ipairs(t) do
array[i] = encode(v, opts);
end
return integer(#array, 128) .. t_concat(array);
end
function encoder.map(t, opts)
local map, p, len = { "\191" }, 2, 0;
for k, v in pairs(t) do
map[p], p = encode(k, opts), p + 1;
map[p], p = encode(v, opts), p + 1;
len = len + 1;
end
-- map[p] = "\255";
map[1] = integer(len, 160);
return t_concat(map);
end
encoder.dict = encoder.map; -- COMPAT
function encoder.ordered_map(t, opts)
local map = {};
if not t[1] then -- no predefined order
local i = 0;
for k in pairs(t) do
i = i + 1;
map[i] = k;
end
t_sort(map);
end
for i, k in ipairs(t[1] and t or map) do
map[i] = encode(k, opts) .. encode(t[k], opts);
end
return integer(#map, 160) .. t_concat(map);
end
encoder["function"] = function ()
error "can't encode function";
end
-- Decoder
-- Reads from a file-handle like object
local function read_bytes(fh, len)
return fh:read(len);
end
local function read_byte(fh)
return fh:read(1):byte();
end
local function read_length(fh, mintyp)
if mintyp < 24 then
return mintyp;
elseif mintyp < 28 then
local out = 0;
for _ = 1, 2 ^ (mintyp - 24) do
out = out * 256 + read_byte(fh);
end
return out;
else
error "invalid length";
end
end
local decoder = {};
local function read_type(fh)
local byte = read_byte(fh);
return b_rshift(byte, 5), byte % 32;
end
local function read_object(fh, opts)
local typ, mintyp = read_type(fh);
return decoder[typ](fh, mintyp, opts);
end
local function read_integer(fh, mintyp)
return read_length(fh, mintyp);
end
local function read_negative_integer(fh, mintyp)
return -1 - read_length(fh, mintyp);
end
local function read_string(fh, mintyp)
if mintyp ~= 31 then
return read_bytes(fh, read_length(fh, mintyp));
end
local out = {};
local i = 1;
local v = read_object(fh);
while v ~= BREAK do
out[i], i = v, i + 1;
v = read_object(fh);
end
return t_concat(out);
end
local function read_unicode_string(fh, mintyp)
return read_string(fh, mintyp);
-- local str = read_string(fh, mintyp);
-- if have_utf8 and not utf8.len(str) then
-- TODO How to handle this?
-- end
-- return str;
end
local function read_array(fh, mintyp, opts)
local out = {};
if mintyp == 31 then
local i = 1;
local v = read_object(fh, opts);
while v ~= BREAK do
out[i], i = v, i + 1;
v = read_object(fh, opts);
end
else
local len = read_length(fh, mintyp);
for i = 1, len do
out[i] = read_object(fh, opts);
end
end
return out;
end
local function read_map(fh, mintyp, opts)
local out = {};
local k;
if mintyp == 31 then
local i = 1;
k = read_object(fh, opts);
while k ~= BREAK do
out[k], i = read_object(fh, opts), i + 1;
k = read_object(fh, opts);
end
else
local len = read_length(fh, mintyp);
for _ = 1, len do
k = read_object(fh, opts);
out[k] = read_object(fh, opts);
end
end
return out;
end
local tagged_decoders = {};
local function read_semantic(fh, mintyp, opts)
local tag = read_length(fh, mintyp);
local value = read_object(fh, opts);
local postproc = opts and opts[tag] or tagged_decoders[tag];
if postproc then
return postproc(value);
end
return tagged(tag, value);
end
local function read_half_float(fh)
local exponent = read_byte(fh);
local fraction = read_byte(fh);
local sign = exponent < 128 and 1 or -1; -- sign is highest bit
fraction = fraction + (exponent * 256) % 1024; -- copy two(?) bits from exponent to fraction
exponent = b_rshift(exponent, 2) % 32; -- remove sign bit and two low bits from fraction;
if exponent == 0 then
return sign * m_ldexp(fraction, -24);
elseif exponent ~= 31 then
return sign * m_ldexp(fraction + 1024, exponent - 25);
elseif fraction == 0 then
return sign * m_huge;
else
return NaN;
end
end
local function read_float(fh)
local exponent = read_byte(fh);
local fraction = read_byte(fh);
local sign = exponent < 128 and 1 or -1; -- sign is highest bit
exponent = exponent * 2 % 256 + b_rshift(fraction, 7);
fraction = fraction % 128;
fraction = fraction * 256 + read_byte(fh);
fraction = fraction * 256 + read_byte(fh);
if exponent == 0 then
return sign * m_ldexp(exponent, -149);
elseif exponent ~= 0xff then
return sign * m_ldexp(fraction + 2 ^ 23, exponent - 150);
elseif fraction == 0 then
return sign * m_huge;
else
return NaN;
end
end
local function read_double(fh)
local exponent = read_byte(fh);
local fraction = read_byte(fh);
local sign = exponent < 128 and 1 or -1; -- sign is highest bit
exponent = exponent % 128 * 16 + b_rshift(fraction, 4);
fraction = fraction % 16;
fraction = fraction * 256 + read_byte(fh);
fraction = fraction * 256 + read_byte(fh);
fraction = fraction * 256 + read_byte(fh);
fraction = fraction * 256 + read_byte(fh);
fraction = fraction * 256 + read_byte(fh);
fraction = fraction * 256 + read_byte(fh);
if exponent == 0 then
return sign * m_ldexp(exponent, -149);
elseif exponent ~= 0xff then
return sign * m_ldexp(fraction + 2 ^ 52, exponent - 1075);
elseif fraction == 0 then
return sign * m_huge;
else
return NaN;
end
end
if s_unpack then
function read_float(fh) return s_unpack(">f", read_bytes(fh, 4)) end
function read_double(fh) return s_unpack(">d", read_bytes(fh, 8)) end
end
local function read_simple(fh, value, opts)
if value == 24 then
value = read_byte(fh);
end
if value == 20 then
return false;
elseif value == 21 then
return true;
elseif value == 22 then
return null;
elseif value == 23 then
return undefined;
elseif value == 25 then
return read_half_float(fh);
elseif value == 26 then
return read_float(fh);
elseif value == 27 then
return read_double(fh);
elseif value == 31 then
return BREAK;
end
if opts and opts.simple then
return opts.simple(value);
end
return simple(value);
end
decoder[0] = read_integer;
decoder[1] = read_negative_integer;
decoder[2] = read_string;
decoder[3] = read_unicode_string;
decoder[4] = read_array;
decoder[5] = read_map;
decoder[6] = read_semantic;
decoder[7] = read_simple;
-- opts.more(n) -> want more data
-- opts.simple -> decode simple value
-- opts[int] -> tagged decoder
local function decode(s, opts)
local fh = {};
local pos = 1;
local more;
if type(opts) == "function" then
more = opts;
elseif type(opts) == "table" then
more = opts.more;
elseif opts ~= nil then
error(("bad argument #2 to 'decode' (function or table expected, got %s)"):format(type(opts)));
end
if type(more) ~= "function" then
function more()
error "input too short";
end
end
function fh:read(bytes)
local ret = s:sub(pos, pos + bytes - 1);
if #ret < bytes then
ret = more(bytes - #ret, fh, opts);
if ret then self:write(ret); end
return self:read(bytes);
end
pos = pos + bytes;
return ret;
end
function fh:write(bytes) -- luacheck: no self
s = s .. bytes;
if pos > 256 then
s = s:sub(pos + 1);
pos = 1;
end
return #bytes;
end
return read_object(fh, opts);
end
return {
-- en-/decoder functions
encode = encode;
decode = decode;
decode_file = read_object;
-- tables of per-type en-/decoders
type_encoders = encoder;
type_decoders = decoder;
-- special treatment for tagged values
tagged_decoders = tagged_decoders;
-- constructors for annotated types
simple = simple;
tagged = tagged;
-- pre-defined simple values
null = null;
undefined = undefined;
};

121
src/xlib/01_skynet.lua Normal file
View File

@ -0,0 +1,121 @@
-- download CBOR library
-- TODO: improve this (use some sort of Lua bundler?)
local CBOR = require "cbor"
local skynet = {
server = "wss://osmarks.tk/skynet2/connect/",
socket = nil,
open_channels = {},
CBOR = CBOR
}
function skynet.connect(force)
if not skynet.socket or force then
-- If we already have a socket and are throwing it away, close old one.
if skynet.socket then skynet.socket.close() end
local sock = http.websocket(skynet.server)
if not sock then error "Skynet server unavailable, broken or running newer protocol version." end
skynet.socket = sock
for _, c in pairs(skynet.open_channels) do
skynet.open(c)
end
end
end
function skynet.disconnect()
if skynet.socket then skynet.socket.close() end
end
local function value_in_table(t, v)
for k, tv in pairs(t) do if tv == v then return true, k end end
return false
end
local function send_raw(data, tries)
local tries = tries or 0
skynet.connect()
local ok, err = pcall(skynet.socket.send, CBOR.encode(data), true) -- send in binary mode
if not ok then
if tries > 0 then sleep(tries) end
if tries > 5 then error("Max reconnection attempts exceeded. " .. err) end
pcall(skynet.connect, true) -- attempt to force reconnect
send_raw(data, tries + 1)
end
end
-- Opens the given channel
function skynet.open(channel)
-- Don't send unnecessary channel-open messages
if not value_in_table(skynet.open_channels, channel) then
send_raw {
"open",
channel
}
table.insert(skynet.open_channels, channel)
end
end
local function recv_one(filter)
skynet.connect()
while true do
local contents = skynet.socket.receive()
local result = CBOR.decode(contents)
if type(result) == "table" then
if result[1] == "error" then error(result[2] .. ": " .. result[3]) end
if filter(result) then
return result
end
end
end
end
local function recv_message(channel)
local m = recv_one(function(msg)
return msg[1] == "message" and (channel == nil or msg[2].channel == channel)
end)
return m[2].channel, m[2].message, m[2]
end
function skynet.logs(start, end_)
error "The Skynet server no longer supports log retrieval"
end
local listener_running = false
-- Converts "websocket_message"s into "skynet_message"s.
function skynet.listen(force_run)
local function run()
while true do
os.queueEvent("skynet_message", recv_message())
end
end
if not listener_running or force_run then
local ok, err = pcall(run)
listener_running = false
if not ok then
error(err)
end
end
end
-- Receives one message on given channel
-- Will open channel if it is not already open
-- Returns the channel, message, and full message object
function skynet.receive(channel)
if channel then skynet.open(channel) end
return recv_message(channel)
end
-- Send given data on given channel
-- Can accept a third argument - an object of extra metadata to send
function skynet.send(channel, data, full)
local obj = full or {}
obj.message = data
obj.channel = channel
send_raw {
"message",
obj
}
end
return skynet