Reformat + add prefix selection for config
This commit is contained in:
parent
88f4173c8e
commit
262c0b5408
13 changed files with 1191 additions and 1141 deletions
87
readme.md
87
readme.md
|
|
@ -21,6 +21,52 @@
|
||||||
- Chatbox support for withdrawing and depositing items
|
- Chatbox support for withdrawing and depositing items
|
||||||
- Remote access via enderstorages
|
- 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
|
## RA
|
||||||
SiSS has a remote access system that allows you to access your items via modem.
|
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.
|
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")
|
print("deposits 16 items from the first slot")
|
||||||
ra.depositBySlots({1}, 16)
|
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 :)
|
|
||||||
|
|
|
||||||
|
|
@ -3,75 +3,77 @@ local api_url = base_url .. "?ls"
|
||||||
local download_root = "storage-solution"
|
local download_root = "storage-solution"
|
||||||
|
|
||||||
local function fetch_folder_list()
|
local function fetch_folder_list()
|
||||||
local response = http.get(api_url)
|
local response = http.get(api_url)
|
||||||
if not response then
|
if not response then
|
||||||
error("Failed to get folder list from Copyparty API")
|
error("Failed to get folder list from Copyparty API")
|
||||||
end
|
end
|
||||||
local body = response.readAll()
|
local body = response.readAll()
|
||||||
response.close()
|
response.close()
|
||||||
local data = textutils.unserializeJSON(body)
|
local data = textutils.unserializeJSON(body)
|
||||||
return data
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
local function download_file(path)
|
local function download_file(path)
|
||||||
local file_url = base_url .. "/" .. path
|
local file_url = base_url .. "/" .. path
|
||||||
local local_path = download_root .. "/" .. path
|
local local_path = download_root .. "/" .. path
|
||||||
local response = http.get(file_url)
|
local response = http.get(file_url)
|
||||||
if response then
|
if response then
|
||||||
local file = fs.open(local_path, "wb")
|
local file = fs.open(local_path, "wb")
|
||||||
if file then
|
if file then
|
||||||
file.write(response.readAll())
|
file.write(response.readAll())
|
||||||
file.close()
|
file.close()
|
||||||
end
|
|
||||||
response.close()
|
|
||||||
else
|
|
||||||
print("Failed to download: " .. file_url)
|
|
||||||
end
|
end
|
||||||
|
response.close()
|
||||||
|
else
|
||||||
|
print("Failed to download: " .. file_url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function traverse_and_download(folder_data, prefix)
|
local function traverse_and_download(folder_data, prefix)
|
||||||
prefix = prefix or ""
|
prefix = prefix or ""
|
||||||
|
|
||||||
for _, file in ipairs(folder_data.files or {}) do
|
for _, file in ipairs(folder_data.files or {}) do
|
||||||
print("Downloading: " .. prefix .. file.href)
|
print("Downloading: " .. prefix .. file.href)
|
||||||
download_file(prefix .. file.href)
|
download_file(prefix .. file.href)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, dir in ipairs(folder_data.dirs or {}) do
|
for _, dir in ipairs(folder_data.dirs or {}) do
|
||||||
fs.makeDir(download_root .. "/" .. prefix .. dir.href)
|
fs.makeDir(download_root .. "/" .. prefix .. dir.href)
|
||||||
local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. 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)
|
local subdir_url = base_url .. "/" .. new_prefix .. "?ls"
|
||||||
if response then
|
local response = http.get(subdir_url)
|
||||||
local body = response.readAll()
|
if response then
|
||||||
if not body then return end
|
local body = response.readAll()
|
||||||
response.close()
|
if not body then return end
|
||||||
local subdir_data = textutils.unserializeJSON(body)
|
response.close()
|
||||||
traverse_and_download(subdir_data, new_prefix)
|
local subdir_data = textutils.unserializeJSON(body)
|
||||||
else
|
traverse_and_download(subdir_data, new_prefix)
|
||||||
print("Failed to get subdirectory: " .. subdir_url)
|
else
|
||||||
end
|
print("Failed to get subdirectory: " .. subdir_url)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if fs.exists(download_root) then
|
if fs.exists(download_root) then
|
||||||
if fs.exists(download_root .. "/version") then
|
if fs.exists(download_root .. "/version") then
|
||||||
local previousVersion = fs.open(download_root .. "/version", "r").readAll()
|
local previousVersion = fs.open(download_root .. "/version", "r").readAll()
|
||||||
local currentVersion = http.get(base_url .. "/version").readAll();
|
local currentVersion = http.get(base_url .. "/version").readAll();
|
||||||
|
|
||||||
if previousVersion == currentVersion then
|
if previousVersion == currentVersion then
|
||||||
print("Previous version " .. previousVersion .. " is already installed, we're on " .. currentVersion .. " aswell, so skipping installation.")
|
print("Previous version " ..
|
||||||
shell.run("storage-solution/main.lua")
|
previousVersion .. " is already installed, we're on " .. currentVersion .. " aswell, so skipping installation.")
|
||||||
return
|
shell.run("storage-solution/main.lua")
|
||||||
else
|
return
|
||||||
print("Version " .. previousVersion .. " was already installed. Uninstalling.")
|
|
||||||
fs.delete(download_root)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
print("Version marker does not exist. Cannot install.")
|
print("Version " .. previousVersion .. " was already installed. Uninstalling.")
|
||||||
shell.run("storage-solution/main.lua")
|
fs.delete(download_root)
|
||||||
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
print("Version marker does not exist. Cannot install.")
|
||||||
|
shell.run("storage-solution/main.lua")
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
fs.makeDir(download_root)
|
fs.makeDir(download_root)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,79 +5,79 @@ local download_root = "storage-solution"
|
||||||
local remote_folder = "src"
|
local remote_folder = "src"
|
||||||
|
|
||||||
local function fetch_commit_hash()
|
local function fetch_commit_hash()
|
||||||
local url = repo_api .. "/branches/" .. branch
|
local url = repo_api .. "/branches/" .. branch
|
||||||
local response = http.get(url)
|
local response = http.get(url)
|
||||||
if not response then
|
if not response then
|
||||||
error("Failed to fetch branch info: " .. url)
|
error("Failed to fetch branch info: " .. url)
|
||||||
end
|
end
|
||||||
local body = response.readAll()
|
local body = response.readAll()
|
||||||
response.close()
|
response.close()
|
||||||
local data = textutils.unserializeJSON(body)
|
local data = textutils.unserializeJSON(body)
|
||||||
return data.commit.id
|
return data.commit.id
|
||||||
end
|
end
|
||||||
|
|
||||||
local function fetch_repo_tree(path)
|
local function fetch_repo_tree(path)
|
||||||
local url = repo_api .. "/contents" .. (path and ("/" .. path) or "")
|
local url = repo_api .. "/contents" .. (path and ("/" .. path) or "")
|
||||||
local response = http.get(url)
|
local response = http.get(url)
|
||||||
if not response then
|
if not response then
|
||||||
error("Failed to fetch repo tree: " .. url)
|
error("Failed to fetch repo tree: " .. url)
|
||||||
end
|
end
|
||||||
local body = response.readAll()
|
local body = response.readAll()
|
||||||
response.close()
|
response.close()
|
||||||
return textutils.unserializeJSON(body)
|
return textutils.unserializeJSON(body)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function download_file(remote_path, local_path)
|
local function download_file(remote_path, local_path)
|
||||||
local url = raw_base .. "/" .. remote_path
|
local url = raw_base .. "/" .. remote_path
|
||||||
fs.makeDir(fs.getDir(local_path))
|
fs.makeDir(fs.getDir(local_path))
|
||||||
local response = http.get(url)
|
local response = http.get(url)
|
||||||
if response then
|
if response then
|
||||||
local file = fs.open(local_path, "wb")
|
local file = fs.open(local_path, "wb")
|
||||||
if file then
|
if file then
|
||||||
file.write(response.readAll())
|
file.write(response.readAll())
|
||||||
file.close()
|
file.close()
|
||||||
end
|
|
||||||
response.close()
|
|
||||||
print("Downloaded: " .. local_path)
|
|
||||||
else
|
|
||||||
print("Failed to download: " .. url)
|
|
||||||
end
|
end
|
||||||
|
response.close()
|
||||||
|
print("Downloaded: " .. local_path)
|
||||||
|
else
|
||||||
|
print("Failed to download: " .. url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function traverse_and_download(remote_path, local_prefix)
|
local function traverse_and_download(remote_path, local_prefix)
|
||||||
local tree = fetch_repo_tree(remote_path)
|
local tree = fetch_repo_tree(remote_path)
|
||||||
for _, entry in ipairs(tree) do
|
for _, entry in ipairs(tree) do
|
||||||
if entry.type == "file" then
|
if entry.type == "file" then
|
||||||
local local_path = local_prefix .. "/" .. fs.getName(entry.path)
|
local local_path = local_prefix .. "/" .. fs.getName(entry.path)
|
||||||
download_file(entry.path, local_path)
|
download_file(entry.path, local_path)
|
||||||
elseif entry.type == "dir" then
|
elseif entry.type == "dir" then
|
||||||
local new_remote = entry.path
|
local new_remote = entry.path
|
||||||
local new_local = local_prefix .. "/" .. fs.getName(entry.path)
|
local new_local = local_prefix .. "/" .. fs.getName(entry.path)
|
||||||
traverse_and_download(new_remote, new_local)
|
traverse_and_download(new_remote, new_local)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local remote_hash = fetch_commit_hash()
|
local remote_hash = fetch_commit_hash()
|
||||||
print("Latest commit: " .. remote_hash)
|
print("Latest commit: " .. remote_hash)
|
||||||
|
|
||||||
if fs.exists(download_root) then
|
if fs.exists(download_root) then
|
||||||
if fs.exists(download_root .. "/version") then
|
if fs.exists(download_root .. "/version") then
|
||||||
local f = fs.open(download_root .. "/version", "r")
|
local f = fs.open(download_root .. "/version", "r")
|
||||||
local local_hash = f.readAll()
|
local local_hash = f.readAll()
|
||||||
f.close()
|
f.close()
|
||||||
if local_hash == remote_hash then
|
if local_hash == remote_hash then
|
||||||
print("Already up to date (commit " .. remote_hash:sub(1,7) .. ").")
|
print("Already up to date (commit " .. remote_hash:sub(1, 7) .. ").")
|
||||||
shell.run(download_root .. "/main.lua")
|
shell.run(download_root .. "/main.lua")
|
||||||
return
|
return
|
||||||
else
|
|
||||||
print("Outdated (" .. local_hash:sub(1,7) .. " -> " .. remote_hash:sub(1,7) .. "), reinstalling...")
|
|
||||||
fs.delete(download_root)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
print("Version marker missing, reinstalling.")
|
print("Outdated (" .. local_hash:sub(1, 7) .. " -> " .. remote_hash:sub(1, 7) .. "), reinstalling...")
|
||||||
fs.delete(download_root)
|
fs.delete(download_root)
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
print("Version marker missing, reinstalling.")
|
||||||
|
fs.delete(download_root)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
fs.makeDir(download_root)
|
fs.makeDir(download_root)
|
||||||
|
|
@ -89,5 +89,5 @@ local f = fs.open(download_root .. "/version", "w")
|
||||||
f.write(remote_hash)
|
f.write(remote_hash)
|
||||||
f.close()
|
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")
|
shell.run(download_root .. "/main.lua")
|
||||||
|
|
|
||||||
|
|
@ -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 function frombytes(t) return string.char(table.unpack(t)) end
|
||||||
|
|
||||||
local b64abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
local b64abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||||
local function b64u_encode(bin)
|
local function b64u_encode(bin)
|
||||||
local t, n, out = tobytes(bin), #bin, {}
|
local t, n, out = tobytes(bin), #bin, {}
|
||||||
local i=1
|
local i = 1
|
||||||
while i<=n do
|
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 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
|
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, 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] = 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 + 1 <= n and
|
||||||
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 ''
|
b64abc:sub(bit32.band(bit32.rshift(triple, 6), 63) + 1, bit32.band(bit32.rshift(triple, 6), 63) + 1) or ''
|
||||||
i=i+3
|
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
|
end
|
||||||
return table.concat(out)
|
return table.concat(out)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function b64u_decode(txt)
|
local function b64u_decode(txt)
|
||||||
local map = {}
|
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 out = {}
|
||||||
local i=1
|
local i = 1
|
||||||
while i<=#txt do
|
while i <= #txt do
|
||||||
local a = map[txt:sub(i,i)]; i=i+1
|
local a = map[txt:sub(i, i)]; i = i + 1
|
||||||
local b = 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 c = map[txt:sub(i, i)]; i = i + 1
|
||||||
local d = 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
|
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))
|
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))
|
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 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
|
if d ~= nil then out[#out + 1] = string.char(bit32.band(triple, 255)) end
|
||||||
end
|
end
|
||||||
return table.concat(out)
|
return table.concat(out)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function hmac_sha256(key, msg)
|
local function hmac_sha256(key, msg)
|
||||||
local block = 64
|
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
|
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 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)
|
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)
|
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
|
end
|
||||||
|
|
||||||
local function int_be(n) -- 32-bit BE
|
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
|
end
|
||||||
|
|
||||||
local function pbkdf2_sha256(password, salt, iterations, dkLen)
|
local function pbkdf2_sha256(password, salt, iterations, dkLen)
|
||||||
local hLen = 32
|
local hLen = 32
|
||||||
local l = math.ceil(dkLen / hLen)
|
local l = math.ceil(dkLen / hLen)
|
||||||
local r = dkLen - (l-1)*hLen
|
local r = dkLen - (l - 1) * hLen
|
||||||
local dk = {}
|
local dk = {}
|
||||||
for i=1,l do
|
for i = 1, l do
|
||||||
local U = hmac_sha256(password, salt .. int_be(i))
|
local U = hmac_sha256(password, salt .. int_be(i))
|
||||||
local T = {U:byte(1,hLen)}
|
local T = { U:byte(1, hLen) }
|
||||||
for itr=2,iterations do
|
for itr = 2, iterations do
|
||||||
U = hmac_sha256(password, U)
|
U = hmac_sha256(password, U)
|
||||||
local Ub = {U:byte(1,hLen)}
|
local Ub = { U:byte(1, hLen) }
|
||||||
for j=1,hLen do T[j] = bit32.bxor(T[j], Ub[j]) end
|
for j = 1, hLen do T[j] = bit32.bxor(T[j], Ub[j]) end
|
||||||
if itr % 500 == 0 then sleep(0) end
|
if itr % 500 == 0 then sleep(0) end
|
||||||
end
|
end
|
||||||
if i<l then
|
if i < l then
|
||||||
dk[#dk+1] = string.char(table.unpack(T))
|
dk[#dk + 1] = string.char(table.unpack(T))
|
||||||
else
|
else
|
||||||
dk[#dk+1] = string.char(table.unpack(T, 1, r))
|
dk[#dk + 1] = string.char(table.unpack(T, 1, r))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return table.concat(dk)
|
return table.concat(dk)
|
||||||
|
|
@ -83,20 +96,20 @@ end
|
||||||
|
|
||||||
-- HMAC-DRBG
|
-- HMAC-DRBG
|
||||||
local SEED_PATH = "siss_drg_seed.bin"
|
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)
|
local function drbg_update(provided_data)
|
||||||
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\0" .. (provided_data or ""))
|
DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\0" .. (provided_data or ""))
|
||||||
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
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.K = hmac_sha256(DRBG.K, DRBG.V .. "\1" .. provided_data)
|
||||||
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function drbg_init(seed)
|
local function drbg_init(seed)
|
||||||
DRBG.K = string.rep("\0",32)
|
DRBG.K = string.rep("\0", 32)
|
||||||
DRBG.V = string.rep("\1",32)
|
DRBG.V = string.rep("\1", 32)
|
||||||
drbg_update(seed)
|
drbg_update(seed)
|
||||||
DRBG.inited = true
|
DRBG.inited = true
|
||||||
end
|
end
|
||||||
|
|
@ -106,27 +119,27 @@ local function drbg_bytes(n)
|
||||||
local out = {}
|
local out = {}
|
||||||
while #table.concat(out) < n do
|
while #table.concat(out) < n do
|
||||||
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
DRBG.V = hmac_sha256(DRBG.K, DRBG.V)
|
||||||
out[#out+1] = DRBG.V
|
out[#out + 1] = DRBG.V
|
||||||
end
|
end
|
||||||
drbg_update("")
|
drbg_update("")
|
||||||
local buf = table.concat(out)
|
local buf = table.concat(out)
|
||||||
return buf:sub(1,n)
|
return buf:sub(1, n)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function seed_entropy(extra_bytes)
|
local function seed_entropy(extra_bytes)
|
||||||
local accum = {}
|
local accum = {}
|
||||||
if fs.exists(SEED_PATH) then
|
if fs.exists(SEED_PATH) then
|
||||||
local f = fs.open(SEED_PATH, "rb"); accum[#accum+1]=f.readAll(); f.close()
|
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 seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end)
|
||||||
drbg_init(seed)
|
drbg_init(seed)
|
||||||
else
|
else
|
||||||
term.write("Move around / mash keys then press Enter 4 times to seedâ¦\n")
|
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()
|
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
|
end
|
||||||
if extra_bytes and #extra_bytes>0 then accum[#accum+1]=extra_bytes 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)
|
local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end)
|
||||||
drbg_init(seed)
|
drbg_init(seed)
|
||||||
local f = fs.open(SEED_PATH, "wb")
|
local f = fs.open(SEED_PATH, "wb")
|
||||||
f.write(drbg_bytes(48))
|
f.write(drbg_bytes(48))
|
||||||
|
|
@ -138,15 +151,16 @@ local VERSION = "\1"
|
||||||
local KDF_ID = "\1"
|
local KDF_ID = "\1"
|
||||||
local AAD = "SiSS RA rev1|chacha20poly1305"
|
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 u32be(n) return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255),
|
||||||
local function u16be(n) return string.char(bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end
|
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)
|
local function pack_params(iter)
|
||||||
return u32be(iter) .. u16be(32)
|
return u32be(iter) .. u16be(32)
|
||||||
end
|
end
|
||||||
local function unpack_params(s)
|
local function unpack_params(s)
|
||||||
local i1,i2,i3,i4, d1,d2 = s:byte(1,6)
|
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 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 dklen = (bit32.bor(bit32.lshift(d1, 8), d2))
|
||||||
return iters, dklen
|
return iters, dklen
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -170,24 +184,24 @@ local function encrypt_with_password(password, plaintext)
|
||||||
local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext)
|
local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext)
|
||||||
|
|
||||||
local params = pack_params(iters)
|
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)
|
return b64u_encode(blob)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function decrypt_with_password(password, blob)
|
local function decrypt_with_password(password, blob)
|
||||||
local bin = b64u_decode(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 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
|
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
|
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 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
|
-- 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 iv, constant = split_nonce12(nonce12)
|
||||||
|
|
||||||
local tag_len = 16
|
local tag_len = 16
|
||||||
|
|
|
||||||
|
|
@ -23,67 +23,67 @@ local poly1305 = require "lib.plc.poly1305"
|
||||||
-- poly1305 key generation
|
-- poly1305 key generation
|
||||||
|
|
||||||
local poly_keygen = function(key, nonce)
|
local poly_keygen = function(key, nonce)
|
||||||
local counter = 0
|
local counter = 0
|
||||||
local m = string.rep('\0', 64)
|
local m = string.rep('\0', 64)
|
||||||
local e = chacha20.encrypt(key, counter, nonce, m)
|
local e = chacha20.encrypt(key, counter, nonce, m)
|
||||||
-- keep only first the 256 bits (32 bytes)
|
-- keep only first the 256 bits (32 bytes)
|
||||||
return e:sub(1, 32)
|
return e:sub(1, 32)
|
||||||
end
|
end
|
||||||
|
|
||||||
local pad16 = function(s)
|
local pad16 = function(s)
|
||||||
-- return null bytes to add to s so that #s is a multiple of 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))
|
return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16))
|
||||||
end
|
end
|
||||||
|
|
||||||
local app = table.insert
|
local app = table.insert
|
||||||
|
|
||||||
local encrypt = function(aad, key, iv, constant, plain)
|
local encrypt = function(aad, key, iv, constant, plain)
|
||||||
-- aad: additional authenticated data - arbitrary length
|
-- aad: additional authenticated data - arbitrary length
|
||||||
-- key: 32-byte string
|
-- key: 32-byte string
|
||||||
-- iv, constant: concatenated to form the nonce (12 bytes)
|
-- iv, constant: concatenated to form the nonce (12 bytes)
|
||||||
-- (why not one 12-byte param? --maybe because IPsec uses
|
-- (why not one 12-byte param? --maybe because IPsec uses
|
||||||
-- an 8-byte nonce)
|
-- an 8-byte nonce)
|
||||||
-- implementation: RFC 7539 sect 2.8.1
|
-- implementation: RFC 7539 sect 2.8.1
|
||||||
-- (memory inefficient - encr text is copied in mac_data)
|
-- (memory inefficient - encr text is copied in mac_data)
|
||||||
local mt = {} -- mac_data table
|
local mt = {} -- mac_data table
|
||||||
local nonce = constant .. iv
|
local nonce = constant .. iv
|
||||||
local otk = poly_keygen(key, nonce)
|
local otk = poly_keygen(key, nonce)
|
||||||
local encr = chacha20.encrypt(key, 1, nonce, plain)
|
local encr = chacha20.encrypt(key, 1, nonce, plain)
|
||||||
app(mt, aad)
|
app(mt, aad)
|
||||||
app(mt, pad16(aad))
|
app(mt, pad16(aad))
|
||||||
app(mt, encr)
|
app(mt, encr)
|
||||||
app(mt, pad16(encr))
|
app(mt, pad16(encr))
|
||||||
-- aad and encrypted text length must be encoded as
|
-- aad and encrypted text length must be encoded as
|
||||||
-- little endian _u64_ (and not u32) -- see errata at
|
-- little endian _u64_ (and not u32) -- see errata at
|
||||||
-- https://www.rfc-editor.org/errata_search.php?rfc=7539
|
-- https://www.rfc-editor.org/errata_search.php?rfc=7539
|
||||||
app(mt, string.pack('<I8', #aad))
|
app(mt, string.pack('<I8', #aad))
|
||||||
app(mt, string.pack('<I8', #encr))
|
app(mt, string.pack('<I8', #encr))
|
||||||
local mac_data = table.concat(mt)
|
local mac_data = table.concat(mt)
|
||||||
--~ p16('mac', mac_data)
|
--~ p16('mac', mac_data)
|
||||||
local tag = poly1305.auth(mac_data, otk)
|
local tag = poly1305.auth(mac_data, otk)
|
||||||
return encr, tag
|
return encr, tag
|
||||||
end --chacha20_aead_encrypt()
|
end --chacha20_aead_encrypt()
|
||||||
|
|
||||||
local function decrypt(aad, key, iv, constant, encr, tag)
|
local function decrypt(aad, key, iv, constant, encr, tag)
|
||||||
-- (memory inefficient - encr text is copied in mac_data)
|
-- (memory inefficient - encr text is copied in mac_data)
|
||||||
-- (structure similar to aead_encrypt => what could be factored?)
|
-- (structure similar to aead_encrypt => what could be factored?)
|
||||||
local mt = {} -- mac_data table
|
local mt = {} -- mac_data table
|
||||||
local nonce = constant .. iv
|
local nonce = constant .. iv
|
||||||
local otk = poly_keygen(key, nonce)
|
local otk = poly_keygen(key, nonce)
|
||||||
app(mt, aad)
|
app(mt, aad)
|
||||||
app(mt, pad16(aad))
|
app(mt, pad16(aad))
|
||||||
app(mt, encr)
|
app(mt, encr)
|
||||||
app(mt, pad16(encr))
|
app(mt, pad16(encr))
|
||||||
app(mt, string.pack('<I8', #aad))
|
app(mt, string.pack('<I8', #aad))
|
||||||
app(mt, string.pack('<I8', #encr))
|
app(mt, string.pack('<I8', #encr))
|
||||||
local mac_data = table.concat(mt)
|
local mac_data = table.concat(mt)
|
||||||
local mac = poly1305.auth(mac_data, otk)
|
local mac = poly1305.auth(mac_data, otk)
|
||||||
if mac == tag then
|
if mac == tag then
|
||||||
local plain = chacha20.encrypt(key, 1, nonce, encr)
|
local plain = chacha20.encrypt(key, 1, nonce, encr)
|
||||||
return plain
|
return plain
|
||||||
else
|
else
|
||||||
return nil, "auth failed"
|
return nil, "auth failed"
|
||||||
end
|
end
|
||||||
end --chacha20_aead_decrypt()
|
end --chacha20_aead_decrypt()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ end --chacha20_aead_decrypt()
|
||||||
-- return aead_chacha_poly module
|
-- return aead_chacha_poly module
|
||||||
|
|
||||||
return {
|
return {
|
||||||
poly_keygen = poly_keygen,
|
poly_keygen = poly_keygen,
|
||||||
encrypt = encrypt,
|
encrypt = encrypt,
|
||||||
decrypt = decrypt,
|
decrypt = decrypt,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,178 +27,178 @@ local app, concat = table.insert, table.concat
|
||||||
|
|
||||||
|
|
||||||
local function qround(st, x, y, z, w)
|
local function qround(st, x, y, z, w)
|
||||||
-- st is a chacha state: an array of 16 u32 words
|
-- st is a chacha state: an array of 16 u32 words
|
||||||
-- x,y,z,w are indices in st
|
-- x,y,z,w are indices in st
|
||||||
local a, b, c, d = st[x], st[y], st[z], st[w]
|
local a, b, c, d = st[x], st[y], st[z], st[w]
|
||||||
local t
|
local t
|
||||||
|
|
||||||
a = (a + b) % 0x100000000
|
a = (a + b) % 0x100000000
|
||||||
t = bit32.bxor(d, a)
|
t = bit32.bxor(d, a)
|
||||||
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
|
d = bit32.band(bit32.bor(bit32.lshift(t, 16), bit32.rshift(t, 16)), 0xffffffff)
|
||||||
|
|
||||||
c = (c + d) % 0x100000000
|
c = (c + d) % 0x100000000
|
||||||
t = bit32.bxor(b, c)
|
t = bit32.bxor(b, c)
|
||||||
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
|
b = bit32.band(bit32.bor(bit32.lshift(t, 12), bit32.rshift(t, 20)), 0xffffffff)
|
||||||
|
|
||||||
a = (a + b) % 0x100000000
|
a = (a + b) % 0x100000000
|
||||||
t = bit32.bxor(d, a)
|
t = bit32.bxor(d, a)
|
||||||
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
|
d = bit32.band(bit32.bor(bit32.lshift(t, 8), bit32.rshift(t, 24)), 0xffffffff)
|
||||||
|
|
||||||
c = (c + d) % 0x100000000
|
c = (c + d) % 0x100000000
|
||||||
t = bit32.bxor(b, c)
|
t = bit32.bxor(b, c)
|
||||||
b = bit32.band(bit32.bor(bit32.lshift(t, 7), bit32.rshift(t, 25)), 0xffffffff)
|
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
|
st[x], st[y], st[z], st[w] = a, b, c, d
|
||||||
return st
|
return st
|
||||||
end
|
end
|
||||||
|
|
||||||
-- chacha20 state and working state are allocated once and reused
|
-- chacha20 state and working state are allocated once and reused
|
||||||
-- by each invocation of chacha20_block()
|
-- 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_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_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)
|
local chacha20_block = function(key, counter, nonce)
|
||||||
-- key: u32[8]
|
-- key: u32[8]
|
||||||
-- counter: u32
|
-- counter: u32
|
||||||
-- nonce: u32[3]
|
-- nonce: u32[3]
|
||||||
local st = chacha20_state -- state
|
local st = chacha20_state -- state
|
||||||
local wst = chacha20_working_state -- working state
|
local wst = chacha20_working_state -- working state
|
||||||
-- initialize state
|
-- initialize state
|
||||||
st[1], st[2], st[3], st[4] =
|
st[1], st[2], st[3], st[4] =
|
||||||
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
||||||
for i = 1, 8 do st[i+4] = key[i] end
|
for i = 1, 8 do st[i + 4] = key[i] end
|
||||||
st[13] = counter
|
st[13] = counter
|
||||||
for i = 1, 3 do st[i+13] = nonce[i] end
|
for i = 1, 3 do st[i + 13] = nonce[i] end
|
||||||
-- copy state to working_state
|
-- copy state to working_state
|
||||||
for i = 1, 16 do wst[i] = st[i] end
|
for i = 1, 16 do wst[i] = st[i] end
|
||||||
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
|
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
|
||||||
for _ = 1, 10 do --RFC reference:
|
for _ = 1, 10 do --RFC reference:
|
||||||
qround(wst, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
|
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, 2, 6, 10, 14) --2. QUARTERROUND ( 1, 5, 9,13)
|
||||||
qround(wst, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
|
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, 4, 8, 12, 16) --4. QUARTERROUND ( 3, 7,11,15)
|
||||||
qround(wst, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,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, 2, 7, 12, 13) --6. QUARTERROUND ( 1, 6,11,12)
|
||||||
qround(wst, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
|
qround(wst, 3, 8, 9, 14) --7. QUARTERROUND ( 2, 7, 8,13)
|
||||||
qround(wst, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
|
qround(wst, 4, 5, 10, 15) --8. QUARTERROUND ( 3, 4, 9,14)
|
||||||
end
|
end
|
||||||
-- add working_state to state
|
-- add working_state to state
|
||||||
for i = 1, 16 do st[i] = bit32.band((st[i] + wst[i]), 0xffffffff) end
|
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, an array of 16 u32 words used as a keystream
|
||||||
return st
|
return st
|
||||||
end --chacha20_block()
|
end --chacha20_block()
|
||||||
|
|
||||||
-- pat16: used to unpack a 64-byte string as 16 uint32
|
-- pat16: used to unpack a 64-byte string as 16 uint32
|
||||||
local pat16 = "<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"
|
local pat16 = "<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4"
|
||||||
|
|
||||||
local function chacha20_encrypt_block(key, counter, nonce, pt, ptidx)
|
local function chacha20_encrypt_block(key, counter, nonce, pt, ptidx)
|
||||||
-- encrypt a 64-byte block of plain text.
|
-- encrypt a 64-byte block of plain text.
|
||||||
-- key: 32 bytes as an array of 8 uint32
|
-- key: 32 bytes as an array of 8 uint32
|
||||||
-- counter: an uint32 (must be incremented for each block)
|
-- counter: an uint32 (must be incremented for each block)
|
||||||
-- nonce: 12 bytes as an array of 3 uint32
|
-- nonce: 12 bytes as an array of 3 uint32
|
||||||
-- pt: plain text string,
|
-- pt: plain text string,
|
||||||
-- ptidx: index of beginning of block in plain text (origin=1)
|
-- ptidx: index of beginning of block in plain text (origin=1)
|
||||||
-- if less than 64 bytes are left at position ptidx, it is padded
|
-- if less than 64 bytes are left at position ptidx, it is padded
|
||||||
-- with null bytes before encryption and result is stripped
|
-- with null bytes before encryption and result is stripped
|
||||||
-- accordingly.
|
-- accordingly.
|
||||||
-- return encrypted block as a string (length <= 16)
|
-- return encrypted block as a string (length <= 16)
|
||||||
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
|
local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt
|
||||||
if rbn < 64 then
|
if rbn < 64 then
|
||||||
local tmp = string.sub(pt, ptidx)
|
local tmp = string.sub(pt, ptidx)
|
||||||
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
|
pt = tmp .. string.rep('\0', 64 - rbn) --pad last block
|
||||||
ptidx = 1
|
ptidx = 1
|
||||||
end
|
end
|
||||||
assert(#pt >= 64)
|
assert(#pt >= 64)
|
||||||
local ba = table.pack(string.unpack(pat16, pt, ptidx))
|
local ba = table.pack(string.unpack(pat16, pt, ptidx))
|
||||||
local keystream = chacha20_block(key, counter, nonce)
|
local keystream = chacha20_block(key, counter, nonce)
|
||||||
for i = 1, 16 do
|
for i = 1, 16 do
|
||||||
ba[i] = bit32.bxor(ba[i], keystream[i])
|
ba[i] = bit32.bxor(ba[i], keystream[i])
|
||||||
end
|
end
|
||||||
local es = string.pack(pat16, table.unpack(ba))
|
local es = string.pack(pat16, table.unpack(ba))
|
||||||
if rbn < 64 then
|
if rbn < 64 then
|
||||||
es = string.sub(es, 1, rbn)
|
es = string.sub(es, 1, rbn)
|
||||||
end
|
end
|
||||||
return es
|
return es
|
||||||
end --chacha20_encrypt_block
|
end --chacha20_encrypt_block
|
||||||
|
|
||||||
local chacha20_encrypt = function(key, counter, nonce, pt)
|
local chacha20_encrypt = function(key, counter, nonce, pt)
|
||||||
-- encrypt plain text 'pt', return encrypted text
|
-- encrypt plain text 'pt', return encrypted text
|
||||||
-- key: 32 bytes as a string
|
-- key: 32 bytes as a string
|
||||||
-- counter: an uint32 (must be incremented for each block)
|
-- counter: an uint32 (must be incremented for each block)
|
||||||
-- nonce: 8 bytes as a string
|
-- nonce: 8 bytes as a string
|
||||||
-- pt: plain text string,
|
-- pt: plain text string,
|
||||||
|
|
||||||
-- ensure counter can fit an uint32 --although it's unlikely
|
-- ensure counter can fit an uint32 --although it's unlikely
|
||||||
-- that we hit this wall with pure Lua encryption :-)
|
-- that we hit this wall with pure Lua encryption :-)
|
||||||
assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff,
|
assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff,
|
||||||
"block counter must fit an uint32")
|
"block counter must fit an uint32")
|
||||||
|
|
||||||
assert(#key == 32, "#key must be 32")
|
assert(#key == 32, "#key must be 32")
|
||||||
assert(#nonce == 12, "#nonce must be 12")
|
assert(#nonce == 12, "#nonce must be 12")
|
||||||
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
|
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
|
||||||
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
|
local noncea = table.pack(string.unpack("<I4I4I4", nonce))
|
||||||
local t = {} -- used to collect all encrypted blocks
|
local t = {} -- used to collect all encrypted blocks
|
||||||
local ptidx = 1
|
local ptidx = 1
|
||||||
while ptidx <= #pt do
|
while ptidx <= #pt do
|
||||||
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
|
app(t, chacha20_encrypt_block(keya, counter, noncea, pt, ptidx))
|
||||||
ptidx = ptidx + 64
|
ptidx = ptidx + 64
|
||||||
counter = counter + 1
|
counter = counter + 1
|
||||||
end
|
end
|
||||||
local et = concat(t)
|
local et = concat(t)
|
||||||
return et
|
return et
|
||||||
end --chacha20_encrypt()
|
end --chacha20_encrypt()
|
||||||
|
|
||||||
local function hchacha20(key, nonce16)
|
local function hchacha20(key, nonce16)
|
||||||
-- key: string(32)
|
-- key: string(32)
|
||||||
-- nonce16: string(16)
|
-- nonce16: string(16)
|
||||||
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
|
local keya = table.pack(string.unpack("<I4I4I4I4I4I4I4I4", key))
|
||||||
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
|
local noncea = table.pack(string.unpack("<I4I4I4I4", nonce16))
|
||||||
local st = {} -- chacha working state
|
local st = {} -- chacha working state
|
||||||
-- initialize state
|
-- initialize state
|
||||||
st[1], st[2], st[3], st[4] =
|
st[1], st[2], st[3], st[4] =
|
||||||
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
||||||
for i = 1, 8 do st[i+4] = keya[i] end
|
for i = 1, 8 do st[i + 4] = keya[i] end
|
||||||
for i = 1, 4 do st[i+12] = noncea[i] end
|
for i = 1, 4 do st[i + 12] = noncea[i] end
|
||||||
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
|
-- run 20 rounds, ie. 10 iterations of 8 quarter rounds
|
||||||
for _ = 1, 10 do --RFC reference:
|
for _ = 1, 10 do --RFC reference:
|
||||||
qround(st, 1,5,9,13) --1. QUARTERROUND ( 0, 4, 8,12)
|
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, 2, 6, 10, 14) --2. QUARTERROUND ( 1, 5, 9,13)
|
||||||
qround(st, 3,7,11,15) --3. QUARTERROUND ( 2, 6,10,14)
|
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, 4, 8, 12, 16) --4. QUARTERROUND ( 3, 7,11,15)
|
||||||
qround(st, 1,6,11,16) --5. QUARTERROUND ( 0, 5,10,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, 2, 7, 12, 13) --6. QUARTERROUND ( 1, 6,11,12)
|
||||||
qround(st, 3,8,9,14) --7. QUARTERROUND ( 2, 7, 8,13)
|
qround(st, 3, 8, 9, 14) --7. QUARTERROUND ( 2, 7, 8,13)
|
||||||
qround(st, 4,5,10,15) --8. QUARTERROUND ( 3, 4, 9,14)
|
qround(st, 4, 5, 10, 15) --8. QUARTERROUND ( 3, 4, 9,14)
|
||||||
end
|
end
|
||||||
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
|
local subkey = string.pack("<I4I4I4I4I4I4I4I4",
|
||||||
st[1], st[2], st[3], st[4],
|
st[1], st[2], st[3], st[4],
|
||||||
st[13], st[14], st[15], st[16] )
|
st[13], st[14], st[15], st[16])
|
||||||
return subkey
|
return subkey
|
||||||
end --hchacha20()
|
end --hchacha20()
|
||||||
|
|
||||||
local function xchacha20_encrypt(key, counter, nonce, pt)
|
local function xchacha20_encrypt(key, counter, nonce, pt)
|
||||||
assert(#key == 32, "#key must be 32")
|
assert(#key == 32, "#key must be 32")
|
||||||
assert(#nonce == 24, "#nonce must be 24")
|
assert(#nonce == 24, "#nonce must be 24")
|
||||||
local subkey = hchacha20(key, nonce:sub(1, 16))
|
local subkey = hchacha20(key, nonce:sub(1, 16))
|
||||||
local nonce12 = '\0\0\0\0'..nonce:sub(17)
|
local nonce12 = '\0\0\0\0' .. nonce:sub(17)
|
||||||
return chacha20_encrypt(subkey, counter, nonce12, pt)
|
return chacha20_encrypt(subkey, counter, nonce12, pt)
|
||||||
end --xchacha20_encrypt()
|
end --xchacha20_encrypt()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
return {
|
return {
|
||||||
chacha20_encrypt = chacha20_encrypt,
|
chacha20_encrypt = chacha20_encrypt,
|
||||||
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
|
chacha20_decrypt = chacha20_encrypt, -- xor encryption is symmetric
|
||||||
encrypt = chacha20_encrypt, --alias
|
encrypt = chacha20_encrypt, --alias
|
||||||
decrypt = chacha20_encrypt, --alias
|
decrypt = chacha20_encrypt, --alias
|
||||||
hchacha20 = hchacha20,
|
hchacha20 = hchacha20,
|
||||||
xchacha20_encrypt = xchacha20_encrypt,
|
xchacha20_encrypt = xchacha20_encrypt,
|
||||||
xchacha20_decrypt = xchacha20_encrypt,
|
xchacha20_decrypt = xchacha20_encrypt,
|
||||||
--
|
--
|
||||||
key_size = 32,
|
key_size = 32,
|
||||||
nonce_size = 12, -- nonce size for chacha20_encrypt
|
nonce_size = 12, -- nonce size for chacha20_encrypt
|
||||||
xnonce_size = 24, -- nonce size for xchacha20_encrypt
|
xnonce_size = 24, -- nonce size for xchacha20_encrypt
|
||||||
}
|
}
|
||||||
|
|
||||||
--end of chacha20
|
--end of chacha20
|
||||||
|
|
|
||||||
|
|
@ -13,192 +13,192 @@ local sunp = string.unpack
|
||||||
local bit32 = bit32 -- alias
|
local bit32 = bit32 -- alias
|
||||||
|
|
||||||
local function poly_init(k)
|
local function poly_init(k)
|
||||||
-- k: 32-byte key as a string
|
-- k: 32-byte key as a string
|
||||||
-- initialize internal state
|
-- initialize internal state
|
||||||
local st = {
|
local st = {
|
||||||
r = {
|
r = {
|
||||||
bit32.band(sunp('<I4', k, 1) , 0x3ffffff), -- r0
|
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, 4), 2), 0x3ffff03), -- r1
|
||||||
bit32.band(bit32.rshift(sunp('<I4', k, 7), 4), 0x3ffc0ff), -- r2
|
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, 10), 6), 0x3f03fff), -- r3
|
||||||
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
|
bit32.band(bit32.rshift(sunp('<I4', k, 13), 8), 0x00fffff), -- r4
|
||||||
},
|
},
|
||||||
h = { 0,0,0,0,0 },
|
h = { 0, 0, 0, 0, 0 },
|
||||||
pad = { sunp('<I4', k, 17), -- 's' in rfc
|
pad = { sunp('<I4', k, 17), -- 's' in rfc
|
||||||
sunp('<I4', k, 21),
|
sunp('<I4', k, 21),
|
||||||
sunp('<I4', k, 25),
|
sunp('<I4', k, 25),
|
||||||
sunp('<I4', k, 29),
|
sunp('<I4', k, 29),
|
||||||
},
|
},
|
||||||
buffer = "", --
|
buffer = "", --
|
||||||
leftover = 0,
|
leftover = 0,
|
||||||
final = false,
|
final = false,
|
||||||
}--st
|
} --st
|
||||||
return st
|
return st
|
||||||
end --poly_init()
|
end --poly_init()
|
||||||
|
|
||||||
local function poly_blocks(st, m)
|
local function poly_blocks(st, m)
|
||||||
-- st: internal state
|
-- st: internal state
|
||||||
-- m: message:string
|
-- m: message:string
|
||||||
local bytes = #m
|
local bytes = #m
|
||||||
local midx = 1
|
local midx = 1
|
||||||
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
|
local hibit = st.final and 0 or 0x01000000 -- 1 << 24
|
||||||
local r0 = st.r[1]
|
local r0 = st.r[1]
|
||||||
local r1 = st.r[2]
|
local r1 = st.r[2]
|
||||||
local r2 = st.r[3]
|
local r2 = st.r[3]
|
||||||
local r3 = st.r[4]
|
local r3 = st.r[4]
|
||||||
local r4 = st.r[5]
|
local r4 = st.r[5]
|
||||||
local s1 = r1 * 5
|
local s1 = r1 * 5
|
||||||
local s2 = r2 * 5
|
local s2 = r2 * 5
|
||||||
local s3 = r3 * 5
|
local s3 = r3 * 5
|
||||||
local s4 = r4 * 5
|
local s4 = r4 * 5
|
||||||
local h0 = st.h[1]
|
local h0 = st.h[1]
|
||||||
local h1 = st.h[2]
|
local h1 = st.h[2]
|
||||||
local h2 = st.h[3]
|
local h2 = st.h[3]
|
||||||
local h3 = st.h[4]
|
local h3 = st.h[4]
|
||||||
local h4 = st.h[5]
|
local h4 = st.h[5]
|
||||||
local d0, d1, d2, d3, d4, c
|
local d0, d1, d2, d3, d4, c
|
||||||
--
|
--
|
||||||
while bytes >= 16 do -- 16 = poly1305_block_size
|
while bytes >= 16 do -- 16 = poly1305_block_size
|
||||||
-- h += m[i] (in rfc: a += n with 0x01 byte)
|
-- h += m[i] (in rfc: a += n with 0x01 byte)
|
||||||
h0 = h0 + bit32.band(sunp('<I4', m, midx ), 0x3ffffff)
|
h0 = h0 + bit32.band(sunp('<I4', m, midx), 0x3ffffff)
|
||||||
h1 = h1 + bit32.band(bit32.rshift(sunp('<I4', m, midx + 3), 2), 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)
|
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)
|
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
|
h4 = h4 + bit32.bor(bit32.rshift(sunp('<I4', m, midx + 12), 8), hibit) -- 0x01 byte
|
||||||
--
|
--
|
||||||
-- h *= r % p (partial)
|
-- h *= r % p (partial)
|
||||||
d0 = h0*r0 + h1*s4 + h2*s3 + h3*s2 + h4*s1
|
d0 = h0 * r0 + h1 * s4 + h2 * s3 + h3 * s2 + h4 * s1
|
||||||
d1 = h0*r1 + h1*r0 + h2*s4 + h3*s3 + h4*s2
|
d1 = h0 * r1 + h1 * r0 + h2 * s4 + h3 * s3 + h4 * s2
|
||||||
d2 = h0*r2 + h1*r1 + h2*r0 + h3*s4 + h4*s3
|
d2 = h0 * r2 + h1 * r1 + h2 * r0 + h3 * s4 + h4 * s3
|
||||||
d3 = h0*r3 + h1*r2 + h2*r1 + h3*r0 + h4*s4
|
d3 = h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * s4
|
||||||
d4 = h0*r4 + h1*r3 + h2*r2 + h3*r1 + h4*r0
|
d4 = h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0
|
||||||
--
|
--
|
||||||
c = bit32.band(bit32.rshift(d0,26), 0xffffffff) ; h0 = bit32.band(d0, 0x3ffffff)
|
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)
|
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)
|
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)
|
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)
|
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)
|
h0 = h0 + (c * 5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
|
||||||
h1 = h1 + c
|
h1 = h1 + c
|
||||||
--
|
--
|
||||||
midx = midx + 16 -- 16 = poly1305_block_size
|
midx = midx + 16 -- 16 = poly1305_block_size
|
||||||
bytes = bytes - 16
|
bytes = bytes - 16
|
||||||
end --while
|
end --while
|
||||||
st.h[1] = h0
|
st.h[1] = h0
|
||||||
st.h[2] = h1
|
st.h[2] = h1
|
||||||
st.h[3] = h2
|
st.h[3] = h2
|
||||||
st.h[4] = h3
|
st.h[4] = h3
|
||||||
st.h[5] = h4
|
st.h[5] = h4
|
||||||
st.bytes = bytes -- remaining bytes. must be < 16 here
|
st.bytes = bytes -- remaining bytes. must be < 16 here
|
||||||
st.midx = midx -- index of first remaining bytes
|
st.midx = midx -- index of first remaining bytes
|
||||||
return st
|
return st
|
||||||
end --poly_blocks()
|
end --poly_blocks()
|
||||||
|
|
||||||
local function poly_update(st, m)
|
local function poly_update(st, m)
|
||||||
-- st: internal state
|
-- st: internal state
|
||||||
-- m: message:string
|
-- m: message:string
|
||||||
st.bytes, st.midx = #m, 1
|
st.bytes, st.midx = #m, 1
|
||||||
-- process full blocks if any
|
-- process full blocks if any
|
||||||
if st.bytes >= 16 then
|
if st.bytes >= 16 then
|
||||||
poly_blocks(st, m)
|
poly_blocks(st, m)
|
||||||
end
|
end
|
||||||
--handle remaining bytes
|
--handle remaining bytes
|
||||||
if st.bytes == 0 then -- no bytes left
|
if st.bytes == 0 then -- no bytes left
|
||||||
-- nothing to do? no add 0x01? - apparently not.
|
-- nothing to do? no add 0x01? - apparently not.
|
||||||
else
|
else
|
||||||
local buffer = string.sub(m, st.midx)
|
local buffer = string.sub(m, st.midx)
|
||||||
.. '\x01' .. string.rep('\0', 16 - st.bytes -1)
|
.. '\x01' .. string.rep('\0', 16 - st.bytes - 1)
|
||||||
assert(#buffer == 16)
|
assert(#buffer == 16)
|
||||||
st.final = true -- this is the last block
|
st.final = true -- this is the last block
|
||||||
--~ p16(buffer)
|
--~ p16(buffer)
|
||||||
poly_blocks(st, buffer)
|
poly_blocks(st, buffer)
|
||||||
end
|
end
|
||||||
--
|
--
|
||||||
return st
|
return st
|
||||||
end --poly_update
|
end --poly_update
|
||||||
|
|
||||||
local function poly_finish(st)
|
local function poly_finish(st)
|
||||||
--
|
--
|
||||||
local c, mask --u32
|
local c, mask --u32
|
||||||
local f --u64
|
local f --u64
|
||||||
-- fully carry h
|
-- fully carry h
|
||||||
local h0 = st.h[1]
|
local h0 = st.h[1]
|
||||||
local h1 = st.h[2]
|
local h1 = st.h[2]
|
||||||
local h2 = st.h[3]
|
local h2 = st.h[3]
|
||||||
local h3 = st.h[4]
|
local h3 = st.h[4]
|
||||||
local h4 = st.h[5]
|
local h4 = st.h[5]
|
||||||
--
|
--
|
||||||
c = bit32.rshift(h1,26); h1 = bit32.band(h1, 0x3ffffff)
|
c = bit32.rshift(h1, 26); h1 = bit32.band(h1, 0x3ffffff)
|
||||||
h2 = h2 + c; c = bit32.rshift(h2, 26); h2 = bit32.band(h2, 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)
|
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)
|
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)
|
h0 = h0 + (c * 5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff)
|
||||||
h1 = h1 + c
|
h1 = h1 + c
|
||||||
--
|
--
|
||||||
--compute h + -p
|
--compute h + -p
|
||||||
local g0 = (h0 + 5) ; c = bit32.rshift(g0, 26); g0 = bit32.band(g0, 0x3ffffff)
|
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 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 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 g3 = (h3 + c); c = bit32.rshift(g3, 26); g3 = bit32.band(g3, 0x3ffffff)
|
||||||
local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26)
|
local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26)
|
||||||
--
|
--
|
||||||
-- select h if h < p, or h + -p if h >= p
|
-- select h if h < p, or h + -p if h >= p
|
||||||
mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff)
|
mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff)
|
||||||
--
|
--
|
||||||
g0 = bit32.band(g0, mask)
|
g0 = bit32.band(g0, mask)
|
||||||
g1 = bit32.band(g1, mask)
|
g1 = bit32.band(g1, mask)
|
||||||
g2 = bit32.band(g2, mask)
|
g2 = bit32.band(g2, mask)
|
||||||
g3 = bit32.band(g3, mask)
|
g3 = bit32.band(g3, mask)
|
||||||
g4 = bit32.band(g4, mask)
|
g4 = bit32.band(g4, mask)
|
||||||
--
|
--
|
||||||
mask = bit32.band(bit32.bnot(mask), 0xffffffff)
|
mask = bit32.band(bit32.bnot(mask), 0xffffffff)
|
||||||
h0 = bit32.bor(bit32.band(h0, mask), g0)
|
h0 = bit32.bor(bit32.band(h0, mask), g0)
|
||||||
h1 = bit32.bor(bit32.band(h1, mask), g1)
|
h1 = bit32.bor(bit32.band(h1, mask), g1)
|
||||||
h2 = bit32.bor(bit32.band(h2, mask), g2)
|
h2 = bit32.bor(bit32.band(h2, mask), g2)
|
||||||
h3 = bit32.bor(bit32.band(h3, mask), g3)
|
h3 = bit32.bor(bit32.band(h3, mask), g3)
|
||||||
h4 = bit32.bor(bit32.band(h4, mask), g4)
|
h4 = bit32.bor(bit32.band(h4, mask), g4)
|
||||||
--
|
--
|
||||||
--h = h % (2^128)
|
--h = h % (2^128)
|
||||||
h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff)
|
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)
|
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)
|
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)
|
h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff)
|
||||||
--
|
--
|
||||||
-- mac = (h + pad) % (2^128)
|
-- mac = (h + pad) % (2^128)
|
||||||
f = h0 + st.pad[1] ; h0 = bit32.band(f, 0xffffffff)
|
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 = 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 = 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)
|
f = h3 + st.pad[4] + bit32.rshift(f, 32); h3 = bit32.band(f, 0xffffffff)
|
||||||
--
|
--
|
||||||
local mac = string.pack('<I4I4I4I4', h0, h1, h2, h3)
|
local mac = string.pack('<I4I4I4I4', h0, h1, h2, h3)
|
||||||
-- (should zero out the state?)
|
-- (should zero out the state?)
|
||||||
--
|
--
|
||||||
return mac
|
return mac
|
||||||
end --poly_finish()
|
end --poly_finish()
|
||||||
|
|
||||||
local function poly_auth(m, k)
|
local function poly_auth(m, k)
|
||||||
-- m: msg string
|
-- m: msg string
|
||||||
-- k: key string (must be 32 bytes)
|
-- k: key string (must be 32 bytes)
|
||||||
-- return mac 16-byte string
|
-- return mac 16-byte string
|
||||||
assert(#k == 32)
|
assert(#k == 32)
|
||||||
local st = poly_init(k)
|
local st = poly_init(k)
|
||||||
poly_update(st, m)
|
poly_update(st, m)
|
||||||
local mac = poly_finish(st)
|
local mac = poly_finish(st)
|
||||||
return mac
|
return mac
|
||||||
end --poly_auth()
|
end --poly_auth()
|
||||||
|
|
||||||
local function poly_verify(m, k, mac)
|
local function poly_verify(m, k, mac)
|
||||||
local macm = poly_auth(m, k)
|
local macm = poly_auth(m, k)
|
||||||
return macm == mac
|
return macm == mac
|
||||||
end --poly_verify()
|
end --poly_verify()
|
||||||
|
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
-- return poly1305 module
|
-- return poly1305 module
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init = poly_init,
|
init = poly_init,
|
||||||
update = poly_update,
|
update = poly_update,
|
||||||
finish = poly_finish,
|
finish = poly_finish,
|
||||||
auth = poly_auth,
|
auth = poly_auth,
|
||||||
verify = poly_verify,
|
verify = poly_verify,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,84 +9,84 @@ local expect = require "cc.expect".expect
|
||||||
-- Initialization code
|
-- Initialization code
|
||||||
local PrimeUI = {}
|
local PrimeUI = {}
|
||||||
do
|
do
|
||||||
local coros = {}
|
local coros = {}
|
||||||
local restoreCursor
|
local restoreCursor
|
||||||
|
|
||||||
--- Adds a task to run in the main loop.
|
--- Adds a task to run in the main loop.
|
||||||
---@param func function The function to run, usually an `os.pullEvent` loop
|
---@param func function The function to run, usually an `os.pullEvent` loop
|
||||||
function PrimeUI.addTask(func)
|
function PrimeUI.addTask(func)
|
||||||
expect(1, func, "function")
|
expect(1, func, "function")
|
||||||
local t = {coro = coroutine.create(func)}
|
local t = { coro = coroutine.create(func) }
|
||||||
coros[#coros+1] = t
|
coros[#coros + 1] = t
|
||||||
_, t.filter = coroutine.resume(t.coro)
|
_, 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
|
end
|
||||||
|
return x, y
|
||||||
|
end
|
||||||
|
|
||||||
--- Sends the provided arguments to the run loop, where they will be returned.
|
--- Runs the main loop, returning information on an action.
|
||||||
---@param ... any The parameters to send
|
---@return any ... The result of the coroutine that exited
|
||||||
function PrimeUI.resolve(...)
|
function PrimeUI.run()
|
||||||
coroutine.yield(coros, ...)
|
while true do
|
||||||
end
|
-- Restore the cursor and wait for the next event.
|
||||||
|
if restoreCursor then restoreCursor() end
|
||||||
--- Clears the screen and resets all components. Do not use any previously
|
local ev = table.pack(os.pullEvent())
|
||||||
--- created components after calling this function.
|
-- Run all coroutines.
|
||||||
function PrimeUI.clear()
|
for _, v in ipairs(coros) do
|
||||||
-- Reset the screen.
|
if v.filter == nil or v.filter == ev[1] then
|
||||||
term.setCursorPos(1, 1)
|
-- Resume the coroutine, passing the current event.
|
||||||
term.setCursorBlink(false)
|
local res = table.pack(coroutine.resume(v.coro, table.unpack(ev, 1, ev.n)))
|
||||||
term.setBackgroundColor(colors.black)
|
-- If the call failed, bail out. Coroutines should never exit.
|
||||||
term.setTextColor(colors.white)
|
if not res[1] then error(res[2], 2) end
|
||||||
term.clear()
|
-- If the coroutine resolved, return its values.
|
||||||
-- Reset the task list and cursor restore function.
|
if res[2] == coros then return table.unpack(res, 3, res.n) end
|
||||||
coros = {}
|
-- Set the next event filter.
|
||||||
restoreCursor = nil
|
v.filter = res[2]
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Creates a text input box.
|
--- Creates a text input box.
|
||||||
|
|
@ -102,102 +102,103 @@ end
|
||||||
---@param replacement string|nil A character to replace typed characters with
|
---@param replacement string|nil A character to replace typed characters with
|
||||||
---@param history string[]|nil A list of previous entries to provide
|
---@param history string[]|nil A list of previous entries to provide
|
||||||
---@param completion function|nil A function to call to provide completion
|
---@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)
|
function PrimeUI.inputBox(win, x, y, width, action, placeholder, fgColor, bgColor, placeholderFg, replacement, history,
|
||||||
expect(1, win, "table")
|
completion, default)
|
||||||
expect(2, x, "number")
|
expect(1, win, "table")
|
||||||
expect(3, y, "number")
|
expect(2, x, "number")
|
||||||
expect(4, width, "number")
|
expect(3, y, "number")
|
||||||
expect(5, action, "function", "string")
|
expect(4, width, "number")
|
||||||
expect(6, placeholder, "string", "nil")
|
expect(5, action, "function", "string")
|
||||||
fgColor = expect(7, fgColor, "number", "nil") or colors.white
|
expect(6, placeholder, "string", "nil")
|
||||||
bgColor = expect(8, bgColor, "number", "nil") or colors.black
|
fgColor = expect(7, fgColor, "number", "nil") or colors.white
|
||||||
placeholderFg = expect(9, placeholderFg, "number", "nil") or colors.lightGray
|
bgColor = expect(8, bgColor, "number", "nil") or colors.black
|
||||||
expect(10, replacement, "string", "nil")
|
placeholderFg = expect(9, placeholderFg, "number", "nil") or colors.lightGray
|
||||||
expect(11, history, "table", "nil")
|
expect(10, replacement, "string", "nil")
|
||||||
expect(12, completion, "function", "nil")
|
expect(11, history, "table", "nil")
|
||||||
expect(13, default, "string", "nil")
|
expect(12, completion, "function", "nil")
|
||||||
|
expect(13, default, "string", "nil")
|
||||||
|
|
||||||
local box = window.create(win, x, y, width, 1)
|
local box = window.create(win, x, y, width, 1)
|
||||||
box.setTextColor(fgColor)
|
box.setTextColor(fgColor)
|
||||||
box.setBackgroundColor(bgColor)
|
box.setBackgroundColor(bgColor)
|
||||||
box.clear()
|
box.clear()
|
||||||
|
|
||||||
PrimeUI.addTask(function()
|
PrimeUI.addTask(function()
|
||||||
local text = default or ""
|
local text = default or ""
|
||||||
local cursor = #text + 1
|
local cursor = #text + 1
|
||||||
local histIndex = nil
|
local histIndex = nil
|
||||||
|
|
||||||
local function runAction()
|
local function runAction()
|
||||||
if type(action) == "string" then
|
if type(action) == "string" then
|
||||||
PrimeUI.resolve("inputBox", action, text)
|
PrimeUI.resolve("inputBox", action, text)
|
||||||
else
|
else
|
||||||
action(text)
|
action(text)
|
||||||
end
|
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
|
||||||
|
end
|
||||||
|
box.setCursorPos(cursor, 1)
|
||||||
|
|
||||||
|
runAction()
|
||||||
|
end
|
||||||
|
|
||||||
local function redraw()
|
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
|
|
||||||
|
|
||||||
|
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()
|
redraw()
|
||||||
|
elseif ev == "key" then
|
||||||
while true do
|
if p1 == keys.enter then
|
||||||
local ev, p1 = os.pullEvent()
|
runAction()
|
||||||
if ev == "char" then
|
elseif p1 == keys.left then
|
||||||
text = text:sub(1, cursor - 1) .. p1 .. text:sub(cursor)
|
cursor = math.max(1, cursor - 1)
|
||||||
cursor = cursor + 1
|
box.setCursorPos(cursor, 1)
|
||||||
redraw()
|
elseif p1 == keys.right then
|
||||||
elseif ev == "key" then
|
cursor = math.min(#text + 1, cursor + 1)
|
||||||
if p1 == keys.enter then
|
box.setCursorPos(cursor, 1)
|
||||||
runAction()
|
elseif p1 == keys.backspace then
|
||||||
elseif p1 == keys.left then
|
if cursor > 1 then
|
||||||
cursor = math.max(1, cursor - 1)
|
text = text:sub(1, cursor - 2) .. text:sub(cursor)
|
||||||
box.setCursorPos(cursor, 1)
|
cursor = cursor - 1
|
||||||
elseif p1 == keys.right then
|
redraw()
|
||||||
cursor = math.min(#text + 1, cursor + 1)
|
end
|
||||||
box.setCursorPos(cursor, 1)
|
elseif p1 == keys.delete then
|
||||||
elseif p1 == keys.backspace then
|
text = text:sub(1, cursor - 1) .. text:sub(cursor + 1)
|
||||||
if cursor > 1 then
|
redraw()
|
||||||
text = text:sub(1, cursor - 2) .. text:sub(cursor)
|
elseif p1 == keys.up and history then
|
||||||
cursor = cursor - 1
|
if not histIndex then histIndex = #history + 1 end
|
||||||
redraw()
|
histIndex = math.max(1, histIndex - 1)
|
||||||
end
|
text = history[histIndex] or ""
|
||||||
elseif p1 == keys.delete then
|
cursor = #text + 1
|
||||||
text = text:sub(1, cursor - 1) .. text:sub(cursor + 1)
|
redraw()
|
||||||
redraw()
|
elseif p1 == keys.down and history then
|
||||||
elseif p1 == keys.up and history then
|
if histIndex then
|
||||||
if not histIndex then histIndex = #history + 1 end
|
histIndex = math.min(#history + 1, histIndex + 1)
|
||||||
histIndex = math.max(1, histIndex - 1)
|
text = history[histIndex] or ""
|
||||||
text = history[histIndex] or ""
|
cursor = #text + 1
|
||||||
cursor = #text + 1
|
redraw()
|
||||||
redraw()
|
end
|
||||||
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
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Draws a line of text at a position.
|
--- 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 fgColor color|nil The color of the text (defaults to white)
|
||||||
---@param bgColor color|nil The color of the background (defaults to black)
|
---@param bgColor color|nil The color of the background (defaults to black)
|
||||||
function PrimeUI.label(win, x, y, text, fgColor, bgColor)
|
function PrimeUI.label(win, x, y, text, fgColor, bgColor)
|
||||||
expect(1, win, "table")
|
expect(1, win, "table")
|
||||||
expect(2, x, "number")
|
expect(2, x, "number")
|
||||||
expect(3, y, "number")
|
expect(3, y, "number")
|
||||||
expect(4, text, "string")
|
expect(4, text, "string")
|
||||||
fgColor = expect(5, fgColor, "number", "nil") or colors.white
|
fgColor = expect(5, fgColor, "number", "nil") or colors.white
|
||||||
bgColor = expect(6, bgColor, "number", "nil") or colors.black
|
bgColor = expect(6, bgColor, "number", "nil") or colors.black
|
||||||
win.setCursorPos(x, y)
|
win.setCursorPos(x, y)
|
||||||
win.setTextColor(fgColor)
|
win.setTextColor(fgColor)
|
||||||
win.setBackgroundColor(bgColor)
|
win.setBackgroundColor(bgColor)
|
||||||
win.write(text)
|
win.write(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Creates a scrollable window, which allows drawing large content in a small area.
|
--- 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 window inner The inner window to draw inside
|
||||||
---@return fun(pos:number) scroll A function to manually set the scroll position of the window
|
---@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)
|
function PrimeUI.scrollBox(win, x, y, width, height, innerHeight, allowArrowKeys, showScrollIndicators, fgColor, bgColor)
|
||||||
expect(1, win, "table")
|
expect(1, win, "table")
|
||||||
expect(2, x, "number")
|
expect(2, x, "number")
|
||||||
expect(3, y, "number")
|
expect(3, y, "number")
|
||||||
expect(4, width, "number")
|
expect(4, width, "number")
|
||||||
expect(5, height, "number")
|
expect(5, height, "number")
|
||||||
expect(6, innerHeight, "number")
|
expect(6, innerHeight, "number")
|
||||||
expect(7, allowArrowKeys, "boolean", "nil")
|
expect(7, allowArrowKeys, "boolean", "nil")
|
||||||
expect(8, showScrollIndicators, "boolean", "nil")
|
expect(8, showScrollIndicators, "boolean", "nil")
|
||||||
fgColor = expect(9, fgColor, "number", "nil") or colors.white
|
fgColor = expect(9, fgColor, "number", "nil") or colors.white
|
||||||
bgColor = expect(10, bgColor, "number", "nil") or colors.black
|
bgColor = expect(10, bgColor, "number", "nil") or colors.black
|
||||||
if allowArrowKeys == nil then allowArrowKeys = true end
|
if allowArrowKeys == nil then allowArrowKeys = true end
|
||||||
-- Create the outer container box.
|
-- Create the outer container box.
|
||||||
local outer = window.create(win == term and term.current() or win, x, y, width, height)
|
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.setBackgroundColor(bgColor)
|
||||||
outer.clear()
|
outer.setTextColor(fgColor)
|
||||||
-- Create the inner scrolling box.
|
outer.setCursorPos(width, height)
|
||||||
local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight)
|
outer.write(innerHeight > height and "\31" or " ")
|
||||||
inner.setBackgroundColor(bgColor)
|
end
|
||||||
inner.clear()
|
-- Get the absolute position of the window.
|
||||||
-- Draw scroll indicators if desired.
|
x, y = PrimeUI.getWindowPos(win, x, y)
|
||||||
if showScrollIndicators then
|
-- 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.setBackgroundColor(bgColor)
|
||||||
outer.setTextColor(fgColor)
|
outer.setTextColor(fgColor)
|
||||||
|
outer.setCursorPos(width, 1)
|
||||||
|
outer.write(scrollPos > 1 and "\30" or " ")
|
||||||
outer.setCursorPos(width, height)
|
outer.setCursorPos(width, height)
|
||||||
outer.write(innerHeight > height and "\31" or " ")
|
outer.write(scrollPos < innerHeight - height and "\31" or " ")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
-- Get the absolute position of the window.
|
end)
|
||||||
x, y = PrimeUI.getWindowPos(win, x, y)
|
-- Make a function to allow external scrolling.
|
||||||
-- Add the scroll handler.
|
local function scroll(pos)
|
||||||
local scrollPos = 1
|
expect(1, pos, "number")
|
||||||
PrimeUI.addTask(function()
|
pos = math.floor(pos)
|
||||||
while true do
|
expect.range(pos, 1, innerHeight - height)
|
||||||
-- Wait for next event.
|
-- Scroll the window.
|
||||||
local ev = table.pack(os.pullEvent())
|
scrollPos = pos
|
||||||
-- Update inner height in case it changed.
|
inner.reposition(1, 2 - scrollPos)
|
||||||
innerHeight = select(2, inner.getSize())
|
-- Redraw scroll indicators if desired.
|
||||||
-- Check for scroll events and set direction.
|
if showScrollIndicators then
|
||||||
local dir
|
outer.setBackgroundColor(bgColor)
|
||||||
if ev[1] == "key" and allowArrowKeys then
|
outer.setTextColor(fgColor)
|
||||||
if ev[2] == keys.up then dir = -1
|
outer.setCursorPos(width, 1)
|
||||||
elseif ev[2] == keys.down then dir = 1 end
|
outer.write(scrollPos > 1 and "\30" or " ")
|
||||||
elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then
|
outer.setCursorPos(width, height)
|
||||||
dir = ev[2]
|
outer.write(scrollPos < innerHeight - height and "\31" or " ")
|
||||||
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
|
end
|
||||||
return inner, scroll
|
end
|
||||||
|
return inner, scroll
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Creates a list of entries that can each be selected.
|
--- Creates a list of entries that can each be selected.
|
||||||
---@param win window The window to draw on
|
---@param win window The window to draw on
|
||||||
---@param x number The X coordinate of the inside of the box
|
---@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 fgColor color|nil The color of the text (defaults to white)
|
||||||
---@param bgColor color|nil The color of the background (defaults to black)
|
---@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)
|
function PrimeUI.selectionBox(win, x, y, width, height, entries, action, selectChangeAction, fgColor, bgColor)
|
||||||
expect(1, win, "table")
|
expect(1, win, "table")
|
||||||
expect(2, x, "number")
|
expect(2, x, "number")
|
||||||
expect(3, y, "number")
|
expect(3, y, "number")
|
||||||
expect(4, width, "number")
|
expect(4, width, "number")
|
||||||
expect(5, height, "number")
|
expect(5, height, "number")
|
||||||
expect(6, entries, "function")
|
expect(6, entries, "function")
|
||||||
expect(7, action, "function", "string")
|
expect(7, action, "function", "string")
|
||||||
expect(8, selectChangeAction, "function", "string", "nil")
|
expect(8, selectChangeAction, "function", "string", "nil")
|
||||||
fgColor = expect(9, fgColor, "number", "nil") or colors.white
|
fgColor = expect(9, fgColor, "number", "nil") or colors.white
|
||||||
bgColor = expect(10, bgColor, "number", "nil") or colors.black
|
bgColor = expect(10, bgColor, "number", "nil") or colors.black
|
||||||
|
|
||||||
local entrywin = window.create(win, x, y, width, height)
|
local entrywin = window.create(win, x, y, width, height)
|
||||||
local selection, scroll = 1, 1
|
local selection, scroll = 1, 1
|
||||||
-- Create a function to redraw the entries on screen.
|
-- Create a function to redraw the entries on screen.
|
||||||
local function drawEntries()
|
local function drawEntries()
|
||||||
-- Clear and set invisible for performance.
|
-- Clear and set invisible for performance.
|
||||||
entrywin.setVisible(false)
|
entrywin.setVisible(false)
|
||||||
entrywin.setBackgroundColor(bgColor)
|
entrywin.setBackgroundColor(bgColor)
|
||||||
entrywin.clear()
|
entrywin.clear()
|
||||||
-- Draw each entry in the scrolled region.
|
-- Draw each entry in the scrolled region.
|
||||||
for i = scroll, scroll + height - 1 do
|
for i = scroll, scroll + height - 1 do
|
||||||
-- Get the entry; stop if there's no more.
|
-- Get the entry; stop if there's no more.
|
||||||
local e = entries()[i]
|
local e = entries()[i]
|
||||||
if not e then break end
|
if not e then break end
|
||||||
-- Set the colors: invert if selected.
|
-- Set the colors: invert if selected.
|
||||||
entrywin.setCursorPos(2, i - scroll + 1)
|
entrywin.setCursorPos(2, i - scroll + 1)
|
||||||
if i == selection then
|
if i == selection then
|
||||||
entrywin.setBackgroundColor(fgColor)
|
entrywin.setBackgroundColor(fgColor)
|
||||||
entrywin.setTextColor(bgColor)
|
entrywin.setTextColor(bgColor)
|
||||||
else
|
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.
|
|
||||||
entrywin.setBackgroundColor(bgColor)
|
entrywin.setBackgroundColor(bgColor)
|
||||||
entrywin.setTextColor(fgColor)
|
entrywin.setTextColor(fgColor)
|
||||||
entrywin.setCursorPos(width, 1)
|
end
|
||||||
entrywin.write("\30")
|
-- Draw the selection.
|
||||||
entrywin.setCursorPos(width, height)
|
entrywin.clearLine()
|
||||||
entrywin.write("\31")
|
entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e)
|
||||||
-- Send updates to the screen.
|
|
||||||
entrywin.setVisible(true)
|
|
||||||
end
|
end
|
||||||
-- Draw first screen.
|
-- Draw scroll arrows.
|
||||||
drawEntries()
|
entrywin.setBackgroundColor(bgColor)
|
||||||
-- Add a task for selection keys.
|
entrywin.setTextColor(fgColor)
|
||||||
PrimeUI.addTask(function()
|
entrywin.setCursorPos(width, 1)
|
||||||
while true do
|
entrywin.write("\30")
|
||||||
local event, key, cx, cy = os.pullEvent()
|
entrywin.setCursorPos(width, height)
|
||||||
if event == "key" then
|
entrywin.write("\31")
|
||||||
if key == keys.down and selection < #entries() then
|
-- Send updates to the screen.
|
||||||
-- Move selection down.
|
entrywin.setVisible(true)
|
||||||
selection = selection + 1
|
end
|
||||||
if selection > scroll + height - 1 then scroll = scroll + 1 end
|
-- Draw first screen.
|
||||||
-- Send action if necessary.
|
drawEntries()
|
||||||
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
|
-- Add a task for selection keys.
|
||||||
elseif selectChangeAction then selectChangeAction(selection) end
|
PrimeUI.addTask(function()
|
||||||
-- Redraw screen.
|
while true do
|
||||||
drawEntries()
|
local event, key, cx, cy = os.pullEvent()
|
||||||
elseif key == keys.up and selection > 1 then
|
if event == "key" then
|
||||||
-- Move selection up.
|
if key == keys.down and selection < #entries() then
|
||||||
selection = selection - 1
|
-- Move selection down.
|
||||||
if selection < scroll then scroll = scroll - 1 end
|
selection = selection + 1
|
||||||
-- Send action if necessary.
|
if selection > scroll + height - 1 then scroll = scroll + 1 end
|
||||||
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
|
-- Send action if necessary.
|
||||||
elseif selectChangeAction then selectChangeAction(selection) end
|
if type(selectChangeAction) == "string" then
|
||||||
-- Redraw screen.
|
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
|
||||||
drawEntries()
|
elseif selectChangeAction then
|
||||||
elseif key == keys.enter then
|
selectChangeAction(selection)
|
||||||
-- Select the entry: send the action.
|
end
|
||||||
if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection])
|
-- Redraw screen.
|
||||||
else action(entries()[selection]) end
|
drawEntries()
|
||||||
end
|
elseif key == keys.up and selection > 1 then
|
||||||
elseif event == "mouse_click" and key == 1 then
|
-- Move selection up.
|
||||||
-- Handle clicking the scroll arrows.
|
selection = selection - 1
|
||||||
local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1)
|
if selection < scroll then scroll = scroll - 1 end
|
||||||
if cx == wx + width - 1 then
|
-- Send action if necessary.
|
||||||
if cy == wy and selection > 1 then
|
if type(selectChangeAction) == "string" then
|
||||||
-- Move selection up.
|
PrimeUI.resolve("selectionBox", selectChangeAction, selection)
|
||||||
selection = selection - 1
|
elseif selectChangeAction then
|
||||||
if selection < scroll then scroll = scroll - 1 end
|
selectChangeAction(selection)
|
||||||
-- Send action if necessary.
|
end
|
||||||
if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection)
|
-- Redraw screen.
|
||||||
elseif selectChangeAction then selectChangeAction(selection) end
|
drawEntries()
|
||||||
-- Redraw screen.
|
elseif key == keys.enter then
|
||||||
drawEntries()
|
-- Select the entry: send the action.
|
||||||
elseif cy == wy + height - 1 and selection < #entries() then
|
if type(action) == "string" then
|
||||||
-- Move selection down.
|
PrimeUI.resolve("selectionBox", action, entries()[selection])
|
||||||
selection = selection + 1
|
else
|
||||||
if selection > scroll + height - 1 then scroll = scroll + 1 end
|
action(entries()[selection])
|
||||||
-- Send action if necessary.
|
end
|
||||||
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
|
||||||
end)
|
elseif event == "mouse_click" and key == 1 then
|
||||||
return drawEntries
|
-- 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
|
end
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
PrimeUI = PrimeUI
|
PrimeUI = PrimeUI
|
||||||
}
|
}
|
||||||
|
|
|
||||||
55
src/main.lua
55
src/main.lua
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
---@class InnerRemote
|
---@class InnerRemote
|
||||||
---@field port number
|
---@field port number
|
||||||
---@field password string
|
---@field password string
|
||||||
|
|
@ -8,11 +7,15 @@
|
||||||
---@field ender_storage string
|
---@field ender_storage string
|
||||||
---@field modem string
|
---@field modem string
|
||||||
---@field remotes table<string, InnerRemote>
|
---@field remotes table<string, InnerRemote>
|
||||||
---
|
|
||||||
|
---@class Chatbox
|
||||||
|
---@field players table<string, string>
|
||||||
|
---@field prefix string|nil
|
||||||
|
|
||||||
---@class Config
|
---@class Config
|
||||||
---@field inventories string[]
|
---@field inventories string[]
|
||||||
---@field import string[]|nil
|
---@field import string[]|nil
|
||||||
---@field chatbox table<string, string>|nil
|
---@field chatbox Chatbox|nil
|
||||||
---@field remote Remote
|
---@field remote Remote
|
||||||
|
|
||||||
local inv = require("modules.inv")
|
local inv = require("modules.inv")
|
||||||
|
|
@ -23,32 +26,32 @@ local ra = require("modules.ra")
|
||||||
local config = require("../config") ---@type Config
|
local config = require("../config") ---@type Config
|
||||||
|
|
||||||
local function importMechanism()
|
local function importMechanism()
|
||||||
if config.import == nil then
|
if config.import == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if #config.import == 0 then
|
if #config.import == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
for _, import_from_inv in ipairs(config.import) do
|
for _, import_from_inv in ipairs(config.import) do
|
||||||
---@type ccTweaked.peripheral.Inventory
|
---@type ccTweaked.peripheral.Inventory
|
||||||
local perip = peripheral.wrap(import_from_inv)
|
local perip = peripheral.wrap(import_from_inv)
|
||||||
if perip and perip.size then
|
if perip and perip.size then
|
||||||
local slotsToSend = {}
|
local slotsToSend = {}
|
||||||
for slot = 1, perip.size() do
|
for slot = 1, perip.size() do
|
||||||
local item = perip.getItemDetail(slot)
|
local item = perip.getItemDetail(slot)
|
||||||
if item then
|
if item then
|
||||||
table.insert(slotsToSend, slot)
|
table.insert(slotsToSend, slot)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
if #slotsToSend > 0 then
|
|
||||||
inv.sendItemAwayMultiple(slotsToSend, perip, import_from_inv)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
sleep(0.1)
|
if #slotsToSend > 0 then
|
||||||
|
inv.sendItemAwayMultiple(slotsToSend, perip, import_from_inv)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
sleep(0.1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
inv.sync()
|
inv.sync()
|
||||||
|
|
|
||||||
|
|
@ -2,91 +2,90 @@ local config = require("../../config") ---@type Config
|
||||||
local inv = require("modules.inv")
|
local inv = require("modules.inv")
|
||||||
|
|
||||||
local function levDist(s, t)
|
local function levDist(s, t)
|
||||||
local n = #s
|
local n = #s
|
||||||
local m = #t
|
local m = #t
|
||||||
|
|
||||||
if n == 0 then return m end
|
if n == 0 then return m end
|
||||||
if m == 0 then return n end
|
if m == 0 then return n end
|
||||||
|
|
||||||
-- create matrix
|
-- create matrix
|
||||||
local d = {}
|
local d = {}
|
||||||
for i = 0, n do
|
for i = 0, n do
|
||||||
d[i] = {}
|
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
|
||||||
|
end
|
||||||
|
|
||||||
-- initialize
|
return d[n][m]
|
||||||
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]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function findBest(data, item)
|
local function findBest(data, item)
|
||||||
|
local sorted_data = {}
|
||||||
|
|
||||||
local sorted_data = {}
|
for _, z in ipairs(data) do
|
||||||
|
local parts = {}
|
||||||
for _, z in ipairs(data) do
|
for part in string.gmatch(z, "([^:]+)") do
|
||||||
local parts = {}
|
table.insert(parts, part)
|
||||||
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 })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
table.sort(sorted_data, function(a, b)
|
local key = parts[1] .. ":" .. (parts[2] or "")
|
||||||
return a[2] < b[2]
|
local dist = levDist(item, parts[2] or "")
|
||||||
end)
|
table.insert(sorted_data, { key, dist })
|
||||||
|
end
|
||||||
|
|
||||||
local best = {}
|
table.sort(sorted_data, function(a, b)
|
||||||
local best_dist = sorted_data[1][2]
|
return a[2] < b[2]
|
||||||
for _, z in ipairs(sorted_data) do
|
end)
|
||||||
if z[2] == best_dist then
|
|
||||||
table.insert(best, z)
|
local best = {}
|
||||||
else
|
local best_dist = sorted_data[1][2]
|
||||||
break
|
for _, z in ipairs(sorted_data) do
|
||||||
end
|
if z[2] == best_dist then
|
||||||
|
table.insert(best, z)
|
||||||
|
else
|
||||||
|
break
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return best
|
return best
|
||||||
end
|
end
|
||||||
|
|
||||||
local BOT_NAME = "&cS &eI&an&3c &5S&cI&6S"
|
local BOT_NAME = "&cS &eI&an&3c &5S&cI&6S"
|
||||||
|
|
||||||
function auth(user)
|
function auth(user)
|
||||||
local manip = config.chatbox[user]
|
local manip = config.chatbox.players[user]
|
||||||
if manip then
|
if manip then
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
chatbox.tell(user, "You are not authorized to use this command.", BOT_NAME)
|
chatbox.tell(user, "You are not authorized to use this command.", BOT_NAME)
|
||||||
return false
|
return false
|
||||||
|
|
@ -99,11 +98,11 @@ function run()
|
||||||
while true do
|
while true do
|
||||||
local _, user, command, args = os.pullEvent("command")
|
local _, user, command, args = os.pullEvent("command")
|
||||||
|
|
||||||
if command == "sis" then
|
if command == (config.chatbox.prefix or "sis") then
|
||||||
if args[1] == "whoami" then
|
if args[1] == "whoami" then
|
||||||
local manip = config.chatbox[user]
|
local manip = config.chatbox.players[user]
|
||||||
if manip then
|
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
|
else
|
||||||
chatbox.tell(user, "You are not registered. Ask the creator of this SIS to be registered.", BOT_NAME)
|
chatbox.tell(user, "You are not registered. Ask the creator of this SIS to be registered.", BOT_NAME)
|
||||||
end
|
end
|
||||||
|
|
@ -116,7 +115,7 @@ function run()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local perip = peripheral.wrap(config.chatbox[user])
|
local perip = peripheral.wrap(config.chatbox.players[user])
|
||||||
|
|
||||||
---@type ccTweaked.peripheral.Inventory
|
---@type ccTweaked.peripheral.Inventory
|
||||||
local peripInventory = perip.getInventory()
|
local peripInventory = perip.getInventory()
|
||||||
|
|
@ -126,11 +125,11 @@ function run()
|
||||||
local seen = {}
|
local seen = {}
|
||||||
|
|
||||||
for _, stack in pairs(invList) do
|
for _, stack in pairs(invList) do
|
||||||
local name = stack and stack.name
|
local name = stack and stack.name
|
||||||
if name and not seen[name] then
|
if name and not seen[name] then
|
||||||
seen[name] = true
|
seen[name] = true
|
||||||
allItems[#allItems + 1] = name
|
allItems[#allItems + 1] = name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local best = findBest(allItems, args[2])
|
local best = findBest(allItems, args[2])
|
||||||
|
|
@ -138,27 +137,27 @@ function run()
|
||||||
local itemName = ""
|
local itemName = ""
|
||||||
|
|
||||||
if #best > 1 then
|
if #best > 1 then
|
||||||
itemName = best[1][1]
|
itemName = best[1][1]
|
||||||
if string.find(":", args[2]) then
|
if string.find(":", args[2]) then
|
||||||
itemName = args[2]
|
itemName = args[2]
|
||||||
end
|
end
|
||||||
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
|
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
|
||||||
else
|
else
|
||||||
itemName = best[1][1]
|
itemName = best[1][1]
|
||||||
end
|
end
|
||||||
|
|
||||||
local slots = {}
|
local slots = {}
|
||||||
|
|
||||||
for slot, item in pairs(peripInventory.list()) do
|
for slot, item in pairs(peripInventory.list()) do
|
||||||
if item.name == itemName then
|
if item.name == itemName then
|
||||||
table.insert(slots, slot)
|
table.insert(slots, slot)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local amount = nil
|
local amount = nil
|
||||||
|
|
||||||
if args[3] then
|
if args[3] then
|
||||||
amount = tonumber(args[3], 10)
|
amount = tonumber(args[3], 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
local moved = inv.sendItemAwayMultiple(slots, peripInventory, peripInventory, amount)
|
local moved = inv.sendItemAwayMultiple(slots, peripInventory, peripInventory, amount)
|
||||||
|
|
@ -168,11 +167,11 @@ function run()
|
||||||
if not auth(user) then goto continue end
|
if not auth(user) then goto continue end
|
||||||
|
|
||||||
if not args[2] then
|
if not args[2] then
|
||||||
chatbox.tell(user, "Supply a item (minecraft:`diamond` part) to withdraw it!", BOT_NAME)
|
chatbox.tell(user, "Supply a item (minecraft:`diamond` part) to withdraw it!", BOT_NAME)
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local perip = peripheral.wrap(config.chatbox[user])
|
local perip = peripheral.wrap(config.chatbox.players[user])
|
||||||
|
|
||||||
---@type ccTweaked.peripheral.Inventory
|
---@type ccTweaked.peripheral.Inventory
|
||||||
local peripInventory = perip.getInventory()
|
local peripInventory = perip.getInventory()
|
||||||
|
|
@ -182,19 +181,19 @@ function run()
|
||||||
local itemName = ""
|
local itemName = ""
|
||||||
|
|
||||||
if #best > 1 then
|
if #best > 1 then
|
||||||
itemName = best[1][1]
|
itemName = best[1][1]
|
||||||
if string.find(":", args[2]) then
|
if string.find(":", args[2]) then
|
||||||
itemName = args[2]
|
itemName = args[2]
|
||||||
end
|
end
|
||||||
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
|
chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME)
|
||||||
else
|
else
|
||||||
itemName = best[1][1]
|
itemName = best[1][1]
|
||||||
end
|
end
|
||||||
|
|
||||||
local amount = nil
|
local amount = nil
|
||||||
|
|
||||||
if args[3] then
|
if args[3] then
|
||||||
amount = tonumber(args[3], 10)
|
amount = tonumber(args[3], 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
local moved = inv.sendItemToSelf(itemName, peripInventory, amount)
|
local moved = inv.sendItemToSelf(itemName, peripInventory, amount)
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ local function sendItemToSelf(itemName, perip, maxAmount)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- sleep(0.1)
|
|
||||||
turtleMoveAllowed = true
|
turtleMoveAllowed = true
|
||||||
|
|
||||||
return total
|
return total
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,13 @@ local function run()
|
||||||
|
|
||||||
for color, remote in pairs(config.remote.remotes) do
|
for color, remote in pairs(config.remote.remotes) do
|
||||||
if remote.port == channel then
|
if remote.port == channel then
|
||||||
|
local err, data = pcall(function()
|
||||||
local err, data = pcall(function ()
|
|
||||||
return hmac.decrypt_with_password(remote.password, message)
|
return hmac.decrypt_with_password(remote.password, message)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not data then return end
|
if not data then return end
|
||||||
|
|
||||||
if err == true then
|
if err == true then
|
||||||
|
|
||||||
---@type RemoteAccess
|
---@type RemoteAccess
|
||||||
local json, errj = textutils.unserialiseJSON(data)
|
local json, errj = textutils.unserialiseJSON(data)
|
||||||
if json or errj ~= nil then
|
if json or errj ~= nil then
|
||||||
|
|
@ -57,12 +55,12 @@ local function run()
|
||||||
elseif json.type == "deposit" then
|
elseif json.type == "deposit" then
|
||||||
if json.data.itemName then
|
if json.data.itemName then
|
||||||
local move = {}
|
local move = {}
|
||||||
for s, i in pairs(ender_storage.list()) do
|
for s, i in pairs(ender_storage.list()) do
|
||||||
if i.name == json.data.itemName then
|
if i.name == json.data.itemName then
|
||||||
table.insert(move, s)
|
table.insert(move, s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount)
|
inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount)
|
||||||
elseif json.data.slots then
|
elseif json.data.slots then
|
||||||
if type(json.data.slots) ~= 'table' then
|
if type(json.data.slots) ~= 'table' then
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -8,71 +8,72 @@ local function is_beta(ver)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run()
|
local function run()
|
||||||
local search = ""
|
local search = ""
|
||||||
local win = term.current();
|
local win = term.current();
|
||||||
local w , h= term.getSize()
|
local w, h = term.getSize()
|
||||||
|
|
||||||
local function getFiltered()
|
local function getFiltered()
|
||||||
local filtered = {}
|
local filtered = {}
|
||||||
for name, count in pairs(inv.getAIL().listItemAmounts()) do
|
for name, count in pairs(inv.getAIL().listItemAmounts()) do
|
||||||
if search == "" or string.find(name:lower(), search:lower(), 1, true) then
|
if search == "" or string.find(name:lower(), search:lower(), 1, true) then
|
||||||
table.insert(filtered, { name = name, count = count })
|
table.insert(filtered, { name = name, count = count })
|
||||||
end
|
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
|
|
||||||
end
|
end
|
||||||
|
table.sort(filtered, function(a, b) return a.count > b.count end)
|
||||||
|
|
||||||
local com = getFiltered()
|
local refiltered = {}
|
||||||
|
|
||||||
PrimeUI.clear()
|
for i, filter in ipairs(filtered) do
|
||||||
local updateEntries = PrimeUI.selectionBox(
|
refiltered[i] = filter.name ..
|
||||||
win, 1, 4, w, h-7,
|
string.rep(" ", w - (2 + #filter.name + #tostring(filter.count))) .. tostring(filter.count)
|
||||||
function ()
|
end
|
||||||
return com
|
return refiltered
|
||||||
end,
|
end
|
||||||
function(option)
|
|
||||||
local z = option:match("^(%S+)")
|
|
||||||
|
|
||||||
if inv.getAIL().getItem(z) then
|
local com = getFiltered()
|
||||||
inv.sendItemToSelf(z)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
)
|
|
||||||
|
|
||||||
PrimeUI.inputBox(win, 2, 2, w-2, function (data)
|
PrimeUI.clear()
|
||||||
search = data
|
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()
|
com = getFiltered()
|
||||||
updateEntries()
|
updateEntries()
|
||||||
end, "Input a item to search..")
|
timer = os.startTimer(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
local ver = fs.open("storage-solution/version", "r").readAll()
|
PrimeUI.run()
|
||||||
|
|
||||||
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()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
run = run
|
run = run
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue