forked from osmarks/potatOS
initial commit of vaguely working ish build
This commit is contained in:
commit
ff85c35873
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dist
|
||||||
|
update-key
|
||||||
|
__pycache__
|
13
build.sh
Executable file
13
build.sh
Executable 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
219
ccecc.py
Normal 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
63
generate_manifest.py
Executable 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
38
genkeys.py
Executable 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
2
manifest
Normal 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
88
src/LICENSES
Normal 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
16
src/bin/5rot26.lua
Normal 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
33
src/bin/chronometer.lua
Normal 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
8
src/bin/kristminer.lua
Normal 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
36
src/bin/livegps.lua
Normal 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
250
src/bin/loading.lua
Normal 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
119
src/bin/potatoflight.lua
Normal 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
242
src/bin/potatoplex.lua
Normal 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
87
src/bin/relay.lua
Normal 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
79
src/bin/tryhaskell.lua
Normal 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
|
330
src/lib/binary-serialization.lua
Normal file
330
src/lib/binary-serialization.lua
Normal 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
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
2135
src/lib/ecc.lua
Normal file
File diff suppressed because it is too large
Load Diff
213
src/lib/gps.lua
Normal file
213
src/lib/gps.lua
Normal 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
400
src/lib/json.lua
Normal 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
28
src/lib/meta.lua
Normal 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
49
src/lib/persistence.lua
Normal 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
72
src/lib/registry.lua
Normal 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
172
src/lib/sha256.lua
Normal 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
94
src/lib/stack_trace.lua
Normal 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
40
src/lib/urandom.lua
Normal 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
469
src/lib/yafss.lua
Normal 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
1585
src/main.lua
Normal file
File diff suppressed because it is too large
Load Diff
295
src/polychoron.lua
Normal file
295
src/polychoron.lua
Normal 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
1820
src/potatobios.lua
Normal file
File diff suppressed because one or more lines are too long
27
src/signing-key.tbl
Normal file
27
src/signing-key.tbl
Normal 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
610
src/xlib/00_cbor.lua
Normal 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
121
src/xlib/01_skynet.lua
Normal 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
|
Loading…
Reference in New Issue
Block a user