From 262c0b540815ce1439db074d908c9cc05d31ef05 Mon Sep 17 00:00:00 2001 From: yourfriendoss Date: Fri, 14 Nov 2025 22:43:49 +0200 Subject: [PATCH] Reformat + add prefix selection for config --- readme.md | 87 ++-- src/.beta-installer.lua | 108 +++-- src/.main-installer.lua | 110 ++--- src/lib/hmac-pbkdf2-aead.lua | 140 +++--- src/lib/plc/aead_chacha_poly.lua | 108 ++--- src/lib/plc/chacha20.lua | 276 +++++------ src/lib/plc/poly1305.lua | 336 ++++++------- src/lib/primeui.lua | 797 ++++++++++++++++--------------- src/main.lua | 55 ++- src/modules/chatbox.lua | 189 ++++---- src/modules/inv.lua | 1 - src/modules/ra.lua | 16 +- src/modules/ui.lua | 109 ++--- 13 files changed, 1191 insertions(+), 1141 deletions(-) diff --git a/readme.md b/readme.md index 42a8bb4..98fb6d3 100644 --- a/readme.md +++ b/readme.md @@ -21,6 +21,52 @@ - Chatbox support for withdrawing and depositing items - Remote access via enderstorages +## How to use +1. First, in your /config.lua file, add the following: +```lua +return { + ["inventories"] = { -- REQUIRED!! Please set at least one inventory pattern. + ".*barrel.*" -- Lua patterns to match all barrels + }, + ["import"] = { -- Not required, just don't set! + "ender_storage_156" -- A inventory I want to move all items from into our storage + }, + ["chatbox"] = { -- Not required, just don't set! + ["prefix"] = "home", + ["players"] = { + ["hartbreix"] = "manipulator_42" -- Chatbox support + } + }, + ["remote"] = { -- Access your items via modem, not required, just don't set! + ["ender_storage"] = "ender_storage_493", -- Enderstorage that will be changed (set this to owner-only and computer-chanagable!) + ["modem"] = "modem_1277", -- Modem to recieve messages + ["remotes"] = { -- Remote access + ["red/green/green"] = { + ["port"] = 42420, + ["password"] = "test123" -- Required! + } + } + } +} +``` +2. Run the following command: + ``` + wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua + ``` + + This will install storage-solution to /storage-solution. + To make it start every time you turn on the computer, add + + ``` + shell.run("wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua") + ``` + to startup.lua + +### [EXTRA!] + +You can also run `wget run https://files.sad.ovh/public/storage-solution/.beta-installer.lua`, which is quite unstable. You may experience issues while running the beta version, but also new juicy features :) + + ## RA SiSS has a remote access system that allows you to access your items via modem. To use it, first set up your config.lua correctly. After that you need to implement the tiny_ra_library into your program. @@ -57,44 +103,3 @@ sleep(2) print("deposits 16 items from the first slot") ra.depositBySlots({1}, 16) ``` -## How to use -1. First, in your /config.lua file, add the following: -```lua -return { - ["inventories"] = { -- REQUIRED!! Please set at least one inventory pattern. - ".*barrel.*" -- Lua patterns to match all barrels - }, - ["import"] = { -- Not required, just don't set! - "ender_storage_156" -- A inventory I want to move all items from into our storage - }, - ["chatbox"] = { -- Not required, just don't set! - ["hartbreix"] = "manipulator_42" -- Chatbox support - }, - ["remote"] = { -- Access your items via modem, not required, just don't set! - ["ender_storage"] = "ender_storage_493", -- Enderstorage that will be changed (set this to owner-only and computer-chanagable!) - ["modem"] = "modem_1277", -- Modem to recieve messages - ["remotes"] = { -- Remote access - ["red/green/green"] = { - ["port"] = 42420, - ["password"] = "test123" -- Required! - } - } - } -} -``` -2. Run the following command: - ``` - wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua - ``` - - This will install storage-solution to /storage-solution. - To make it start every time you turn on the computer, add - - ``` - shell.run("wget run https://git.sad.ovh/sophie/storage-solution/raw/branch/main/src/.main-installer.lua") - ``` - to startup.lua - -### [EXTRA!] - -You can also run `wget run https://files.sad.ovh/public/storage-solution/.beta-installer.lua`, which is quite unstable. You may experience issues while running the beta version, but also new juicy features :) diff --git a/src/.beta-installer.lua b/src/.beta-installer.lua index c6d12a3..d4cd45c 100644 --- a/src/.beta-installer.lua +++ b/src/.beta-installer.lua @@ -3,75 +3,77 @@ local api_url = base_url .. "?ls" local download_root = "storage-solution" local function fetch_folder_list() - local response = http.get(api_url) - if not response then - error("Failed to get folder list from Copyparty API") - end - local body = response.readAll() - response.close() - local data = textutils.unserializeJSON(body) - return data + local response = http.get(api_url) + if not response then + error("Failed to get folder list from Copyparty API") + end + local body = response.readAll() + response.close() + local data = textutils.unserializeJSON(body) + return data end local function download_file(path) - local file_url = base_url .. "/" .. path - local local_path = download_root .. "/" .. path - local response = http.get(file_url) - if response then - local file = fs.open(local_path, "wb") - if file then - file.write(response.readAll()) - file.close() - end - response.close() - else - print("Failed to download: " .. file_url) + local file_url = base_url .. "/" .. path + local local_path = download_root .. "/" .. path + local response = http.get(file_url) + if response then + local file = fs.open(local_path, "wb") + if file then + file.write(response.readAll()) + file.close() end + response.close() + else + print("Failed to download: " .. file_url) + end end local function traverse_and_download(folder_data, prefix) - prefix = prefix or "" + prefix = prefix or "" - for _, file in ipairs(folder_data.files or {}) do - print("Downloading: " .. prefix .. file.href) - download_file(prefix .. file.href) - end + for _, file in ipairs(folder_data.files or {}) do + print("Downloading: " .. prefix .. file.href) + download_file(prefix .. file.href) + end - for _, dir in ipairs(folder_data.dirs or {}) do - fs.makeDir(download_root .. "/" .. prefix .. dir.href) - local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. dir.href - local subdir_url = base_url .. "/" .. new_prefix .. "?ls" local response = http.get(subdir_url) - if response then - local body = response.readAll() - if not body then return end - response.close() - local subdir_data = textutils.unserializeJSON(body) - traverse_and_download(subdir_data, new_prefix) - else - print("Failed to get subdirectory: " .. subdir_url) - end + for _, dir in ipairs(folder_data.dirs or {}) do + fs.makeDir(download_root .. "/" .. prefix .. dir.href) + local new_prefix = (prefix ~= "" and (prefix .. "/") or "") .. dir.href + local subdir_url = base_url .. "/" .. new_prefix .. "?ls" + local response = http.get(subdir_url) + if response then + local body = response.readAll() + if not body then return end + response.close() + local subdir_data = textutils.unserializeJSON(body) + traverse_and_download(subdir_data, new_prefix) + else + print("Failed to get subdirectory: " .. subdir_url) end + end end if fs.exists(download_root) then - if fs.exists(download_root .. "/version") then - local previousVersion = fs.open(download_root .. "/version", "r").readAll() - local currentVersion = http.get(base_url .. "/version").readAll(); + if fs.exists(download_root .. "/version") then + local previousVersion = fs.open(download_root .. "/version", "r").readAll() + local currentVersion = http.get(base_url .. "/version").readAll(); - if previousVersion == currentVersion then - print("Previous version " .. previousVersion .. " is already installed, we're on " .. currentVersion .. " aswell, so skipping installation.") - shell.run("storage-solution/main.lua") - return - else - print("Version " .. previousVersion .. " was already installed. Uninstalling.") - fs.delete(download_root) - end + if previousVersion == currentVersion then + print("Previous version " .. + previousVersion .. " is already installed, we're on " .. currentVersion .. " aswell, so skipping installation.") + shell.run("storage-solution/main.lua") + return else - print("Version marker does not exist. Cannot install.") - shell.run("storage-solution/main.lua") - - return + print("Version " .. previousVersion .. " was already installed. Uninstalling.") + fs.delete(download_root) end + else + print("Version marker does not exist. Cannot install.") + shell.run("storage-solution/main.lua") + + return + end end fs.makeDir(download_root) diff --git a/src/.main-installer.lua b/src/.main-installer.lua index 739a982..60ed337 100644 --- a/src/.main-installer.lua +++ b/src/.main-installer.lua @@ -5,79 +5,79 @@ local download_root = "storage-solution" local remote_folder = "src" local function fetch_commit_hash() - local url = repo_api .. "/branches/" .. branch - local response = http.get(url) - if not response then - error("Failed to fetch branch info: " .. url) - end - local body = response.readAll() - response.close() - local data = textutils.unserializeJSON(body) - return data.commit.id + local url = repo_api .. "/branches/" .. branch + local response = http.get(url) + if not response then + error("Failed to fetch branch info: " .. url) + end + local body = response.readAll() + response.close() + local data = textutils.unserializeJSON(body) + return data.commit.id end local function fetch_repo_tree(path) - local url = repo_api .. "/contents" .. (path and ("/" .. path) or "") - local response = http.get(url) - if not response then - error("Failed to fetch repo tree: " .. url) - end - local body = response.readAll() - response.close() - return textutils.unserializeJSON(body) + local url = repo_api .. "/contents" .. (path and ("/" .. path) or "") + local response = http.get(url) + if not response then + error("Failed to fetch repo tree: " .. url) + end + local body = response.readAll() + response.close() + return textutils.unserializeJSON(body) end local function download_file(remote_path, local_path) - local url = raw_base .. "/" .. remote_path - fs.makeDir(fs.getDir(local_path)) - local response = http.get(url) - if response then - local file = fs.open(local_path, "wb") - if file then - file.write(response.readAll()) - file.close() - end - response.close() - print("Downloaded: " .. local_path) - else - print("Failed to download: " .. url) + local url = raw_base .. "/" .. remote_path + fs.makeDir(fs.getDir(local_path)) + local response = http.get(url) + if response then + local file = fs.open(local_path, "wb") + if file then + file.write(response.readAll()) + file.close() end + response.close() + print("Downloaded: " .. local_path) + else + print("Failed to download: " .. url) + end end local function traverse_and_download(remote_path, local_prefix) - local tree = fetch_repo_tree(remote_path) - for _, entry in ipairs(tree) do - if entry.type == "file" then - local local_path = local_prefix .. "/" .. fs.getName(entry.path) - download_file(entry.path, local_path) - elseif entry.type == "dir" then - local new_remote = entry.path - local new_local = local_prefix .. "/" .. fs.getName(entry.path) - traverse_and_download(new_remote, new_local) - end + local tree = fetch_repo_tree(remote_path) + for _, entry in ipairs(tree) do + if entry.type == "file" then + local local_path = local_prefix .. "/" .. fs.getName(entry.path) + download_file(entry.path, local_path) + elseif entry.type == "dir" then + local new_remote = entry.path + local new_local = local_prefix .. "/" .. fs.getName(entry.path) + traverse_and_download(new_remote, new_local) end + end end local remote_hash = fetch_commit_hash() print("Latest commit: " .. remote_hash) if fs.exists(download_root) then - if fs.exists(download_root .. "/version") then - local f = fs.open(download_root .. "/version", "r") - local local_hash = f.readAll() - f.close() - if local_hash == remote_hash then - print("Already up to date (commit " .. remote_hash:sub(1,7) .. ").") - shell.run(download_root .. "/main.lua") - return - else - print("Outdated (" .. local_hash:sub(1,7) .. " -> " .. remote_hash:sub(1,7) .. "), reinstalling...") - fs.delete(download_root) - end + if fs.exists(download_root .. "/version") then + local f = fs.open(download_root .. "/version", "r") + local local_hash = f.readAll() + f.close() + if local_hash == remote_hash then + print("Already up to date (commit " .. remote_hash:sub(1, 7) .. ").") + shell.run(download_root .. "/main.lua") + return else - print("Version marker missing, reinstalling.") - fs.delete(download_root) + print("Outdated (" .. local_hash:sub(1, 7) .. " -> " .. remote_hash:sub(1, 7) .. "), reinstalling...") + fs.delete(download_root) end + else + print("Version marker missing, reinstalling.") + fs.delete(download_root) + end end fs.makeDir(download_root) @@ -89,5 +89,5 @@ local f = fs.open(download_root .. "/version", "w") f.write(remote_hash) f.close() -print("Installed Sophie's Storage Solution (commit " .. remote_hash:sub(1,7) .. ")") +print("Installed Sophie's Storage Solution (commit " .. remote_hash:sub(1, 7) .. ")") shell.run(download_root .. "/main.lua") diff --git a/src/lib/hmac-pbkdf2-aead.lua b/src/lib/hmac-pbkdf2-aead.lua index 3a87e1d..3304f1d 100644 --- a/src/lib/hmac-pbkdf2-aead.lua +++ b/src/lib/hmac-pbkdf2-aead.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] block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h,16)) end)) end + if #key > block then key = (sha256(key):gsub('..', function(h) return string.char(tonumber(h, 16)) end)) end if #key < block then key = key .. string.rep("\0", block - #key) end local o_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x5c)) end) - local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c) , 0x36)) end) + local i_key_pad = key:gsub('.', function(c) return string.char(bit32.bxor(string.byte(c), 0x36)) end) local inner = sha256(i_key_pad .. msg) - inner = inner:gsub('..', function(h) return string.char(tonumber(h,16)) end) + inner = inner:gsub('..', function(h) return string.char(tonumber(h, 16)) end) local mac = sha256(o_key_pad .. inner) - return mac:gsub('..', function(h) return string.char(tonumber(h,16)) end) + return mac:gsub('..', function(h) return string.char(tonumber(h, 16)) end) end local function int_be(n) -- 32-bit BE - return string.char(bit32.band(bit32.rshift(n,24), 255), bit32.band(bit32.rshift(n,16), 255), bit32.band(bit32.rshift(n,8), 255), bit32.band(n, 255)) + return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255), + bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end local function pbkdf2_sha256(password, salt, iterations, dkLen) local hLen = 32 local l = math.ceil(dkLen / hLen) - local r = dkLen - (l-1)*hLen + local r = dkLen - (l - 1) * hLen local dk = {} - for i=1,l do + for i = 1, l do local U = hmac_sha256(password, salt .. int_be(i)) - local T = {U:byte(1,hLen)} - for itr=2,iterations do + local T = { U:byte(1, hLen) } + for itr = 2, iterations do U = hmac_sha256(password, U) - local Ub = {U:byte(1,hLen)} - for j=1,hLen do T[j] = bit32.bxor(T[j], Ub[j]) end + local Ub = { U:byte(1, hLen) } + for j = 1, hLen do T[j] = bit32.bxor(T[j], Ub[j]) end if itr % 500 == 0 then sleep(0) end end - if i0 then + if provided_data and #provided_data > 0 then DRBG.K = hmac_sha256(DRBG.K, DRBG.V .. "\1" .. provided_data) DRBG.V = hmac_sha256(DRBG.K, DRBG.V) end end local function drbg_init(seed) - DRBG.K = string.rep("\0",32) - DRBG.V = string.rep("\1",32) + DRBG.K = string.rep("\0", 32) + DRBG.V = string.rep("\1", 32) drbg_update(seed) DRBG.inited = true end @@ -106,27 +119,27 @@ local function drbg_bytes(n) local out = {} while #table.concat(out) < n do DRBG.V = hmac_sha256(DRBG.K, DRBG.V) - out[#out+1] = DRBG.V + out[#out + 1] = DRBG.V end drbg_update("") local buf = table.concat(out) - return buf:sub(1,n) + return buf:sub(1, n) end local function seed_entropy(extra_bytes) local accum = {} if fs.exists(SEED_PATH) then - local f = fs.open(SEED_PATH, "rb"); accum[#accum+1]=f.readAll(); f.close() - local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end) + local f = fs.open(SEED_PATH, "rb"); accum[#accum + 1] = f.readAll(); f.close() + local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end) drbg_init(seed) else term.write("Move around / mash keys then press Enter 4 times to seed…\n") - for i=1,4 do + for i = 1, 4 do local t1 = os.clock(); read(); local t2 = os.clock() - accum[#accum+1] = int_be(math.floor((t2 - t1)*1e9)) .. tostring({}):sub(8) + accum[#accum + 1] = int_be(math.floor((t2 - t1) * 1e9)) .. tostring({}):sub(8) end - if extra_bytes and #extra_bytes>0 then accum[#accum+1]=extra_bytes end - local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h,16)) end) + if extra_bytes and #extra_bytes > 0 then accum[#accum + 1] = extra_bytes end + local seed = sha256(table.concat(accum)):gsub('..', function(h) return string.char(tonumber(h, 16)) end) drbg_init(seed) local f = fs.open(SEED_PATH, "wb") f.write(drbg_bytes(48)) @@ -138,15 +151,16 @@ local VERSION = "\1" local KDF_ID = "\1" local AAD = "SiSS RA rev1|chacha20poly1305" -local function u32be(n) return string.char(bit32.band(bit32.rshift(n,24),255), bit32.band(bit32.rshift(n,16),255), bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end -local function u16be(n) return string.char(bit32.band(bit32.rshift(n,8),255), bit32.band(n,255)) end +local function u32be(n) return string.char(bit32.band(bit32.rshift(n, 24), 255), bit32.band(bit32.rshift(n, 16), 255), + bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end +local function u16be(n) return string.char(bit32.band(bit32.rshift(n, 8), 255), bit32.band(n, 255)) end local function pack_params(iter) return u32be(iter) .. u16be(32) end local function unpack_params(s) - local i1,i2,i3,i4, d1,d2 = s:byte(1,6) - local iters = (bit32.bor(bit32.lshift(i1,24),bit32.lshift(i2,16),bit32.lshift(i3,8),i4)) - local dklen = (bit32.bor(bit32.lshift(d1,8),d2)) + local i1, i2, i3, i4, d1, d2 = s:byte(1, 6) + local iters = (bit32.bor(bit32.lshift(i1, 24), bit32.lshift(i2, 16), bit32.lshift(i3, 8), i4)) + local dklen = (bit32.bor(bit32.lshift(d1, 8), d2)) return iters, dklen end @@ -170,24 +184,24 @@ local function encrypt_with_password(password, plaintext) local ciphertext, tag = aead.encrypt(AAD, key, iv, constant, plaintext) local params = pack_params(iters) - local blob = table.concat{ VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag } + local blob = table.concat { VERSION, KDF_ID, params, salt, nonce12, ciphertext, tag } return b64u_encode(blob) end local function decrypt_with_password(password, blob) local bin = b64u_decode(blob) - if #bin < 1+1+6+16+12+16 then return nil, "ciphertext too short" end + if #bin < 1 + 1 + 6 + 16 + 12 + 16 then return nil, "ciphertext too short" end local pos = 1 - local ver = bin:sub(pos,pos); pos=pos+1 + local ver = bin:sub(pos, pos); pos = pos + 1 if ver ~= VERSION then return nil, "version mismatch" end - local kdfid = bin:sub(pos,pos); pos=pos+1 + local kdfid = bin:sub(pos, pos); pos = pos + 1 if kdfid ~= KDF_ID then return nil, "kdf mismatch" end - local params = bin:sub(pos,pos+5); pos=pos+6 + local params = bin:sub(pos, pos + 5); pos = pos + 6 local iters, dklen = unpack_params(params); if dklen ~= 32 then return nil, "bad dkLen" end - local salt = bin:sub(pos,pos+15); pos=pos+16 + local salt = bin:sub(pos, pos + 15); pos = pos + 16 -- read the same 12-byte nonce and split the same way - local nonce12 = bin:sub(pos,pos+11); pos=pos+12 + local nonce12 = bin:sub(pos, pos + 11); pos = pos + 12 local iv, constant = split_nonce12(nonce12) local tag_len = 16 diff --git a/src/lib/plc/aead_chacha_poly.lua b/src/lib/plc/aead_chacha_poly.lua index 4f12e44..101d926 100644 --- a/src/lib/plc/aead_chacha_poly.lua +++ b/src/lib/plc/aead_chacha_poly.lua @@ -23,67 +23,67 @@ local poly1305 = require "lib.plc.poly1305" -- poly1305 key generation local poly_keygen = function(key, nonce) - local counter = 0 - local m = string.rep('\0', 64) - local e = chacha20.encrypt(key, counter, nonce, m) - -- keep only first the 256 bits (32 bytes) - return e:sub(1, 32) + local counter = 0 + local m = string.rep('\0', 64) + local e = chacha20.encrypt(key, counter, nonce, m) + -- keep only first the 256 bits (32 bytes) + return e:sub(1, 32) end local pad16 = function(s) - -- return null bytes to add to s so that #s is a multiple of 16 - return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16)) + -- return null bytes to add to s so that #s is a multiple of 16 + return (#s % 16 == 0) and "" or ('\0'):rep(16 - (#s % 16)) end local app = table.insert local encrypt = function(aad, key, iv, constant, plain) - -- aad: additional authenticated data - arbitrary length - -- key: 32-byte string - -- iv, constant: concatenated to form the nonce (12 bytes) - -- (why not one 12-byte param? --maybe because IPsec uses - -- an 8-byte nonce) - -- implementation: RFC 7539 sect 2.8.1 - -- (memory inefficient - encr text is copied in mac_data) - local mt = {} -- mac_data table - local nonce = constant .. iv - local otk = poly_keygen(key, nonce) - local encr = chacha20.encrypt(key, 1, nonce, plain) - app(mt, aad) - app(mt, pad16(aad)) - app(mt, encr) - app(mt, pad16(encr)) - -- aad and encrypted text length must be encoded as - -- little endian _u64_ (and not u32) -- see errata at - -- https://www.rfc-editor.org/errata_search.php?rfc=7539 - app(mt, string.pack(' what could be factored?) - local mt = {} -- mac_data table - local nonce = constant .. iv - local otk = poly_keygen(key, nonce) - app(mt, aad) - app(mt, pad16(aad)) - app(mt, encr) - app(mt, pad16(encr)) - app(mt, string.pack(' what could be factored?) + local mt = {} -- mac_data table + local nonce = constant .. iv + local otk = poly_keygen(key, nonce) + app(mt, aad) + app(mt, pad16(aad)) + app(mt, encr) + app(mt, pad16(encr)) + app(mt, string.pack('= 64) - local ba = table.pack(string.unpack(pat16, pt, ptidx)) - local keystream = chacha20_block(key, counter, nonce) - for i = 1, 16 do - ba[i] = bit32.bxor(ba[i], keystream[i]) - end - local es = string.pack(pat16, table.unpack(ba)) - if rbn < 64 then - es = string.sub(es, 1, rbn) - end - return es + -- encrypt a 64-byte block of plain text. + -- key: 32 bytes as an array of 8 uint32 + -- counter: an uint32 (must be incremented for each block) + -- nonce: 12 bytes as an array of 3 uint32 + -- pt: plain text string, + -- ptidx: index of beginning of block in plain text (origin=1) + -- if less than 64 bytes are left at position ptidx, it is padded + -- with null bytes before encryption and result is stripped + -- accordingly. + -- return encrypted block as a string (length <= 16) + local rbn = #pt - ptidx + 1 -- number of remaining bytes in pt + if rbn < 64 then + local tmp = string.sub(pt, ptidx) + pt = tmp .. string.rep('\0', 64 - rbn) --pad last block + ptidx = 1 + end + assert(#pt >= 64) + local ba = table.pack(string.unpack(pat16, pt, ptidx)) + local keystream = chacha20_block(key, counter, nonce) + for i = 1, 16 do + ba[i] = bit32.bxor(ba[i], keystream[i]) + end + local es = string.pack(pat16, table.unpack(ba)) + if rbn < 64 then + es = string.sub(es, 1, rbn) + end + return es end --chacha20_encrypt_block local chacha20_encrypt = function(key, counter, nonce, pt) - -- encrypt plain text 'pt', return encrypted text - -- key: 32 bytes as a string - -- counter: an uint32 (must be incremented for each block) - -- nonce: 8 bytes as a string - -- pt: plain text string, + -- encrypt plain text 'pt', return encrypted text + -- key: 32 bytes as a string + -- counter: an uint32 (must be incremented for each block) + -- nonce: 8 bytes as a string + -- pt: plain text string, - -- ensure counter can fit an uint32 --although it's unlikely - -- that we hit this wall with pure Lua encryption :-) - assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff, + -- ensure counter can fit an uint32 --although it's unlikely + -- that we hit this wall with pure Lua encryption :-) + assert((counter + math.floor(#pt / 64) + 1) < 0xffffffff, "block counter must fit an uint32") - assert(#key == 32, "#key must be 32") - assert(#nonce == 12, "#nonce must be 12") - local keya = table.pack(string.unpack("= 16 do -- 16 = poly1305_block_size - -- h += m[i] (in rfc: a += n with 0x01 byte) - h0 = h0 + bit32.band(sunp('= 16 do -- 16 = poly1305_block_size + -- h += m[i] (in rfc: a += n with 0x01 byte) + h0 = h0 + bit32.band(sunp('= 16 then - poly_blocks(st, m) - end - --handle remaining bytes - if st.bytes == 0 then -- no bytes left - -- nothing to do? no add 0x01? - apparently not. - else - local buffer = string.sub(m, st.midx) - .. '\x01' .. string.rep('\0', 16 - st.bytes -1) - assert(#buffer == 16) - st.final = true -- this is the last block ---~ p16(buffer) - poly_blocks(st, buffer) - end - -- - return st + -- st: internal state + -- m: message:string + st.bytes, st.midx = #m, 1 + -- process full blocks if any + if st.bytes >= 16 then + poly_blocks(st, m) + end + --handle remaining bytes + if st.bytes == 0 then -- no bytes left + -- nothing to do? no add 0x01? - apparently not. + else + local buffer = string.sub(m, st.midx) + .. '\x01' .. string.rep('\0', 16 - st.bytes - 1) + assert(#buffer == 16) + st.final = true -- this is the last block + --~ p16(buffer) + poly_blocks(st, buffer) + end + -- + return st end --poly_update local function poly_finish(st) - -- - local c, mask --u32 - local f --u64 - -- fully carry h - local h0 = st.h[1] - local h1 = st.h[2] - local h2 = st.h[3] - local h3 = st.h[4] - local h4 = st.h[5] - -- - c = bit32.rshift(h1,26); h1 = bit32.band(h1, 0x3ffffff) - h2 = h2 + c; c = bit32.rshift(h2, 26); h2 = bit32.band(h2, 0x3ffffff) - h3 = h3 + c; c = bit32.rshift(h3, 26); h3 = bit32.band(h3, 0x3ffffff) - h4 = h4 + c; c = bit32.rshift(h4, 26); h4 = bit32.band(h4, 0x3ffffff) - h0 = h0 + (c*5); c = bit32.rshift(h0, 26); h0 = bit32.band(h0, 0x3ffffff) - h1 = h1 + c - -- - --compute h + -p - local g0 = (h0 + 5) ; c = bit32.rshift(g0, 26); g0 = bit32.band(g0, 0x3ffffff) - local g1 = (h1 + c) ; c = bit32.rshift(g1, 26); g1 = bit32.band(g1, 0x3ffffff) - local g2 = (h2 + c) ; c = bit32.rshift(g2, 26); g2 = bit32.band(g2, 0x3ffffff) - local g3 = (h3 + c) ; c = bit32.rshift(g3, 26); g3 = bit32.band(g3, 0x3ffffff) - local g4 = bit32.band(h4 + c - 0x4000000, 0xffffffff) -- (1 << 26) - -- - -- select h if h < p, or h + -p if h >= p - mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff) - -- - g0 = bit32.band(g0, mask) - g1 = bit32.band(g1, mask) - g2 = bit32.band(g2, mask) - g3 = bit32.band(g3, mask) - g4 = bit32.band(g4, mask) - -- - mask = bit32.band(bit32.bnot(mask), 0xffffffff) - h0 = bit32.bor(bit32.band(h0, mask), g0) - h1 = bit32.bor(bit32.band(h1, mask), g1) - h2 = bit32.bor(bit32.band(h2, mask), g2) - h3 = bit32.bor(bit32.band(h3, mask), g3) - h4 = bit32.bor(bit32.band(h4, mask), g4) - -- - --h = h % (2^128) - h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff) - h1 = bit32.band(bit32.bor(bit32.rshift(h1, 6), bit32.lshift(h2, 20)), 0xffffffff) - h2 = bit32.band(bit32.bor(bit32.rshift(h2, 12), bit32.lshift(h3, 14)), 0xffffffff) - h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff) - -- - -- mac = (h + pad) % (2^128) - f = h0 + st.pad[1] ; h0 = bit32.band(f, 0xffffffff) - f = h1 + st.pad[2] + bit32.rshift(f, 32) ; h1 = bit32.band(f, 0xffffffff) - f = h2 + st.pad[3] + bit32.rshift(f, 32) ; h2 = bit32.band(f, 0xffffffff) - f = h3 + st.pad[4] + bit32.rshift(f, 32) ; h3 = bit32.band(f, 0xffffffff) - -- - local mac = string.pack('= p + mask = bit32.band(bit32.rshift(g4, 31) - 1, 0xffffffff) + -- + g0 = bit32.band(g0, mask) + g1 = bit32.band(g1, mask) + g2 = bit32.band(g2, mask) + g3 = bit32.band(g3, mask) + g4 = bit32.band(g4, mask) + -- + mask = bit32.band(bit32.bnot(mask), 0xffffffff) + h0 = bit32.bor(bit32.band(h0, mask), g0) + h1 = bit32.bor(bit32.band(h1, mask), g1) + h2 = bit32.bor(bit32.band(h2, mask), g2) + h3 = bit32.bor(bit32.band(h3, mask), g3) + h4 = bit32.bor(bit32.band(h4, mask), g4) + -- + --h = h % (2^128) + h0 = bit32.band(bit32.bor((h0), bit32.lshift(h1, 26)), 0xffffffff) + h1 = bit32.band(bit32.bor(bit32.rshift(h1, 6), bit32.lshift(h2, 20)), 0xffffffff) + h2 = bit32.band(bit32.bor(bit32.rshift(h2, 12), bit32.lshift(h3, 14)), 0xffffffff) + h3 = bit32.band(bit32.bor(bit32.rshift(h3, 18), bit32.lshift(h4, 8)), 0xffffffff) + -- + -- mac = (h + pad) % (2^128) + f = h0 + st.pad[1]; h0 = bit32.band(f, 0xffffffff) + f = h1 + st.pad[2] + bit32.rshift(f, 32); h1 = bit32.band(f, 0xffffffff) + f = h2 + st.pad[3] + bit32.rshift(f, 32); h2 = bit32.band(f, 0xffffffff) + f = h3 + st.pad[4] + bit32.rshift(f, 32); h3 = bit32.band(f, 0xffffffff) + -- + local mac = string.pack(' 1 then - text = text:sub(1, cursor - 2) .. text:sub(cursor) - cursor = cursor - 1 - redraw() - end - elseif p1 == keys.delete then - text = text:sub(1, cursor - 1) .. text:sub(cursor + 1) - redraw() - elseif p1 == keys.up and history then - if not histIndex then histIndex = #history + 1 end - histIndex = math.max(1, histIndex - 1) - text = history[histIndex] or "" - cursor = #text + 1 - redraw() - elseif p1 == keys.down and history then - if histIndex then - histIndex = math.min(#history + 1, histIndex + 1) - text = history[histIndex] or "" - cursor = #text + 1 - redraw() - end - end - end + elseif ev == "key" then + if p1 == keys.enter then + runAction() + elseif p1 == keys.left then + cursor = math.max(1, cursor - 1) + box.setCursorPos(cursor, 1) + elseif p1 == keys.right then + cursor = math.min(#text + 1, cursor + 1) + box.setCursorPos(cursor, 1) + elseif p1 == keys.backspace then + if cursor > 1 then + text = text:sub(1, cursor - 2) .. text:sub(cursor) + cursor = cursor - 1 + redraw() + end + elseif p1 == keys.delete then + text = text:sub(1, cursor - 1) .. text:sub(cursor + 1) + redraw() + elseif p1 == keys.up and history then + if not histIndex then histIndex = #history + 1 end + histIndex = math.max(1, histIndex - 1) + text = history[histIndex] or "" + cursor = #text + 1 + redraw() + elseif p1 == keys.down and history then + if histIndex then + histIndex = math.min(#history + 1, histIndex + 1) + text = history[histIndex] or "" + cursor = #text + 1 + redraw() + end end - end) + end + end + end) end --- Draws a line of text at a position. @@ -208,16 +209,16 @@ end ---@param fgColor color|nil The color of the text (defaults to white) ---@param bgColor color|nil The color of the background (defaults to black) function PrimeUI.label(win, x, y, text, fgColor, bgColor) - expect(1, win, "table") - expect(2, x, "number") - expect(3, y, "number") - expect(4, text, "string") - fgColor = expect(5, fgColor, "number", "nil") or colors.white - bgColor = expect(6, bgColor, "number", "nil") or colors.black - win.setCursorPos(x, y) - win.setTextColor(fgColor) - win.setBackgroundColor(bgColor) - win.write(text) + expect(1, win, "table") + expect(2, x, "number") + expect(3, y, "number") + expect(4, text, "string") + fgColor = expect(5, fgColor, "number", "nil") or colors.white + bgColor = expect(6, bgColor, "number", "nil") or colors.black + win.setCursorPos(x, y) + win.setTextColor(fgColor) + win.setBackgroundColor(bgColor) + win.write(text) end --- Creates a scrollable window, which allows drawing large content in a small area. @@ -234,88 +235,90 @@ end ---@return window inner The inner window to draw inside ---@return fun(pos:number) scroll A function to manually set the scroll position of the window function PrimeUI.scrollBox(win, x, y, width, height, innerHeight, allowArrowKeys, showScrollIndicators, fgColor, bgColor) - expect(1, win, "table") - expect(2, x, "number") - expect(3, y, "number") - expect(4, width, "number") - expect(5, height, "number") - expect(6, innerHeight, "number") - expect(7, allowArrowKeys, "boolean", "nil") - expect(8, showScrollIndicators, "boolean", "nil") - fgColor = expect(9, fgColor, "number", "nil") or colors.white - bgColor = expect(10, bgColor, "number", "nil") or colors.black - if allowArrowKeys == nil then allowArrowKeys = true end - -- Create the outer container box. - local outer = window.create(win == term and term.current() or win, x, y, width, height) + expect(1, win, "table") + expect(2, x, "number") + expect(3, y, "number") + expect(4, width, "number") + expect(5, height, "number") + expect(6, innerHeight, "number") + expect(7, allowArrowKeys, "boolean", "nil") + expect(8, showScrollIndicators, "boolean", "nil") + fgColor = expect(9, fgColor, "number", "nil") or colors.white + bgColor = expect(10, bgColor, "number", "nil") or colors.black + if allowArrowKeys == nil then allowArrowKeys = true end + -- Create the outer container box. + local outer = window.create(win == term and term.current() or win, x, y, width, height) + outer.setBackgroundColor(bgColor) + outer.clear() + -- Create the inner scrolling box. + local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight) + inner.setBackgroundColor(bgColor) + inner.clear() + -- Draw scroll indicators if desired. + if showScrollIndicators then outer.setBackgroundColor(bgColor) - outer.clear() - -- Create the inner scrolling box. - local inner = window.create(outer, 1, 1, width - (showScrollIndicators and 1 or 0), innerHeight) - inner.setBackgroundColor(bgColor) - inner.clear() - -- Draw scroll indicators if desired. - if showScrollIndicators then + outer.setTextColor(fgColor) + outer.setCursorPos(width, height) + outer.write(innerHeight > height and "\31" or " ") + end + -- Get the absolute position of the window. + x, y = PrimeUI.getWindowPos(win, x, y) + -- Add the scroll handler. + local scrollPos = 1 + PrimeUI.addTask(function() + while true do + -- Wait for next event. + local ev = table.pack(os.pullEvent()) + -- Update inner height in case it changed. + innerHeight = select(2, inner.getSize()) + -- Check for scroll events and set direction. + local dir + if ev[1] == "key" and allowArrowKeys then + if ev[2] == keys.up then + dir = -1 + elseif ev[2] == keys.down then + dir = 1 + end + elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then + dir = ev[2] + end + -- If there's a scroll event, move the window vertically. + if dir and (scrollPos + dir >= 1 and scrollPos + dir <= innerHeight - height) then + scrollPos = scrollPos + dir + inner.reposition(1, 2 - scrollPos) + end + -- Redraw scroll indicators if desired. + if showScrollIndicators then outer.setBackgroundColor(bgColor) outer.setTextColor(fgColor) + outer.setCursorPos(width, 1) + outer.write(scrollPos > 1 and "\30" or " ") outer.setCursorPos(width, height) - outer.write(innerHeight > height and "\31" or " ") + outer.write(scrollPos < innerHeight - height and "\31" or " ") + end end - -- Get the absolute position of the window. - x, y = PrimeUI.getWindowPos(win, x, y) - -- Add the scroll handler. - local scrollPos = 1 - PrimeUI.addTask(function() - while true do - -- Wait for next event. - local ev = table.pack(os.pullEvent()) - -- Update inner height in case it changed. - innerHeight = select(2, inner.getSize()) - -- Check for scroll events and set direction. - local dir - if ev[1] == "key" and allowArrowKeys then - if ev[2] == keys.up then dir = -1 - elseif ev[2] == keys.down then dir = 1 end - elseif ev[1] == "mouse_scroll" and ev[3] >= x and ev[3] < x + width and ev[4] >= y and ev[4] < y + height then - dir = ev[2] - end - -- If there's a scroll event, move the window vertically. - if dir and (scrollPos + dir >= 1 and scrollPos + dir <= innerHeight - height) then - scrollPos = scrollPos + dir - inner.reposition(1, 2 - scrollPos) - end - -- Redraw scroll indicators if desired. - if showScrollIndicators then - outer.setBackgroundColor(bgColor) - outer.setTextColor(fgColor) - outer.setCursorPos(width, 1) - outer.write(scrollPos > 1 and "\30" or " ") - outer.setCursorPos(width, height) - outer.write(scrollPos < innerHeight - height and "\31" or " ") - end - end - end) - -- Make a function to allow external scrolling. - local function scroll(pos) - expect(1, pos, "number") - pos = math.floor(pos) - expect.range(pos, 1, innerHeight - height) - -- Scroll the window. - scrollPos = pos - inner.reposition(1, 2 - scrollPos) - -- Redraw scroll indicators if desired. - if showScrollIndicators then - outer.setBackgroundColor(bgColor) - outer.setTextColor(fgColor) - outer.setCursorPos(width, 1) - outer.write(scrollPos > 1 and "\30" or " ") - outer.setCursorPos(width, height) - outer.write(scrollPos < innerHeight - height and "\31" or " ") - end + end) + -- Make a function to allow external scrolling. + local function scroll(pos) + expect(1, pos, "number") + pos = math.floor(pos) + expect.range(pos, 1, innerHeight - height) + -- Scroll the window. + scrollPos = pos + inner.reposition(1, 2 - scrollPos) + -- Redraw scroll indicators if desired. + if showScrollIndicators then + outer.setBackgroundColor(bgColor) + outer.setTextColor(fgColor) + outer.setCursorPos(width, 1) + outer.write(scrollPos > 1 and "\30" or " ") + outer.setCursorPos(width, height) + outer.write(scrollPos < innerHeight - height and "\31" or " ") end - return inner, scroll + end + return inner, scroll end - --- Creates a list of entries that can each be selected. ---@param win window The window to draw on ---@param x number The X coordinate of the inside of the box @@ -328,152 +331,178 @@ end ---@param fgColor color|nil The color of the text (defaults to white) ---@param bgColor color|nil The color of the background (defaults to black) function PrimeUI.selectionBox(win, x, y, width, height, entries, action, selectChangeAction, fgColor, bgColor) - expect(1, win, "table") - expect(2, x, "number") - expect(3, y, "number") - expect(4, width, "number") - expect(5, height, "number") - expect(6, entries, "function") - expect(7, action, "function", "string") - expect(8, selectChangeAction, "function", "string", "nil") - fgColor = expect(9, fgColor, "number", "nil") or colors.white - bgColor = expect(10, bgColor, "number", "nil") or colors.black + expect(1, win, "table") + expect(2, x, "number") + expect(3, y, "number") + expect(4, width, "number") + expect(5, height, "number") + expect(6, entries, "function") + expect(7, action, "function", "string") + expect(8, selectChangeAction, "function", "string", "nil") + fgColor = expect(9, fgColor, "number", "nil") or colors.white + bgColor = expect(10, bgColor, "number", "nil") or colors.black - local entrywin = window.create(win, x, y, width, height) - local selection, scroll = 1, 1 - -- Create a function to redraw the entries on screen. - local function drawEntries() - -- Clear and set invisible for performance. - entrywin.setVisible(false) - entrywin.setBackgroundColor(bgColor) - entrywin.clear() - -- Draw each entry in the scrolled region. - for i = scroll, scroll + height - 1 do - -- Get the entry; stop if there's no more. - local e = entries()[i] - if not e then break end - -- Set the colors: invert if selected. - entrywin.setCursorPos(2, i - scroll + 1) - if i == selection then - entrywin.setBackgroundColor(fgColor) - entrywin.setTextColor(bgColor) - else - entrywin.setBackgroundColor(bgColor) - entrywin.setTextColor(fgColor) - end - -- Draw the selection. - entrywin.clearLine() - entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e) - end - -- Draw scroll arrows. + local entrywin = window.create(win, x, y, width, height) + local selection, scroll = 1, 1 + -- Create a function to redraw the entries on screen. + local function drawEntries() + -- Clear and set invisible for performance. + entrywin.setVisible(false) + entrywin.setBackgroundColor(bgColor) + entrywin.clear() + -- Draw each entry in the scrolled region. + for i = scroll, scroll + height - 1 do + -- Get the entry; stop if there's no more. + local e = entries()[i] + if not e then break end + -- Set the colors: invert if selected. + entrywin.setCursorPos(2, i - scroll + 1) + if i == selection then + entrywin.setBackgroundColor(fgColor) + entrywin.setTextColor(bgColor) + else entrywin.setBackgroundColor(bgColor) entrywin.setTextColor(fgColor) - entrywin.setCursorPos(width, 1) - entrywin.write("\30") - entrywin.setCursorPos(width, height) - entrywin.write("\31") - -- Send updates to the screen. - entrywin.setVisible(true) + end + -- Draw the selection. + entrywin.clearLine() + entrywin.write(#e > width - 1 and e:sub(1, width - 4) .. "..." or e) end - -- Draw first screen. - drawEntries() - -- Add a task for selection keys. - PrimeUI.addTask(function() - while true do - local event, key, cx, cy = os.pullEvent() - if event == "key" then - if key == keys.down and selection < #entries() then - -- Move selection down. - selection = selection + 1 - if selection > scroll + height - 1 then scroll = scroll + 1 end - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - elseif key == keys.up and selection > 1 then - -- Move selection up. - selection = selection - 1 - if selection < scroll then scroll = scroll - 1 end - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - elseif key == keys.enter then - -- Select the entry: send the action. - if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection]) - else action(entries()[selection]) end - end - elseif event == "mouse_click" and key == 1 then - -- Handle clicking the scroll arrows. - local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1) - if cx == wx + width - 1 then - if cy == wy and selection > 1 then - -- Move selection up. - selection = selection - 1 - if selection < scroll then scroll = scroll - 1 end - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - elseif cy == wy + height - 1 and selection < #entries() then - -- Move selection down. - selection = selection + 1 - if selection > scroll + height - 1 then scroll = scroll + 1 end - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - end - elseif cx >= wx and cx < wx + width - 1 and cy >= wy and cy < wy + height then - local sel = scroll + (cy - wy) - if sel == selection then - -- Select the entry: send the action. - if type(action) == "string" then PrimeUI.resolve("selectionBox", action, entries()[selection]) - else action(entries()[selection]) end - else - selection = sel - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - end - end - elseif event == "mouse_scroll" then - -- Handle mouse scrolling. - local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1) - if cx >= wx and cx < wx + width and cy >= wy and cy < wy + height then - if key < 0 and selection > 1 then - -- Move selection up. - selection = selection - 1 - if selection < scroll then scroll = scroll - 1 end - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - elseif key > 0 and selection < #entries() then - -- Move selection down. - selection = selection + 1 - if selection > scroll + height - 1 then scroll = scroll + 1 end - -- Send action if necessary. - if type(selectChangeAction) == "string" then PrimeUI.resolve("selectionBox", selectChangeAction, selection) - elseif selectChangeAction then selectChangeAction(selection) end - -- Redraw screen. - drawEntries() - end - end - end + -- Draw scroll arrows. + entrywin.setBackgroundColor(bgColor) + entrywin.setTextColor(fgColor) + entrywin.setCursorPos(width, 1) + entrywin.write("\30") + entrywin.setCursorPos(width, height) + entrywin.write("\31") + -- Send updates to the screen. + entrywin.setVisible(true) + end + -- Draw first screen. + drawEntries() + -- Add a task for selection keys. + PrimeUI.addTask(function() + while true do + local event, key, cx, cy = os.pullEvent() + if event == "key" then + if key == keys.down and selection < #entries() then + -- Move selection down. + selection = selection + 1 + if selection > scroll + height - 1 then scroll = scroll + 1 end + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + elseif key == keys.up and selection > 1 then + -- Move selection up. + selection = selection - 1 + if selection < scroll then scroll = scroll - 1 end + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + elseif key == keys.enter then + -- Select the entry: send the action. + if type(action) == "string" then + PrimeUI.resolve("selectionBox", action, entries()[selection]) + else + action(entries()[selection]) + end end - end) - return drawEntries + elseif event == "mouse_click" and key == 1 then + -- Handle clicking the scroll arrows. + local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1) + if cx == wx + width - 1 then + if cy == wy and selection > 1 then + -- Move selection up. + selection = selection - 1 + if selection < scroll then scroll = scroll - 1 end + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + elseif cy == wy + height - 1 and selection < #entries() then + -- Move selection down. + selection = selection + 1 + if selection > scroll + height - 1 then scroll = scroll + 1 end + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + end + elseif cx >= wx and cx < wx + width - 1 and cy >= wy and cy < wy + height then + local sel = scroll + (cy - wy) + if sel == selection then + -- Select the entry: send the action. + if type(action) == "string" then + PrimeUI.resolve("selectionBox", action, entries()[selection]) + else + action(entries()[selection]) + end + else + selection = sel + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + end + end + elseif event == "mouse_scroll" then + -- Handle mouse scrolling. + local wx, wy = PrimeUI.getWindowPos(entrywin, 1, 1) + if cx >= wx and cx < wx + width and cy >= wy and cy < wy + height then + if key < 0 and selection > 1 then + -- Move selection up. + selection = selection - 1 + if selection < scroll then scroll = scroll - 1 end + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + elseif key > 0 and selection < #entries() then + -- Move selection down. + selection = selection + 1 + if selection > scroll + height - 1 then scroll = scroll + 1 end + -- Send action if necessary. + if type(selectChangeAction) == "string" then + PrimeUI.resolve("selectionBox", selectChangeAction, selection) + elseif selectChangeAction then + selectChangeAction(selection) + end + -- Redraw screen. + drawEntries() + end + end + end + end + end) + return drawEntries end - return { - PrimeUI = PrimeUI + PrimeUI = PrimeUI } diff --git a/src/main.lua b/src/main.lua index 29f19c8..d67e735 100644 --- a/src/main.lua +++ b/src/main.lua @@ -1,4 +1,3 @@ - ---@class InnerRemote ---@field port number ---@field password string @@ -8,11 +7,15 @@ ---@field ender_storage string ---@field modem string ---@field remotes table ---- + +---@class Chatbox +---@field players table +---@field prefix string|nil + ---@class Config ---@field inventories string[] ---@field import string[]|nil ----@field chatbox table|nil +---@field chatbox Chatbox|nil ---@field remote Remote local inv = require("modules.inv") @@ -23,32 +26,32 @@ local ra = require("modules.ra") local config = require("../config") ---@type Config local function importMechanism() - if config.import == nil then - return - end - if #config.import == 0 then - return - end + if config.import == nil then + return + end + if #config.import == 0 then + return + end - while true do - for _, import_from_inv in ipairs(config.import) do - ---@type ccTweaked.peripheral.Inventory - local perip = peripheral.wrap(import_from_inv) - if perip and perip.size then - local slotsToSend = {} - for slot = 1, perip.size() do - local item = perip.getItemDetail(slot) - if item then - table.insert(slotsToSend, slot) - end - end - if #slotsToSend > 0 then - inv.sendItemAwayMultiple(slotsToSend, perip, import_from_inv) - end - end + while true do + for _, import_from_inv in ipairs(config.import) do + ---@type ccTweaked.peripheral.Inventory + local perip = peripheral.wrap(import_from_inv) + if perip and perip.size then + local slotsToSend = {} + for slot = 1, perip.size() do + local item = perip.getItemDetail(slot) + if item then + table.insert(slotsToSend, slot) + end end - sleep(0.1) + if #slotsToSend > 0 then + inv.sendItemAwayMultiple(slotsToSend, perip, import_from_inv) + end + end end + sleep(0.1) + end end inv.sync() diff --git a/src/modules/chatbox.lua b/src/modules/chatbox.lua index c3536e0..8a99c14 100644 --- a/src/modules/chatbox.lua +++ b/src/modules/chatbox.lua @@ -2,91 +2,90 @@ local config = require("../../config") ---@type Config local inv = require("modules.inv") local function levDist(s, t) - local n = #s - local m = #t + local n = #s + local m = #t - if n == 0 then return m end - if m == 0 then return n end + if n == 0 then return m end + if m == 0 then return n end - -- create matrix - local d = {} - for i = 0, n do - d[i] = {} + -- create matrix + local d = {} + for i = 0, n do + d[i] = {} + end + + -- initialize + for i = 0, n do d[i][0] = i end + for j = 0, m do d[0][j] = j end + + -- main loop + for i = 1, n do + local s_i = s:sub(i, i) + + for j = 1, m do + -- safeguard shortcut + if i == j and d[i][j] and d[i][j] > 4 then + return n + end + + local t_j = t:sub(j, j) + local cost = (s_i == t_j) and 0 or 1 + + local mi = math.min( + d[i - 1][j] + 1, + d[i][j - 1] + 1, + d[i - 1][j - 1] + cost + ) + + d[i][j] = mi + + -- Damerau transposition + if i > 1 and j > 1 and s_i == t:sub(j - 1, j - 1) and s:sub(i - 1, i - 1) == t_j then + d[i][j] = math.min(d[i][j], d[i - 2][j - 2] + cost) + end end + end - -- initialize - for i = 0, n do d[i][0] = i end - for j = 0, m do d[0][j] = j end - - -- main loop - for i = 1, n do - local s_i = s:sub(i, i) - - for j = 1, m do - -- safeguard shortcut - if i == j and d[i][j] and d[i][j] > 4 then - return n - end - - local t_j = t:sub(j, j) - local cost = (s_i == t_j) and 0 or 1 - - local mi = math.min( - d[i - 1][j] + 1, - d[i][j - 1] + 1, - d[i - 1][j - 1] + cost - ) - - d[i][j] = mi - - -- Damerau transposition - if i > 1 and j > 1 and s_i == t:sub(j - 1, j - 1) and s:sub(i - 1, i - 1) == t_j then - d[i][j] = math.min(d[i][j], d[i - 2][j - 2] + cost) - end - end - end - - return d[n][m] + return d[n][m] end local function findBest(data, item) + local sorted_data = {} - local sorted_data = {} - - for _, z in ipairs(data) do - local parts = {} - for part in string.gmatch(z, "([^:]+)") do - table.insert(parts, part) - end - - local key = parts[1] .. ":" .. (parts[2] or "") - local dist = levDist(item, parts[2] or "") - table.insert(sorted_data, { key, dist }) + for _, z in ipairs(data) do + local parts = {} + for part in string.gmatch(z, "([^:]+)") do + table.insert(parts, part) end - table.sort(sorted_data, function(a, b) - return a[2] < b[2] - end) + local key = parts[1] .. ":" .. (parts[2] or "") + local dist = levDist(item, parts[2] or "") + table.insert(sorted_data, { key, dist }) + end - local best = {} - local best_dist = sorted_data[1][2] - for _, z in ipairs(sorted_data) do - if z[2] == best_dist then - table.insert(best, z) - else - break - end + table.sort(sorted_data, function(a, b) + return a[2] < b[2] + end) + + local best = {} + local best_dist = sorted_data[1][2] + for _, z in ipairs(sorted_data) do + if z[2] == best_dist then + table.insert(best, z) + else + break end + end - return best + return best end local BOT_NAME = "&cS &eI&an&3c &5S&cI&6S" function auth(user) - local manip = config.chatbox[user] + local manip = config.chatbox.players[user] if manip then - return true + return true else chatbox.tell(user, "You are not authorized to use this command.", BOT_NAME) return false @@ -99,11 +98,11 @@ function run() while true do local _, user, command, args = os.pullEvent("command") - if command == "sis" then + if command == (config.chatbox.prefix or "sis") then if args[1] == "whoami" then - local manip = config.chatbox[user] + local manip = config.chatbox.players[user] if manip then - chatbox.tell(user, "You are " .. user .. ", linked with `" .. manip .."`.", BOT_NAME) + chatbox.tell(user, "You are " .. user .. ", linked with `" .. manip .. "`.", BOT_NAME) else chatbox.tell(user, "You are not registered. Ask the creator of this SIS to be registered.", BOT_NAME) end @@ -116,7 +115,7 @@ function run() end - local perip = peripheral.wrap(config.chatbox[user]) + local perip = peripheral.wrap(config.chatbox.players[user]) ---@type ccTweaked.peripheral.Inventory local peripInventory = perip.getInventory() @@ -126,11 +125,11 @@ function run() local seen = {} for _, stack in pairs(invList) do - local name = stack and stack.name - if name and not seen[name] then - seen[name] = true - allItems[#allItems + 1] = name - end + local name = stack and stack.name + if name and not seen[name] then + seen[name] = true + allItems[#allItems + 1] = name + end end local best = findBest(allItems, args[2]) @@ -138,27 +137,27 @@ function run() local itemName = "" if #best > 1 then - itemName = best[1][1] - if string.find(":", args[2]) then - itemName = args[2] - end - chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME) + itemName = best[1][1] + if string.find(":", args[2]) then + itemName = args[2] + end + chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME) else - itemName = best[1][1] + itemName = best[1][1] end local slots = {} for slot, item in pairs(peripInventory.list()) do - if item.name == itemName then - table.insert(slots, slot) - end + if item.name == itemName then + table.insert(slots, slot) + end end local amount = nil if args[3] then - amount = tonumber(args[3], 10) + amount = tonumber(args[3], 10) end local moved = inv.sendItemAwayMultiple(slots, peripInventory, peripInventory, amount) @@ -168,11 +167,11 @@ function run() if not auth(user) then goto continue end if not args[2] then - chatbox.tell(user, "Supply a item (minecraft:`diamond` part) to withdraw it!", BOT_NAME) - goto continue + chatbox.tell(user, "Supply a item (minecraft:`diamond` part) to withdraw it!", BOT_NAME) + goto continue end - local perip = peripheral.wrap(config.chatbox[user]) + local perip = peripheral.wrap(config.chatbox.players[user]) ---@type ccTweaked.peripheral.Inventory local peripInventory = perip.getInventory() @@ -182,19 +181,19 @@ function run() local itemName = "" if #best > 1 then - itemName = best[1][1] - if string.find(":", args[2]) then - itemName = args[2] - end - chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME) + itemName = best[1][1] + if string.find(":", args[2]) then + itemName = args[2] + end + chatbox.tell(user, "`WARNING`: Item most likely inaccurate. Assuming " .. itemName, BOT_NAME) else - itemName = best[1][1] + itemName = best[1][1] end local amount = nil if args[3] then - amount = tonumber(args[3], 10) + amount = tonumber(args[3], 10) end local moved = inv.sendItemToSelf(itemName, peripInventory, amount) diff --git a/src/modules/inv.lua b/src/modules/inv.lua index 0bfd4d3..e9a9481 100644 --- a/src/modules/inv.lua +++ b/src/modules/inv.lua @@ -89,7 +89,6 @@ local function sendItemToSelf(itemName, perip, maxAmount) end end end - -- sleep(0.1) turtleMoveAllowed = true return total diff --git a/src/modules/ra.lua b/src/modules/ra.lua index 9847ce3..e1ff104 100644 --- a/src/modules/ra.lua +++ b/src/modules/ra.lua @@ -30,15 +30,13 @@ local function run() for color, remote in pairs(config.remote.remotes) do if remote.port == channel then - - local err, data = pcall(function () + local err, data = pcall(function() return hmac.decrypt_with_password(remote.password, message) end) if not data then return end if err == true then - ---@type RemoteAccess local json, errj = textutils.unserialiseJSON(data) if json or errj ~= nil then @@ -57,12 +55,12 @@ local function run() elseif json.type == "deposit" then if json.data.itemName then local move = {} - for s, i in pairs(ender_storage.list()) do - if i.name == json.data.itemName then - table.insert(move, s) - end - end - inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount) + for s, i in pairs(ender_storage.list()) do + if i.name == json.data.itemName then + table.insert(move, s) + end + end + inv.sendItemAwayMultiple(move, ender_storage, config.remote.ender_storage, json.data.amount) elseif json.data.slots then if type(json.data.slots) ~= 'table' then return diff --git a/src/modules/ui.lua b/src/modules/ui.lua index e979401..9031d76 100644 --- a/src/modules/ui.lua +++ b/src/modules/ui.lua @@ -8,71 +8,72 @@ local function is_beta(ver) end local function run() - local search = "" - local win = term.current(); - local w , h= term.getSize() + local search = "" + local win = term.current(); + local w, h = term.getSize() - local function getFiltered() - local filtered = {} - for name, count in pairs(inv.getAIL().listItemAmounts()) do - if search == "" or string.find(name:lower(), search:lower(), 1, true) then - table.insert(filtered, { name = name, count = count }) - end - end - table.sort(filtered, function(a, b) return a.count > b.count end) - - local refiltered = {} - - for i, filter in ipairs(filtered) do - refiltered[i] = filter.name .. string.rep(" ", w-(2+#filter.name+#tostring(filter.count))) .. tostring(filter.count) - end - return refiltered + local function getFiltered() + local filtered = {} + for name, count in pairs(inv.getAIL().listItemAmounts()) do + if search == "" or string.find(name:lower(), search:lower(), 1, true) then + table.insert(filtered, { name = name, count = count }) + end end + table.sort(filtered, function(a, b) return a.count > b.count end) - local com = getFiltered() + local refiltered = {} - PrimeUI.clear() - local updateEntries = PrimeUI.selectionBox( - win, 1, 4, w, h-7, - function () - return com - end, - function(option) - local z = option:match("^(%S+)") + for i, filter in ipairs(filtered) do + refiltered[i] = filter.name .. + string.rep(" ", w - (2 + #filter.name + #tostring(filter.count))) .. tostring(filter.count) + end + return refiltered + end - if inv.getAIL().getItem(z) then - inv.sendItemToSelf(z) - end - end - ) + local com = getFiltered() - PrimeUI.inputBox(win, 2, 2, w-2, function (data) - search = data + PrimeUI.clear() + local updateEntries = PrimeUI.selectionBox( + win, 1, 4, w, h - 7, + function() + return com + end, + function(option) + local z = option:match("^(%S+)") + + if inv.getAIL().getItem(z) then + inv.sendItemToSelf(z) + end + end + ) + + PrimeUI.inputBox(win, 2, 2, w - 2, function(data) + search = data + com = getFiltered() + updateEntries() + end, "Input a item to search..") + + local ver = fs.open("storage-solution/version", "r").readAll() + + PrimeUI.label(win, 2, h - 1, is_beta(ver) and "you're running beta :3" or "welcome to SiSS", colors.lightGray) + PrimeUI.label(win, 2, h - 2, "rev. " .. ver, colors.lightGray) + + local timer = os.startTimer(1) + PrimeUI.addTask(function() + while true do + local _, osTimer = os.pullEvent("timer") + if osTimer == timer then com = getFiltered() updateEntries() - end, "Input a item to search..") + timer = os.startTimer(1) + end + end + end) - local ver = fs.open("storage-solution/version", "r").readAll() - - PrimeUI.label(win, 2, h-1, is_beta(ver) and "you're running beta :3" or "welcome to SiSS", colors.lightGray) - PrimeUI.label(win, 2, h-2, "rev. " .. ver, colors.lightGray) - - local timer = os.startTimer(1) - PrimeUI.addTask(function() - while true do - local _, osTimer = os.pullEvent("timer") - if osTimer == timer then - com = getFiltered() - updateEntries() - timer = os.startTimer(1) - end - end - end) - - PrimeUI.run() + PrimeUI.run() end return { - run = run + run = run }