Reformat + add prefix selection for config

This commit is contained in:
Soph :3 2025-11-14 22:43:49 +02:00
parent 88f4173c8e
commit 262c0b5408
13 changed files with 1191 additions and 1141 deletions

View file

@ -21,6 +21,52 @@
- Chatbox support for withdrawing and depositing items
- Remote access via enderstorages
## How to use
1. First, in your /config.lua file, add the following:
```lua
return {
["inventories"] = { -- REQUIRED!! Please set at least one inventory pattern.
".*barrel.*" -- Lua patterns to match all barrels
},
["import"] = { -- Not required, just don't set!
"ender_storage_156" -- A inventory I want to move all items from into our storage
},
["chatbox"] = { -- Not required, just don't set!
["prefix"] = "home",
["players"] = {
["hartbreix"] = "manipulator_42" -- Chatbox support
}
},
["remote"] = { -- Access your items via modem, not required, just don't set!
["ender_storage"] = "ender_storage_493", -- Enderstorage that will be changed (set this to owner-only and computer-chanagable!)
["modem"] = "modem_1277", -- Modem to recieve messages
["remotes"] = { -- Remote access
["red/green/green"] = {
["port"] = 42420,
["password"] = "test123" -- Required!
}
}
}
}
```
2. Run the following command:
```
wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua
```
This will install storage-solution to /storage-solution.
To make it start every time you turn on the computer, add
```
shell.run("wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua")
```
to startup.lua
### [EXTRA!]
You can also run `wget run https://files.sad.ovh/public/storage-solution/.beta-installer.lua`, which is quite unstable. You may experience issues while running the beta version, but also new juicy features :)
## RA
SiSS has a remote access system that allows you to access your items via modem.
To use it, first set up your config.lua correctly. After that you need to implement the tiny_ra_library into your program.
@ -57,44 +103,3 @@ sleep(2)
print("deposits 16 items from the first slot")
ra.depositBySlots({1}, 16)
```
## How to use
1. First, in your /config.lua file, add the following:
```lua
return {
["inventories"] = { -- REQUIRED!! Please set at least one inventory pattern.
".*barrel.*" -- Lua patterns to match all barrels
},
["import"] = { -- Not required, just don't set!
"ender_storage_156" -- A inventory I want to move all items from into our storage
},
["chatbox"] = { -- Not required, just don't set!
["hartbreix"] = "manipulator_42" -- Chatbox support
},
["remote"] = { -- Access your items via modem, not required, just don't set!
["ender_storage"] = "ender_storage_493", -- Enderstorage that will be changed (set this to owner-only and computer-chanagable!)
["modem"] = "modem_1277", -- Modem to recieve messages
["remotes"] = { -- Remote access
["red/green/green"] = {
["port"] = 42420,
["password"] = "test123" -- Required!
}
}
}
}
```
2. Run the following command:
```
wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua
```
This will install storage-solution to /storage-solution.
To make it start every time you turn on the computer, add
```
shell.run("wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua")
```
to startup.lua
### [EXTRA!]
You can also run `wget run https://files.sad.ovh/public/storage-solution/.beta-installer.lua`, which is quite unstable. You may experience issues while running the beta version, but also new juicy features :)

View file

@ -3,75 +3,77 @@ local api_url = base_url .. "?ls"
local download_root = "storage-solution"
local function fetch_folder_list()
local response = http.get(api_url)
if not response then
error("Failed to get folder list from Copyparty API")
end
local body = response.readAll()
response.close()
local data = textutils.unserializeJSON(body)
return data
local response = http.get(api_url)
if not response then
error("Failed to get folder list from Copyparty API")
end
local body = response.readAll()
response.close()
local data = textutils.unserializeJSON(body)
return data
end
local function download_file(path)
local file_url = base_url .. "/" .. path
local local_path = download_root .. "/" .. path
local response = http.get(file_url)
if response then
local file = fs.open(local_path, "wb")
if file then
file.write(response.readAll())
file.close()
end
response.close()
else
print("Failed to download: " .. file_url)
local file_url = base_url .. "/" .. path
local local_path = download_root .. "/" .. path
local response = http.get(file_url)
if response then
local file = fs.open(local_path, "wb")
if file then
file.write(response.readAll())
file.close()
end
response.close()
else
print("Failed to download: " .. file_url)
end
end
local function traverse_and_download(folder_data, prefix)
prefix = prefix or ""
prefix = prefix or ""
for _, file in ipairs(folder_data.files or {}) do
print("Downloading: " .. prefix .. file.href)
download_file(prefix .. file.href)
end
for _, file in ipairs(folder_data.files or {}) do
print("Downloading: " .. prefix .. file.href)
download_file(prefix .. file.href)
end
for _, dir in ipairs(folder_data.dirs or {}) do
fs.makeDir(download_root .. "/" .. prefix .. dir.href)
local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. dir.href
local subdir_url = base_url .. "/" .. new_prefix .. "?ls" local response = http.get(subdir_url)
if response then
local body = response.readAll()
if not body then return end
response.close()
local subdir_data = textutils.unserializeJSON(body)
traverse_and_download(subdir_data, new_prefix)
else
print("Failed to get subdirectory: " .. subdir_url)
end
for _, dir in ipairs(folder_data.dirs or {}) do
fs.makeDir(download_root .. "/" .. prefix .. dir.href)
local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. dir.href
local subdir_url = base_url .. "/" .. new_prefix .. "?ls"
local response = http.get(subdir_url)
if response then
local body = response.readAll()
if not body then return end
response.close()
local subdir_data = textutils.unserializeJSON(body)
traverse_and_download(subdir_data, new_prefix)
else
print("Failed to get subdirectory: " .. subdir_url)
end
end
end
if fs.exists(download_root) then
if fs.exists(download_root .. "/version") then
local previousVersion = fs.open(download_root .. "/version", "r").readAll()
local currentVersion = http.get(base_url .. "/version").readAll();
if fs.exists(download_root .. "/version") then
local previousVersion = fs.open(download_root .. "/version", "r").readAll()
local currentVersion = http.get(base_url .. "/version").readAll();
if previousVersion == currentVersion then
print("Previous version " .. previousVersion .. " is already installed, we're on " .. currentVersion .. " aswell, so skipping installation.")
shell.run("storage-solution/main.lua")
return
else
print("Version " .. previousVersion .. " was already installed. Uninstalling.")
fs.delete(download_root)
end
if previousVersion == currentVersion then
print("Previous version " ..
previousVersion .. " is already installed, we're on " .. currentVersion .. " aswell, so skipping installation.")
shell.run("storage-solution/main.lua")
return
else
print("Version marker does not exist. Cannot install.")
shell.run("storage-solution/main.lua")
return
print("Version " .. previousVersion .. " was already installed. Uninstalling.")
fs.delete(download_root)
end
else
print("Version marker does not exist. Cannot install.")
shell.run("storage-solution/main.lua")
return
end
end
fs.makeDir(download_root)

View file

@ -5,79 +5,79 @@ local download_root = "storage-solution"
local remote_folder = "src"
local function fetch_commit_hash()
local url = repo_api .. "/branches/" .. branch
local response = http.get(url)
if not response then
error("Failed to fetch branch info: " .. url)
end
local body = response.readAll()
response.close()
local data = textutils.unserializeJSON(body)
return data.commit.id
local url = repo_api .. "/branches/" .. branch
local response = http.get(url)
if not response then
error("Failed to fetch branch info: " .. url)
end
local body = response.readAll()
response.close()
local data = textutils.unserializeJSON(body)
return data.commit.id
end
local function fetch_repo_tree(path)
local url = repo_api .. "/contents" .. (path and ("/" .. path) or "")
local response = http.get(url)
if not response then
error("Failed to fetch repo tree: " .. url)
end
local body = response.readAll()
response.close()
return textutils.unserializeJSON(body)
local url = repo_api .. "/contents" .. (path and ("/" .. path) or "")
local response = http.get(url)
if not response then
error("Failed to fetch repo tree: " .. url)
end
local body = response.readAll()
response.close()
return textutils.unserializeJSON(body)
end
local function download_file(remote_path, local_path)
local url = raw_base .. "/" .. remote_path
fs.makeDir(fs.getDir(local_path))
local response = http.get(url)
if response then
local file = fs.open(local_path, "wb")
if file then
file.write(response.readAll())
file.close()
end
response.close()
print("Downloaded: " .. local_path)
else
print("Failed to download: " .. url)
local url = raw_base .. "/" .. remote_path
fs.makeDir(fs.getDir(local_path))
local response = http.get(url)
if response then
local file = fs.open(local_path, "wb")
if file then
file.write(response.readAll())
file.close()
end
response.close()
print("Downloaded: " .. local_path)
else
print("Failed to download: " .. url)
end
end
local function traverse_and_download(remote_path, local_prefix)
local tree = fetch_repo_tree(remote_path)
for _, entry in ipairs(tree) do
if entry.type == "file" then
local local_path = local_prefix .. "/" .. fs.getName(entry.path)
download_file(entry.path, local_path)
elseif entry.type == "dir" then
local new_remote = entry.path
local new_local = local_prefix .. "/" .. fs.getName(entry.path)
traverse_and_download(new_remote, new_local)
end
local tree = fetch_repo_tree(remote_path)
for _, entry in ipairs(tree) do
if entry.type == "file" then
local local_path = local_prefix .. "/" .. fs.getName(entry.path)
download_file(entry.path, local_path)
elseif entry.type == "dir" then
local new_remote = entry.path
local new_local = local_prefix .. "/" .. fs.getName(entry.path)
traverse_and_download(new_remote, new_local)
end
end
end
local remote_hash = fetch_commit_hash()
print("Latest commit: " .. remote_hash)
if fs.exists(download_root) then
if fs.exists(download_root .. "/version") then
local f = fs.open(download_root .. "/version", "r")
local local_hash = f.readAll()
f.close()
if local_hash == remote_hash then
print("Already up to date (commit " .. remote_hash:sub(1,7) .. ").")
shell.run(download_root .. "/main.lua")
return
else
print("Outdated (" .. local_hash:sub(1,7) .. " -> " .. remote_hash:sub(1,7) .. "), reinstalling...")
fs.delete(download_root)
end
if fs.exists(download_root .. "/version") then
local f = fs.open(download_root .. "/version", "r")
local local_hash = f.readAll()
f.close()
if local_hash == remote_hash then
print("Already up to date (commit " .. remote_hash:sub(1, 7) .. ").")
shell.run(download_root .. "/main.lua")
return
else
print("Version marker missing, reinstalling.")
fs.delete(download_root)
print("Outdated (" .. local_hash:sub(1, 7) .. " -> " .. remote_hash:sub(1, 7) .. "), reinstalling...")
fs.delete(download_root)
end
else
print("Version marker missing, reinstalling.")
fs.delete(download_root)
end
end
fs.makeDir(download_root)
@ -89,5 +89,5 @@ local f = fs.open(download_root .. "/version", "w")
f.write(remote_hash)
f.close()
print("Installed Sophie's Storage Solution (commit " .. remote_hash:sub(1,7) .. ")")
print("Installed Sophie's Storage Solution (commit " .. remote_hash:sub(1, 7) .. ")")
shell.run(download_root .. "/main.lua")

View file

@ -1,81 +1,94 @@
local g = string.gsub
local g = string.gsub
sha256 = loadstring(g(g(g(g(g(g(g(g('Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',"S"," local "),"T",",0x"),"U"," return "),"V"," end "),"W","or "),"X","bit32."),"Y","function "),"Z"," do "))()
sha256 = loadstring(g(
g(
g(
g(
g(
g(
g(
g(
'Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',
"S", " local "), "T", ",0x"), "U", " return "), "V", " end "), "W", "or "), "X", "bit32."), "Y", "function "), "Z",
" do "))()
local aead = require("lib.plc.aead_chacha_poly")
local aead = require("lib.plc.aead_chacha_poly")
local function tobytes(s) return {s:byte(1, #s)} end
local function tobytes(s) return { s:byte(1, #s) } end
local function frombytes(t) return string.char(table.unpack(t)) end
local b64abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
local function b64u_encode(bin)
local t, n, out = tobytes(bin), #bin, {}
local i=1
while i<=n do
local a=t[i] or 0; local b=t[i+1] or 0; local c=t[i+2] or 0
local triple = a*65536 + b*256 + c
out[#out+1]=b64abc:sub(bit32.band(bit32.rshift(triple,18), 63)+1,bit32.band(bit32.rshift(triple,18), 63)+1)
out[#out+1]=b64abc:sub(bit32.band(bit32.rshift(triple,12), 63)+1,bit32.band(bit32.rshift(triple,12), 63)+1)
out[#out+1]= i+1<=n and b64abc:sub(bit32.band(bit32.rshift(triple,6), 63)+1,bit32.band(bit32.rshift(triple,6), 63)+1) or ''
out[#out+1]= i+2<=n and b64abc:sub(bit32.band(bit32.rshift(triple,0), 63)+1,bit32.band(bit32.rshift(triple,0), 63)+1) or ''
i=i+3
local i = 1
while i <= n do
local a = t[i] or 0; local b = t[i + 1] or 0; local c = t[i + 2] or 0
local triple = a * 65536 + b * 256 + c
out[#out + 1] = b64abc:sub(bit32.band(bit32.rshift(triple, 18), 63) + 1, bit32.band(bit32.rshift(triple, 18), 63) + 1)
out[#out + 1] = b64abc:sub(bit32.band(bit32.rshift(triple, 12), 63) + 1, bit32.band(bit32.rshift(triple, 12), 63) + 1)
out[#out + 1] = i + 1 <= n and
b64abc:sub(bit32.band(bit32.rshift(triple, 6), 63) + 1, bit32.band(bit32.rshift(triple, 6), 63) + 1) or ''
out[#out + 1] = i + 2 <= n and
b64abc:sub(bit32.band(bit32.rshift(triple, 0), 63) + 1, bit32.band(bit32.rshift(triple, 0), 63) + 1) or ''
i = i + 3
end
return table.concat(out)
end
local function b64u_decode(txt)
local map = {}
for i=1,#b64abc do map[b64abc:sub(i,i)] = i-1 end
for i = 1, #b64abc do map[b64abc:sub(i, i)] = i - 1 end
local out = {}
local i=1
while i<=#txt do
local a = map[txt:sub(i,i)]; i=i+1
local b = map[txt:sub(i,i)]; i=i+1
local c = map[txt:sub(i,i)]; i=i+1
local d = map[txt:sub(i,i)]; i=i+1
if a==nil or b==nil then break end
local triple = bit32.bor(bit32.lshift(a,18) , bit32.lshift(b,12) , bit32.lshift(c or 0,6) , (d or 0))
out[#out+1] = string.char(bit32.band(bit32.rshift(triple,16) , 255))
if c ~= nil then out[#out+1] = string.char(bit32.band(bit32.rshift(triple,8) , 255)) end
if d ~= nil then out[#out+1] = string.char(bit32.band(triple , 255)) end
local i = 1
while i <= #txt do
local a = map[txt:sub(i, i)]; i = i + 1
local b = map[txt:sub(i, i)]; i = i + 1
local c = map[txt:sub(i, i)]; i = i + 1
local d = map[txt:sub(i, i)]; i = i + 1
if a == nil or b == nil then break end
local triple = bit32.bor(bit32.lshift(a, 18), bit32.lshift(b, 12), bit32.lshift(c or 0, 6), (d or 0))
out[#out + 1] = string.char(bit32.band(bit32.rshift(triple, 16), 255))
if c ~= nil then out[#out + 1] = string.char(bit32.band(bit32.rshift(triple, 8), 255)) end
if d ~= nil then out[#out + 1] = string.char(bit32.band(triple, 255)) end
end
return table.concat(out)
end
local function hmac_sha256(key, msg)
local block = 64
if #key > block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h,16)) end)) end
if #key > block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h, 16)) end)) end
if #key < block then key = key .. string.rep("\0", block - #key) end
local o_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x5c)) end)
local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c) , 0x36)) end)
local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x36)) end)
local inner = sha256(i_key_pad .. msg)
inner = inner:gsub('..', function(h) return string.char(tonumber(h,16)) end)
inner = inner:gsub('..', function(h) return string.char(tonumber(h, 16)) end)
local mac = sha256(o_key_pad .. inner)
return mac:gsub('..', function(h) return string.char(tonumber(h,16)) end)
return mac:gsub('..', function(h) return string.char(tonumber(h, 16)) end)
end
local function int_be(n) -- 32-bit BE
return string.char(bit32.band(bit32.rshift(n,24), 255), bit32.band(bit32.rshift(n,16), 255), bit32.band(bit32.rshift(n,8), 255), bit32.band(n, 255))
return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255),
bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255))
end
local function pbkdf2_sha256(password, salt, iterations, dkLen)
local hLen = 32
local l = math.ceil(dkLen / hLen)
local r = dkLen - (l-1)*hLen
local r = dkLen - (l - 1) * hLen
local dk = {}
for i=1,l do
for i = 1, l do
local U = hmac_sha256(password, salt .. int_be(i))
local T = {U:byte(1,hLen)}
for itr=2,iterations do
local T = { U:byte(1, hLen) }
for itr = 2, iterations do
U = hmac_sha256(password, U)
local Ub = {U:byte(1,hLen)}
for j=1,hLen do T[j] = bit32.bxor(T[j], Ub[j]) end
local Ub = { U:byte(1, hLen) }
for j = 1, hLen do T[j] = bit32.bxor(T[j], Ub[j]) end
if itr % 500 == 0 then sleep(0) end
end
if i<l then
dk[#dk+1] = string.char(table.unpack(T))
if i < l then
dk[#dk + 1] = string.char(table.unpack(T))
else
dk[#dk+1] = string.char(table.unpack(T, 1, r))
dk[#dk + 1] = string.char(table.unpack(T, 1, r))
end
end
return table.concat(dk)
@ -83,20 +96,20 @@ end
-- HMAC-DRBG
local SEED_PATH = "siss_drg_seed.bin"
local DRBG = {K = string.rep("\0",32), V = string.rep("\1",32), inited=false}
local DRBG = { K = string.rep("\0", 32), V = string.rep("\1", 32), inited = false }
local function drbg_update(provided_data)
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\0" .. (provided_data or ""))
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
if provided_data and #provided_data>0 then
if provided_data and #provided_data > 0 then
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\1" .. provided_data)
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
end
end
local function drbg_init(seed)
DRBG.K = string.rep("\0",32)
DRBG.V = string.rep("\1",32)
DRBG.K = string.rep("\0", 32)
DRBG.V = string.rep("\1", 32)
drbg_update(seed)
DRBG.inited = true
end
@ -106,27 +119,27 @@ local function drbg_bytes(n)
local out = {}
while #table.concat(out) < n do
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
out[#out+1] = DRBG.V
out[#out + 1] = DRBG.V
end
drbg_update("")
local buf = table.concat(out)
return buf:sub(1,n)
return buf:sub(1, n)
end
local function seed_entropy(extra_bytes)
local accum = {}
if fs.exists(SEED_PATH) then
local f = fs.open(SEED_PATH, "rb"); accum[#accum+1]=f.readAll(); f.close()
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end)
local f = fs.open(SEED_PATH, "rb"); accum[#accum + 1] = f.readAll(); f.close()
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end)
drbg_init(seed)
else
term.write("Move around / mash keys then press Enter 4 times to seed…\n")
for i=1,4 do
for i = 1, 4 do
local t1 = os.clock(); read(); local t2 = os.clock()
accum[#accum+1] = int_be(math.floor((t2 - t1)*1e9)) .. tostring({}):sub(8)
accum[#accum + 1] = int_be(math.floor((t2 - t1) * 1e9)) .. tostring({}):sub(8)
end
if extra_bytes and #extra_bytes>0 then accum[#accum+1]=extra_bytes end
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end)
if extra_bytes and #extra_bytes > 0 then accum[#accum + 1] = extra_bytes end
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end)
drbg_init(seed)
local f = fs.open(SEED_PATH, "wb")
f.write(drbg_bytes(48))
@ -138,15 +151,16 @@ local VERSION = "\1"
local KDF_ID = "\1"
local AAD = "SiSS RA rev1|chacha20poly1305"
local function u32be(n) return string.char(bit32.band(bit32.rshift(n,24),255), bit32.band(bit32.rshift(n,16),255), bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end
local function u16be(n) return string.char(bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end
local function u32be(n) return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255),
bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end
local function u16be(n) return string.char(bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end
local function pack_params(iter)
return u32be(iter) .. u16be(32)
end
local function unpack_params(s)
local i1,i2,i3,i4, d1,d2 = s:byte(1,6)
local iters = (bit32.bor(bit32.lshift(i1,24),bit32.lshift(i2,16),bit32.lshift(i3,8),i4))
local dklen = (bit32.bor(bit32.lshift(d1,8),d2))
local i1, i2, i3, i4, d1, d2 = s:byte(1, 6)
local iters = (bit32.bor(bit32.lshift(i1, 24), bit32.lshift(i2, 16), bit32.lshift(i3, 8), i4))
local dklen = (bit32.bor(bit32.lshift(d1, 8), d2))
return iters, dklen
end
@ -170,24 +184,24 @@ local function encrypt_with_password(password, plaintext)
local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext)
local params = pack_params(iters)
local blob = table.concat{ VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag }
local blob = table.concat { VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag }
return b64u_encode(blob)
end
local function decrypt_with_password(password, blob)
local bin = b64u_decode(blob)
if #bin < 1+1+6+16+12+16 then return nil, "ciphertext too short" end
if #bin < 1 + 1 + 6 + 16 + 12 + 16 then return nil, "ciphertext too short" end
local pos = 1
local ver = bin:sub(pos,pos); pos=pos+1
local ver = bin:sub(pos, pos); pos = pos + 1
if ver ~= VERSION then return nil, "version mismatch" end
local kdfid = bin:sub(pos,pos); pos=pos+1
local kdfid = bin:sub(pos, pos); pos = pos + 1
if kdfid ~= KDF_ID then return nil, "kdf mismatch" end
local params = bin:sub(pos,pos+5); pos=pos+6
local params = bin:sub(pos, pos + 5); pos = pos + 6
local iters, dklen = unpack_params(params); if dklen ~= 32 then return nil, "bad dkLen" end
local salt = bin:sub(pos,pos+15); pos=pos+16
local salt = bin:sub(pos, pos + 15); pos = pos + 16
-- read the same 12-byte nonce and split the same way
local nonce12 = bin:sub(pos,pos+11); pos=pos+12
local nonce12 = bin:sub(pos, pos + 11); pos = pos + 12
local iv, constant = split_nonce12(nonce12)
local tag_len = 16

View file

@ -23,67 +23,67 @@ local poly1305 = require "lib.plc.poly1305"
-- poly1305 key generation
local poly_keygen = function(key, nonce)
local counter = 0
local m = string.rep('\0', 64)
local e = chacha20.encrypt(key, counter, nonce, m)
-- keep only first the 256 bits (32 bytes)
return e:sub(1, 32)
local counter = 0
local m = string.rep('\0', 64)
local e = chacha20.encrypt(key, counter, nonce, m)
-- keep only first the 256 bits (32 bytes)
return e:sub(1, 32)
end
local pad16 = function(s)
-- return null bytes to add to s so that #s is a multiple of 16
return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16))
-- return null bytes to add to s so that #s is a multiple of 16
return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16))
end
local app = table.insert
local encrypt = function(aad, key, iv, constant, plain)
-- aad: additional authenticated data - arbitrary length
-- key: 32-byte string
-- iv, constant: concatenated to form the nonce (12 bytes)
-- (why not one 12-byte param? --maybe because IPsec uses
-- an 8-byte nonce)
-- implementation: RFC 7539 sect 2.8.1
-- (memory inefficient - encr text is copied in mac_data)
local mt = {} -- mac_data table
local nonce = constant .. iv
local otk = poly_keygen(key, nonce)
local encr = chacha20.encrypt(key, 1, nonce, plain)
app(mt, aad)
app(mt, pad16(aad))
app(mt, encr)
app(mt, pad16(encr))
-- aad and encrypted text length must be encoded as
-- little endian _u64_ (and not u32) -- see errata at
-- https://www.rfc-editor.org/errata_search.php?rfc=7539
app(mt, string.pack('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
--~ p16('mac', mac_data)
local tag = poly1305.auth(mac_data, otk)
return encr, tag
-- aad: additional authenticated data - arbitrary length
-- key: 32-byte string
-- iv, constant: concatenated to form the nonce (12 bytes)
-- (why not one 12-byte param? --maybe because IPsec uses
-- an 8-byte nonce)
-- implementation: RFC 7539 sect 2.8.1
-- (memory inefficient - encr text is copied in mac_data)
local mt = {} -- mac_data table
local nonce = constant .. iv
local otk = poly_keygen(key, nonce)
local encr = chacha20.encrypt(key, 1, nonce, plain)
app(mt, aad)
app(mt, pad16(aad))
app(mt, encr)
app(mt, pad16(encr))
-- aad and encrypted text length must be encoded as
-- little endian _u64_ (and not u32) -- see errata at
-- https://www.rfc-editor.org/errata_search.php?rfc=7539
app(mt, string.pack('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
--~ p16('mac', mac_data)
local tag = poly1305.auth(mac_data, otk)
return encr, tag
end --chacha20_aead_encrypt()
local function decrypt(aad, key, iv, constant, encr, tag)
-- (memory inefficient - encr text is copied in mac_data)
-- (structure similar to aead_encrypt => what could be factored?)
local mt = {} -- mac_data table
local nonce = constant .. iv
local otk = poly_keygen(key, nonce)
app(mt, aad)
app(mt, pad16(aad))
app(mt, encr)
app(mt, pad16(encr))
app(mt, string.pack('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
local mac = poly1305.auth(mac_data, otk)
if mac == tag then
local plain = chacha20.encrypt(key, 1, nonce, encr)
return plain
else
return nil, "auth failed"
end
-- (memory inefficient - encr text is copied in mac_data)
-- (structure similar to aead_encrypt => what could be factored?)
local mt = {} -- mac_data table
local nonce = constant .. iv
local otk = poly_keygen(key, nonce)
app(mt, aad)
app(mt, pad16(aad))
app(mt, encr)
app(mt, pad16(encr))
app(mt, string.pack('<I8', #aad))
app(mt, string.pack('<I8', #encr))
local mac_data = table.concat(mt)
local mac = poly1305.auth(mac_data, otk)
if mac == tag then
local plain = chacha20.encrypt(key, 1, nonce, encr)
return plain
else
return nil, "auth failed"
end
end --chacha20_aead_decrypt()
@ -91,7 +91,7 @@ end --chacha20_aead_decrypt()
-- return aead_chacha_poly module
return {
poly_keygen = poly_keygen,
encrypt = encrypt,
decrypt = decrypt,
}
poly_keygen = poly_keygen,
encrypt = encrypt,
decrypt = decrypt,
}

View file

@ -27,178 +27,178 @@ local app, concat = table.insert, table.concat
local function qround(st, x, y, z, w)
-- st is a chacha state: an array of 16 u32 words
-- x,y,z,w are indices in st
local a, b, c, d = st[x], st[y], st[z], st[w]
local t
-- st is a chacha state: an array of 16 u32 words
-- x,y,z,w are indices in st
local a, b, c, d = st[x], st[y], st[z], st[w]
local t
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
a = (a + b) % 0x100000000
t = bit32.bxor(d, a)
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 7), bit32.rshift(t, 25)), 0xffffffff)
c = (c + d) % 0x100000000
t = bit32.bxor(b, c)
b = bit32.band(bit32.bor(bit32.lshift(t, 7), bit32.rshift(t, 25)), 0xffffffff)
st[x], st[y], st[z], st[w] = a, b, c, d
return st
st[x], st[y], st[z], st[w] = a, b, c, d
return st
end
-- chacha20 state and working state are allocated once and reused
-- by each invocation of chacha20_block()
local chacha20_state = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
local chacha20_working_state = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
local chacha20_state = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
local chacha20_working_state = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
local chacha20_block = function(key, counter, nonce)
-- key: u32[8]
-- counter: u32
-- nonce: u32[3]
local st = chacha20_state -- state
local wst = chacha20_working_state -- working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i+4] = key[i] end
st[13] = counter
for i = 1, 3 do st[i+13] = nonce[i] end
-- copy state to working_state
for i = 1, 16 do wst[i] = st[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(wst, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(wst, 2,6,10,14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(wst, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
qround(wst, 4,8,12,16) --4. QUARTERROUND ( 3, 7,11,15)
qround(wst, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,15)
qround(wst, 2,7,12,13) --6. QUARTERROUND ( 1, 6,11,12)
qround(wst, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(wst, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
end
-- add working_state to state
for i = 1, 16 do st[i] = bit32.band((st[i] + wst[i]), 0xffffffff) end
-- return st, an array of 16 u32 words used as a keystream
return st
-- key: u32[8]
-- counter: u32
-- nonce: u32[3]
local st = chacha20_state -- state
local wst = chacha20_working_state -- working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i + 4] = key[i] end
st[13] = counter
for i = 1, 3 do st[i + 13] = nonce[i] end
-- copy state to working_state
for i = 1, 16 do wst[i] = st[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(wst, 1, 5, 9, 13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(wst, 2, 6, 10, 14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(wst, 3, 7, 11, 15) --3. QUARTERROUND ( 2, 6,10,14)
qround(wst, 4, 8, 12, 16) --4. QUARTERROUND ( 3, 7,11,15)
qround(wst, 1, 6, 11, 16) --5. QUARTERROUND ( 0, 5,10,15)
qround(wst, 2, 7, 12, 13) --6. QUARTERROUND ( 1, 6,11,12)
qround(wst, 3, 8, 9, 14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(wst, 4, 5, 10, 15) --8. QUARTERROUND ( 3, 4, 9,14)
end
-- add working_state to state
for i = 1, 16 do st[i] = bit32.band((st[i] + wst[i]), 0xffffffff) end
-- return st, an array of 16 u32 words used as a keystream
return st
end --chacha20_block()
-- pat16: used to unpack a 64-byte string as 16 uint32
local pat16 = "<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"
local function chacha20_encrypt_block(key, counter, nonce, pt, ptidx)
-- encrypt a 64-byte block of plain text.
-- key: 32 bytes as an array of 8 uint32
-- counter: an uint32 (must be incremented for each block)
-- nonce: 12 bytes as an array of 3 uint32
-- pt: plain text string,
-- ptidx: index of beginning of block in plain text (origin=1)
-- if less than 64 bytes are left at position ptidx, it is padded
-- with null bytes before encryption and result is stripped
-- accordingly.
-- return encrypted block as a string (length <= 16)
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
if rbn < 64 then
local tmp = string.sub(pt, ptidx)
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
ptidx = 1
end
assert(#pt >= 64)
local ba = table.pack(string.unpack(pat16, pt, ptidx))
local keystream = chacha20_block(key, counter, nonce)
for i = 1, 16 do
ba[i] = bit32.bxor(ba[i], keystream[i])
end
local es = string.pack(pat16, table.unpack(ba))
if rbn < 64 then
es = string.sub(es, 1, rbn)
end
return es
-- encrypt a 64-byte block of plain text.
-- key: 32 bytes as an array of 8 uint32
-- counter: an uint32 (must be incremented for each block)
-- nonce: 12 bytes as an array of 3 uint32
-- pt: plain text string,
-- ptidx: index of beginning of block in plain text (origin=1)
-- if less than 64 bytes are left at position ptidx, it is padded
-- with null bytes before encryption and result is stripped
-- accordingly.
-- return encrypted block as a string (length <= 16)
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
if rbn < 64 then
local tmp = string.sub(pt, ptidx)
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
ptidx = 1
end
assert(#pt >= 64)
local ba = table.pack(string.unpack(pat16, pt, ptidx))
local keystream = chacha20_block(key, counter, nonce)
for i = 1, 16 do
ba[i] = bit32.bxor(ba[i], keystream[i])
end
local es = string.pack(pat16, table.unpack(ba))
if rbn < 64 then
es = string.sub(es, 1, rbn)
end
return es
end --chacha20_encrypt_block
local chacha20_encrypt = function(key, counter, nonce, pt)
-- encrypt plain text 'pt', return encrypted text
-- key: 32 bytes as a string
-- counter: an uint32 (must be incremented for each block)
-- nonce: 8 bytes as a string
-- pt: plain text string,
-- encrypt plain text 'pt', return encrypted text
-- key: 32 bytes as a string
-- counter: an uint32 (must be incremented for each block)
-- nonce: 8 bytes as a string
-- pt: plain text string,
-- ensure counter can fit an uint32 --although it's unlikely
-- that we hit this wall with pure Lua encryption :-)
assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff,
-- ensure counter can fit an uint32 --although it's unlikely
-- that we hit this wall with pure Lua encryption :-)
assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff,
"block counter must fit an uint32")
assert(#key == 32, "#key must be 32")
assert(#nonce == 12, "#nonce must be 12")
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
local t = {} -- used to collect all encrypted blocks
local ptidx = 1
while ptidx <= #pt do
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
ptidx = ptidx + 64
counter = counter + 1
end
local et = concat(t)
return et
assert(#key == 32, "#key must be 32")
assert(#nonce == 12, "#nonce must be 12")
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
local t = {} -- used to collect all encrypted blocks
local ptidx = 1
while ptidx <= #pt do
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
ptidx = ptidx + 64
counter = counter + 1
end
local et = concat(t)
return et
end --chacha20_encrypt()
local function hchacha20(key, nonce16)
-- key: string(32)
-- nonce16: string(16)
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
local st = {} -- chacha working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i+4] = keya[i] end
for i = 1, 4 do st[i+12] = noncea[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(st, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(st, 2,6,10,14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(st, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
qround(st, 4,8,12,16) --4. QUARTERROUND ( 3, 7,11,15)
qround(st, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,15)
qround(st, 2,7,12,13) --6. QUARTERROUND ( 1, 6,11,12)
qround(st, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(st, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
end
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
st[1], st[2], st[3], st[4],
st[13], st[14], st[15], st[16] )
return subkey
-- key: string(32)
-- nonce16: string(16)
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
local st = {} -- chacha working state
-- initialize state
st[1], st[2], st[3], st[4] =
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
for i = 1, 8 do st[i + 4] = keya[i] end
for i = 1, 4 do st[i + 12] = noncea[i] end
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
for _ = 1, 10 do --RFC reference:
qround(st, 1, 5, 9, 13) --1. QUARTERROUND ( 0, 4, 8,12)
qround(st, 2, 6, 10, 14) --2. QUARTERROUND ( 1, 5, 9,13)
qround(st, 3, 7, 11, 15) --3. QUARTERROUND ( 2, 6,10,14)
qround(st, 4, 8, 12, 16) --4. QUARTERROUND ( 3, 7,11,15)
qround(st, 1, 6, 11, 16) --5. QUARTERROUND ( 0, 5,10,15)
qround(st, 2, 7, 12, 13) --6. QUARTERROUND ( 1, 6,11,12)
qround(st, 3, 8, 9, 14) --7. QUARTERROUND ( 2, 7, 8,13)
qround(st, 4, 5, 10, 15) --8. QUARTERROUND ( 3, 4, 9,14)
end
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
st[1], st[2], st[3], st[4],
st[13], st[14], st[15], st[16])
return subkey
end --hchacha20()
local function xchacha20_encrypt(key, counter, nonce, pt)
assert(#key == 32, "#key must be 32")
assert(#nonce == 24, "#nonce must be 24")
local subkey = hchacha20(key, nonce:sub(1, 16))
local nonce12 = '\0\0\0\0'..nonce:sub(17)
return chacha20_encrypt(subkey, counter, nonce12, pt)
assert(#key == 32, "#key must be 32")
assert(#nonce == 24, "#nonce must be 24")
local subkey = hchacha20(key, nonce:sub(1, 16))
local nonce12 = '\0\0\0\0' .. nonce:sub(17)
return chacha20_encrypt(subkey, counter, nonce12, pt)
end --xchacha20_encrypt()
------------------------------------------------------------
return {
chacha20_encrypt = chacha20_encrypt,
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
encrypt = chacha20_encrypt, --alias
decrypt = chacha20_encrypt, --alias
hchacha20 = hchacha20,
xchacha20_encrypt = xchacha20_encrypt,
xchacha20_decrypt = xchacha20_encrypt,
--
key_size = 32,
nonce_size = 12, -- nonce size for chacha20_encrypt
xnonce_size = 24, -- nonce size for xchacha20_encrypt
}
chacha20_encrypt = chacha20_encrypt,
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
encrypt = chacha20_encrypt, --alias
decrypt = chacha20_encrypt, --alias
hchacha20 = hchacha20,
xchacha20_encrypt = xchacha20_encrypt,
xchacha20_decrypt = xchacha20_encrypt,
--
key_size = 32,
nonce_size = 12, -- nonce size for chacha20_encrypt
xnonce_size = 24, -- nonce size for xchacha20_encrypt
}
--end of chacha20

View file

@ -13,192 +13,192 @@ local sunp = string.unpack
local bit32 = bit32 -- alias
local function poly_init(k)
-- k: 32-byte key as a string
-- initialize internal state
local st = {
r = {
bit32.band(sunp('<I4', k, 1) , 0x3ffffff), -- r0
bit32.band(bit32.rshift(sunp('<I4', k, 4), 2), 0x3ffff03), -- r1
bit32.band(bit32.rshift(sunp('<I4', k, 7), 4), 0x3ffc0ff), -- r2
bit32.band(bit32.rshift(sunp('<I4', k, 10), 6), 0x3f03fff), -- r3
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
},
h = { 0,0,0,0,0 },
pad = { sunp('<I4', k, 17), -- 's' in rfc
sunp('<I4', k, 21),
sunp('<I4', k, 25),
sunp('<I4', k, 29),
},
buffer = "", --
leftover = 0,
final = false,
}--st
return st
-- k: 32-byte key as a string
-- initialize internal state
local st = {
r = {
bit32.band(sunp('<I4', k, 1), 0x3ffffff), -- r0
bit32.band(bit32.rshift(sunp('<I4', k, 4), 2), 0x3ffff03), -- r1
bit32.band(bit32.rshift(sunp('<I4', k, 7), 4), 0x3ffc0ff), -- r2
bit32.band(bit32.rshift(sunp('<I4', k, 10), 6), 0x3f03fff), -- r3
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
},
h = { 0, 0, 0, 0, 0 },
pad = { sunp('<I4', k, 17), -- 's' in rfc
sunp('<I4', k, 21),
sunp('<I4', k, 25),
sunp('<I4', k, 29),
},
buffer = "", --
leftover = 0,
final = false,
} --st
return st
end --poly_init()
local function poly_blocks(st, m)
-- st: internal state
-- m: message:string
local bytes = #m
local midx = 1
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
local r0 = st.r[1]
local r1 = st.r[2]
local r2 = st.r[3]
local r3 = st.r[4]
local r4 = st.r[5]
local s1 = r1 * 5
local s2 = r2 * 5
local s3 = r3 * 5
local s4 = r4 * 5
local h0 = st.h[1]
local h1 = st.h[2]
local h2 = st.h[3]
local h3 = st.h[4]
local h4 = st.h[5]
local d0, d1, d2, d3, d4, c
--
while bytes >= 16 do -- 16 = poly1305_block_size
-- h += m[i] (in rfc: a += n with 0x01 byte)
h0 = h0 + bit32.band(sunp('<I4', m, midx ), 0x3ffffff)
h1 = h1 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 3), 2), 0x3ffffff)
h2 = h2 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 6), 4), 0x3ffffff)
h3 = h3 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 9), 6), 0x3ffffff)
h4 = h4 + bit32.bor(bit32.rshift(sunp('<I4', m, midx + 12), 8), hibit) -- 0x01 byte
--
-- h *= r % p (partial)
d0 = h0*r0 + h1*s4 + h2*s3 + h3*s2 + h4*s1
d1 = h0*r1 + h1*r0 + h2*s4 + h3*s3 + h4*s2
d2 = h0*r2 + h1*r1 + h2*r0 + h3*s4 + h4*s3
d3 = h0*r3 + h1*r2 + h2*r1 + h3*r0 + h4*s4
d4 = h0*r4 + h1*r3 + h2*r2 + h3*r1 + h4*r0
--
c = bit32.band(bit32.rshift(d0,26), 0xffffffff) ; h0 = bit32.band(d0, 0x3ffffff)
d1 = d1 + c ; c = bit32.band(bit32.rshift(d1,26), 0xffffffff) ; h1 = bit32.band(d1, 0x3ffffff)
d2 = d2 + c ; c = bit32.band(bit32.rshift(d2,26), 0xffffffff) ; h2 = bit32.band(d2, 0x3ffffff)
d3 = d3 + c ; c = bit32.band(bit32.rshift(d3,26), 0xffffffff) ; h3 = bit32.band(d3, 0x3ffffff)
d4 = d4 + c ; c = bit32.band(bit32.rshift(d4,26), 0xffffffff) ; h4 = bit32.band(d4, 0x3ffffff)
h0 = h0 + (c*5) ; c = bit32.rshift(h0,26) ; h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
midx = midx + 16 -- 16 = poly1305_block_size
bytes = bytes - 16
end --while
st.h[1] = h0
st.h[2] = h1
st.h[3] = h2
st.h[4] = h3
st.h[5] = h4
st.bytes = bytes -- remaining bytes. must be < 16 here
st.midx = midx -- index of first remaining bytes
return st
end --poly_blocks()
-- st: internal state
-- m: message:string
local bytes = #m
local midx = 1
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
local r0 = st.r[1]
local r1 = st.r[2]
local r2 = st.r[3]
local r3 = st.r[4]
local r4 = st.r[5]
local s1 = r1 * 5
local s2 = r2 * 5
local s3 = r3 * 5
local s4 = r4 * 5
local h0 = st.h[1]
local h1 = st.h[2]
local h2 = st.h[3]
local h3 = st.h[4]
local h4 = st.h[5]
local d0, d1, d2, d3, d4, c
--
while bytes >= 16 do -- 16 = poly1305_block_size
-- h += m[i] (in rfc: a += n with 0x01 byte)
h0 = h0 + bit32.band(sunp('<I4', m, midx), 0x3ffffff)
h1 = h1 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 3), 2), 0x3ffffff)
h2 = h2 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 6), 4), 0x3ffffff)
h3 = h3 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 9), 6), 0x3ffffff)
h4 = h4 + bit32.bor(bit32.rshift(sunp('<I4', m, midx + 12), 8), hibit) -- 0x01 byte
--
-- h *= r % p (partial)
d0 = h0 * r0 + h1 * s4 + h2 * s3 + h3 * s2 + h4 * s1
d1 = h0 * r1 + h1 * r0 + h2 * s4 + h3 * s3 + h4 * s2
d2 = h0 * r2 + h1 * r1 + h2 * r0 + h3 * s4 + h4 * s3
d3 = h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * s4
d4 = h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0
--
c = bit32.band(bit32.rshift(d0, 26), 0xffffffff); h0 = bit32.band(d0, 0x3ffffff)
d1 = d1 + c; c = bit32.band(bit32.rshift(d1, 26), 0xffffffff); h1 = bit32.band(d1, 0x3ffffff)
d2 = d2 + c; c = bit32.band(bit32.rshift(d2, 26), 0xffffffff); h2 = bit32.band(d2, 0x3ffffff)
d3 = d3 + c; c = bit32.band(bit32.rshift(d3, 26), 0xffffffff); h3 = bit32.band(d3, 0x3ffffff)
d4 = d4 + c; c = bit32.band(bit32.rshift(d4, 26), 0xffffffff); h4 = bit32.band(d4, 0x3ffffff)
h0 = h0 + (c * 5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
midx = midx + 16 -- 16 = poly1305_block_size
bytes = bytes - 16
end --while
st.h[1] = h0
st.h[2] = h1
st.h[3] = h2
st.h[4] = h3
st.h[5] = h4
st.bytes = bytes -- remaining bytes. must be < 16 here
st.midx = midx -- index of first remaining bytes
return st
end --poly_blocks()
local function poly_update(st, m)
-- st: internal state
-- m: message:string
st.bytes, st.midx = #m, 1
-- process full blocks if any
if st.bytes >= 16 then
poly_blocks(st, m)
end
--handle remaining bytes
if st.bytes == 0 then -- no bytes left
-- nothing to do? no add 0x01? - apparently not.
else
local buffer = string.sub(m, st.midx)
.. '\x01' .. string.rep('\0', 16 - st.bytes -1)
assert(#buffer == 16)
st.final = true -- this is the last block
--~ p16(buffer)
poly_blocks(st, buffer)
end
--
return st
-- st: internal state
-- m: message:string
st.bytes, st.midx = #m, 1
-- process full blocks if any
if st.bytes >= 16 then
poly_blocks(st, m)
end
--handle remaining bytes
if st.bytes == 0 then -- no bytes left
-- nothing to do? no add 0x01? - apparently not.
else
local buffer = string.sub(m, st.midx)
.. '\x01' .. string.rep('\0', 16 - st.bytes - 1)
assert(#buffer == 16)
st.final = true -- this is the last block
--~ p16(buffer)
poly_blocks(st, buffer)
end
--
return st
end --poly_update
local function poly_finish(st)
--
local c, mask --u32
local f --u64
-- fully carry h
local h0 = st.h[1]
local h1 = st.h[2]
local h2 = st.h[3]
local h3 = st.h[4]
local h4 = st.h[5]
--
c = bit32.rshift(h1,26); h1 = bit32.band(h1, 0x3ffffff)
h2 = h2 + c; c = bit32.rshift(h2, 26); h2 = bit32.band(h2, 0x3ffffff)
h3 = h3 + c; c = bit32.rshift(h3, 26); h3 = bit32.band(h3, 0x3ffffff)
h4 = h4 + c; c = bit32.rshift(h4, 26); h4 = bit32.band(h4, 0x3ffffff)
h0 = h0 + (c*5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
--compute h + -p
local g0 = (h0 + 5) ; c = bit32.rshift(g0, 26); g0 = bit32.band(g0, 0x3ffffff)
local g1 = (h1 + c) ; c = bit32.rshift(g1, 26); g1 = bit32.band(g1, 0x3ffffff)
local g2 = (h2 + c) ; c = bit32.rshift(g2, 26); g2 = bit32.band(g2, 0x3ffffff)
local g3 = (h3 + c) ; c = bit32.rshift(g3, 26); g3 = bit32.band(g3, 0x3ffffff)
local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26)
--
-- select h if h < p, or h + -p if h >= p
mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff)
--
g0 = bit32.band(g0, mask)
g1 = bit32.band(g1, mask)
g2 = bit32.band(g2, mask)
g3 = bit32.band(g3, mask)
g4 = bit32.band(g4, mask)
--
mask = bit32.band(bit32.bnot(mask), 0xffffffff)
h0 = bit32.bor(bit32.band(h0, mask), g0)
h1 = bit32.bor(bit32.band(h1, mask), g1)
h2 = bit32.bor(bit32.band(h2, mask), g2)
h3 = bit32.bor(bit32.band(h3, mask), g3)
h4 = bit32.bor(bit32.band(h4, mask), g4)
--
--h = h % (2^128)
h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff)
h1 = bit32.band(bit32.bor(bit32.rshift(h1, 6), bit32.lshift(h2, 20)), 0xffffffff)
h2 = bit32.band(bit32.bor(bit32.rshift(h2, 12), bit32.lshift(h3, 14)), 0xffffffff)
h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff)
--
-- mac = (h + pad) % (2^128)
f = h0 + st.pad[1] ; h0 = bit32.band(f, 0xffffffff)
f = h1 + st.pad[2] + bit32.rshift(f, 32) ; h1 = bit32.band(f, 0xffffffff)
f = h2 + st.pad[3] + bit32.rshift(f, 32) ; h2 = bit32.band(f, 0xffffffff)
f = h3 + st.pad[4] + bit32.rshift(f, 32) ; h3 = bit32.band(f, 0xffffffff)
--
local mac = string.pack('<I4I4I4I4', h0, h1, h2, h3)
-- (should zero out the state?)
--
return mac
--
local c, mask --u32
local f --u64
-- fully carry h
local h0 = st.h[1]
local h1 = st.h[2]
local h2 = st.h[3]
local h3 = st.h[4]
local h4 = st.h[5]
--
c = bit32.rshift(h1, 26); h1 = bit32.band(h1, 0x3ffffff)
h2 = h2 + c; c = bit32.rshift(h2, 26); h2 = bit32.band(h2, 0x3ffffff)
h3 = h3 + c; c = bit32.rshift(h3, 26); h3 = bit32.band(h3, 0x3ffffff)
h4 = h4 + c; c = bit32.rshift(h4, 26); h4 = bit32.band(h4, 0x3ffffff)
h0 = h0 + (c * 5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
h1 = h1 + c
--
--compute h + -p
local g0 = (h0 + 5); c = bit32.rshift(g0, 26); g0 = bit32.band(g0, 0x3ffffff)
local g1 = (h1 + c); c = bit32.rshift(g1, 26); g1 = bit32.band(g1, 0x3ffffff)
local g2 = (h2 + c); c = bit32.rshift(g2, 26); g2 = bit32.band(g2, 0x3ffffff)
local g3 = (h3 + c); c = bit32.rshift(g3, 26); g3 = bit32.band(g3, 0x3ffffff)
local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26)
--
-- select h if h < p, or h + -p if h >= p
mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff)
--
g0 = bit32.band(g0, mask)
g1 = bit32.band(g1, mask)
g2 = bit32.band(g2, mask)
g3 = bit32.band(g3, mask)
g4 = bit32.band(g4, mask)
--
mask = bit32.band(bit32.bnot(mask), 0xffffffff)
h0 = bit32.bor(bit32.band(h0, mask), g0)
h1 = bit32.bor(bit32.band(h1, mask), g1)
h2 = bit32.bor(bit32.band(h2, mask), g2)
h3 = bit32.bor(bit32.band(h3, mask), g3)
h4 = bit32.bor(bit32.band(h4, mask), g4)
--
--h = h % (2^128)
h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff)
h1 = bit32.band(bit32.bor(bit32.rshift(h1, 6), bit32.lshift(h2, 20)), 0xffffffff)
h2 = bit32.band(bit32.bor(bit32.rshift(h2, 12), bit32.lshift(h3, 14)), 0xffffffff)
h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff)
--
-- mac = (h + pad) % (2^128)
f = h0 + st.pad[1]; h0 = bit32.band(f, 0xffffffff)
f = h1 + st.pad[2] + bit32.rshift(f, 32); h1 = bit32.band(f, 0xffffffff)
f = h2 + st.pad[3] + bit32.rshift(f, 32); h2 = bit32.band(f, 0xffffffff)
f = h3 + st.pad[4] + bit32.rshift(f, 32); h3 = bit32.band(f, 0xffffffff)
--
local mac = string.pack('<I4I4I4I4', h0, h1, h2, h3)
-- (should zero out the state?)
--
return mac
end --poly_finish()
local function poly_auth(m, k)
-- m: msg string
-- k: key string (must be 32 bytes)
-- return mac 16-byte string
assert(#k == 32)
local st = poly_init(k)
poly_update(st, m)
local mac = poly_finish(st)
return mac
-- m: msg string
-- k: key string (must be 32 bytes)
-- return mac 16-byte string
assert(#k == 32)
local st = poly_init(k)
poly_update(st, m)
local mac = poly_finish(st)
return mac
end --poly_auth()
local function poly_verify(m, k, mac)
local macm = poly_auth(m, k)
return macm == mac
local macm = poly_auth(m, k)
return macm == mac
end --poly_verify()
------------------------------------------------------------
-- return poly1305 module
return {
init = poly_init,
update = poly_update,
finish = poly_finish,
auth = poly_auth,
verify = poly_verify,
init = poly_init,
update = poly_update,
finish = poly_finish,
auth = poly_auth,
verify = poly_verify,
}

View file

@ -9,84 +9,84 @@ local expect = require "cc.expect".expect
-- Initialization code
local PrimeUI = {}
do
local coros = {}
local restoreCursor
local coros = {}
local restoreCursor
--- Adds a task to run in the main loop.
---@param func function The function to run, usually an `os.pullEvent` loop
function PrimeUI.addTask(func)
expect(1, func, "function")
local t = {coro = coroutine.create(func)}
coros[#coros+1] = t
_, t.filter = coroutine.resume(t.coro)
--- Adds a task to run in the main loop.
---@param func function The function to run, usually an `os.pullEvent` loop
function PrimeUI.addTask(func)
expect(1, func, "function")
local t = { coro = coroutine.create(func) }
coros[#coros + 1] = t
_, t.filter = coroutine.resume(t.coro)
end
--- Sends the provided arguments to the run loop, where they will be returned.
---@param ... any The parameters to send
function PrimeUI.resolve(...)
coroutine.yield(coros, ...)
end
--- Clears the screen and resets all components. Do not use any previously
--- created components after calling this function.
function PrimeUI.clear()
-- Reset the screen.
term.setCursorPos(1, 1)
term.setCursorBlink(false)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
-- Reset the task list and cursor restore function.
coros = {}
restoreCursor = nil
end
--- Sets or clears the window that holds where the cursor should be.
---@param win window|nil The window to set as the active window
function PrimeUI.setCursorWindow(win)
expect(1, win, "table", "nil")
restoreCursor = win and win.restoreCursor
end
--- Gets the absolute position of a coordinate relative to a window.
---@param win window The window to check
---@param x number The relative X position of the point
---@param y number The relative Y position of the point
---@return number x The absolute X position of the window
---@return number y The absolute Y position of the window
function PrimeUI.getWindowPos(win, x, y)
if win == term then return x, y end
while win ~= term.native() and win ~= term.current() do
if not win.getPosition then return x, y end
local wx, wy = win.getPosition()
x, y = x + wx - 1, y + wy - 1
_, win = debug.getupvalue(select(2, debug.getupvalue(win.isColor, 1)), 1) -- gets the parent window through an upvalue
end
return x, y
end
--- Sends the provided arguments to the run loop, where they will be returned.
---@param ... any The parameters to send
function PrimeUI.resolve(...)
coroutine.yield(coros, ...)
end
--- Clears the screen and resets all components. Do not use any previously
--- created components after calling this function.
function PrimeUI.clear()
-- Reset the screen.
term.setCursorPos(1, 1)
term.setCursorBlink(false)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
-- Reset the task list and cursor restore function.
coros = {}
restoreCursor = nil
end
--- Sets or clears the window that holds where the cursor should be.
---@param win window|nil The window to set as the active window
function PrimeUI.setCursorWindow(win)
expect(1, win, "table", "nil")
restoreCursor = win and win.restoreCursor
end
--- Gets the absolute position of a coordinate relative to a window.
---@param win window The window to check
---@param x number The relative X position of the point
---@param y number The relative Y position of the point
---@return number x The absolute X position of the window
---@return number y The absolute Y position of the window
function PrimeUI.getWindowPos(win, x, y)
if win == term then return x, y end
while win ~= term.native() and win ~= term.current() do
if not win.getPosition then return x, y end
local wx, wy = win.getPosition()
x, y = x + wx - 1, y + wy - 1
_, win = debug.getupvalue(select(2, debug.getupvalue(win.isColor, 1)), 1) -- gets the parent window through an upvalue
end
return x, y
end
--- Runs the main loop, returning information on an action.
---@return any ... The result of the coroutine that exited
function PrimeUI.run()
while true do
-- Restore the cursor and wait for the next event.
if restoreCursor then restoreCursor() end
local ev = table.pack(os.pullEvent())
-- Run all coroutines.
for _, v in ipairs(coros) do
if v.filter == nil or v.filter == ev[1] then
-- Resume the coroutine, passing the current event.
local res = table.pack(coroutine.resume(v.coro, table.unpack(ev, 1, ev.n)))
-- If the call failed, bail out. Coroutines should never exit.
if not res[1] then error(res[2], 2) end
-- If the coroutine resolved, return its values.
if res[2] == coros then return table.unpack(res, 3, res.n) end
-- Set the next event filter.
v.filter = res[2]
end
end
--- Runs the main loop, returning information on an action.
---@return any ... The result of the coroutine that exited
function PrimeUI.run()
while true do
-- Restore the cursor and wait for the next event.
if restoreCursor then restoreCursor() end
local ev = table.pack(os.pullEvent())
-- Run all coroutines.
for _, v in ipairs(coros) do
if v.filter == nil or v.filter == ev[1] then
-- Resume the coroutine, passing the current event.
local res = table.pack(coroutine.resume(v.coro, table.unpack(ev, 1, ev.n)))
-- If the call failed, bail out. Coroutines should never exit.
if not res[1] then error(res[2], 2) end
-- If the coroutine resolved, return its values.
if res[2] == coros then return table.unpack(res, 3, res.n) end
-- Set the next event filter.
v.filter = res[2]
end
end
end
end
end
--- Creates a text input box.
@ -102,102 +102,103 @@ end
---@param replacement string|nil A character to replace typed characters with
---@param history string[]|nil A list of previous entries to provide
---@param completion function|nil A function to call to provide completion
function PrimeUI.inputBox(win, x, y, width, action, placeholder, fgColor, bgColor, placeholderFg, replacement, history, completion, default)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, action, "function", "string")
expect(6, placeholder, "string", "nil")
fgColor = expect(7, fgColor, "number", "nil") or colors.white
bgColor = expect(8, bgColor, "number", "nil") or colors.black
placeholderFg = expect(9, placeholderFg, "number", "nil") or colors.lightGray
expect(10, replacement, "string", "nil")
expect(11, history, "table", "nil")
expect(12, completion, "function", "nil")
expect(13, default, "string", "nil")
function PrimeUI.inputBox(win, x, y, width, action, placeholder, fgColor, bgColor, placeholderFg, replacement, history,
completion, default)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, action, "function", "string")
expect(6, placeholder, "string", "nil")
fgColor = expect(7, fgColor, "number", "nil") or colors.white
bgColor = expect(8, bgColor, "number", "nil") or colors.black
placeholderFg = expect(9, placeholderFg, "number", "nil") or colors.lightGray
expect(10, replacement, "string", "nil")
expect(11, history, "table", "nil")
expect(12, completion, "function", "nil")
expect(13, default, "string", "nil")
local box = window.create(win, x, y, width, 1)
box.setTextColor(fgColor)
box.setBackgroundColor(bgColor)
box.clear()
local box = window.create(win, x, y, width, 1)
box.setTextColor(fgColor)
box.setBackgroundColor(bgColor)
box.clear()
PrimeUI.addTask(function()
local text = default or ""
local cursor = #text + 1
local histIndex = nil
PrimeUI.addTask(function()
local text = default or ""
local cursor = #text + 1
local histIndex = nil
local function runAction()
if type(action) == "string" then
PrimeUI.resolve("inputBox", action, text)
else
action(text)
end
local function runAction()
if type(action) == "string" then
PrimeUI.resolve("inputBox", action, text)
else
action(text)
end
end
local function redraw()
box.clear()
box.setCursorPos(1, 1)
if replacement then
box.write(string.rep(replacement, #text))
else
if #text == 0 and placeholder then
box.setTextColor(placeholderFg)
box.write(placeholder)
box.setTextColor(fgColor)
else
box.write(text)
end
end
box.setCursorPos(cursor, 1)
runAction()
end
local function redraw()
box.clear()
box.setCursorPos(1, 1)
if replacement then
box.write(string.rep(replacement, #text))
else
if #text == 0 and placeholder then
box.setTextColor(placeholderFg)
box.write(placeholder)
box.setTextColor(fgColor)
else
box.write(text)
end
end
box.setCursorPos(cursor, 1)
runAction()
end
redraw()
while true do
local ev, p1 = os.pullEvent()
if ev == "char" then
text = text:sub(1, cursor - 1) .. p1 .. text:sub(cursor)
cursor = cursor + 1
redraw()
while true do
local ev, p1 = os.pullEvent()
if ev == "char" then
text = text:sub(1, cursor - 1) .. p1 .. text:sub(cursor)
cursor = cursor + 1
redraw()
elseif ev == "key" then
if p1 == keys.enter then
runAction()
elseif p1 == keys.left then
cursor = math.max(1, cursor - 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.right then
cursor = math.min(#text + 1, cursor + 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.backspace then
if cursor > 1 then
text = text:sub(1, cursor - 2) .. text:sub(cursor)
cursor = cursor - 1
redraw()
end
elseif p1 == keys.delete then
text = text:sub(1, cursor - 1) .. text:sub(cursor + 1)
redraw()
elseif p1 == keys.up and history then
if not histIndex then histIndex = #history + 1 end
histIndex = math.max(1, histIndex - 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
elseif p1 == keys.down and history then
if histIndex then
histIndex = math.min(#history + 1, histIndex + 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
end
end
end
elseif ev == "key" then
if p1 == keys.enter then
runAction()
elseif p1 == keys.left then
cursor = math.max(1, cursor - 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.right then
cursor = math.min(#text + 1, cursor + 1)
box.setCursorPos(cursor, 1)
elseif p1 == keys.backspace then
if cursor > 1 then
text = text:sub(1, cursor - 2) .. text:sub(cursor)
cursor = cursor - 1
redraw()
end
elseif p1 == keys.delete then
text = text:sub(1, cursor - 1) .. text:sub(cursor + 1)
redraw()
elseif p1 == keys.up and history then
if not histIndex then histIndex = #history + 1 end
histIndex = math.max(1, histIndex - 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
elseif p1 == keys.down and history then
if histIndex then
histIndex = math.min(#history + 1, histIndex + 1)
text = history[histIndex] or ""
cursor = #text + 1
redraw()
end
end
end)
end
end
end)
end
--- Draws a line of text at a position.
@ -208,16 +209,16 @@ end
---@param fgColor color|nil The color of the text (defaults to white)
---@param bgColor color|nil The color of the background (defaults to black)
function PrimeUI.label(win, x, y, text, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, text, "string")
fgColor = expect(5, fgColor, "number", "nil") or colors.white
bgColor = expect(6, bgColor, "number", "nil") or colors.black
win.setCursorPos(x, y)
win.setTextColor(fgColor)
win.setBackgroundColor(bgColor)
win.write(text)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, text, "string")
fgColor = expect(5, fgColor, "number", "nil") or colors.white
bgColor = expect(6, bgColor, "number", "nil") or colors.black
win.setCursorPos(x, y)
win.setTextColor(fgColor)
win.setBackgroundColor(bgColor)
win.write(text)
end
--- Creates a scrollable window, which allows drawing large content in a small area.
@ -234,88 +235,90 @@ end
---@return window inner The inner window to draw inside
---@return fun(pos:number) scroll A function to manually set the scroll position of the window
function PrimeUI.scrollBox(win, x, y, width, height, innerHeight, allowArrowKeys, showScrollIndicators, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, innerHeight, "number")
expect(7, allowArrowKeys, "boolean", "nil")
expect(8, showScrollIndicators, "boolean", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
if allowArrowKeys == nil then allowArrowKeys = true end
-- Create the outer container box.
local outer = window.create(win == term and term.current() or win, x, y, width, height)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, innerHeight, "number")
expect(7, allowArrowKeys, "boolean", "nil")
expect(8, showScrollIndicators, "boolean", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
if allowArrowKeys == nil then allowArrowKeys = true end
-- Create the outer container box.
local outer = window.create(win == term and term.current() or win, x, y, width, height)
outer.setBackgroundColor(bgColor)
outer.clear()
-- Create the inner scrolling box.
local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight)
inner.setBackgroundColor(bgColor)
inner.clear()
-- Draw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.clear()
-- Create the inner scrolling box.
local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight)
inner.setBackgroundColor(bgColor)
inner.clear()
-- Draw scroll indicators if desired.
if showScrollIndicators then
outer.setTextColor(fgColor)
outer.setCursorPos(width, height)
outer.write(innerHeight > height and "\31" or " ")
end
-- Get the absolute position of the window.
x, y = PrimeUI.getWindowPos(win, x, y)
-- Add the scroll handler.
local scrollPos = 1
PrimeUI.addTask(function()
while true do
-- Wait for next event.
local ev = table.pack(os.pullEvent())
-- Update inner height in case it changed.
innerHeight = select(2, inner.getSize())
-- Check for scroll events and set direction.
local dir
if ev[1] == "key" and allowArrowKeys then
if ev[2] == keys.up then
dir = -1
elseif ev[2] == keys.down then
dir = 1
end
elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then
dir = ev[2]
end
-- If there's a scroll event, move the window vertically.
if dir and (scrollPos + dir >= 1 and scrollPos + dir <= innerHeight - height) then
scrollPos = scrollPos + dir
inner.reposition(1, 2 - scrollPos)
end
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(innerHeight > height and "\31" or " ")
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
end
-- Get the absolute position of the window.
x, y = PrimeUI.getWindowPos(win, x, y)
-- Add the scroll handler.
local scrollPos = 1
PrimeUI.addTask(function()
while true do
-- Wait for next event.
local ev = table.pack(os.pullEvent())
-- Update inner height in case it changed.
innerHeight = select(2, inner.getSize())
-- Check for scroll events and set direction.
local dir
if ev[1] == "key" and allowArrowKeys then
if ev[2] == keys.up then dir = -1
elseif ev[2] == keys.down then dir = 1 end
elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then
dir = ev[2]
end
-- If there's a scroll event, move the window vertically.
if dir and (scrollPos + dir >= 1 and scrollPos + dir <= innerHeight - height) then
scrollPos = scrollPos + dir
inner.reposition(1, 2 - scrollPos)
end
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
end
end)
-- Make a function to allow external scrolling.
local function scroll(pos)
expect(1, pos, "number")
pos = math.floor(pos)
expect.range(pos, 1, innerHeight - height)
-- Scroll the window.
scrollPos = pos
inner.reposition(1, 2 - scrollPos)
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
end)
-- Make a function to allow external scrolling.
local function scroll(pos)
expect(1, pos, "number")
pos = math.floor(pos)
expect.range(pos, 1, innerHeight - height)
-- Scroll the window.
scrollPos = pos
inner.reposition(1, 2 - scrollPos)
-- Redraw scroll indicators if desired.
if showScrollIndicators then
outer.setBackgroundColor(bgColor)
outer.setTextColor(fgColor)
outer.setCursorPos(width, 1)
outer.write(scrollPos > 1 and "\30" or " ")
outer.setCursorPos(width, height)
outer.write(scrollPos < innerHeight - height and "\31" or " ")
end
return inner, scroll
end
return inner, scroll
end
--- Creates a list of entries that can each be selected.
---@param win window The window to draw on
---@param x number The X coordinate of the inside of the box
@ -328,152 +331,178 @@ end
---@param fgColor color|nil The color of the text (defaults to white)
---@param bgColor color|nil The color of the background (defaults to black)
function PrimeUI.selectionBox(win, x, y, width, height, entries, action, selectChangeAction, fgColor, bgColor)
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, entries, "function")
expect(7, action, "function", "string")
expect(8, selectChangeAction, "function", "string", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
expect(1, win, "table")
expect(2, x, "number")
expect(3, y, "number")
expect(4, width, "number")
expect(5, height, "number")
expect(6, entries, "function")
expect(7, action, "function", "string")
expect(8, selectChangeAction, "function", "string", "nil")
fgColor = expect(9, fgColor, "number", "nil") or colors.white
bgColor = expect(10, bgColor, "number", "nil") or colors.black
local entrywin = window.create(win, x, y, width, height)
local selection, scroll = 1, 1
-- Create a function to redraw the entries on screen.
local function drawEntries()
-- Clear and set invisible for performance.
entrywin.setVisible(false)
entrywin.setBackgroundColor(bgColor)
entrywin.clear()
-- Draw each entry in the scrolled region.
for i = scroll, scroll + height - 1 do
-- Get the entry; stop if there's no more.
local e = entries()[i]
if not e then break end
-- Set the colors: invert if selected.
entrywin.setCursorPos(2, i - scroll + 1)
if i == selection then
entrywin.setBackgroundColor(fgColor)
entrywin.setTextColor(bgColor)
else
entrywin.setBackgroundColor(bgColor)
entrywin.setTextColor(fgColor)
end
-- Draw the selection.
entrywin.clearLine()
entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e)
end
-- Draw scroll arrows.
local entrywin = window.create(win, x, y, width, height)
local selection, scroll = 1, 1
-- Create a function to redraw the entries on screen.
local function drawEntries()
-- Clear and set invisible for performance.
entrywin.setVisible(false)
entrywin.setBackgroundColor(bgColor)
entrywin.clear()
-- Draw each entry in the scrolled region.
for i = scroll, scroll + height - 1 do
-- Get the entry; stop if there's no more.
local e = entries()[i]
if not e then break end
-- Set the colors: invert if selected.
entrywin.setCursorPos(2, i - scroll + 1)
if i == selection then
entrywin.setBackgroundColor(fgColor)
entrywin.setTextColor(bgColor)
else
entrywin.setBackgroundColor(bgColor)
entrywin.setTextColor(fgColor)
entrywin.setCursorPos(width, 1)
entrywin.write("\30")
entrywin.setCursorPos(width, height)
entrywin.write("\31")
-- Send updates to the screen.
entrywin.setVisible(true)
end
-- Draw the selection.
entrywin.clearLine()
entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e)
end
-- Draw first screen.
drawEntries()
-- Add a task for selection keys.
PrimeUI.addTask(function()
while true do
local event, key, cx, cy = os.pullEvent()
if event == "key" then
if key == keys.down and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif key == keys.up and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif key == keys.enter then
-- Select the entry: send the action.
if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection])
else action(entries()[selection]) end
end
elseif event == "mouse_click" and key == 1 then
-- Handle clicking the scroll arrows.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx == wx + width - 1 then
if cy == wy and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif cy == wy + height - 1 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
end
elseif cx >= wx and cx < wx + width - 1 and cy >= wy and cy < wy + height then
local sel = scroll + (cy - wy)
if sel == selection then
-- Select the entry: send the action.
if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection])
else action(entries()[selection]) end
else
selection = sel
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
end
end
elseif event == "mouse_scroll" then
-- Handle mouse scrolling.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx >= wx and cx < wx + width and cy >= wy and cy < wy + height then
if key < 0 and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
elseif key > 0 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then selectChangeAction(selection) end
-- Redraw screen.
drawEntries()
end
end
end
-- Draw scroll arrows.
entrywin.setBackgroundColor(bgColor)
entrywin.setTextColor(fgColor)
entrywin.setCursorPos(width, 1)
entrywin.write("\30")
entrywin.setCursorPos(width, height)
entrywin.write("\31")
-- Send updates to the screen.
entrywin.setVisible(true)
end
-- Draw first screen.
drawEntries()
-- Add a task for selection keys.
PrimeUI.addTask(function()
while true do
local event, key, cx, cy = os.pullEvent()
if event == "key" then
if key == keys.down and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif key == keys.up and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif key == keys.enter then
-- Select the entry: send the action.
if type(action) == "string" then
PrimeUI.resolve("selectionBox", action, entries()[selection])
else
action(entries()[selection])
end
end
end)
return drawEntries
elseif event == "mouse_click" and key == 1 then
-- Handle clicking the scroll arrows.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx == wx + width - 1 then
if cy == wy and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif cy == wy + height - 1 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
end
elseif cx >= wx and cx < wx + width - 1 and cy >= wy and cy < wy + height then
local sel = scroll + (cy - wy)
if sel == selection then
-- Select the entry: send the action.
if type(action) == "string" then
PrimeUI.resolve("selectionBox", action, entries()[selection])
else
action(entries()[selection])
end
else
selection = sel
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
end
end
elseif event == "mouse_scroll" then
-- Handle mouse scrolling.
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
if cx >= wx and cx < wx + width and cy >= wy and cy < wy + height then
if key < 0 and selection > 1 then
-- Move selection up.
selection = selection - 1
if selection < scroll then scroll = scroll - 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
elseif key > 0 and selection < #entries() then
-- Move selection down.
selection = selection + 1
if selection > scroll + height - 1 then scroll = scroll + 1 end
-- Send action if necessary.
if type(selectChangeAction) == "string" then
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
elseif selectChangeAction then
selectChangeAction(selection)
end
-- Redraw screen.
drawEntries()
end
end
end
end
end)
return drawEntries
end
return {
PrimeUI = PrimeUI
PrimeUI = PrimeUI
}

View file

@ -1,4 +1,3 @@
---@class InnerRemote
---@field port number
---@field password string
@ -8,11 +7,15 @@
---@field ender_storage string
---@field modem string
---@field remotes table<string, InnerRemote>
---
---@class Chatbox
---@field players table<string, string>
---@field prefix string|nil
---@class Config
---@field inventories string[]
---@field import string[]|nil
---@field chatbox table<string, string>|nil
---@field chatbox Chatbox|nil
---@field remote Remote
local inv = require("modules.inv")
@ -23,32 +26,32 @@ local ra = require("modules.ra")
local config = require("../config") ---@type Config
local function importMechanism()
if config.import == nil then
return
end
if #config.import == 0 then
return
end
if config.import == nil then
return
end
if #config.import == 0 then
return
end
while true do
for _, import_from_inv in ipairs(config.import) do
---@type ccTweaked.peripheral.Inventory
local perip = peripheral.wrap(import_from_inv)
if perip and perip.size then
local slotsToSend = {}
for slot = 1, perip.size() do
local item = perip.getItemDetail(slot)
if item then
table.insert(slotsToSend, slot)
end
end
if #slotsToSend > 0 then
inv.sendItemAwayMultiple(slotsToSend, perip, import_from_inv)
end
end
while true do
for _, import_from_inv in ipairs(config.import) do
---@type ccTweaked.peripheral.Inventory
local perip = peripheral.wrap(import_from_inv)
if perip and perip.size then
local slotsToSend = {}
for slot = 1, perip.size() do
local item = perip.getItemDetail(slot)
if item then
table.insert(slotsToSend, slot)
end
end
sleep(0.1)
if #slotsToSend > 0 then
inv.sendItemAwayMultiple(slotsToSend, perip, import_from_inv)
end
end
end
sleep(0.1)
end
end
inv.sync()

View file

@ -2,91 +2,90 @@ local config = require("../../config") ---@type Config
local inv = require("modules.inv")
local function levDist(s, t)
local n = #s
local m = #t
local n = #s
local m = #t
if n == 0 then return m end
if m == 0 then return n end
if n == 0 then return m end
if m == 0 then return n end
-- create matrix
local d = {}
for i = 0, n do
d[i] = {}
-- create matrix
local d = {}
for i = 0, n do
d[i] = {}
end
-- initialize
for i = 0, n do d[i][0] = i end
for j = 0, m do d[0][j] = j end
-- main loop
for i = 1, n do
local s_i = s:sub(i, i)
for j = 1, m do
-- safeguard shortcut
if i == j and d[i][j] and d[i][j] > 4 then
return n
end
local t_j = t:sub(j, j)
local cost = (s_i == t_j) and 0 or 1
local mi = math.min(
d[i - 1][j] + 1,
d[i][j - 1] + 1,
d[i - 1][j - 1] + cost
)
d[i][j] = mi
-- Damerau transposition
if i > 1 and j > 1 and s_i == t:sub(j - 1, j - 1) and s:sub(i - 1, i - 1) == t_j then
d[i][j] = math.min(d[i][j], d[i - 2][j - 2] + cost)
end
end
end
-- initialize
for i = 0, n do d[i][0] = i end
for j = 0, m do d[0][j] = j end
-- main loop
for i = 1, n do
local s_i = s:sub(i, i)
for j = 1, m do
-- safeguard shortcut
if i == j and d[i][j] and d[i][j] > 4 then
return n
end
local t_j = t:sub(j, j)
local cost = (s_i == t_j) and 0 or 1
local mi = math.min(
d[i - 1][j] + 1,
d[i][j - 1] + 1,
d[i - 1][j - 1] + cost
)
d[i][j] = mi
-- Damerau transposition
if i > 1 and j > 1 and s_i == t:sub(j - 1, j - 1) and s:sub(i - 1, i - 1) == t_j then
d[i][j] = math.min(d[i][j], d[i - 2][j - 2] + cost)
end
end
end
return d[n][m]
return d[n][m]
end
local function findBest(data, item)
local sorted_data = {}
local sorted_data = {}
for _, z in ipairs(data) do
local parts = {}
for part in string.gmatch(z, "([^:]+)") do
table.insert(parts, part)
end
local key = parts[1] .. ":" .. (parts[2] or "")
local dist = levDist(item, parts[2] or "")
table.insert(sorted_data, { key, dist })
for _, z in ipairs(data) do
local parts = {}
for part in string.gmatch(z, "([^:]+)") do
table.insert(parts, part)
end
table.sort(sorted_data, function(a, b)
return a[2] < b[2]
end)
local key = parts[1] .. ":" .. (parts[2] or "")
local dist = levDist(item, parts[2] or "")
table.insert(sorted_data, { key, dist })
end
local best = {}
local best_dist = sorted_data[1][2]
for _, z in ipairs(sorted_data) do
if z[2] == best_dist then
table.insert(best, z)
else
break
end
table.sort(sorted_data, function(a, b)
return a[2] < b[2]
end)
local best = {}
local best_dist = sorted_data[1][2]
for _, z in ipairs(sorted_data) do
if z[2] == best_dist then
table.insert(best, z)
else
break
end
end
return best
return best
end
local BOT_NAME = "&cS &eI&an&3c &5S&cI&6S"
function auth(user)
local manip = config.chatbox[user]
local manip = config.chatbox.players[user]
if manip then
return true
return true
else
chatbox.tell(user, "You are not authorized to use this command.", BOT_NAME)
return false
@ -99,11 +98,11 @@ function run()
while true do
local _, user, command, args = os.pullEvent("command")
if command == "sis" then
if command == (config.chatbox.prefix or "sis") then
if args[1] == "whoami" then
local manip = config.chatbox[user]
local manip = config.chatbox.players[user]
if manip then
chatbox.tell(user, "You are " .. user .. ", linked with `" .. manip .."`.", BOT_NAME)
chatbox.tell(user, "You are " .. user .. ", linked with `" .. manip .. "`.", BOT_NAME)
else
chatbox.tell(user, "You are not registered. Ask the creator of this SIS to be registered.", BOT_NAME)
end
@ -116,7 +115,7 @@ function run()
end
local perip = peripheral.wrap(config.chatbox[user])
local perip = peripheral.wrap(config.chatbox.players[user])
---@type ccTweaked.peripheral.Inventory
local peripInventory = perip.getInventory()
@ -126,11 +125,11 @@ function run()
local seen = {}
for _, stack in pairs(invList) do
local name = stack and stack.name
if name and not seen[name] then
seen[name] = true
allItems[#allItems + 1] = name
end
local name = stack and stack.name
if name and not seen[name] then
seen[name] = true
allItems[#allItems + 1] = name
end
end
local best = findBest(allItems, args[2])
@ -138,27 +137,27 @@ function run()
local itemName = ""
if #best > 1 then
itemName = best[1][1]
if string.find(":", args[2]) then
itemName = args[2]
end
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
itemName = best[1][1]
if string.find(":", args[2]) then
itemName = args[2]
end
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
else
itemName = best[1][1]
itemName = best[1][1]
end
local slots = {}
for slot, item in pairs(peripInventory.list()) do
if item.name == itemName then
table.insert(slots, slot)
end
if item.name == itemName then
table.insert(slots, slot)
end
end
local amount = nil
if args[3] then
amount = tonumber(args[3], 10)
amount = tonumber(args[3], 10)
end
local moved = inv.sendItemAwayMultiple(slots, peripInventory, peripInventory, amount)
@ -168,11 +167,11 @@ function run()
if not auth(user) then goto continue end
if not args[2] then
chatbox.tell(user, "Supply a item (minecraft:`diamond` part) to withdraw it!", BOT_NAME)
goto continue
chatbox.tell(user, "Supply a item (minecraft:`diamond` part) to withdraw it!", BOT_NAME)
goto continue
end
local perip = peripheral.wrap(config.chatbox[user])
local perip = peripheral.wrap(config.chatbox.players[user])
---@type ccTweaked.peripheral.Inventory
local peripInventory = perip.getInventory()
@ -182,19 +181,19 @@ function run()
local itemName = ""
if #best > 1 then
itemName = best[1][1]
if string.find(":", args[2]) then
itemName = args[2]
end
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
itemName = best[1][1]
if string.find(":", args[2]) then
itemName = args[2]
end
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
else
itemName = best[1][1]
itemName = best[1][1]
end
local amount = nil
if args[3] then
amount = tonumber(args[3], 10)
amount = tonumber(args[3], 10)
end
local moved = inv.sendItemToSelf(itemName, peripInventory, amount)

View file

@ -89,7 +89,6 @@ local function sendItemToSelf(itemName, perip, maxAmount)
end
end
end
-- sleep(0.1)
turtleMoveAllowed = true
return total

View file

@ -30,15 +30,13 @@ local function run()
for color, remote in pairs(config.remote.remotes) do
if remote.port == channel then
local err, data = pcall(function ()
local err, data = pcall(function()
return hmac.decrypt_with_password(remote.password, message)
end)
if not data then return end
if err == true then
---@type RemoteAccess
local json, errj = textutils.unserialiseJSON(data)
if json or errj ~= nil then
@ -57,12 +55,12 @@ local function run()
elseif json.type == "deposit" then
if json.data.itemName then
local move = {}
for s, i in pairs(ender_storage.list()) do
if i.name == json.data.itemName then
table.insert(move, s)
end
end
inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount)
for s, i in pairs(ender_storage.list()) do
if i.name == json.data.itemName then
table.insert(move, s)
end
end
inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount)
elseif json.data.slots then
if type(json.data.slots) ~= 'table' then
return

View file

@ -8,71 +8,72 @@ local function is_beta(ver)
end
local function run()
local search = ""
local win = term.current();
local w , h= term.getSize()
local search = ""
local win = term.current();
local w, h = term.getSize()
local function getFiltered()
local filtered = {}
for name, count in pairs(inv.getAIL().listItemAmounts()) do
if search == "" or string.find(name:lower(), search:lower(), 1, true) then
table.insert(filtered, { name = name, count = count })
end
end
table.sort(filtered, function(a, b) return a.count > b.count end)
local refiltered = {}
for i, filter in ipairs(filtered) do
refiltered[i] = filter.name .. string.rep(" ", w-(2+#filter.name+#tostring(filter.count))) .. tostring(filter.count)
end
return refiltered
local function getFiltered()
local filtered = {}
for name, count in pairs(inv.getAIL().listItemAmounts()) do
if search == "" or string.find(name:lower(), search:lower(), 1, true) then
table.insert(filtered, { name = name, count = count })
end
end
table.sort(filtered, function(a, b) return a.count > b.count end)
local com = getFiltered()
local refiltered = {}
PrimeUI.clear()
local updateEntries = PrimeUI.selectionBox(
win, 1, 4, w, h-7,
function ()
return com
end,
function(option)
local z = option:match("^(%S+)")
for i, filter in ipairs(filtered) do
refiltered[i] = filter.name ..
string.rep(" ", w - (2 + #filter.name + #tostring(filter.count))) .. tostring(filter.count)
end
return refiltered
end
if inv.getAIL().getItem(z) then
inv.sendItemToSelf(z)
end
end
)
local com = getFiltered()
PrimeUI.inputBox(win, 2, 2, w-2, function (data)
search = data
PrimeUI.clear()
local updateEntries = PrimeUI.selectionBox(
win, 1, 4, w, h - 7,
function()
return com
end,
function(option)
local z = option:match("^(%S+)")
if inv.getAIL().getItem(z) then
inv.sendItemToSelf(z)
end
end
)
PrimeUI.inputBox(win, 2, 2, w - 2, function(data)
search = data
com = getFiltered()
updateEntries()
end, "Input a item to search..")
local ver = fs.open("storage-solution/version", "r").readAll()
PrimeUI.label(win, 2, h - 1, is_beta(ver) and "you're running beta :3" or "welcome to SiSS", colors.lightGray)
PrimeUI.label(win, 2, h - 2, "rev. " .. ver, colors.lightGray)
local timer = os.startTimer(1)
PrimeUI.addTask(function()
while true do
local _, osTimer = os.pullEvent("timer")
if osTimer == timer then
com = getFiltered()
updateEntries()
end, "Input a item to search..")
timer = os.startTimer(1)
end
end
end)
local ver = fs.open("storage-solution/version", "r").readAll()
PrimeUI.label(win, 2, h-1, is_beta(ver) and "you're running beta :3" or "welcome to SiSS", colors.lightGray)
PrimeUI.label(win, 2, h-2, "rev. " .. ver, colors.lightGray)
local timer = os.startTimer(1)
PrimeUI.addTask(function()
while true do
local _, osTimer = os.pullEvent("timer")
if osTimer == timer then
com = getFiltered()
updateEntries()
timer = os.startTimer(1)
end
end
end)
PrimeUI.run()
PrimeUI.run()
end
return {
run = run
run = run
}