diff --git a/.gitignore b/.gitignore index c9f2f04..e1953f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ # This is released into the public domain. # No warranty is provided, implied or otherwise. -# leaving in preSH.tar.gz for anyone who's interested -# in how NOT to do compression code.tar code/data/app-claw/* work.tar @@ -32,7 +30,9 @@ laboratory/*/*/*/* inst.lua # Available as the respective release inst-gold.lua -com2/code.tar.bd +# Compression stuff +inst/iSymTab +# internal upldr.sh upldr-dev.sh upldr-gold.sh diff --git a/claw/code-claw.lua b/claw/code-claw.lua index baacf03..13545c3 100644 --- a/claw/code-claw.lua +++ b/claw/code-claw.lua @@ -3,7 +3,7 @@ return { ["neo"] = { desc = "KittenOS NEO Kernel & Base Libs", - v = 8, + v = 9, deps = { }, dirs = { @@ -18,6 +18,7 @@ return { "libs/serial.lua", "libs/fmttext.lua", "libs/neoux.lua", + "libs/lineedit.lua", "libs/braille.lua", "libs/bmp.lua", "libs/sys-filewrap.lua", @@ -54,7 +55,7 @@ return { }, ["neo-everest"] = { desc = "KittenOS NEO / Everest (windowing)", - v = 5, + v = 9, deps = { "neo" }, @@ -67,7 +68,7 @@ return { }, ["neo-icecap"] = { desc = "KittenOS NEO / Icecap", - v = 8, + v = 9, deps = { "neo" }, @@ -84,7 +85,7 @@ return { }, ["neo-secpolicy"] = { desc = "KittenOS NEO / Secpolicy", - v = 8, + v = 9, deps = { }, dirs = { @@ -96,7 +97,7 @@ return { }, ["neo-coreapps"] = { desc = "KittenOS NEO Core Apps", - v = 5, + v = 9, deps = { "neo" }, @@ -125,7 +126,7 @@ return { }, ["neo-logo"] = { desc = "KittenOS NEO Logo (data)", - v = 8, + v = 9, deps = { }, dirs = { @@ -175,6 +176,20 @@ return { "apps/svc-app-claw-worker.lua" }, }, + ["svc-t"] = { + desc = "KittenOS NEO Terminal System", + v = 9, + deps = { + "neo" + }, + dirs = { + "apps" + }, + files = { + "apps/svc-t.lua", + "apps/app-luashell.lua" + }, + }, ["neo-meta"] = { desc = "KittenOS NEO: Use 'All' to install to other disks", v = 5, @@ -190,6 +205,7 @@ return { "app-bmpview", "app-flash", "app-claw", + "svc-t", "app-wget" }, dirs = { diff --git a/claw/repo-claw.lua b/claw/repo-claw.lua index 27dab20..e0cceb2 100644 --- a/claw/repo-claw.lua +++ b/claw/repo-claw.lua @@ -24,7 +24,7 @@ return { }, ["neo-docs"] = { desc = "KittenOS NEO system documentation", - v = 8, + v = 9, deps = { "zzz-license-pd" }, @@ -43,6 +43,7 @@ return { "docs/us-setti", "docs/us-evrst", "docs/us-clawf", + "docs/us-termi", "docs/ul-seria", "docs/ul-fwrap", "docs/ul-event", @@ -50,6 +51,7 @@ return { "docs/ul-neoux", "docs/ul-brail", "docs/ul-bmp__", + "docs/ul-linee", "docs/gp-pedan", "docs/repoauthors/neo-docs" }, @@ -89,11 +91,12 @@ return { "docs/repoauthors/app-allmem" }, }, - ["app-kmt"] = { - desc = "Line-terminal for MUDs & such", - v = 1, + ["app-telnet"] = { + desc = "TELNET client", + v = 0, deps = { "neo", + "svc-t", "zzz-license-pd" }, dirs = { @@ -102,8 +105,8 @@ return { "docs/repoauthors" }, files = { - "apps/app-kmt.lua", - "docs/repoauthors/app-kmt" + "apps/app-telnet.lua", + "docs/repoauthors/app-telnet" }, }, ["svc-ghostie"] = { diff --git a/code/apps/app-control.lua b/code/apps/app-control.lua index 1fb90f2..04d929e 100644 --- a/code/apps/app-control.lua +++ b/code/apps/app-control.lua @@ -226,7 +226,7 @@ window = neoux.create(currentGen()) while running do local src, id, k, v = event.pull() if src == "x.neo.sys.manage" then - if id == "set_setting" then + if id == "set_setting" and currentGen ~= logGen then window.reset(currentGen()) end end diff --git a/code/apps/app-luashell.lua b/code/apps/app-luashell.lua new file mode 100644 index 0000000..9ac6572 --- /dev/null +++ b/code/apps/app-luashell.lua @@ -0,0 +1,164 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +local _, _, termId = ... +local ok = pcall(function () + assert(string.sub(termId, 1, 12) == "x.neo.pub.t/") +end) + +local termClose + +if not ok then + termId = nil + assert(neo.executeAsync("svc-t", function (res) + termId = res.access + termClose = res.close + neo.scheduleTimer(0) + end, "luashell")) + while not termId do + coroutine.yield() + end +end +TERM = neo.requireAccess(termId, "terminal") + +-- Using event makes it easier for stuff +-- within the shell to not spectacularly explode. +event = require("event")(neo) + +local alive = true +event.listen("k.procdie", function (_, _, pid) + if pid == TERM.pid then + alive = false + end +end) + +TERM.write([[ + KittenOS NEO Shell Usage Notes + +Prefixing = is an alias for 'return '. +io.read(): Reads a line. +print: 'print with table dumping' impl. +TERM: Your terminal. (see us-termi doc.) +os.execute(): launch terminal apps! +tries '*', 'sys-t-*', 'svc-t-*', 'app-*' + example: os.execute("luashell") +os.exit(): quit the shell +=listCmdApps(): -t- (terminal) apps +event: useful for setting up listeners + without breaking shell functionality +]]) + +function listCmdApps() + local apps = {} + for _, v in ipairs(neo.listApps()) do + if v:sub(4, 6) == "-t-" then + table.insert(apps, v) + end + end + return apps +end + +local function vPrint(slike, ...) + local s = {...} + if #s > 1 then + for i = 1, #s do + if i ~= 1 then TERM.write("\t") end + vPrint(slike, s[i]) + end + elseif slike and type(s[1]) == "string" then + TERM.write("\"" .. s[1] .. "\"") + elseif type(s[1]) ~= "table" then + TERM.write(tostring(s[1])) + else + TERM.write("{") + for k, v in pairs(s[1]) do + TERM.write("[") + vPrint(true, k) + TERM.write("] = ") + vPrint(true, v) + TERM.write(", ") + end + TERM.write("}") + end +end + +print = function (...) + vPrint(false, ...) + TERM.write("\r\n") +end + +local ioBuffer = "" + +io = { + read = function () + while alive do + local pos = ioBuffer:find("\n") + if pos then + local line = ioBuffer:sub(1, pos):gsub("\r", "") + ioBuffer = ioBuffer:sub(pos + 1) + return line + end + local e = {event.pull()} + if e[1] == TERM.id then + if e[2] == "data" then + ioBuffer = ioBuffer .. e[3] + end + end + end + end, + write = function (s) TERM.write(s) end +} + +local originalOS = os +os = setmetatable({}, { + __index = originalOS +}) + +function os.exit() + alive = false +end + +function os.execute(x, ...) + local subPid = neo.executeAsync(x, TERM.id, ...) + if not subPid then + subPid = neo.executeAsync("sys-t-" .. x, TERM.id, ...) + end + if not subPid then + subPid = neo.executeAsync("svc-t-" .. x, TERM.id, ...) + end + if not subPid then + subPid = neo.executeAsync("app-" .. x, TERM.id, ...) + end + if not subPid then + error("cannot find " .. x) + end + while true do + local e = {event.pull()} + if e[1] == "k.procdie" then + if e[3] == subPid then + return + end + end + end +end + +while alive do + TERM.write("> ") + local code = io.read() + if code then + local ok, err = pcall(function () + if code:sub(1, 1) == "=" then + code = "return " .. code:sub(2) + end + print(assert(load(code))()) + end) + if not ok then + TERM.write(tostring(err) .. "\r\n") + end + end +end + +if termClose then + termClose() +end + diff --git a/code/apps/app-textedit.lua b/code/apps/app-textedit.lua index 5afec38..ac9ccbe 100644 --- a/code/apps/app-textedit.lua +++ b/code/apps/app-textedit.lua @@ -30,32 +30,17 @@ local clipsrc = neo.requireAccess("x.neo.pub.globals", "clipboard") local windows = neo.requireAccess("x.neo.pub.window", "windows") local files = neo.requireAccess("x.neo.pub.base", "files").showFileDialogAsync +local lineEdit = require("lineedit") + local cursorX = 1 local cursorY = math.ceil(#lines / 2) -local cFlash = true -local ctrlFlag, focFlag, appendFlag +local ctrlFlag, appendFlag local dialogLock = false local sW, sH = 37, #lines + 2 local window = windows(sW, sH) local filedialog = nil local flush -local function splitCur() - local s = lines[cursorY] - local st = unicode.sub(s, 1, cursorX - 1) - local en = unicode.sub(s, cursorX) - return st, en -end - -local function clampCursorX() - local s = lines[cursorY] - if unicode.len(s) < (cursorX - 1) then - cursorX = unicode.len(s) + 1 - return true - end - return false -end - local cbs = {} local function fileDialog(writing, callback) @@ -139,31 +124,14 @@ local function getline(y) -- rX is difficult! local rX = 1 local Xthold = math.max(1, math.floor(sW / 2) - 1) - local _, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX) + local cLine, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX) rX = (math.max(0, math.floor(cursorXP / Xthold) - 1) * Xthold) + 1 local line = lines[rY] if not line then return ("¬"):rep(sW) end line = unicode.safeTextFormat(line) - -- - local tl = unicode.sub(line, rX, rX + sW - 1) - cursorXP = (cursorXP - rX) + 1 - if cFlash then - if rY == cursorY then - if cursorXP >= 1 then - if cursorXP <= sW then - local start = unicode.sub(tl, 1, cursorXP - 1) - local endx = unicode.sub(tl, cursorXP + 1) - tl = start .. "_" .. endx - end - end - end - end - while unicode.len(tl) < sW do - tl = tl .. " " - end - return tl + return lineEdit.draw(sW, line, rY == cursorY and cursorXP, rX) end local function delLine() local contents = lines[cursorY] @@ -179,22 +147,7 @@ local function delLine() end return contents end --- add a single character -local function putLetter(ch) - if ch == "\r" then - local a, b = splitCur() - lines[cursorY] = a - table.insert(lines, cursorY + 1, b) - cursorY = cursorY + 1 - cursorX = 1 - return - end - local a, b = splitCur() - a = a .. ch - lines[cursorY] = a .. b - cursorX = unicode.len(a) + 1 -end -local function key(ka, kc, down) +local function key(ks, kc, down) if dialogLock then return false end @@ -212,18 +165,22 @@ local function key(ka, kc, down) sH = 1 end sW, sH = window.setSize(sW, sH) + return false elseif kc == 208 then -- Down sH = sH + 1 sW, sH = window.setSize(sW, sH) + return false elseif kc == 203 then -- Left sW = sW - 1 if sW == 0 then sW = 1 end sW, sH = window.setSize(sW, sH) + return false elseif kc == 205 then -- Right sW = sW + 1 sW, sH = window.setSize(sW, sH) + return false elseif kc == 14 then -- ^Backspace delLine() return true @@ -240,7 +197,7 @@ local function key(ka, kc, down) if cursorY < 1 then cursorY = 1 end - clampCursorX() + cursorX = lineEdit.clamp(lines[cursorY], cursorX) return true elseif kc == 208 or kc == 209 then -- Go down one - go down page local moveAmount = 1 @@ -251,36 +208,7 @@ local function key(ka, kc, down) if cursorY > #lines then cursorY = #lines end - clampCursorX() - return true - elseif kc == 203 then - if cursorX > 1 then - cursorX = cursorX - 1 - else - if cursorY > 1 then - cursorY = cursorY - 1 - cursorX = unicode.len(lines[cursorY]) + 1 - else - return false - end - end - return true - elseif kc == 205 then - cursorX = cursorX + 1 - if clampCursorX() then - if cursorY < #lines then - cursorY = cursorY + 1 - cursorX = 1 - end - end - return true - end - -- Extra Non-Control Keys - if kc == 199 then - cursorX = 1 - return true - elseif kc == 207 then - cursorX = unicode.len(lines[cursorY]) + 1 + cursorX = lineEdit.clamp(lines[cursorY], cursorX) return true end -- Major Actions @@ -291,10 +219,13 @@ local function key(ka, kc, down) return true elseif kc == 61 then -- F3 startLoad() + return false elseif kc == 62 then -- F4 startSave() + return false elseif kc == 63 then -- F5 clipsrc.setSetting("clipboard", lines[cursorY]) + return false elseif kc == 64 then -- F6 local tx = clipsrc.getSetting("clipboard") or "" local txi = tx:find("\n") @@ -311,6 +242,7 @@ local function key(ka, kc, down) return true elseif kc == 65 then -- F7 appendFlag = false + return false elseif kc == 66 then -- F8 if appendFlag then local base = clipsrc.getSetting("clipboard") @@ -322,29 +254,37 @@ local function key(ka, kc, down) return true end end - -- Letters - if ka == 8 or kc == 211 then - if cursorX == 1 then - if cursorY == 1 then - return false - end - local l = table.remove(lines, cursorY) - cursorY = cursorY - 1 - cursorX = unicode.len(lines[cursorY]) + 1 - lines[cursorY] = lines[cursorY] .. l - else - local a, b = splitCur() - a = unicode.sub(a, 1, unicode.len(a) - 1) - lines[cursorY] = a.. b - cursorX = cursorX - 1 - end - return true + -- LEL Keys + local lT, lC, lX = lineEdit.key(ks, kc, lines[cursorY], cursorX) + if lT then + lines[cursorY] = lT end - if ka ~= 0 then - putLetter(unicode.char(ka)) - return true + if lC then + cursorX = lC end - return false + if lX == "l<" and cursorY > 1 then + cursorY = cursorY - 1 + cursorX = unicode.len(lines[cursorY]) + 1 + elseif lX == "l>" and cursorY < #lines then + cursorY = cursorY + 1 + cursorX = 1 + elseif lX == "w<" and cursorY ~= 1 then + local l = table.remove(lines, cursorY) + cursorY = cursorY - 1 + cursorX = unicode.len(lines[cursorY]) + 1 + lines[cursorY] = lines[cursorY] .. l + elseif lX == "w>" and cursorY ~= #lines then + local l = table.remove(lines, cursorY) + cursorX = unicode.len(l) + 1 + lines[cursorY] = l .. lines[cursorY] + elseif lX == "nl" then + local line = lines[cursorY] + lines[cursorY] = unicode.sub(line, 1, cursorX - 1) + table.insert(lines, cursorY + 1, unicode.sub(line, cursorX)) + cursorX = 1 + cursorY = cursorY + 1 + end + return true end flush = function () @@ -353,16 +293,9 @@ flush = function () end end -neo.scheduleTimer(os.uptime() + 0.5) - while true do local e = {coroutine.yield()} - if e[1] == "k.timer" and e[2] == focFlag then - cFlash = not cFlash - local csY = math.ceil(sH / 2) - window.span(1, csY, getline(csY), 0xFFFFFF, 0) - focFlag = neo.scheduleTimer(os.uptime() + 0.5) - elseif e[1] == "x.neo.pub.window" then + if e[1] == "x.neo.pub.window" then if e[2] == window.id then if e[3] == "line" then window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0) @@ -373,14 +306,13 @@ while true do local csY = math.ceil(sH / 2) local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY)) cursorY = nY - clampCursorX() + cursorX = lineEdit.clamp(lines[cursorY], cursorX) flush() elseif e[3] == "key" then - if key(e[4], e[5], e[6]) then + if key(e[4] ~= 0 and unicode.char(e[4]), e[5], e[6]) then flush() end elseif e[3] == "focus" then - focFlag = e[4] and neo.scheduleTimer(0) ctrlFlag = false elseif e[3] == "close" then return @@ -392,7 +324,7 @@ while true do if c == "\n" then c = "\r" end - putLetter(c) + key(c, 0, true) end end flush() diff --git a/code/apps/svc-t.lua b/code/apps/svc-t.lua new file mode 100644 index 0000000..3baa3db --- /dev/null +++ b/code/apps/svc-t.lua @@ -0,0 +1,491 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- svc-t.lua : terminal + +local _, _, retTbl, title = ... + +assert(retTbl, "need to alert creator") + +if title ~= nil then + assert(type(title) == "string", "title must be string") +end + +local function rW() + return string.format("%04x", math.random(0, 65535)) +end + +local id = "neo.pub.t/" .. rW() .. rW() .. rW() .. rW() +local closeNow = false + +-- Terminus Registration State -- + +local tReg = neo.requireAccess("r." .. id, "registration") +local sendSigs = {} + +-- Display State -- +-- unicode.safeTextFormat'd lines. +-- The size of this must not go below 1. +local console = {} +-- This must not go below 3. +local conW = 40 +local conCX, conCY = 1, 1 +local conSCX, conSCY = 1, 1 +-- Performance +local consoleShown = {} +local conCYShown +for i = 1, 14 do + console[i] = (" "):rep(conW) +end + +-- Line Editing State -- +-- Nil if line editing is off. +-- In this case, the console height +-- must be adjusted accordingly. +local leText = "" +-- These are NOT nil'd out, +-- particularly not the history buffer. +local leCX = 1 +local leHistory = { + -- Size = history buffer size + "", "", "", "" +} +local function cycleHistoryUp() + local backupFirst = leHistory[1] + for i = 1, #leHistory - 1 do + leHistory[i] = leHistory[i + 1] + end + leHistory[#leHistory] = backupFirst +end +local function cycleHistoryDown() + local backup = leHistory[1] + for i = 2, #leHistory do + backup, leHistory[i] = leHistory[i], backup + end + leHistory[1] = backup +end + +-- Window -- + +local window = neo.requireAccess("x.neo.pub.window", "window")(conW, #console + 1, title) + +-- Core Terminal Functions -- + +local function setSize(w, h) + conW = w + for i = 1, #console do + consoleShown[i] = nil + end + while #console < h do + table.insert(console, "") + end + while #console > h do + table.remove(console, 1) + end + for i = 1, #console do + console[i] = unicode.sub(console[i], 1, w) .. (" "):rep(w - unicode.len(console[i])) + end + if leText then + window.setSize(w, h + 1) + else + window.setSize(w, h) + end + conCX, conCY = 1, h +end + +local function setLineEditing(state) + if state and not leText then + leText = "" + leCX = 1 + setSize(conW, #console) + elseif leText and not state then + leText = nil + setSize(conW, #console) + end +end + +local function draw(i) + if console[i] then + window.span(1, i, console[i], 0, 0xFFFFFF) + if i == conCY and not leText then + window.span(conCX, i, unicode.sub(console[i], conCX, conCX), 0xFFFFFF, 0) + end + elseif leText then + window.span(1, i, require("lineedit").draw(conW, unicode.safeTextFormat(leText, leCX)), 0xFFFFFF, 0) + end +end +local function drawDisplay() + for i = 1, #console do + if consoleShown[i] ~= console[i] or i == conCY or i == conCYShown then + draw(i) + consoleShown[i] = console[i] + end + end + conCYShown = conCY +end + +-- Terminal Visual -- + +local function consoleSD() + for i = 1, #console - 1 do + console[i] = console[i + 1] + end + console[#console] = (" "):rep(conW) +end +local function consoleSU() + local backup = (" "):rep(conW) + for i = 1, #console do + backup, console[i] = console[i], backup + end +end + +local function consoleCLS() + for i = 1, #console do + console[i] = (" "):rep(conW) + end + conCX, conCY = 1, 1 +end + +local function writeFF() + if conCY ~= #console then + conCY = conCY + 1 + else + consoleSD() + end +end + +local function writeData(data) + -- handle data until completion + while #data > 0 do + local char = unicode.sub(data, 1, 1) + --neo.emergency("svc-t.data: " .. char:byte()) + data = unicode.sub(data, 2) + -- handle character + if char == "\t" then + -- not ideal, but allowed + char = " " + end + if char == "\r" then + conCX = 1 + elseif char == "\x00" then + -- caused by TELNET \r rules + elseif char == "\n" then + conCX = 1 + writeFF() + elseif char == "\a" then + -- Bell (er...) + elseif char == "\b" then + conCX = math.max(1, conCX - 1) + elseif char == "\v" or char == "\f" then + writeFF() + else + local charL = unicode.wlen(char) + if (conCX + charL - 1) > conW then + conCX = 1 + writeFF() + end + local spaces = (" "):rep(charL - 1) + console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. char .. spaces .. unicode.sub(console[conCY], conCX + charL) + conCX = conCX + charL + -- Cursor can be (intentionally!) off-screen here + end + end +end + +local function writeANSI(s) + --neo.emergency("svc-t.ansi: " .. s) + -- This supports just about enough to get by. + if s == "c" then + consoleCLS() + return + end + local pfx = s:sub(1, 1) + local cmd = s:sub(#s) + if pfx == "[" then + local np = tonumber(s:sub(2, -2)) or 1 + if cmd == "A" then + conCY = conCY - np + elseif cmd == "B" then + conCY = conCY + np + elseif cmd == "C" then + conCX = conCX + np + elseif cmd == "D" then + conCX = conCX - np + elseif cmd == "f" or cmd == "H" then + local p = s:find(";") + if not p then + conCY = np + conCX = 1 + else + conCY = tonumber(s:sub(2, p - 1)) or 1 + conCX = tonumber(s:sub(p + 1, -2)) or 1 + end + elseif cmd == "J" then + consoleCLS() + elseif cmd == "K" then + if s == "[K" or s == "[0K" then + -- bash needs this + console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. (" "):rep(1 + conW - conCX) + else + console[conCY] = (" "):rep(conW) + end + elseif cmd == "n" then + if s == "[6n" then + for _, v in pairs(sendSigs) do + v("data", "\x1b[" .. conY .. ";" .. conX .. "R") + end + end + elseif cmd == "s" then + conSCX, conSCY = conCX, conCY + elseif cmd == "u" then + conCX, conCY = conSCX, conSCY + end + end + conCX = math.min(math.max(math.floor(conCX), 1), conW) + conCY = math.min(math.max(math.floor(conCY), 1), #console) +end + +-- The Terminus -- + +local tvBuildingCmd = "" +local tvBuildingUTF = "" +local tvSubnegotiation = false +local function incoming(s) + tvBuildingCmd = tvBuildingCmd .. s + -- Flush Cmd + while #tvBuildingCmd > 0 do + if tvBuildingCmd:byte() == 255 then + -- It's a command. Uhoh. + if #tvBuildingCmd < 2 then break end + local cmd = tvBuildingCmd:byte(2) + local param = tvBuildingCmd:byte(3) + local cmdLen = 2 + -- Command Lengths + if cmd >= 251 and cmd <= 254 then cmdLen = 3 end + if #tvBuildingCmd < cmdLen then break end + if cmd == 240 then + -- SE + tvSubnegotiation = false + elseif cmd == 250 then + -- SB + tvSubnegotiation = true + elseif cmd == 251 and param == 1 then + -- WILL ECHO (respond with DO ECHO, disable line editing) + -- test using io.write("\xFF\xFB\x01") + for _, v in pairs(sendSigs) do + v("telnet", "\xFF\xFD\x01") + end + setLineEditing(false) + elseif cmd == 252 and param == 1 then + -- WON'T ECHO (respond with DON'T ECHO, enable line editing) + for _, v in pairs(sendSigs) do + v("telnet", "\xFF\xFE\x01") + end + setLineEditing(true) + elseif cmd == 251 or cmd == 252 then + -- WILL/WON'T (x) (respond with DON'T (X)) + local res = "\xFF\xFE" .. string.char(param) + for _, v in pairs(sendSigs) do + v("telnet", res) + end + elseif cmd == 253 or cmd == 254 then + -- DO/DON'T (x) (respond with WON'T (X)) + local res = "\xFF\xFC" .. string.char(param) + for _, v in pairs(sendSigs) do + v("telnet", res) + end + elseif cmd == 255 then + if not tvSubnegotiation then + tvBuildingUTF = tvBuildingUTF .. "\xFF" + end + end + tvBuildingCmd = tvBuildingCmd:sub(cmdLen + 1) + else + if not tvSubnegotiation then + tvBuildingUTF = tvBuildingUTF .. tvBuildingCmd:sub(1, 1) + end + tvBuildingCmd = tvBuildingCmd:sub(2) + end + end + -- Flush UTF/Display + while #tvBuildingUTF > 0 do + local head = tvBuildingUTF:byte() + local len = 1 + local handled = false + if head == 27 then + local h2 = tvBuildingUTF:byte(2) + if h2 == 91 then + for i = 3, #tvBuildingUTF do + local cmd = tvBuildingUTF:byte(i) + if cmd >= 0x40 and cmd <= 0x7E then + writeANSI(tvBuildingUTF:sub(2, i)) + len = i + handled = true + break + end + end + elseif h2 then + len = 2 + writeANSI(tvBuildingUTF:sub(2, 2)) + handled = true + end + if not handled then break end + end + if not handled then + if head < 192 then + len = 1 + elseif head < 224 then + len = 2 + elseif head < 240 then + len = 3 + elseif head < 248 then + len = 4 + elseif head < 252 then + len = 5 + elseif head < 254 then + len = 6 + end + if #tvBuildingUTF < len then + break + end + -- verified one full character... + writeData(tvBuildingUTF:sub(1, len)) + end + tvBuildingUTF = tvBuildingUTF:sub(len + 1) + end +end + +do + tReg(function (_, pid, sendSig) + sendSigs[pid] = sendSig + return { + id = "x." .. id, + pid = neo.pid, + write = function (text) + incoming(tostring(text)) + drawDisplay() + end + } + end, true) + + if retTbl then + coroutine.resume(coroutine.create(retTbl), { + access = "x." .. id, + close = function () + closeNow = true + neo.scheduleTimer(0) + end + }) + end +end + +local control = false + +local function key(a, c) + if control then + if c == 203 and conW > 8 then + setSize(conW - 1, #console) + return + elseif c == 200 and #console > 1 then + setSize(conW, #console - 1) + return + elseif c == 205 then + setSize(conW + 1, #console) + return + elseif c == 208 then + setSize(conW, #console + 1) + return + end + end + -- so with the reserved ones dealt with... + if not leText then + -- Line Editing not active. + -- For now support a bare minimum. + for _, v in pairs(sendSigs) do + if a == "\x03" then + v("telnet", "\xFF\xF4") + elseif c == 199 then + v("data", "\x1b[H") + elseif c == 201 then + v("data", "\x1b[5~") + elseif c == 207 then + v("data", "\x1b[F") + elseif c == 209 then + v("data", "\x1b[6~") + elseif c == 203 then + v("data", "\x1b[D") + elseif c == 205 then + v("data", "\x1b[C") + elseif c == 200 then + v("data", "\x1b[A") + elseif c == 208 then + v("data", "\x1b[B") + elseif a == "\r" then + v("data", "\r\n") + elseif a then + v("data", a) + end + end + elseif not control then + -- Line Editing active and control isn't involved + if c == 200 or c == 208 then + -- History cursor up (history down) + leText = leHistory[#leHistory] + leCX = unicode.len(leText) + 1 + if c == 208 then + cycleHistoryUp() + else + cycleHistoryDown() + end + return + end + local lT, lC, lX = require("lineedit").key(a, c, leText, leCX) + leText = lT or leText + leCX = lC or leCX + if lX == "nl" then + cycleHistoryUp() + leHistory[#leHistory] = leText + -- the whole thing { + local fullText = leText .. "\r\n" + writeData(fullText) + drawDisplay() + for _, v in pairs(sendSigs) do + v("data", fullText) + end + -- } + leText = "" + leCX = 1 + end + end +end + +while not closeNow do + local e = {coroutine.yield()} + if e[1] == "k.procdie" then + sendSigs[e[3]] = nil + elseif e[1] == "x.neo.pub.window" then + if e[3] == "close" then + break + elseif e[3] == "clipboard" then + for i = 1, unicode.len(e[4]) do + local c = unicode.sub(e[4], i, i) + if c ~= "\r" then + if c == "\n" then + c = "\r" + end + key(c, 0) + end + end + draw(#console + 1) + elseif e[3] == "key" then + if e[5] == 29 or e[5] == 157 then + control = e[6] + elseif e[6] then + key(e[4] ~= 0 and unicode.char(e[4]), e[5]) + draw(#console + 1) + end + elseif e[3] == "line" then + draw(e[4]) + end + end +end diff --git a/code/apps/sys-everest.lua b/code/apps/sys-everest.lua index 5c100e3..884a4b8 100644 --- a/code/apps/sys-everest.lua +++ b/code/apps/sys-everest.lua @@ -600,65 +600,55 @@ end local isAltDown = false local isCtrDown = false local function key(ku, ka, kc, down) - local ku = screens.getMonitorByKeyboard(ku) - if not ku then return end + local ku, lin = screens.getMonitorByKeyboard(ku) for k, v in ipairs(monitors) do - if v[2] == mu then - lIM = k + if ku and v[2] == ku then + lin = k + break end end + if not lin then return end + lIM = lin + local focus = surfaces[1] - if kc == 29 then isCtrDown = down end - if kc == 56 then isAltDown = down end - if isAltDown then - if ka == 120 then - if focus and down then ofsMSurface(focus, 1) end return - end - if kc == 200 then - if focus and down then ofsSurface(focus, 0, -1) end return - end - if kc == 208 then - if focus and down then ofsSurface(focus, 0, 1) end return - end - if kc == 203 then - if focus and down then ofsSurface(focus, -1, 0) end return - end - if kc == 205 then - if focus and down then ofsSurface(focus, 1, 0) end return - end - if ka == 122 then - if focus and down then - local n = table.remove(surfaces, 1) - table.insert(surfaces, n) - changeFocus(n) - end return - end - if ka == 97 then - if not down then - isAltDown = false - end - return - end - if ka == 3 or ka == 99 then - if down then - if isCtrDown then - error("User-authorized Everest crash.") - else - if focus then - focus[6](focus[8], "close") - end - end - end - return - end - if ka == 13 then - if down then - startLauncher() - end - return - end + if kc == 29 then + isCtrDown = down + elseif kc == 56 then + isAltDown = down end - if focus then + if isAltDown and ka == 122 then + if focus and down then + local n = table.remove(surfaces, 1) + table.insert(surfaces, n) + changeFocus(n) + end + elseif isAltDown and kc == 200 then + if focus and down then ofsSurface(focus, 0, -1) end + elseif isAltDown and kc == 208 then + if focus and down then ofsSurface(focus, 0, 1) end + elseif isAltDown and kc == 203 then + if focus and down then ofsSurface(focus, -1, 0) end + elseif isAltDown and kc == 205 then + if focus and down then ofsSurface(focus, 1, 0) end + elseif isAltDown and ka == 120 then + if focus and down then ofsMSurface(focus, 1) end + elseif isAltDown and ka == 97 then + if not down then + isAltDown = false + end + elseif isAltDown and (ka == 3 or ka == 99) then + if down then + if isCtrDown then + error("User-authorized Everest crash.") + elseif focus then + focus[6](focus[8], "close") + end + end + elseif isAltDown and ka == 13 then + if down then + startLauncher() + end + elseif focus then if kc ~= 56 then lIM = focus[1] end diff --git a/code/apps/sys-icecap.lua b/code/apps/sys-icecap.lua index 43dc924..e767aef 100644 --- a/code/apps/sys-icecap.lua +++ b/code/apps/sys-icecap.lua @@ -81,20 +81,12 @@ local function getPfx(xd, pkg) end end -local endAcPattern = "/[a-z0-9/%.]*$" - -local function matchesSvc(xd, pkg, perm) - local pfx = getPfx(xd, pkg) - if pfx then - local permAct = perm - local paP = permAct:match(endAcPattern) - if paP then - permAct = permAct:sub(1, #permAct - #paP) - end - if permAct == pfx then - return "allow" - end +local function splitAC(ac) + local sb = ac:match("/[a-z0-9/%.]*$") + if sb then + return ac:sub(1, #ac - #sb), sb end + return ac end donkonitDFProvider(function (pkg, pid, sendSig) @@ -130,7 +122,8 @@ donkonitDFProvider(function (pkg, pid, sendSig) myApi = getPfx("", pkg), lockPerm = function (perm) -- Are we allowed to? - if not matchesSvc("x.", pkg, perm) then + local permPfx, detail = splitAC(perm) + if getPfx("x.", pkg) ~= permPfx then return false, "You don't own this permission." end local set = "perm|*|" .. perm @@ -223,7 +216,11 @@ local function secPolicyStage2(pid, proc, perm, req) -- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context neo.scheduleTimer(0) table.insert(todo, function () - local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, perm, req, matchesSvc) + local fPerm = perm + if fPerm:sub(1, 2) == "r." then + fPerm = splitAC(fPerm) + end + local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, fPerm, req, getPfx("", proc.pkg)) if not ok then neo.emergency("Used fallback policy because of run-err: " .. err) req(def) @@ -243,11 +240,7 @@ rootAccess.securityPolicy = function (pid, proc, perm, req) end -- Do we need to start it? if perm:sub(1, 6) == "x.svc." and not neo.usAccessExists(perm) then - local appAct = perm:sub(7) - local paP = appAct:match(endAcPattern) - if paP then - appAct = appAct:sub(1, #appAct - #paP) - end + local appAct = splitAC(perm:sub(3)) -- Prepare for success onReg[perm] = onReg[perm] or {} local orp = onReg[perm] diff --git a/code/docs/logo.bmp b/code/docs/logo.bmp index 3802fb1..9365026 100644 Binary files a/code/docs/logo.bmp and b/code/docs/logo.bmp differ diff --git a/code/init.lua b/code/init.lua index 405c7b2..1dd47a2 100644 --- a/code/init.lua +++ b/code/init.lua @@ -459,7 +459,7 @@ function retrieveAccess(perm, pkg, pid) accesses[uid] = function (pkg, pid) return nil end - return function (f) + return function (f, secret) -- Registration function ensureType(f, "function") local accessObjectCache = {} @@ -481,8 +481,10 @@ function retrieveAccess(perm, pkg, pid) end -- returns nil and fails end - -- Announce registration - distEvent(nil, "k.registration", uid) + if not secret then + -- Announce registration + distEvent(nil, "k.registration", uid) + end end, function () -- Registration becomes null (access is held but other processes cannot retrieve object) if accesses[uid] then diff --git a/code/libs/lineedit.lua b/code/libs/lineedit.lua new file mode 100644 index 0000000..6b27d11 --- /dev/null +++ b/code/libs/lineedit.lua @@ -0,0 +1,73 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +return { + -- note: everything must already be unicode.safeTextFormat'd + draw = function (sW, line, cursorX, rX) + -- if no camera, provide a default + rX = rX or math.max(1, (cursorX or 1) - math.floor(sW * 2 / 3)) + -- transform into area-relative + local tl = unicode.sub(line, rX, rX + sW - 1) + -- inject cursor + if cursorX then + cursorX = (cursorX - rX) + 1 + if cursorX >= 1 then + if cursorX <= sW then + tl = unicode.sub(tl, 1, cursorX - 1) .. "_" .. unicode.sub(tl, cursorX + 1) + end + end + end + return tl .. (" "):rep(sW - unicode.len(tl)) + end, + clamp = function (tl, cursorX) + tl = unicode.len(tl) + if tl < cursorX - 1 then + return tl + 1 + end + return cursorX + end, + -- returns line, cursorX, special + -- return values may be nil if irrelevant + key = function (ks, kc, line, cursorX) + local cS = unicode.sub(line, 1, cursorX - 1) + local cE = unicode.sub(line, cursorX) + local ll = unicode.len(line) + if kc == 203 then -- navi < + if cursorX > 1 then + return nil, cursorX - 1 + else + -- cline underflow + return nil, nil, "l<" + end + elseif kc == 205 then -- navi > + if cursorX > ll then + -- cline overflow + return nil, nil, "l>" + end + return nil, cursorX + 1 + elseif kc == 199 then -- home + return nil, 1 + elseif kc == 207 then -- end + return nil, unicode.len(line) + 1 + elseif ks == "\8" then -- del + if cursorX == 1 then + -- weld prev + return nil, nil, "w<" + else + return unicode.sub(cS, 1, unicode.len(cS) - 1) .. cE, cursorX - 1 + end + elseif kc == 211 then -- del + if cursorX > ll then + return nil, nil, "w>" + end + return cS .. unicode.sub(cE, 2) + elseif ks then -- standard letters + if ks == "\r" then + -- new line + return nil, nil, "nl" + end + return cS .. ks .. cE, cursorX + unicode.len(ks) + end + -- :( + end +} diff --git a/code/libs/neoux.lua b/code/libs/neoux.lua index 0731de6..28da638 100644 --- a/code/libs/neoux.lua +++ b/code/libs/neoux.lua @@ -339,6 +339,7 @@ newNeoux = function (event, neo) end -- Note: w should be at least 2 - this is similar to buttons. neoux.tcfield = function (x, y, w, textprop) + local p = 1 return { x = x, y = y, @@ -347,22 +348,24 @@ newNeoux = function (event, neo) selectable = true, key = function (window, update, a, c, d, f) if d then + local ot = textprop() + local le = require("lineedit") + p = le.clamp(ot, p) if c == 63 then - neo.requireAccess("x.neo.pub.globals", "clipboard").setSetting("clipboard", textprop()) + neo.requireAccess("x.neo.pub.globals", "clipboard").setSetting("clipboard", ot) elseif c == 64 then local contents = neo.requireAccess("x.neo.pub.globals", "clipboard").getSetting("clipboard") contents = contents:match("^[^\r\n]*") textprop(contents) update() - elseif a == 8 then - local str = textprop() - textprop(unicode.sub(str, 1, unicode.len(str) - 1)) - update() - return true - elseif a >= 32 then - textprop(textprop() .. unicode.char(a)) - update() - return true + elseif a ~= 9 then + local lT, lC, lX = le.key(a ~= 0 and unicode.char(a), c, ot, p) + if lT or lC then + if lT then textprop(lT) end + p = lC or p + update() + return true + end end end end, @@ -377,9 +380,10 @@ newNeoux = function (event, neo) fg = bg bg = fg1 end - local text = unicode.safeTextFormat(textprop()) - text = "[" .. neoux.pad(text, w - 2, false, true, true) .. "]" - window.span(x, y, text, bg, fg) + local t, e, r = textprop(), require("lineedit") + p = e.clamp(t, p) + t, r = unicode.safeTextFormat(t, p) + window.span(x, y, "[" .. e.draw(w - 2, t, selected and r) .. "]", bg, fg) end } end diff --git a/code/libs/sys-filevfs.lua b/code/libs/sys-filevfs.lua index 2ecb42f..3e39aca 100644 --- a/code/libs/sys-filevfs.lua +++ b/code/libs/sys-filevfs.lua @@ -94,11 +94,25 @@ function getFsNode(fs, parent, fsc, path, mode, impliedName) for k, v in ipairs(fsc.list(path)) do local nm = "F: " .. v local fp = path .. v - if fsc.isDirectory(fp) then + local cDir = fsc.isDirectory(fp) + if cDir then nm = "D: " .. v end - n[k + 1] = {nm, function () return nil, getFsNode(fs, t, fsc, fp, mode, impliedName) end} + if (not cDir) and (fscrw or mode == false) and (mode ~= nil) then + local vn = v + n[k + 1] = {nm, function () return selectUnknown(vn) end} + else + n[k + 1] = {nm, function () return nil, getFsNode(fs, t, fsc, fp, mode, impliedName) end} + end end + else + table.insert(n, {"Copy", function () + local rt, re = require("sys-filewrap").create(fsc, path, false) + if not rt then + return false, dialog("Open Error: " .. tostring(re), parent) + end + return nil, setupCopyVirtualEnvironment(fs, parent, rt, path:match("[^/]*$") or "") + end}) end if fscrw then if dir then @@ -151,24 +165,6 @@ function getFsNode(fs, parent, fsc, path, mode, impliedName) end end if not dir then - table.insert(n, {"Copy", function () - local rt, re = require("sys-filewrap").create(fsc, path, false) - if not rt then - return false, dialog("Open Error: " .. tostring(re), parent) - end - return nil, setupCopyVirtualEnvironment(fs, parent, rt, path:match("[^/]*$") or "") - end}) - if (fscrw or mode == false) and (mode ~= nil) then - local tx = "Open" - if mode == true then - tx = "Save (Overwrite)" - elseif mode == "append" then - tx = "Append" - end - if fscrw or mode == false then - table.insert(n, {tx, selectUnknown}) - end - end elseif impliedName then table.insert(n, {"Implied: " .. impliedName, function () return selectUnknown(impliedName) diff --git a/code/libs/sys-secpolicy.lua b/code/libs/sys-secpolicy.lua index 10414d6..5cda471 100644 --- a/code/libs/sys-secpolicy.lua +++ b/code/libs/sys-secpolicy.lua @@ -11,13 +11,19 @@ -- IRC is usually pretty safe, but no guarantees. -- Returns "allow", "deny", or "ask". -local function actualPolicy(pkg, pid, perm, matchesSvc) +local function actualPolicy(pkg, pid, perm, pkgSvcPfx) -- System stuff is allowed. if pkg:sub(1, 4) == "sys-" then return "allow" end + -- svc-t's job is solely to emulate terminals + -- TO INSTALL YOUR OWN TERMINAL EMULATOR: + -- perm|app-yourterm|r.neo.t + if pkg == "svc-t" and perm == "r.neo.pub.t" then + return "allow" + end -- - -- x.neo.pub (aka Icecap) is open to all + -- x.neo.pub.* is open to all if perm:sub(1, 10) == "x.neo.pub." then return "allow" end @@ -25,7 +31,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc) if perm == "s.h.component_added" or perm == "s.h.component_removed" or perm == "s.h.tablet_use" or perm == "c.tablet" then return "allow" end - if matchesSvc("r.", pkg, perm) then + -- Userlevel can register for itself + if perm == "r." .. pkgSvcPfx then return "allow" end -- Userlevel has no other registration rights @@ -44,8 +51,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc) return "ask" end -return function (nexus, settings, pkg, pid, perm, rsp, matchesSvc) - local res = actualPolicy(pkg, pid, perm, matchesSvc) +return function (nexus, settings, pkg, pid, perm, rsp, pkgSvcPfx) + local res = actualPolicy(pkg, pid, perm, pkgSvcPfx) if settings then res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or settings.getSetting("perm|*|" .. perm) or res diff --git a/com2/bdivide.lua b/com2/bdivide.lua deleted file mode 100644 index 54b40a5..0000000 --- a/com2/bdivide.lua +++ /dev/null @@ -1,49 +0,0 @@ --- This is released into the public domain. --- No warranty is provided, implied or otherwise. - --- BDIVIDE r5 edition --- Algorithm simplified for smaller implementation and potentially better compression --- format: --- 0-127 for constants --- <128 + (length - 4)>, , --- Position is where in the window it was found, minus 1. --- windowSize must be the same between the encoder and decoder, --- and is the amount of data preserved after cropping. - -io.write("\x00") -- initiation character - -local blk = io.read("*a") -local windowSize = 0x10000 -local windowData = ("\x00"):rep(windowSize) - -local function crop(data) - windowData = (windowData .. data):sub(-windowSize) -end - -while blk ~= "" do - local bestData = blk:sub(1, 1) - local bestRes = bestData - for lm = 0, 127 do - local al = lm + 4 - local pfx = blk:sub(1, al) - if #pfx ~= al then - break - end - local p = windowData:find(pfx, 1, true) - if not p then - break - end - local pm = p - 1 - local thirdByte = pm % 256 - -- anti ']'-corruption helper - if thirdByte ~= 93 then - bestData = string.char(128 + lm, math.floor(pm / 256), thirdByte) - bestRes = pfx - end - end - -- ok, encode! - io.write(bestData) - crop(bestRes) - blk = blk:sub(#bestRes + 1) -end - diff --git a/com2/bonecrunch.lua b/com2/bonecrunch.lua deleted file mode 100644 index ed49707..0000000 --- a/com2/bonecrunch.lua +++ /dev/null @@ -1,88 +0,0 @@ --- This is released into the public domain. --- No warranty is provided, implied or otherwise. - --- This program tries to crunch down the installer a bit further. --- Specific target in mind, it has no support for string escapes. --- It also does this: -for i = 1, 3 do - print(io.read()) -end - -local sequences = { - {"\n", " "}, - {" ", " "}, - {" #", "#"}, - {"# ", "#"}, - {" ,", ","}, - {", ", ","}, - {" (", "("}, - {"( ", "("}, - {" )", ")"}, - {") ", ")"}, - {" <", "<"}, - {"< ", "<"}, - {" >", ">"}, - {"> ", ">"}, - {" *", "*"}, - {"* ", "*"}, - {" ~", "~"}, - {"~ ", "~"}, - {" /", "/"}, - {"/ ", "/"}, - {" %", "%"}, - {"% ", "%"}, - {" =", "="}, - {"= ", "="}, - {" -", "-"}, - {"- ", "-"}, - {" +", "+"}, - {"+ ", "+"}, - {".. ", ".."}, - {" ..", ".."}, - {"\"\" ", "\"\""}, - {"=0 t", "=0t"}, - {">0 t", ">0t"}, - {">1 t", ">1t"}, - {"=1 w", "=1w"}, - {"=380 l", "=380l"}, - {"=127 t", "=127t"}, - {"<128 t", "<128t"}, - {"=128 t", "=128t"}, - {">255 t", ">255t"}, - {"=512 t", "=512t"} -} - -local function pass(buffer) - local ob = "" - local smode = false - while #buffer > 0 do - if not smode then - local ds = true - while ds do - ds = false - for _, v in ipairs(sequences) do - if buffer:sub(1, #(v[1])) == v[1] then - buffer = v[2] .. buffer:sub(#(v[1]) + 1) - ds = true - end - end - end - end - local ch = buffer:sub(1, 1) - buffer = buffer:sub(2) - ob = ob .. ch - if ch == "\"" then - smode = not smode - end - end - return ob -end -local op = io.read("*a") -while true do - local np = pass(op) - if np == op then - io.write(np) - return - end - op = np -end diff --git a/com2/bundiv.lua b/com2/bundiv.lua deleted file mode 100644 index 3e75bb8..0000000 --- a/com2/bundiv.lua +++ /dev/null @@ -1,72 +0,0 @@ --- This is released into the public domain. XX --- No warranty is provided, implied or otherwise. XX -local sector = io.write -- XX --- XX --- BUNDIVIDE (r5 edition) reference implementation for integration XX --- Lines ending with XX are not included in the output. XX --- Lines that both start and end with -- are only for use in the output, XX --- and are thus not executed during any sanity-check procedure. XX --- XX -Cp,Ct,Cc,Cw="","","",("\x00"):rep(65536) --- High-level breakdown: XX --- CP is unescaper & TAR-sector-breakup. XX --- It'll only begin to input if at least 3 bytes are available, XX --- so you'll want to throw in 2 extra zeroes at the end of stream as done here. XX --- It uses Ct (input buffer) and Cp (output buffer). XX --- Ignore its second argument, as that's a lie, it's just there for a local. XX --- CD is the actual decompressor. It has the same quirk as CP, wanting two more bytes. XX --- It stores to Cc (compressed), and Cw (window). XX --- It uses Ca as the "first null" activation flag. XX --- It outputs that which goes to the window to CP also. XX --- And it also uses a fake local. XX -CP = function (d, b) - Ct = Ct .. d - while #Ct > 2 do - b = Ct:byte() - Ct = Ct:sub(2) - if b == 127 then - b = Ct:byte() - Ct = Ct:sub(2) - if b == 127 then - b = Ct:byte() + 254 - if b > 255 then - b = b - 256 - end - Ct = Ct:sub(2) - else - b = b + 127 - end - end - Cp = Cp .. string.char(b) - if #Cp == 512 then - sector(Cp) - Cp = "" - end - end -end --- XX -CD = function (d, b, p) - Cc = Cc .. d - while #Cc > 2 do - b = Cc:byte() - if not Ca then - Ca, b, Cc = b < 1, "", Cc:sub(2) - elseif b < 128 then - b, Cc = Cc:sub(1, 1), Cc:sub(2) - else - p = Cc:byte(2) * 256 + Cc:byte(3) + 1 - b, Cc = Cw:sub(p, p + b - 125), Cc:sub(4) - end - CP(b) - Cw = (Cw .. b):sub(-65536) - end -end --- XX -CD(io.read("*a")) -- XX ---D.remove("init-bdivide.lua")-- ---D.rename("init.lua","init-bdivide.lua")-- ---local Ch=D.open("init-bdivide.lua","rb")-- ---dieCB=function()D.close(Ch)D.remove("init-bdivide.lua")end-- ---while true do local t=D.read(Ch, 64)if not t then break end CD(t)end-- --- XX -CD("\x00\x00")CP("\x00\x00") diff --git a/com2/preproc.lua b/com2/preproc.lua deleted file mode 100644 index 98c8b8a..0000000 --- a/com2/preproc.lua +++ /dev/null @@ -1,20 +0,0 @@ --- PREPROC: preprocess input to be 7-bit --- This is released into the public domain. --- No warranty is provided, implied or otherwise. - -while true do - local c = io.read(1) - if not c then return end - local bc = c:byte() - if bc < 127 then - io.write(c) - else - if bc <= 253 then - -- 127(0) through 253(126) - io.write("\x7F" .. string.char(bc - 127)) - else - -- 254(0) through 255 (1) - io.write("\x7F\x7F" .. string.char(bc - 254)) - end - end -end diff --git a/heroes.lua b/heroes.lua deleted file mode 100644 index 4b65f66..0000000 --- a/heroes.lua +++ /dev/null @@ -1,27 +0,0 @@ --- This is released into the public domain. --- No warranty is provided, implied or otherwise. - --- arg is the size of the code.tar file -local arg = ... -os.execute("lua com2/preproc.lua < code.tar | lua com2/bdivide.lua > com2/code.tar.bd") -os.execute("cat insthead.lua") -print("sC=" .. math.ceil(tonumber(arg) / 512)) -local src = io.open("com2/bundiv.lua", "r") -while true do - local line = src:read() - if not line then - io.flush() - src:close() - return - end - local endix = line:sub(#line-1,#line) - if endix ~= "XX" then - if endix == "--" then - -- included - print(line:sub(3,#line-2)) - else - print(line) - end - end - -- XX means ignored. -end diff --git a/inst/bdivide/bga.lua b/inst/bdivide/bga.lua new file mode 100644 index 0000000..0ecde89 --- /dev/null +++ b/inst/bdivide/bga.lua @@ -0,0 +1,36 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +local bga = {} + +local str = io.read("*a") + +for i = 1, #str - 1 do + local bg = str:sub(i, i + 1) + bga[bg] = (bga[bg] or 0) + 1 +end + +local first = {} +local second = {} + +local mode = ... + +for k, v in pairs(bga) do + if mode == "combined" then + print(string.format("%08i: %02x%02x : %s", v, k:byte(1), k:byte(2), k)) + end + first[k:sub(1, 1)] = (first[k:sub(1, 1)] or 0) + v + second[k:sub(1, 1)] = (second[k:sub(1, 1)] or 0) + v +end + +for k, v in pairs(first) do + if mode == "first" then + print(string.format("%08i: %s", v, k)) + end +end + +for k, v in pairs(second) do + if mode == "second" then + print(string.format("%08i: %s", v, k)) + end +end diff --git a/inst/bdivide/compress.lua b/inst/bdivide/compress.lua new file mode 100644 index 0000000..6a36450 --- /dev/null +++ b/inst/bdivide/compress.lua @@ -0,0 +1,85 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- PREPROC (r9 edition): preprocess input to be 7-bit + +local frw = require("libs.frw") + +local +-- SHARED WITH DECOMPRESSION ENGINE +function p(x, y) + if x == 126 then + return string.char(y), 3 + elseif x == 127 then + return string.char(128 + y), 3 + elseif x >= 32 then + return string.char(x), 2 + elseif x == 31 then + return "\n", 2 + elseif x == 30 then + return "\x00", 2 + end + return string.char(("enart"):byte(x % 5 + 1), ("ndtelh"):byte((x - x % 5) / 5 + 1)), 2 +end + +local preprocParts = {} +local preprocMaxLen = 0 +for i = 0, 127 do + for j = 0, 127 do + local d, l = p(i, j) + if d then + preprocMaxLen = math.max(preprocMaxLen, #d) + l = l - 1 + if (not preprocParts[d]) or (#preprocParts[d] > l) then + if l == 2 then + preprocParts[d] = string.char(i, j) + else + preprocParts[d] = string.char(i) + end + end + end + end +end + +local function preprocWithPadding(blk, p) + local out = "" + local needsPadding = false + while blk ~= "" do + p(blk) + local len = math.min(preprocMaxLen, #blk) + while len > 0 do + local seg = blk:sub(1, len) + if preprocParts[seg] then + out = out .. preprocParts[seg] + needsPadding = #preprocParts[seg] < 2 + blk = blk:sub(#seg + 1) + break + end + len = len - 1 + end + assert(len ~= 0) + end + -- This needsPadding bit is just sort of quickly added in + -- to keep this part properly maintained + -- even though it might never get used + if needsPadding then + return out .. "\x00" + end + return out +end + +local bdCore = require("bdivide.core") + +return function (data, lexCrunch) + io.stderr:write("preproc: ") + local pi = frw.progress() + local function p(b) + pi(1 - (#b / #data)) + end + data = preprocWithPadding(data, p) + io.stderr:write("\nbdivide: ") + pi = frw.progress() + data = bdCore.bdividePad(bdCore.bdivide(data, p)) + io.stderr:write("\n") + return lexCrunch.process(frw.read("bdivide/instdeco.lua"), {}), data +end diff --git a/inst/bdivide/core.lua b/inst/bdivide/core.lua new file mode 100644 index 0000000..b6a18db --- /dev/null +++ b/inst/bdivide/core.lua @@ -0,0 +1,72 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- BDIVIDE r5 edition +-- Algorithm simplified for smaller implementation and potentially better compression +-- format: +-- 0-127 for constants +-- <128 + (length - 4)>, , +-- Position is where in the window it was found, minus 1. +-- windowSize must be the same between the encoder and decoder, +-- and is the amount of data preserved after cropping. + +local bdivCore = {} + +function bdivCore.bdivide(blk, p) + local out = "" + + local windowSize = 0x10000 + local windowData = ("\x00"):rep(windowSize) + + while blk ~= "" do + p(blk) + local bestData = blk:sub(1, 1) + assert(blk:byte() < 128, "BDIVIDE does not handle 8-bit data") + local bestRes = bestData + for lm = 0, 127 do + local al = lm + 4 + local pfx = blk:sub(1, al) + if #pfx ~= al then + break + end + local p = windowData:find(pfx, 1, true) + if not p then + break + end + local pm = p - 1 + local thirdByte = pm % 256 + bestData = string.char(128 + lm, math.floor(pm / 256), thirdByte) + bestRes = pfx + end + -- ok, encode! + out = out .. bestData + -- crop window + windowData = (windowData .. bestRes):sub(-windowSize) + blk = blk:sub(#bestRes + 1) + end + return out +end + +-- Adds padding if required +function bdivCore.bdividePad(data) + local i = 1 + -- Basically, if it ends on a literal, + -- then the literal won't get read without two padding bytes. + -- Otherwise (including if no data) it's fine. + local needsPadding = false + while i <= #data do + if data:byte(i) > 127 then + i = i + 3 + needsPadding = false + else + i = i + 1 + needsPadding = true + end + end + if needsPadding then + return data .. "\x00\x00" + end + return data +end + +return bdivCore diff --git a/inst/bdivide/instdeco.lua b/inst/bdivide/instdeco.lua new file mode 100644 index 0000000..aef893e --- /dev/null +++ b/inst/bdivide/instdeco.lua @@ -0,0 +1,64 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- BDIVIDE (r5 edition) and PREPROC (r9 edition) +-- decompression engine for installer + +$bdPPBuffer = "" +$bdBDBuffer = "" +$bdBDWindow = ("\x00"):rep(2^16) +-- High-level breakdown: +-- q is unescaper. +-- It'll only begin to input if at least 3 bytes are available, +-- so you'll want to throw in 2 extra zeroes at the end of stream as done here. +-- It uses t (input buffer) and p (output buffer). +-- Ignore its second argument, as that's a lie, it's just there for a local. +-- L is the actual decompressor. It has the same quirk as q, wanting two more bytes. +-- It stores to c (compressed), and w (window). +-- It outputs that which goes to the window to q also. +-- And it also uses a fake local. + +-- SEE compress.lua FOR THIS FUNCTION +function $bdPP(x, y) + if x == 126 then + return string.char(y), 3 + elseif x == 127 then + return string.char(128 + y), 3 + elseif x >= 32 then + return string.char(x), 2 + elseif x == 31 then + return "\n", 2 + elseif x == 30 then + return "\x00", 2 + end + return string.char(("enart"):byte(x % 5 + 1), ("ndtelh"):byte((x - x % 5) / 5 + 1)), 2 +end + +${ +function $engineInput($L|lData) + $bdBDBuffer = $bdBDBuffer .. $lData + while #$bdBDBuffer > 2 do + $lData = $bdBDBuffer:byte() + if $lData < 128 then + $lData = $bdBDBuffer:sub(1, 1) + $bdBDBuffer = $bdBDBuffer:sub(2) + else + ${ + $L|bdBDPtr = $bdBDBuffer:byte(2) * 256 + $bdBDBuffer:byte(3) + 1 + $lData = $bdBDWindow:sub($bdBDPtr, $bdBDPtr + $lData - 125) + $bdBDBuffer = $bdBDBuffer:sub(4) + $} + end + $bdPPBuffer = $bdPPBuffer .. $lData + $bdBDWindow = ($bdBDWindow .. $lData):sub(-2^16) + while #$bdPPBuffer > 1 do + ${ + $lData, $L|bdPPAdv = $bdPP($bdPPBuffer:byte(), $bdPPBuffer:byte(2)) + $bdPPBuffer = $bdPPBuffer:sub($bdPPAdv) + $} + $engineOutput($lData) + end + end +end +$} + diff --git a/inst/bdvlite/compress.lua b/inst/bdvlite/compress.lua new file mode 100644 index 0000000..e6a6605 --- /dev/null +++ b/inst/bdvlite/compress.lua @@ -0,0 +1,18 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +local frw = require("libs.frw") + +local bdCore = require("bdivide.core") + +return function (data, lexCrunch) + io.stderr:write("\nbdivide: ") + local pi = frw.progress() + local function p(b) + pi(1 - (#b / #data)) + end + data = bdCore.bdividePad(bdCore.bdivide(data, p)) + io.stderr:write("\n") + return lexCrunch.process(frw.read("bdvlite/instdeco.lua"), {}), data +end + diff --git a/inst/bdvlite/core.lua b/inst/bdvlite/core.lua new file mode 100644 index 0000000..b6a18db --- /dev/null +++ b/inst/bdvlite/core.lua @@ -0,0 +1,72 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- BDIVIDE r5 edition +-- Algorithm simplified for smaller implementation and potentially better compression +-- format: +-- 0-127 for constants +-- <128 + (length - 4)>, , +-- Position is where in the window it was found, minus 1. +-- windowSize must be the same between the encoder and decoder, +-- and is the amount of data preserved after cropping. + +local bdivCore = {} + +function bdivCore.bdivide(blk, p) + local out = "" + + local windowSize = 0x10000 + local windowData = ("\x00"):rep(windowSize) + + while blk ~= "" do + p(blk) + local bestData = blk:sub(1, 1) + assert(blk:byte() < 128, "BDIVIDE does not handle 8-bit data") + local bestRes = bestData + for lm = 0, 127 do + local al = lm + 4 + local pfx = blk:sub(1, al) + if #pfx ~= al then + break + end + local p = windowData:find(pfx, 1, true) + if not p then + break + end + local pm = p - 1 + local thirdByte = pm % 256 + bestData = string.char(128 + lm, math.floor(pm / 256), thirdByte) + bestRes = pfx + end + -- ok, encode! + out = out .. bestData + -- crop window + windowData = (windowData .. bestRes):sub(-windowSize) + blk = blk:sub(#bestRes + 1) + end + return out +end + +-- Adds padding if required +function bdivCore.bdividePad(data) + local i = 1 + -- Basically, if it ends on a literal, + -- then the literal won't get read without two padding bytes. + -- Otherwise (including if no data) it's fine. + local needsPadding = false + while i <= #data do + if data:byte(i) > 127 then + i = i + 3 + needsPadding = false + else + i = i + 1 + needsPadding = true + end + end + if needsPadding then + return data .. "\x00\x00" + end + return data +end + +return bdivCore diff --git a/inst/bdvlite/instdeco.lua b/inst/bdvlite/instdeco.lua new file mode 100644 index 0000000..3feaee8 --- /dev/null +++ b/inst/bdvlite/instdeco.lua @@ -0,0 +1,30 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- BDIVIDE (r5 edition) +-- decompression engine used to decompress DEFLATE decompression engine + +$bdBDBuffer = "" +$bdBDWindow = ("\x00"):rep(2^16) + +${ +function $engineInput($L|lData) + $bdBDBuffer = $bdBDBuffer .. $lData + while #$bdBDBuffer > 2 do + $lData = $bdBDBuffer:byte() + if $lData < 128 then + $lData = $bdBDBuffer:sub(1, 1) + $bdBDBuffer = $bdBDBuffer:sub(2) + else + ${ + $L|bdBDPtr = $bdBDBuffer:byte(2) * 256 + $bdBDBuffer:byte(3) + 1 + $lData = $bdBDWindow:sub($bdBDPtr, $bdBDPtr + $lData - 125) + $bdBDBuffer = $bdBDBuffer:sub(4) + $} + end + $bdBDWindow = ($bdBDWindow .. $lData):sub(-2^16) + $engineOutput($lData) + end +end +$} + diff --git a/inst/build.lua b/inst/build.lua new file mode 100644 index 0000000..f1491ae --- /dev/null +++ b/inst/build.lua @@ -0,0 +1,78 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- KittenOS NEO Installer Generator -- +local args = {...} + +local cid = args[1] +local tarName = args[2] +local algorithmsInReverseOrder = {} +for i = 3, #args do + table.insert(algorithmsInReverseOrder, 1, args[i]) +end + +local u = require("libs.frw") + +local instSize = 0 +local function put(data) + io.write(data) + instSize = instSize + #data +end + +-- Installer Lexcrunch Context -- +local lexCrunch = require("libs.lexcrunch")() + +-- Installer Core -- + +-- installerFinalized: +-- Stuff that's already finished and put at the end of RISM. Prepend to this. +-- installerPayload / installerProgramLength: +-- The next-outer chunk that hasn't been written to the end of RISM +-- as the compression scheme (if one) has not been applied yet. +-- Really, installerProgramLength is only necessary because of the innermost chunk, +-- as that chunk has the TAR; additional data that's part of the same effective compression block, +-- but requires the program length to avoid it. +local installerPayload +local installerProgramLength +local installerFinalized = "" + +do + local tarData = u.read(tarName) + local tarSectors = math.floor(#tarData / 512) + local installerCore = lexCrunch.process(u.read("instcore.lua"), {["$$SECTORS"] = tostring(tarSectors)}) + installerPayload = installerCore .. tarData + installerProgramLength = #installerCore +end + +-- Installer Compression -- +for _, v in ipairs(algorithmsInReverseOrder) do + io.stderr:write("compressing (" .. v .. ")\n") + local algImpl = require(v .. ".compress") + local algEngine, algData = algImpl(installerPayload, lexCrunch) + io.stderr:write("result: " .. #installerPayload .. " -> " .. #algData .. "\n") + -- prepend the program length of the last section + algEngine = lexCrunch.process("$iBlockingLen = " .. installerProgramLength .. " " .. algEngine, {}) + -- commit + installerPayload = algEngine + installerProgramLength = #installerPayload + installerFinalized = algData .. installerFinalized +end + +-- Installer Final -- + +-- This is a special case, so the program length/payload/etc. business has to be repeated. +put("--" .. cid .. "\n") +put("--This is released into the public domain. No warranty is provided, implied or otherwise.\n") +put(lexCrunch.process(u.read("insthead.lua"), {["$$CORESIZE"] = tostring(installerProgramLength)})) + +local RISM = installerPayload .. installerFinalized +RISM = RISM:gsub("\xFE", "\xFE\xFE") +RISM = RISM:gsub("]]", "]\xFE]") +RISM = "\x00" .. RISM +put("--[[" .. RISM .. "]]") + +-- Dumping debug info -- +local dbg = io.open("iSymTab", "wb") +lexCrunch.dump(dbg) +dbg:close() + diff --git a/inst/deflate/compress.lua b/inst/deflate/compress.lua new file mode 100644 index 0000000..2b6bce9 --- /dev/null +++ b/inst/deflate/compress.lua @@ -0,0 +1,14 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- This is a wrapper around (i.e. does not contain) Zopfli. +local frw = require("libs.frw") + +return function (data, lexCrunch) + frw.write("tempData.bin", data) + os.execute("zopfli --i1 --deflate -c tempData.bin > tempZopf.bin") + local res = frw.read("tempZopf.bin") + os.execute("rm tempData.bin tempZopf.bin") + return lexCrunch.process(frw.read("deflate/instdeco.lua"), {}), res +end + diff --git a/inst/deflate/instdeco.lua b/inst/deflate/instdeco.lua new file mode 100644 index 0000000..43c17d3 --- /dev/null +++ b/inst/deflate/instdeco.lua @@ -0,0 +1,287 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- THIS NEXT LINE IS CLEARLY AWFUL +$bdBDWindow = nil + +-- THE DEFLATE DECOMPRESSOR OF MADNESS -- + +-- Core I/O -- + +-- $dfAlignToByteRemaining is +-- set to 8 in the outer engine + +${ +function $dfGetIntField($L|lLen, $L|lVal) + $lVal = 0 + for $L|lI = 0, $lLen - 1 do + if coroutine.yield() then + $lVal = $lVal + 2^$lI + end + end + return $lVal +end +$} + +-- Huffman Core -- +-- The approach here is based around 1-prefixed integers. +-- These are stored in a flat table. +-- So 0b1000 is the pattern 000. + +${ +function $dfReadHuffmanSymbol($L|lTree, $L|lVal) + $lVal = 1 + while not $lTree[$lVal] do + $lVal = ($lVal * 2) + $dfGetIntField(1) + end + return $lTree[$lVal] +end +$} + +${ +function $dfGenHuffmanTree($L|lCodeLens) + -- $L|lI (used everywhere; defining inside creates a bug because it gets globalized) + $L|lNextCode = {} + ${ + -- To explain: + -- lNextCode is needed all the way to the end. + -- But lBLCount isn't needed after it's used to + -- generate lNextCode. + -- And lCode is very, very temporary. + -- Hence this massive block. + $L|lBLCount = {} + -- Note the 0 + for $lI = 0, 15 do + $lBLCount[$lI] = 0 + end + for $lI = 0, #$lCodeLens do + ${ + $L|lCodeLen = $lCodeLens[$lI] + if $lCodeLen ~= 0 then + $lBLCount[$lCodeLen] = $lBLCount[$lCodeLen] + 1 + end + $} + end + + $L|lCode = 0 + for $lI = 1, 15 do + $lCode = ($lCode + $lBLCount[$lI - 1]) * 2 + $lNextCode[$lI] = $lCode + end + $} + + $L|lTree = {} + for $lI = 0, #$lCodeLens do + ${ + $L|lCodeLen = $lCodeLens[$lI] + if $lCodeLen ~= 0 then + $L|lPow = math.floor(2 ^ $lCodeLen) + assert($lNextCode[$lCodeLen] < $lPow, "Tl" .. $lCodeLen) + $L|lK = $lNextCode[$lCodeLen] + $lPow + assert(not $lTree[$lK], "conflict @ " .. $lK) + $lTree[$lK] = $lI + $lNextCode[$lCodeLen] = $lNextCode[$lCodeLen] + 1 + end + $} + end + return $lTree +end +$} + +-- DEFLATE fixed trees -- +${ +$L|dfFixedTL = {} +for $L|lI = 0, 143 do $dfFixedTL[$lI] = 8 end +for $lI = 144, 255 do $dfFixedTL[$lI] = 9 end +for $lI = 256, 279 do $dfFixedTL[$lI] = 7 end +for $lI = 280, 287 do $dfFixedTL[$lI] = 8 end +$dfFixedLit = $dfGenHuffmanTree($dfFixedTL) +-- last line possibly destroyed dfFixedTL, but that's alright +$dfFixedTL = {} +for $lI = 0, 31 do $dfFixedTL[$lI] = 5 end +$dfFixedDst = $dfGenHuffmanTree($dfFixedTL) +$} + +-- DEFLATE LZ Core -- + +$dfWindow = ("\x00"):rep(2^15) +$dfPushBuf = "" +${ +function $dfOutput($L|lData) + $dfWindow = ($dfWindow .. $lData):sub(-2^15) + $dfPushBuf = $dfPushBuf .. $lData +end +$} + +$dfBittblLength = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, + 5, 5, 5, 5, 0 +} +$dfBasetblLength = { + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 258 +} +$dfBittblDist = { + 0, 0, 0, 0, 1, 1, 2, 2, + 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +} +$dfBasetblDist = { + 1, 2, 3, 4, 5, 7, 9, 13, + 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577 +} + +${ +function $dfReadBlockBody($L|lLit, $L|lDst, $L|lLitSym, $L|lLen, $L|lDCode, $L|lPtr) + while true do + $lLitSym = $dfReadHuffmanSymbol($lLit) + if $lLitSym <= 255 then + $dfOutput(string.char($lLitSym)) + elseif $lLitSym == 256 then + return + elseif $lLitSym <= 285 then + $lLen = $dfBasetblLength[$lLitSym - 256] + $dfGetIntField($dfBittblLength[$lLitSym - 256]) + $lDCode = $dfReadHuffmanSymbol($lDst) + $lPtr = 32769 - ($dfBasetblDist[$lDCode + 1] + $dfGetIntField($dfBittblDist[$lDCode + 1])) + for $L|lI = 1, $lLen do + $dfOutput($dfWindow:sub($lPtr, $lPtr)) + end + else + error("nt" .. v) + end + end +end +$} + +-- Huffman Dynamics -- + +${ +function $dfReadHuffmanDynamicSubcodes($L|lDistLens, $L|lCount, $L|lMetatree, $L|lCode) + -- used a lot: $L|lI + $lCode = 0 + $lDistLens[-1] = 0 + while $lCode < $lCount do + -- use a tmpglb since it's just for the chain + $L|lInstr = $dfReadHuffmanSymbol($lMetatree) + if $lInstr < 16 then + $lDistLens[$lCode] = $lInstr + $lCode = $lCode + 1 + elseif $lInstr == 16 then + for $lI = 1, 3 + $dfGetIntField(2) do + $lDistLens[$lCode] = $lDistLens[$lCode - 1] + $lCode = $lCode + 1 + assert($lCode <= $lCount, "sc.over") + end + elseif $lInstr == 17 then + for $lI = 1, 3 + $dfGetIntField(3) do + $lDistLens[$lCode] = 0 + $lCode = $lCode + 1 + assert($lCode <= $lCount, "sc.over") + end + elseif $lInstr == 18 then + for $lI = 1, 11 + $dfGetIntField(7) do + $lDistLens[$lCode] = 0 + $lCode = $lCode + 1 + assert($lCode <= $lCount, "sc.over") + end + else + -- is this even possible? + error("sc.unki") + end + end + $lDistLens[-1] = nil + return $lDistLens +end +$} + +$dfDynamicMetalensScramble = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15} + +${ +function $dfReadHuffmanDynamic($L|lState, $L|lLiteralSize, $L|lDistanceSize, $L|lDistanceLens) + -- $L|lI loop variable, used all over + -- to save on locals, this is reused + -- as metalens + $lState = {} + for $lI = 0, 18 do $lState[$lI] = 0 end + $lLiteralSize = $dfGetIntField(5) + 257 + $lDistanceSize = $dfGetIntField(5) + 1 + for $lI = 1, $dfGetIntField(4) + 4 do + $lState[$dfDynamicMetalensScramble[$lI]] = $dfGetIntField(3) + end + -- as metatree + $lState = $dfGenHuffmanTree($lState) + -- as concatenated subcodes + $lState = $dfReadHuffmanDynamicSubcodes({}, $lLiteralSize + $lDistanceSize, $lState) + -- The distance lengths are removed from lState, + -- while being added to lDistanceLens + -- The result is completion + $lDistanceLens = {} + for $lI = 0, $lDistanceSize - 1 do + $lDistanceLens[$lI] = $lState[$lLiteralSize + $lI] + $lState[$lLiteralSize + $lI] = nil + end + return $dfGenHuffmanTree($lState), $dfGenHuffmanTree($lDistanceLens) +end +$} + +-- Main Thread -- + +${ +$dfThread = coroutine.create(function ($L|lFinal, $L|lLitLen) + while true do + $lFinal = coroutine.yield() + $L|lBlockType = $dfGetIntField(2) + if $lBlockType == 0 then + -- literal + $dfGetIntField($dfAlignToByteRemaining) + $lLitLen = $dfGetIntField(16) + -- this is weird, ignore it + $dfGetIntField(16) + for $L|lI = 1, $lLitLen do + $dfOutput(string.char($dfGetIntField(8))) + end + elseif $lBlockType == 1 then + -- fixed Huffman + $dfReadBlockBody($dfFixedLit, $dfFixedDst) + elseif $lBlockType == 2 then + -- dynamic Huffman + $dfReadBlockBody($dfReadHuffmanDynamic()) + else + error("b3") + end + while $lFinal do + coroutine.yield() + end + end +end) +$} + +-- The Outer Engine -- + +coroutine.resume($dfThread) +${ +function $engineInput($L|lData, $L|lByte) + for $L|lI = 1, #$lData do + $lByte = $lData:byte($lI) + $dfAlignToByteRemaining = 8 + while $dfAlignToByteRemaining > 0 do + -- If we're providing the first bit (v = 8), then there are 7 bits remaining. + -- So this hits 0 when the *next* 8 yields will provide an as-is byte. + $dfAlignToByteRemaining = $dfAlignToByteRemaining - 1 + assert(coroutine.resume($dfThread, $lByte % 2 == 1)) + $lByte = math.floor($lByte / 2) + end + end + -- flush prepared buffer + $engineOutput($dfPushBuf) + $dfPushBuf = "" +end +$} + diff --git a/inst/instcore.lua b/inst/instcore.lua new file mode 100644 index 0000000..d828934 --- /dev/null +++ b/inst/instcore.lua @@ -0,0 +1,76 @@ +-- KOSNEO installer base +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +$icScreen = $component.list("screen", true)() +$icGPU = $component.list("gpu", true)() + +$icFilename = "Starting..." +$icBytesRemaining = 0 + +if $icScreen and $icGPU then + $icGPU = $component.proxy($icGPU) + $icGPU.bind($icScreen) + $icGPU.setResolution(50, 5) + $icGPU.setBackground(2^24-1) + $icGPU.setForeground(0) + $icGPU.fill(1, 1, 50, 5, "█") + $icGPU.fill(1, 2, 50, 1, " ") + $icGPU.set(2, 2, "KittenOS NEO Installer") +end + +function $icOctalToNumber($a0) + if $a0 == "" then return 0 end + return $icOctalToNumber($a0:sub(1, -2)) * 8 + ($a0:byte(#$a0) - 48) +end + +$icSectorsRead = 0 +$iBlockingLen = 512 +function $iBlockingHook($a0) + if $icBytesRemaining > 0 then + ${ + $L|icByteAdv = math.min(512, $icBytesRemaining) + $icBytesRemaining = $icBytesRemaining - $icByteAdv + if $icFile then + $filesystem.write($icFile, $a0:sub(1, $icByteAdv)) + if $icBytesRemaining <= 0 then + $filesystem.close($icFile) + $icFile = nil + end + end + $} + else + $icFilename = $a0:sub(1, 100):gsub("\x00", "") + -- this sets up the reading/skipping of data + $icBytesRemaining = $icOctalToNumber($a0:sub(125, 135)) + if $icFilename:sub(1, 2) == "./" and $icFilename ~= "./" then + $icFilename = $icFilename:sub(3) + if $icFilename:sub(#$icFilename) == "/" then + $filesystem.makeDirectory($icFilename) + else + $icFile = $filesystem.open($icFilename, "wb") + if $icBytesRemaining == 0 then + $filesystem.close($icFile) + $icFile = nil + end + end + end + end + -- UPDATE DISPLAY -- + $icSectorsRead = $icSectorsRead + 1 + if $icScreen and $icGPU then + $icGPU.fill(1, 2, 50, 1, " ") + $icGPU.set(2, 2, "KittenOS NEO Installer : " .. $icFilename) + $icGPU.fill(2, 4, 48, 1, "█") + $icGPU.fill(2, 4, math.ceil(48 * $icSectorsRead / $$SECTORS), 1, " ") + end + if $icSectorsRead % 16 == 0 then + $computer.pullSignal(0.01) + end + if $icSectorsRead == $$SECTORS then + $filesystem.close($readInFile) + $filesystem.remove("init.neoi.lua") + $computer.shutdown(true) + end +end + diff --git a/inst/insthead.lua b/inst/insthead.lua new file mode 100644 index 0000000..ffb528f --- /dev/null +++ b/inst/insthead.lua @@ -0,0 +1,67 @@ +-- KOSNEO installer base +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +$computer = computer +$component = component +assert($component, "KittenOS NEO installer: Copy as init.lua to the target disk, then remove other disks & reboot.") + +$filesystem = $component.proxy($computer.getBootAddress()) + +$filesystem.remove("init.neoi.lua") +$filesystem.rename("init.lua", "init.neoi.lua") +$readInFile = $filesystem.open("init.neoi.lua", "rb") + +$iBlockingBuffer = "" +$iBlockingLen = $$CORESIZE +${ +function $iBlockingHook($L|lBlock) + -- Run the next script (replacement compression engine,) + assert(load($lBlock))() +end +$} + +${ +function $engineOutput($L|lBlock) + $iBlockingBuffer = $iBlockingBuffer .. $lBlock + while #$iBlockingBuffer >= $iBlockingLen do + $lBlock = $iBlockingBuffer:sub(1, $iBlockingLen) + $iBlockingBuffer = $iBlockingBuffer:sub($iBlockingLen + 1) + $iBlockingHook($lBlock) + end +end +$} +$engineInput = $engineOutput + +while true do + $readInBlock = $filesystem.read($readInFile, 1024) + ${ + for i = 1, #$readInBlock do + -- Read-in state machine + + -- IT IS VERY IMPORTANT that read-in be performed char-by-char. + -- This is because of compression chain-loading; if the switch between engines isn't "clean", + -- bad stuff happens. + + -- This character becomes invalid once + -- it gets passed to engineInput, + -- but that's the last step, so it's ok! + $L|readInChar = $readInBlock:sub(i, i) + if not $readInState then + if $readInChar == "\x00" then + $readInState = 0 + end + elseif $readInState == 0 then + if $readInChar == "\xFE" then + $readInState = 1 + else + $engineInput($readInChar) + end + else + $engineInput($readInChar) + $readInState = 0 + end + end + $} +end + diff --git a/inst/libs/frw.lua b/inst/libs/frw.lua new file mode 100644 index 0000000..272c914 --- /dev/null +++ b/inst/libs/frw.lua @@ -0,0 +1,30 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +return { + read = function (fn) + local f = io.open(fn, "rb") + local d = f:read("*a") + f:close() + return d + end, + write = function (fn, data) + local f = io.open(fn, "wb") + f:write(data) + f:close() + end, + progress = function () + io.stderr:write("00% \\") + local lastPercent = 0 + local chr = 0 + return function (fraction) + local percent = math.ceil(fraction * 100) + if percent ~= lastPercent then + lastPercent = percent + chr = (chr + 1) % 4 + io.stderr:write(string.format("\8\8\8\8\8%02i%% %s", percent, ("\\|/-"):sub(chr + 1, chr + 1))) + end + end + end +} + diff --git a/inst/libs/lexcrunch.lua b/inst/libs/lexcrunch.lua new file mode 100644 index 0000000..d361f2b --- /dev/null +++ b/inst/libs/lexcrunch.lua @@ -0,0 +1,191 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- This library helps in crunching down the installer a bit further. +local sequences = { + {"\n", " "}, + {" ", " "}, + {" #", "#"}, + {"# ", "#"}, + {" ,", ","}, + {", ", ","}, + {" (", "("}, + {"( ", "("}, + {" )", ")"}, + {") ", ")"}, + {" {", "{"}, + {"{ ", "{"}, + {" }", "}"}, + {"} ", "}"}, + {" <", "<"}, + {"< ", "<"}, + {" >", ">"}, + {"> ", ">"}, + {" *", "*"}, + {"* ", "*"}, + {" ~", "~"}, + {"~ ", "~"}, + {" /", "/"}, + {"/ ", "/"}, + {" %", "%"}, + {"% ", "%"}, + {" =", "="}, + {"= ", "="}, + {" -", "-"}, + {"- ", "-"}, + {" +", "+"}, + {"+ ", "+"}, + {".. ", ".."}, + {" ..", ".."}, + {"\"\" ", "\"\""}, + {"=0 t", "=0t"}, + {">0 t", ">0t"}, + {">1 t", ">1t"}, + {"=1 w", "=1w"}, + {"=380 l", "=380l"}, + {"=127 t", "=127t"}, + {"<128 t", "<128t"}, + {"=128 t", "=128t"}, + {">255 t", ">255t"}, + {"=512 t", "=512t"} +} + +local function pass(buffer) + local ob = "" + local smode = false + while #buffer > 0 do + if not smode then + local ds = true + while ds do + ds = false + for _, v in ipairs(sequences) do + if buffer:sub(1, #(v[1])) == v[1] then + buffer = v[2] .. buffer:sub(#(v[1]) + 1) + ds = true + end + end + end + end + local ch = buffer:sub(1, 1) + buffer = buffer:sub(2) + ob = ob .. ch + if ch == "\"" then + smode = not smode + end + end + return ob +end + +-- Context creation -- +return function () + local forwardSymTab = {} + local reverseSymTab = {} + + local temporaryPool = {} + + local stackFrames = {} + + local log = {} + + local possible = {} + for i = 1, 52 do + possible[i] = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"):sub(i, i) + end + + local function allocate(reason) + for _, v in pairs(possible) do + if not reverseSymTab[v] then + reverseSymTab[v] = reason + return v + end + end + end + + local function allocTmp(id) + assert(not forwardSymTab[id], "var already exists: " .. id) + local val = table.remove(temporaryPool, 1) + if not val then val = allocate("temporary") end + forwardSymTab[id] = val + table.insert(log, "allocTmp " .. id .. " -> " .. val) + return val + end + + local lexCrunch = {} + function lexCrunch.dump(file) + file:write("forward table:\n") + for k, v in pairs(forwardSymTab) do + file:write(k .. " -> " .. v .. "\n") + end + file:write("reverse table (where differing):\n") + for k, v in pairs(reverseSymTab) do + if forwardSymTab[v] ~= k then + file:write(v .. " -> " .. k .. "\n") + end + end + file:write("log:\n") + for k, v in ipairs(log) do + file:write(v .. "\n") + end + end + function lexCrunch.process(op, defines) + -- symbol replacement + op = op:gsub("%$[%$a-z%{%}%|A-Z0-9]*", function (str) + if str:sub(2, 2) == "$" then + -- defines + assert(defines[str], "no define " .. str) + return defines[str] + end + local com = {} + for v in str:sub(2):gmatch("[^%|]*") do + table.insert(com, v) + end + if com[1] == "L" then + assert(#com == 2) + local id = "$" .. com[2] + assert(stackFrames[1], "allocation of " .. id .. " outside of stack frame") + table.insert(stackFrames[1], id) + return allocTmp(id) + elseif com[1] == "{" then + assert(#com == 1) + table.insert(stackFrames, 1, {}) + return "" + elseif com[1] == "}" then + assert(#com == 1) + for _, id in ipairs(table.remove(stackFrames, 1)) do + table.insert(temporaryPool, forwardSymTab[id]) + forwardSymTab[id] = nil + end + return "" + else + assert(#com == 1) + local id = "$" .. com[1] + -- normal handling + if forwardSymTab[id] then + return forwardSymTab[id] + end + local v = allocate(id) + forwardSymTab[id] = v + return v + end + end) + -- comment removal + while true do + local np = op:gsub("%-%-[^\n]*\n", " ") + np = np:gsub("%-%-[^\n]*$", "") + if np == op then + break + end + op = np + end + -- stripping + while true do + local np = pass(op) + if np == op then + return np + end + op = np + end + return op + end + return lexCrunch +end diff --git a/inst/status.lua b/inst/status.lua new file mode 100644 index 0000000..1acd41a --- /dev/null +++ b/inst/status.lua @@ -0,0 +1,36 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- Status Screen -- +local target = ... +local u = require("libs.frw") +local instSize = #u.read(target) + +local status = "" +local statusDetail = "" +local blinkI = "" +if instSize > 65536 then + blinkI = "5;31;" + status = " DO NOT SHIP " + statusDetail = "The installer is too big to ship safely.\nIt's possible it may crash on Tier 1 systems.\nUpgrade the compression system or remove existing code." +elseif instSize > 64000 then + blinkI = "33;" + status = " Shippable * " + statusDetail = "The installer is getting dangerously large.\nReserve further room for bugfixes." +else + blinkI = "32;" + status = " All Green " + statusDetail = "The installer is well within budget with room for features.\nDevelop as normal." +end +io.stderr:write("\n") +local ctS, ctM, ctE = " \x1b[1;" .. blinkI .. "7m", "\x1b[0;7m", "\x1b[0m\n" +io.stderr:write(ctS .. " " .. ctM .. " " .. ctE) +io.stderr:write(ctS .. status .. ctM .. string.format(" %07i ", 65536 - instSize) .. ctE) +io.stderr:write(ctS .. " " .. ctM .. " " .. ctE) +io.stderr:write("\n") +io.stderr:write(statusDetail .. "\n") +io.stderr:write("\n") +io.stderr:write("Size: " .. instSize .. "\n") +io.stderr:write(" max. 65536\n") +io.stderr:write("\n") + diff --git a/inst/symbolGuide.md b/inst/symbolGuide.md new file mode 100644 index 0000000..fa36123 --- /dev/null +++ b/inst/symbolGuide.md @@ -0,0 +1,32 @@ +# The Symbol Guide + +## lexCrunch commands + +The following prefixes are really special, + and are lexcrunch's responsibility: + +"$$THING" : These are defines. +"$Thing" : Writes a global into stream. If not already allocated, is allocated a global. + +"${" : Opens a frame. +"$}" : Closes a frame. (Attached temps are released.) +"$L|THING" : Allocates THING from temp pool, attaches to stack frame, writes to stream. + Use inside a comment to erase the written symbol + +## Conventions + +The rest are convention: +"$iThing" symbols are Installer Wrapper. +"$icThing" symbols are Installer Core. +"$dfThing" symbols are DEFLATE Engine. +"$bdThing" symbols are BDIVIDE Engine. + +"$a0", "$a1", etc. are Local Symbols. +DEPRECATED, THESE ARE AN OLD MECHANISM, USE FRAMED TEMPS INSTEAD. +These are reserved only for use in locals. +(For loops count.) + +"$lThing" symbols are used to name Local Symbols using aliases. + +NO THEY ARE NOW USED FOR ALL TEMPS, INCLUDING LOCAL SYMBOLS + diff --git a/inst/uncompressed/compress.lua b/inst/uncompressed/compress.lua new file mode 100644 index 0000000..cf9c013 --- /dev/null +++ b/inst/uncompressed/compress.lua @@ -0,0 +1,10 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- Example compression engine. +-- Given: data, lexCrunch +-- returns compressionEngine, compressedData +return function (data, lexCrunch) + return lexCrunch.process(" $engineInput = $engineOutput ", {}), data +end + diff --git a/insthead.lua b/insthead.lua deleted file mode 100644 index b14fd94..0000000 --- a/insthead.lua +++ /dev/null @@ -1,97 +0,0 @@ --- KOSNEO inst. --- This is released into the public domain. --- No warranty is provided, implied or otherwise. -C, O, G, D = component, computer -assert(C, "To install, please copy as init.lua to a blank disk or a system to update, then remove all other disks and reboot.") - -sa = C.list("screen", true)() -if sa then - G = C.list("gpu", true)() - if G then - G = C.proxy(G) - G.bind(sa) - G.setForeground(0xFFFFFF) - G.setBackground(0x000000) - G.setResolution(50, 5) - G.setDepth(1) - G.fill(1, 1, 50, 5, " ") - G.setBackground(0xFFFFFF) - G.setForeground(0x000000) - G.fill(1, 2, 50, 1, " ") - G.set(2, 2, "KittenOS NEO Installer") - end -end - -D = C.proxy(O.getBootAddress()) - -tFN,tFSR,tW,tF="Starting...",0,0 - -function tO(oct) - local v = oct:byte(#oct) - 0x30 - if #oct > 1 then - return (tO(oct:sub(1, #oct - 1)) * 8) + v - end - return v -end -function tA(s) - if tW > 0 then - tW = tW - 1 - return - end - if tF then - local ta = math.min(512, tFSR) - D.write(tF, s:sub(1, ta)) - tFSR = tFSR - ta - if tFSR == 0 then - D.close(tF) - tF = nil - end - else - tFN = s:sub(1, 100):gsub("\x00", "") - local sz = tO(s:sub(125, 135)) - if tFN:sub(1, 5) ~= "code/" then - tW = math.ceil(sz / 512) - else - tFN = tFN:sub(6) - if tFN == "" then - return - end - if tFN:sub(#tFN) == "/" then - tW = math.ceil(sz / 512) - D.makeDirectory(tFN) - else - tF = D.open(tFN, "wb") - tFSR = sz - if tFSR == 0 then - D.close(tF) - tF = nil - end - end - end - end -end - -sN, sC = 0, 0 - -function sector(n) - tA(n) - sN = sN + 1 - if G then - local a = sN / sC - G.fill(1, 2, 50, 1, " ") - G.set(2, 2, "KittenOS NEO Installer : " .. tFN) - G.setForeground(0xFFFFFF) - G.setBackground(0x000000) - G.fill(2, 4, 48, 1, " ") - G.setBackground(0xFFFFFF) - G.setForeground(0x000000) - G.fill(2, 4, math.ceil(48 * a), 1, " ") - end - if sN % 8 == 0 then - O.pullSignal(0.05) - end - if sN == sC then - dieCB() - O.shutdown(true) - end -end diff --git a/laboratory/ocemu.cfg.default b/laboratory/ocemu.cfg.default index b0c9c2b..994f9c7 100644 --- a/laboratory/ocemu.cfg.default +++ b/laboratory/ocemu.cfg.default @@ -3,8 +3,8 @@ ocemu { components { {"gpu", "c1-gpu-tier3", 0, 160, 50, 3}, {"gpu", "c1-gpu-tier1", 0, 50, 16, 1}, --- {"screen_sdl2", "c1-screen-tier3", -1, 160, 50, 3}, - {"screen_sdl2", "c1-screen-tier1", -1, 50, 16, 1}, + {"screen_sdl2", "c1-screen-tier3", -1, 160, 50, 3}, +-- {"screen_sdl2", "c1-screen-tier1", -1, 50, 16, 1}, {"modem", "c1-modem", 1, false}, {"eeprom", "c1-eeprom", 9, "lua/bios.lua"}, {"filesystem", "c1-tmpfs", -1, "tmpfs", "tmpfs", false, 5}, diff --git a/laboratory/reset-i.sh b/laboratory/reset-i.sh index aed4030..5df39a3 100755 --- a/laboratory/reset-i.sh +++ b/laboratory/reset-i.sh @@ -7,5 +7,5 @@ cp ocemu.cfg.default ocemu.cfg && rm -rf c1-sda c1-sdb tmpfs mkdir c1-sda c1-sdb echo -n c1-sda > c1-eeprom/data.bin cd .. -./package.sh +./package.sh $* cp inst.lua laboratory/c1-sda/init.lua diff --git a/mkucinst.lua b/mkucinst.lua deleted file mode 100644 index 7dbe047..0000000 --- a/mkucinst.lua +++ /dev/null @@ -1,28 +0,0 @@ --- This is released into the public domain. --- No warranty is provided, implied or otherwise. - -os.execute("tar -cf code.tar code") -os.execute("cat insthead.lua > inst.lua") -local f = io.open("inst.lua", "ab") - -local df = io.open("code.tar", "rb") -local sc = 0 -while true do - local d = df:read(512) - if not d then break end - sc = sc + 1 -end -df:close() -local df = io.open("code.tar", "rb") -f:write("dieCB = function () end") -f:write("sC = " .. sc .. "\n") -while true do - local d = df:read(512) - if d then - f:write(string.format("sector(%q)", d)) - else - break - end -end -df:close() -f:close() diff --git a/package.sh b/package.sh index fb89c28..386e29e 100755 --- a/package.sh +++ b/package.sh @@ -9,15 +9,15 @@ lua claw/clawconv.lua code/data/app-claw/ < claw/code-claw.lua > /dev/null rm code.tar # Hey, look behind you, there's nothing to see here. # ... ok, are they seriously all named "Mann"? -tar --mtime=0 --owner=gray:0 --group=mann:0 -cf code.tar code +cd code +tar --mtime=0 --owner=gray:0 --group=mann:0 -cf ../code.tar . +cd .. -# Solely for ensuring that a -gold.lua file can be checked before being pushed to repository. -echo -n "-- commit: " > inst.lua -git status --porcelain=2 --branch | grep branch.oid >> inst.lua -lua heroes.lua `wc -c code.tar` | lua com2/bonecrunch.lua >> inst.lua -echo -n "--[[" >> inst.lua -cat com2/code.tar.bd >> inst.lua -echo -n "]]" >> inst.lua +# The Installer Creator +cd inst +lua build.lua `git status --porcelain=2 --branch | grep branch.oid | grep -E -o "[0-9a-f]*$" -` ../code.tar $* > ../inst.lua +lua status.lua ../inst.lua +cd .. # Common Repository Setup Code ./package-repo.sh inst.lua diff --git a/preSH-Ancient-Est-2-10-2017.lua b/preSH-Ancient-Est-2-10-2017.lua deleted file mode 100644 index 99a8800..0000000 --- a/preSH-Ancient-Est-2-10-2017.lua +++ /dev/null @@ -1,609 +0,0 @@ --- KOSNEO inst. --- This is released into the public domain. --- No warranty is provided, implied or otherwise. - --- PADPADPADPADPADPADPADPAD - -local C, O, G, D = component, computer -local sAddr = C.list("screen", true)() -if sAddr then - G = C.list("gpu", true)() - if G then - G = C.proxy(G) - G.bind(sAddr) - G.setForeground(0xFFFFFF) - G.setBackground(0x000000) - G.setResolution(50, 5) - G.setDepth(1) - G.fill(1, 1, 50, 5, " ") - G.setBackground(0xFFFFFF) - G.setForeground(0x000000) - G.fill(1, 2, 50, 1, " ") - G.set(2, 2, "KittenOS NEO Installer") - end -end - -D = C.proxy(O.getBootAddress()) - -local file = nil -local fileName = "Starting..." -local fileSizeRm = 0 -local ws = 0 - -local convoct -convoct = function (oct) - local v = oct:byte(#oct) - 0x30 - if #oct > 1 then - return (convoct(oct:sub(1, #oct - 1)) * 8) + v - end - return v -end -local function sectorCore(sector) - if ws > 0 then - ws = ws - 1 - return - end - if file then - local takeaway = math.min(512, fileSizeRm) - D.write(file, sector:sub(1, takeaway)) - fileSizeRm = fileSizeRm - takeaway - if fileSizeRm == 0 then - D.close(file) - file = nil - end - else - local name = sector:sub(1, 100):gsub("\x00", "") - local sz = convoct(sector:sub(125, 135)) - if name:sub(1, 5) ~= "code/" then - ws = math.ceil(sz / 512) - else - if name:sub(#name, #name) == "/" then - ws = math.ceil(sz / 512) - D.makeDirectory(name:sub(6)) - else - fileName = name:sub(6) - file = D.open(fileName, "wb") - fileSizeRm = sz - if file then - if fileSizeRm == 0 then - D.close(file) - file = nil - end - end - end - end - end -end - -local dieCB = function () end - -local sectorNum = 0 -local sectorCount = 0 - -local function sector(n) - sectorCore(n) - sectorNum = sectorNum + 1 - if G then - local a = sectorNum / sectorCount - G.fill(1, 2, 50, 1, " ") - G.set(2, 2, "KittenOS NEO Installer : " .. fileName) - G.setForeground(0xFFFFFF) - G.setBackground(0x000000) - G.fill(2, 4, 48, 1, " ") - G.setBackground(0xFFFFFF) - G.setForeground(0x000000) - G.fill(2, 4, math.ceil(48 * a), 1, " ") - end - if sectorNum % 8 == 0 then - O.pullSignal(0.05) - end - if sectorNum == sectorCount then - dieCB() - O.shutdown(true) - end -end - -sectorCount = 260 -D.remove("init-symsear.lua") -D.rename("init.lua", "init-symsear.lua") -local instHandle = D.open("init-symsear.lua", "rb") -dieCB = function () - D.close(instHandle) - D.remove("init-symsear.lua") -end -local syms = {" ","s","e","t","a","i","\24","(","r",".","\ -","p","\"","o","c","m",", ","\1","l","n",")","d","u","\17","\9","x","-","\7","\6","\8","\4","\13","\2","\3","\5","g","\14","\21","\11"," then\ - ","1","w"," ","\12","\18","\22","f","F","y",",","\20","re","b","k"," = ","\23","return "," local ","\16","v"," if ","\ - ","\15","\19","\ - "," ","[","en","/","0","]","path","nction (","\ - ","se","h"," =","or","S","T","le","\ -local ",")\ -","= fu","on"," == ","ne",")\ - ","functio","\ - end\ -","D","{"," t","n ","oc","lo"," end\ - ","\0\0\0\0\0\0\0\0\0","un"," i","W","\0\0\0\0\0\0","fu","et"," end\ -","then ","nd","ni","A","ing"," tab","loca","etting",")\ - ","ct","C","P","}","\ - end\ -",")\ - ","onit","= ","end\ -","\0","I","Y"," do\ - ","return\ -","le.inser"," = func","= \"","al"," r"," e","in","he","nc"," e","j","donkonit","tion ()",") do\ - "," the","\ - ",")\ - ","ur","#","+","N","cursor",".inse",", v ","nction (w"," neo.req"," for "," then\ - ","\0\0\0\0\0\0\0","nd\ - ","()\ - ","neo.","ath","table","l ","do","2","3",":","H"," = nil\ - ","nd\ - e","hen\ - ","\7local ","indowCore",")\ - end\ -","d\") end\ -"," = false","if ","pairs(","dow","string\"","ti","O","uestAcces","nd\ - if ","icode."," if","v in ","pkg, pid","000","750\0000001","end\ - e","750\0000000"," else\ - ","window","end","00",".neo.sys.","neoux.tc","= {}\ - ","(window, ","5","=","E","R",") end\ - "," cursor","request","ode/app"," return e","\" the","equire("," "," then",".neo.pub.","hile true"," end"," en","\0\0","B","M"," ret","for ","in ipai"," true\ - ","close","code.sub","error(\"","return t","oroutine"," end\ - if","end\ - en","tion ","\", funct",":sub(","vailable"," end\ - "," l"," == \"","prima"," if type(","ryWindow","window.s"," end\ - ","L","X","~","on ("," in ipair"," for _, v"," for k","\0\0\0\0\0cod","lose()\ - "," = funct","rror(\"Exp","nsurePath","s.primary","primary","buildingS","unic"," "," re","surfaces","disallowe","ackground","neoux.","ccess","selectUnk"," end\ - "," = uni","\ - end\ -"," f","7","Z","z","for k","rue\ - end\ -","ode.len("," end\ -","lse\ - ",") end","end\ - "," fu",":sub(1, ","sub(1, 4)"," ","neo","the","if not "," th","tion","unknownA","end\ - ","[1] == \"","= unico","Acces","\ -end\ -\ -l","n\ - ","\") end\ -","de.len("," end","\" then\ - ","0\0000001","urface","nd\ -","()\ -","(surfaces"," if ","\0\0\0\0\0\0\0\0","unc","urn","\ - "," end\ -"," neoux","ction","\ - end\ - ","\ - ","neoux","\24\0\0\0\0\0\0\0\0","\ - end"," end\ -end\ -","\ -end\ -"," if "," err","s[1] == \"","unicode."," window"," if ","ocal fun","= false","\0\0\0\0\0\0\0000","\0\0\0\0\0\0\00000","code","_, v in i","()\ - ","d\ - ","f ev == \""," return",", bg, fg"," end"," neoux.","\0\0\0","w.close(","\ - if ev"," i"," if not ","if kc == ",")\ - end\ - ","indow","onkonit","00175","table.",", bg, fg)"," l"," end\ - ",") == \"","d\ -"," return ","rn","ca","q"," tabl"," error(\"","end\ - re"," in ","hen err","nd\ - en","nd\ - ","\ - end","turn ","x.neo.","ion ("," table","false","string","e.len(","d\ - "," re","1, unicod","cursorX ","local ","end\ -end\ -","cal","\" then e","nd\ - if k","ion","00644\00000"," end\ - ","\ -end"," window."," e","if not","== \"",", func"," neo.",", functi","\ - end\ -end"," r","end\ - ","ion (w)\ -","\ - end"," = false\ -"," ret","tu"," end\ - ","f ","unction","= nil\ -","th","n\ -","6","U","_","requir","eturn\ -"," funct","eturn tr","\ -\ -local"," table.","eques","rn ",")\ - r",".insert(","ode.",")\ - end","== ","\0\0\0\0\0\0\0c","n\ - ","\0\0\0\0\0","()\ - "," = nil","able.","n\ - "," = n","return","000644\0000","Access","able","etu","d\ - ","\ - if ","end\ - end","nction (p","ub(1, 4) "," return "," return","lse\ - ","abl","= uni","for k, ","\0\0\0\0\0\0\0\0c","cursorY","ble","bg, fg)\ - ","\ - e","ind","ret","tring\"","000000"," neo","pairs","then\ - ",")\ - ","le.inse","loc","\ - end","n error","\ -end\ -\ -","if s[1] =","lse\ - ","turn","ursor","function","neou","\0\0\0\0\0\0c","function ","\0000001","end\ - e","eo.","Access(\""," re"," lo","\ - e",")\ - end","urn ","\ - re"," end\ - e"," = f","\0\0\0\0","cal ","\0000000644\0","in ip","\ - en"," return",")\ - i","ction ","\ - end\ -","ode","equire(\"","r(\"Expect","ctio"," cursorY","if","%","4","<","G","\ -local f","\ - ret"," for ","d\ - if"," erro","true\ - end","ed\") end","hen ","nd\ - en","al ","on ","] then\ - ","string\")","for _, ","unicode.l","[1] == ","true\ - ","uestAc","nd\ -end\ -"," for k, "," if ev ","then\ - ","\ - en",")\ - en","do\ - ","e.ins",".primary"," true","coroutine","return\ - ","airs(",") end\ - ","nd\ -end\ -\ -","\ - return","window, ","end\ - e","end\ - end","750\0000","eturn t",", functio"," e","d\") end","en\ -","ocal","icode.len","e\ - ","n\ - ","w.close()"," uni","sub(","n ipairs("," for ","end\ - ","ion ()","end\ - en","quest","cursorY ","eturn","unicode.s","x.neo.pub"," = neo","hen\ - ","then\ -","string\") ","in ipa"," retur","indowCor"," re","750\00000"," do\ - ",")\ - if "," then ret","\") end"," en","n\ - ","ipairs"," then err","d\ - en","= f"," local",".neo.","\24\0\0\0\0\0\0\0","func","()\ - ",")\ - en","curs","\ - loca","ode/apps","nd\ - if "," i"," = neo.r","kg, pid, ","\ - i","win","code.sub(","require","wind"," else\ - ","close()"," end\ -e","\ -\ -local "," then re","\ - e",".request","wnAvailab","tion (","\ - local ","error(","\0000001750\0","n\ - ","d\ - "," ","uestA","reques","end\ -end\ -\ -","ion ()\ -"," curso","then\ - ","0001750","surfac",".close("," function","= neo.","if type("," loc","d\ - e","oroutine."," do\ - "," else\ - ","tion ()\ - ",", v in ","window.","neo.re","0000","\000000","\ - end\ - ","ipairs("," ret","\"x.neo.pu","error","\ - i","\ - end","()\ - ","= nil\ - ","nsert(","n erro","rror(\"","nil\ - ","cursorX ="," do\ - "," for","turn f","en\ - "," table.","\ - ","\") end\ - ","\") end\ - ",".close()\ -","\ - re","d\ - e","\ - end\ - ","ownAvaila","000644\00000"," false\ - ","\ - re","false\ -","ion ()\ - ",")\ - e","urn end\ -",")\ - if","x.neo.sys","indow, ","then\ - ",")\ - if t","cti","ion ","ion (w","\" then"," = nil\ -","then","e\" then\ -"," if","on (w)\ - ",") do\ - "," en","tAccess(\"","surePath"," end\ -end","\ - end\ - ","e.inse"," table.i"," local","oca","n (","0 then\ - ",") end\ -"," loc","se\ - ","do\ - ","nd\ - end\ -"," then e","0000644\0","ion ()\ - ","ocal ","end\ - if ","e\ - "," then\ -"," local","icode.le","\" then "," ==","] == \"","eoux","ndow","\0\0\0\0\0c","nownAvail"," functi","0\000000000","eoux.tc","hen error"," = neo.","table.ins"," window","(window","d\ - if ","\ - if ","\ - local","end\ - e","nd\ -end","insert("," local ","k, v i","surface","eturn ","\ -loc","sub(1, ","end\ - if ","io","!","$","&","'","*","8","9",";",">","?","@","J","K","Q","V","\\","^","|","","","","","","","","","","","","","","","","","","","",} -local bytBuf = "" -local bitBuf = "" -local function getByte() - if bytBuf == "" then - bytBuf = D.read(instHandle, 64) - end - local r = bytBuf:byte() - bytBuf = bytBuf:sub(2) - return r -end -while true do - if getByte() == 0 then break end -end -local function pb(c, p) if c % (p * 2) ~= c % p then bitBuf = bitBuf .. "1" else bitBuf = bitBuf .. "0" end end -local stn = 0 -local function getBit() - if bitBuf == "" then - local c = getByte() - c = (c - stn) % 256 stn = stn + 3 - pb(c, 1) - pb(c, 2) - pb(c, 4) - pb(c, 8) - pb(c, 16) - pb(c, 32) - pb(c, 64) - pb(c, 128) - end - local bit = bitBuf:sub(1, 1) == "1" - bitBuf = bitBuf:sub(2) - return bit -end -local buf = "" -local mode = false -local bc2 = 10 -while true do - local bc = getBit() - local v = 0 - if bc then bc = bc2 v = 64 else bc = 6 end - for bit = 0, bc - 1 do - if getBit() then v = v + (2 ^ bit) end - end - buf = buf .. syms[v] - if mode then - while #buf >= 512 do - sector(buf:sub(1, 512)) - buf = buf:sub(513) - end - else - if #buf == 27939 then - stn = 0 - bc2 = 11 - bitBuf = "" - syms = {} - while #buf > 0 do - local len = buf:byte() - if len > 127 then error("symlen") end - buf = buf:sub(2) - local ch = buf:sub(1, len) - buf = buf:sub(len + 1) - table.insert(syms, ch) - end - mode = true - end - end -end ---[[%]-Er=´ZENϯpᄉƈӦ4.6y3bHMvk -p h 3/U-3PMn4;Fag歞y̐xg- {.w1`&RO?!1-y Ol!m<;.@hPb!dd{R8hũr@1F -BSy]S -nn9ZKá_6" ¤qd2YZk(b f_u}̈́1ؠSldx3O -{=|"9s'`%@*QR?\.>e yv>Ã29·( -W/5sЌns'cFnňrO/DIj,yBտ듗 AUЁ^\\ǎ70x1"y g+/I`ޙ.:Gm!ueG4T.pX*Y(1ڋkyaox -rԤ-*λ*B?AiRi6;[q,H#U(;]FSv])]֍pgr/i4(TꕄW+!O+T.R9bނ`?Ɲʏ 0E"e/\&P50SUk6Im0q-kEܷ7~٣[Ҭ7K~q=-fڀ?8 mf*Zq3MISMSnSjwpr("+niU]UMN۝[_n#-<oƨ:)}3lhUY.^챟~ ?L҅h"bY+ҕޚ2 ] ||` ]]#M[nJb6@MyHijkZh:o`ǛIY=0J -XdPm߱ - 3F, ~ ;BTI((lۑh9*j4eY\ť&To=;h(+-<7yVg&Xl6U: )+L07PRCORY.EWx0?_tK$>78 |bB,.u{AILNxwu1=B Zm!a+}/cK,Emqso]cBԖ Tdw'4[OI%FúmLzf]>tKCJ8`~rUvQ]Y٬765*뜓{<Ң0  -e !$sq;tkȌXk%aˍQvAI/&?'?qט>~%CT_W*%\&,m[/lÔso"-#Y>FF]@)xA؍5Tz/hlϿ9"KL ;$hoj?#v\=0<0ec+?nVރIl7nqhm6g?׻*1|+mkFM -I^!z:j{3ѱpƺ:/V\6zT?€Ӝ//eLCg ԥ'2 W#{5Ɨew\(m_%I!1b04y9"26<"Vŵb}pV"Dq-S .߮:cۈL<=~!'8R;TZJNo%UG G[#rKyf׬  ˢ`ڃ`)iRFXCMȢ6@wCWqC,#hiюLKYE@KnZh)91ҵ(dzʸ:fU&9 r*2bg>h~Mtaɠ)\Ky@9`E*05&Jj2p -#iM-i8 -2)%2mBMCƆJ^m~D@~ $4=J$ b2 !n x!f>21a_=[易G<0ZB?~V>^ OFL |睥>ýB[5T^=G\MUMzmdҦ||ք΃ʔgx`յlNc,{80':9 N٘Nk޸8nYP]\`EjCus| 1G)x*wA=pX7`>߰ )>p}gUDE2$k߬!MD -pV!~%kڴ6(ސ(>£Ms -*i %x?c_C Q2XwK=ɀ}k/1maRoD3 -b4٢$E#IJ2O۫5rW~4,;&2ZV(]VpGZ -ۛsr'yEvLtl-=5хfqjsu0(Z(;-arYCrײDNdc^|L<u_yxn[KAxG9 ǡg\ָvfk̼r[ .chJ`|( mg*tI?}CM:$tzI]{vZ\`7-*λaݯ?٥ISK9!i*Z CE4aUylcz|GQ){o7,n=8a4jÏvU%L 0שXDzK)FBi[=eP=F%kB=kǨ:e24Xƅ T?9*78 9KG 0.uW'`H&յ?9 ϕ]}/qNk#ryar(|^ə[Cꊖ3z-ܿKW:E;EV-i&#+?H=q -(ϓR|rpԌu 4*i6~\8mcw/Z»ԗnr]+:qDHSAy0f( -kQ&ל g @fWٻ2ke-(s|c *>`Kў6P{ 'ǛhtxIFӇˀaplg##Ę8=uAXr/ڸS`&{:7@v Qt!%(u-qs~M0Igsٟ<sz58ϼք܉~Tr߅$L]XG1\՛! hpOb*]o7b[%M:IXڊZ++|v,=[F2 1lzP j2@bYC_‡s `߳Ab'L9'iM\n[elQɗN TҺ}d!bŹ1-d vW(.(.S:J](sbtՅS^sFQ#V$#A/8dA'˫瀄˖cC.ӳ x$/'ewUs̫>;]L.uŢE*f.CUDM?fZ^\\jI8 )",SdB{T@Ǣ -L5oE:)2:+h+ԽxG^tHUڎэG5UΞlJ aſֽJIY/5:$E\.S/L?p4u>NĈƱ'+cǖ ZO<⤝VX -jlxRH}%V-Zt^^2o7/F!?k㤩rsZnz?9 }掔:c{$/B%?{Q;r 敂 ^:N/kaH8r79`;9 KXUXb|/ބYǏq,I!&a< O]\F$c0:B"HA&) u)nҴ)~@՝wvdS峧ru{Ns=L£-N"ΐȮǢ[bRJmMQ ECTDz"XrA_ }?6gN$!/HU:< ]!{|g'B - J~g#?|Rf-`pEiKd;UjZoUvz(冈W(({C-T+ Vn{S,bB -lSR5CEkoRc\WväS{̟t^Ir3L qi;,1Lxk 37nI -yW#ew/̯i f%OXJ]إ8VP`!>_">T+!y=?DJL{`4xk wq/ - Ҥڙ<;0t+a/#}q{Ww-Kdž + pk]ԜćO_ BP{v,nݤ( d剚r\Z30W2XF1zVNdqmƞZ#*bo:xhGj ΔTu "TI a|hZKy4|E K\ήzYl7BqJC81 }Y;$ ż]a;W_;Zb3т2o{*-2hI`ÒEkH5iDS+Y&QB-ґ:za -^<|OES< 9^'^6Њ g%Xˮ 7|+$"eK[OhSY iiI _ɡ*^;,.BWtQ$ӑ7^@_*}z@koˢ]uD+It4_ ' -$@"a)֙]x" 4;>nSNYG)IwcZt79 VqzdK R)]~%rEl j?xvxJL&^d̛@ks u|#⌢hC -|a w؅jB_o -Wi}c>6Uc/ й;eMA}|H3_k|kɯ/grb]Ï)?>gS"IKL~ϲ> C8 V+vscA('.F=p|p;> s޲~ja|g؝%v~Ƅot;iDHNvF gqo㌆8jQd-~JCe-ɢԑTM7r>Ą.Z$H*F -cߘT1ҋ+Fn$& wo8#@NrͰV$$9?_F -e! iz}%n>V7 'L '+.Θ?(4CuEi|wJo=ڊ9}CjǍ ZMbHi>ɋ׶z㳵^;⣥\p꣤ b1Q*aM) ĚJĊ~}N՞Bv_—Cq^BPhƤĉ:qx`6ӥP;aM]V6iLѢnZ dtziY'"dװN.XN ) h jsB]#iTSrfi/ORDYcgmdc5r6p&uAv>((fQYW2uI.Ai+o%Z@΃Yr1Bo~csD8xxME#3I0B5!V"Y@H5Mp+!Y)F/#s&#=}Fds[-~f*{z-ӗ́ͬ~U,@F{E3{<0/fao3JCdvpmZ 5E~ZQ~1>mt:ij4n|@:ŷ(S.DL*'xs-W끄W$Vʭ)?OqB" k,VF -|伻04Qp '$Wf;P.}{¨Ur$$7I.D:{{ -c1*Zց}XꨑGAD :%3ufL>y4K3tm - e:ϣ!b.r6+ev[R{ 8C::)%#CןOBq$Y]|Q)`\!/qJ='<#{Ht׭c^^|WTzpUJgWa-GO.A \ -vz!@/ VHb]|l.c_FIsp3pЇ<$㜕z4p809uܾ񫤍(2\$Y2=2~."5L&IMMBE 1viz~ 6ISC.Ccuh1R&84%P)m^[]3sR>nQ:7PHv~r~&ң'}%:Ϻ@DhkeJlj)fSE6dg06ĺ+TT%QJWehEeࠣ ҉D)rEݾT-rΐRp9pz1{ -6$f.e邬y4^m\oUF?=t̃ij<{UGC )W߉h℣9|2ɲaP+QPFV[^Hn_W>cKT%0S|ZڀSg>+p\JųVUFHGu-/p8 -[MKB11yQ5?TbRub -rbj%)T?7gA$wpF --U8,5XJciII*,WPk1EemY\帳~ԟTو( M%E7M-X y$q % $]몋ŝBJ$RQkmvM!k|G3k_""H EBʈ!6*F۠EwA+u3EΙg][$ԕJ::HbY"T:Y(-F8Z9Οx3NY" C,x&3 =+Ԉ["t0sDQ,kHq'TB;c8g)E {d֓!£,#M`^GS6ra&S!2%a0omo3w |[7Qs[2V '# -֠ǰYiI- 6Sټގ̪ArG]k+^e5@=>CM[6R0:2eϒ&Š5 鳦}O- [G.W$\+C-_ -bO{&3?H ~Sj|b5fril=*u7rP>x:r?Iʥ_.e1hP {FBC,{7Lj|WiH,j1t #9&6a~S=!^եhj&m.3OE@U7:ݸ59?rx/ze2Y\(H 1VmiѪ:\2:Mc4f$d(q/U:ig35 -QمH3Hqw dRm4ҖJ>Ʃ%;s -Iz\t`^BwP0)xPgjBoC~Cc8p,=`:E Юwܬv?q< -*-g[ʤ݂JqF(+6%S LHD{^HeQ'u ?xI܊ϏuÈc˱h:zaYݍ&FBxp( =<9lˍYZxQ #/2p?SLe(t/]Q{PC}uQr3.?YVR)%r*6(=L:z*i.d<ޱMq$Ԥ2P@ST -N)=  FGV-l-6O9^g4(b8]sf 4~~T˼d[ X{v(TKYHr?\즹'@BS(X@v”>>Ք+\&z\l϶O3q] =,l16"spˢoq8`a氩?CVn\VqapJIc&E㏉,%ec Ef8bG_^cjnO ;AzpGJ/r4˻g6sG=(. 2YD@'8W_ DQ_UpoթBJJ#dB-m6D@>Hh4w&}5Z!<)YS0IzSrFFr-pE,4e9cnhLd=dG8?WY4.jD9' oE~IOJA޾}/,T^i+>ȅG^`U _de@2ӮWIkkABCkXbr|M+(!tCd+>;Dm۳] N^\w]]SH05|+2,漪oW ?-Ϗq[h^@=AL4^\^dcEVӇcUS#&}δ60PuIV^\mB!DJe@FP+i$smcOz |^ΰU3V}NZ#$/^%topVV̞@ovڟ'^r#j&)Z/=Xy잍48PJr8mEy? ө}SŨ:͐J% -c$B !o>duN,˸쿡+[,ƪn1s9OeQi]2~q1{Tʥ(J60,:z~IY$![ydPmeV ,`&eU0=N߆/7X^GϭB*/, Łbvα4= -ux\k7(oa|x~;Xܢc%ܡ9?;N8}-YVkS5x, /SV4D;9A\-0;kvqpt ͰK򿲭s#C,>G[ɂ:Tg'SG w;_7*%$%iL?V^L5ʇj5=ҧiSt}@>ܑKu}04.M(nK[s|M-N;03\VښRbc98gd nfu==9TȵsvWr@nw^Wa|/:&qfg1M'84DM`n_j?e2kim"/)<릈CX`1g N'mH \rn/8xfM^͕NUZ_P.{ !I@ִmR05< ]}A9\>/DS+'<-F=ͦKNJkM^;W2T A\͢in~!l81?L -764/)Q\p[~?H90tq6əG5̐0G~Y|gT9#|Dz 4z&| B)idshxKDtfjݿ>&xz^ Mwe$E)6><h*YIG{0Q"-Ed?>Gʾ9>˒b[}xDN 4! n.}+ -UC2ńߖrOnP}$tz{4w>f%];qRz]+Ү,?W{ebsR՛Sh{З(@];+xo~^O^yBqknlLfS$X;u\%ҺvBagatb5FV1¿pnMx$9ar9yW`?DF<HoTv,;uB=28E;gHReBbYhsq5uSl_›J乡׊`_+p7R[8ܢIFՀ<^C~-0j^2SmՓ3 %Bo/49U#BZ$B> Kt:HS[dgPff]_baAec+P?`r̽4l, $z$G_0% (Ho;⾌BvŹnJ&[EN^Z/D?] 5r|Q&.G3]-8_+Ү,?WxH@W 32n_@3 -a)T[}}+y6{ ,'P6{r֞l_Gc,{r.xiO>ɘp+67+VOwVoBh%+y2~^<493yBd?SEbz[W'Cq(J߄|:'OTf,*Pp{]s؋샍Y)axaI2}YM8!͏ptB_ -Ք-23{}B}DsX~f5\cķ~:v=6yod0. 80 %\LI0&:i1|9[VlP㉍ۀEj̉jq %T 3w3`hvNvwm3-&Qk!It|z -U:h1*kK^z|p_(Vd$$Y Ÿ5J -+5 qhu5+_R3 -R7<zݟIƩLQ2b%Y͵`z{p~ ϺN~g1kc;bHcEJwy eDʁ.*8[34Y+,vymoa~m m?X;aeed*[䆇[v~S)wZy"eӊvJ-u {K?;m)ncq -!X{Έ*q|Bo -8!jA:lHzy*KD-b&8gځ=×Xаt󟔳ÂRͱعZr 7X@uZ=0+*ȣMОWJ13↷osUWC Wcڙ-ؓٳ`T){PIL+1op4GM`6pkfwZcwp'ЋK .@ž"U$ڧ 'vAZ^r24 $Ga}ycG0X_m|@z 1#Ȥt]-X4L+y qI<1[1-J359'xa\shhh1gcR3co)YQtNpVvA9m}]27f\ ,eΐi^^z^'=ɪ(еStSQF2] -)b˨sҐ)KY[8?H)ORJqI]$g՗p {fQ-b2 Jo ](a8F"~:XBs-]:}*<ђ=?žgzl j"sZeE\Z?/#煲|Ia+^;p9Rb1r$u&Qm6g?)`njXqK|=$+31P8=A{Pf M~~=NŽ:3JSI|QXcaccwwsP# mcGtWBZϘ_j`D ;P"A4!?^3Qq{D$c#@X|qkь" Hq%o8®]^R{[;+ɫ_JC!Wx}| o|)cp]S|NjG8!XUVSimMB~@~ ~ΡM[u llhEogXdo9OzSk-:0 m.1‡+g;9mypz!։U"5\Ao9s68BS^R3C"2qy^u [+g_7!Z'Wea^1y H #tsL*DQƵ9@A~ ]wn+N$h*\ǢԷ'ɀ:%{MTvMW/h2(oGi$5x&GQvYӈ~ ^YXY۝snm#( Isx71=nڣ"7N K${']X/r07Q<&/uX4=7r}_2ܚ2/؉%)/wzJ -αqHrӛ8P(_+-'-;;dW8J+8aZĹ8p.znђ՛!Ʀ+.)<KNzYJ3 -0:M([/a 4lH^b^l]`UKkNl{7)6oɜxЧ=W]|ِm9ESCPVftƴ~_"MTf~zo5YPj+֟HۘrurgR8 ,E%88?K+ `=fI[ctX=3+ޕf -Ū)J S  -j>NJeO<*Fa2P~*ls+ACS,`^NUQ.PgR| #a:'{ P'̙J`Vl/FEX20R"6$)a-:?zBWѵuim'#-9ž+7&wOʁtߢqd vHϕ *< 0uJid/Y\!Z5VWGɍYD_,fOQ݅,K&\vP 34KG0) -uJ}.wNc]>Z3f3i5h5iuj/c X@ T5 -z Z-jB"F/:U}B -KȆ1tuJqى>u} uD@c #[ 9S HCYuCxcz{;tHKxU̷^UorYt`!Tx!䈎-(aQJZbmEw]x8y|5zB.Ǭ]""İTcmCsB}z;+Ѽ9l'Kq G^TZv['q"I8|0=Nͦi9I pst.N:hꮪ"5RC`<˂mϞ>ۭ]Be`6X1&H+_!ܬRײzxFf:RffЍ[|t,1픋Lo[1?HC7$)M=0{-l)0ҞJ|B I cpEoR Ѵ$ɼ91ΐ\WY8 oR[ c=iM{)E}2dZIDCpkVؚ}pkjkO۱ݹMLqd.YpHĨg$1m-.?)*i1sm :P% ZACȪ0h7}Fr^y1C'o.v ɴ&%LE9V("~N=cYBAHrXydD:{-G?Gy:.Ϡ4f:腺Q]ch[ |v(+r@=I9Ąu@}0y˦C߂B~Pff&(u0̽Xm'<!?5 ~^z' P@w駶Q1:LL`|gp/CTanT%%Զ2IWP Kd S 7grMy#r5L3JFJ0Y2я4%k}Q<&-MŒaH -Qx.D p> HTt'.Y]'*wgE B;aUjф ٥t&]wzzլL15?=.3ߛ}3@=>3PpZVclT&̖0 $*{Bҡ٪ -M{THm Ņ7TD,h ,bqƌϋ6X&k -tF{{EXp^C9+LG)ˇs*(y%:qHʯ~;LgePhA4\cY@vJټ]ammMpǜRE..OeE3/M!,6WƁSSiT[y-6l@ϲK)y[h@"8'k *ⴏI& " %5gCHiݵ({ۧ]>c4}\ Icb@ G AQ7V$ 1YWHnxO_VL/\!sem3(ڪ J!owI;N"t_G# :$KǚqmoCƵK֒`]柢Hry,{[e՟Ψڿw*[p2A.7  zT9m`>[IQg `gqu܍Ѵ$9$=4Eʗ`@vO|TPP[l|+|a$ -/A4wV/fye\}d)<L>߃Ȗa:3yGf"%kk .9$.u?<+8jsw6mRI[`ƣfvjXY7, v!]ݕh@9a=f9x^Bg"z]J94[,ӶNsֱ] - ͖,2O YÜĨriQ.JGƧܛ,%%)J:B`0Xa.Q{qZ5ɭMCL1Dj g!W?8Z"(moSAq,o~VJXcHУ}-9ǯ=!ܑp|B{X0*t6fWr6ҥZL$O#5kP(`a*su[VPR2`u:컓>3_ڈb;4Y U4 -z~􎷗Ч|H~a;2IFtT΢ocޞBYā2ޖ͛ң&5YZINIF "w2,&(Ay$TJΠ*-:7(kh5;|;ng -Ot?hM i4ӑ-/jB)S&sEyK˨c?Q"mvLEK*-^{!&BV$\;tpbPfOlh\^C.hUSUghKWr;w 7Ta~B-o骛L4)(4'x(zr! xD X-5-=Q+?9|n W^*rv6@{bcy穋5yI@/ YiCt?6Np4\::)28GH'тܻ+fBݜôcsn'Ga }$j 7'QXHk j 4:[Χ_PjU>'#n7;@\-Ԣ$2X]#B $2qP:UilTS$&jGjᢳQ܊G@ivh^rM9i#q3KBG1ڽ,;_Y&ctٲL g-o}Lv9vt<Ϡf9 =odUN`-æt||mmE J>5abZ_mbNKA/zՎ)o^ W1ϮP]VT^a"z-oy7 ˊj_l7M&M&Q.N ^Ӵo_M;@ /]R~7B8QW}/߆/&%/7.LixXL䦺MN`&p`{`5W4!)#M O!aAt~6bA#3!%U 2&m -buGAO[,4;+h9xl*6t_J pѴWlA1MP)G/R,]ID܃~yF4 1T^2npJ2{?B5Y߿,8 g]e|_oXOYЇDa1 -.2ZA"6pv ',}ぴ*h1 `VčmK&w9g6_K&fjɺ7CY3SpgfƙcAA葈Z^5Dmk~~o R~^+'H{Bco~c5GT&,芌Y[zRRjpj/ :tF £VBHfS Lf0eUA]2۾;e|KUXemC}=L{ńWYei -%;ٸI -aU1м" _UA~/#vUmzdb$B~%yrH"-tXEsھ妈&@Yg6ODX?L& qʕZEѤĀ*v\׺Sm#h. ڽ є?o{}ֺH5QW}X11v]=>9_OZR6Z͈lº -lD+Ƿ4iB^f0)+;/O!M1 @"Ϗ2HaLڕ@zk\7Oؗ6l_年ua4DTl̟qSxrhI@vP1}!!VHCep|rteoԃ*jSv๩TFƶ777gPmߔiΩx:3҅~to0Yܓ,=[@h{{/(~#W/\Dˍ2T WR#`rG_)W哠gҚ v:YwuLߗ u2<7D06K `"u7yF)ķ L&B/KaN4+3 -^vqpt Ͱ׹i\v━̐+6QB04l-fLڡT7*Ij767{^QLK(/qj~ĝ }Bu,0pp˫ӌjUPu?4!f#x0SM1~~sqoErIr[0&ŜJh+@%ȩ)N9D-?jD4|!^ ҫ#r5x`Ihy9Zh<\{'V gA;ݭJiز)i8Aj3Alcdo~>~0ŃQ@mə=l;5\-hJs@8 nE{Phܩ'q"Jp~ZM$7T@:Z[QM4ЉXɁoʾ]N߾h[t9T1#Е^Uֿ5 =jt1uPa"7Yү:˸i_\TV쎑P;hDžԧ"Wӷ7B.rE2;G -<ǜZ1ܢE{M=_tɮH)ǛNXhybCD\]84Ռbd&*$2OBY`~ RvQ)9?4$GLωFUA\ebs->wWvo.ܤr!RNp L{KTot3Ubi;%%NSƔf5;CiГ+q%c'3 9#`n$|&:%nBIpw]V egMd.&bU0u6BB -L'n w7"ak)-EL">}c -ޗ3I=sն狝 A*uDoytPVyX~|mJ -64&%o^Q`!~e6M- -'*&%KJ/ݴy p䐊[_!,a1'M]GcC=pq|F[d JiKO*Q%>%/QdY; -bgr<6/UڡB7]2@ߜ2E#m!$.&E7Jq9ZvRS?bZΒ29wg:>ُڦb $6=2*;?G,wnaX&7N aU6L+Ò,`yOzVf( 1B!!a^uSU`Ƞr@[ $TPq41\(V#*!A5V[7jhCu )="z2"sC z?>V:d̿/w|tT6iWgRmr(.3A@ZP{U=5ak'1@9SF{[!}fcxx+.Z!"%h,'B E'1v )c;{StYrF^r=Dnᆉ~İc,^indk]>Ug}8(0αKZ[hZ6;1xS(T،]9;6oAhR!ƒF/iv'#r ?2Ϩg\A(Y5(z.HCC\Ej.2> Hv' -\n"jRPͩ|վQg_FLLX=:|*O00PƑPv(6|Ž̶7AMȃV4qԒ!z JZf:crHW=_Z)Oaytvwũ]#eeWq)QZ'v ->=~ Ub%_ltxpzdM_o/&(|VmV#mIgfy#AK*CmaN7{ #-Dz=^٧Za`&tӘ?r(հr)7\@ Ssy^&Z:XL |oS71AZ"mOsr2G6[WXu"vt̯x;QR%cxln5] -?:Z4߃Y= -Z6ޟ)J!2$A|8ȴtrauyd#u ?T{NP0YT9 DYNhg1\M=7@} s.&jbd$ƇFԟI@%5gۦ@Xp+"禲EḒVB_,Uphڇ 4$dhX>/# c$4@Ir eA>03ڏ\>cKeluws+>w˙=2%E JJ<ֵF3jL[qSq_{N‰jɠlN!qCy@ԥ׵VniTclص9W cha(jV9SpRa -?J  NS# [ֻZ[S[sC=!R[%tv`f{v+2Ϡ?/nUNd>wS.,~B u (;?45%d;UU6=C ng}0΢}H@/}@$hr)}$ejň&`2YwSE_3IT7^qgAirdّb{1^{ͦ+MgRbw5z(' 䞎`:LACx\Z)`hYӖr =éH;؊$cxؐ,% O\If g/)>JiAG8Dtӹ#LqJ`@dt{yci t{FY -xp/= |Vj&y5!,HE-US^Yr[fZE2rSWjd91x$ΜO[X!ɃR[Jp. -lIBO]305*^-bPx spW3Mkxޫ󞑈ym>^ZQA1=#t( 1z~1 ++ -HkN*"gR5'b$5=`{ %~jIHgF&y4r5CbHhS1AthT8!cGa.0?٧eXq7vۂ PǘGҸ!ƭ(X2 |{]#>rT7Dd6ڧu(a}.r (Ӯ;bPdQ SPe2[Nf*^6A(RCXܡ#֮#|~*STLV@^Y=e1_kqw2 ;);S}B"",@ -/&1Kg !1gOnAyP.^6|Qӑ i <7ڌ'AV>"f9#Z9o*t=aʚTRS'qvyлf"ټ}׵_Es>{4+&q@ )~;Z%Q׭e?Z}XT Aq#|~Ҿ]"tw46 tN@? 㘊q*!0K9"Ę:S\f]"ffpxw~M S/e5ܲY*t -!?0S(4Nz(u.R I۬Ҹ18<0p#zT H\Ot\Y":Ak<12 #| v}.734m*GU*k8Zl$cLQ[MJ/.A_x*z- 6ag/&mV|bݯ C<;y[T&Kg>ъ_l[dm_ =1Y]7<*@FKOEo)mjPK ?o%| .y;^\q*&+e/ { -Ld>U>VߵVdJ6\M_jA$V)G$B,S(颵zLVA;vcVh]!eu}{1AĐJܞ.⇾ÚeU<"ɥv46 .r2Je~,_5\MWIF+*auul\`}pb=-%$¥Rȇ JI_Wq&;ABNm3OrvZgXS2zsn'5aP MR EoWy(׭ l- -c7cc;0D=dnK{Vݵ?B8^$L9O=]ͲgW+E)Oå(W(7VCSM=8f)"8q E3l 𿉉klq3%:h&?T:(]/SJ4|P:\~f̂YFeC@_HÁ'uYk4V1<9bYk6 [X0ul}'M@AeWoB D4`[܆_yk< z2Ɩ ^4D˕ijN@wh 6*tX549? `^%wAcJp-ữ+qE˱ytȁD, Xp\Yّ$0RG46")N脹 xmUTY)iy - C(:{,H+o1VԖA/Zd^-?IO4"+0J*u>;?JƫuTbV$cC̯$ρ3qKR(VP\ڤj0;/Va#Cl=G8aaͬ\'Uca-ȇgPXtʜWY' -2*L6`Ƶa"l'z@.O2p~>]F#ni(JuGXN@heT V'$~!|t$,I3Pt|ʚ9$wB B=vAk/eQOS[_oվ ʋ{7M3Q֝9_zkf!Fg"P. [[Ry3IqBk[svduv<  -E(Lޯ9LC -& -S5f&w6 -&psfK8㋂CLdbz夞iJCY"#c:r)ul.Jl"9f#oQkVYNJVZqV:J lT*\:RW+sæ#b۾.#jkvS^8=_l7o8۝2AR$B }oFϩ0ed9PG{3/DŭF./[!69c:=pw+: KN=?C2x`ad?aCz^$fΘTS9鱪"g].åoaUplbb(④.cb'?,VqCX R%x'Yr'v0j7Ž(¦˯*Y3SG{#yv!c@ VE*#s/i`NBU"Os/~SێȻ,M%EY -؆;תnk`X*z2G7{gyXTb{I2dYMēi.blf ޤ1<+lop>y-LF.Q^th9>ėf S%!Ĺᠨ4p_fpiO!h2F_vvz>mNד۽a%a$[5Up*cLo {jɒ纉7eF(3HxbTY' KJNOW2¥g2{5\F-gfh+qy+8㜥wؖTa#ӿ.ܸ&OsAto -V?W/Pbk[(CJʭ|pT4OØg8 K[<!>RᦕКX$:-+-^2.p+[>?HO ;n'e!/v4٪̼K'fV@J3 cߜ)ƨEDnݒG&)omV?fUziԠkkRһǴٴ#+A̵]Zi@P~|G;#Ԉ$1]@iM=YМtuKv -W]Ұ2PS[ )BկDsRV*pm?J4S+!:WKǠEҜP&Z4eJS[ 3J! Ƹ@ԬXn \\j h*y }@5@5p+/)DjjUP] ܀uդO#}Bjvsܾ쳫 :q{va Xz.h7R~ -[jwei`3v\TV/~rDͣ {=eJk)3 sM)VȰ}WMfj!pa`'w)/oVŜefo9}sfGt&.`^=O C!|~лqhXx!r &q}x -D-ք"4ن̎U0= &K-P:.G5Q23qZ,l5C%Qr~k#d󽇐}zcJرT7k4]Uw:6mQMEНo_(1yJ[E⤎l,D'xhv5&'* ݦ? -c+2:\dfؗ; {_,G&!8wx)qvEvac" )涿Iۏوؚw-8!1]7dG #Ƃ6y4\<A3և4mjD)qr'>>թH=ѩS1Aw@%Iw0#y/7ݑAr>KJ|=ӾN/T'pߎyS9BJŲ8I"V *ߋ޳C zX?K 8KGAD0>$~fL10`KwC2x}ygkR/";T55*,!֛t)I _c=$F)iiz0`>P-j>sa sn~bJR]M9d3g<P#͡&% W.- f`}f~P G3cj@xҁB !׭L!رD挑VXl*:ek)ֱp#@Fn"*u DQXfDx)BIjVG8a_jt!61-p@=Nۣ)*G;@&ܠg$uti'@{7IDB} gM|Z4) _ T&"]74H*vYm[F=Xt^ټb@,;Az -!k ³=!"66X I _5]}/< -E"k!oIKWō`Vb ->K\Ű!_9ni& {5JGvs֢-w2ko}֕IH}05>΀PN hks #2d_$& ;lZ鬋3@rس7܊޴OZI bKlF&s61}. PZX0w$FUWH˯&TWڐu`i 0:냼-[sǚ%0ZRW\T"s8:5  !='-0'6h x4[%V;ɶnnʄz}ـ) ~6NMӋ#-)T}+ĜEy&*"+!dlDUI'c/.O@Ib$ \nw$'/W=nv׵a٨~&)?e9z&1c7l35PfKTU5Dr n|m69KʭW^% -S'2jW-25.z5ق@ʿ$7XT[eL[|P|mni;[TϪoIV_P%ȩi9Z[؅zn9g+oXOR 8!RYb!`VIk -(ap`bfo.nWwk{~ڜpLho/, K. e`A ;|aWա˃m^]pF ݻC!6P`e$B<)zW -[-Q U!*K-3Y?f8T s4w&6hF.h¯Î"䣻29Ig]Y:م+:8 2oO+zqU8j`a3OrS{ ;<ʏH+z*د;kW9ڏ ]wS&7L:U]&1SХѻoau5ysiZxufQ8nZ] ا[iZa+^/A8q(%4QBg(7B炈7~y=hn0"A.^Zկ6]Z;xO0n"3G4g'bࡌ@bMcPP\~V`\#pK > D*bXɩxw?)CfxCԟ 6[ؔ^gI|r%sZVlpςg2H 0@hNt;+K@'tYx PEW7xc봑WA]BFsڿ8yj~S9Դ"أsQWebVyFVt -k4_|UX2lzD ff"5|ğˢ!Iٻ&)Acqva 64a/,3!PX_KͿwPUaA~s慞/ Yrð5-RFg`ϴkc0/5,2?@G5dlu]tsZv3nq9臠fЯPY!*HP%p%3CtМnT%?*x|5(ȮEI@ ~Џ~t`|,(t ,8(0ts,![ -zc};Y-CVe)3 -}ʗPԞ~leBw_pc5N; 鱺Fʤ<(`g{ jr$07l p3ml?Ր=!׼?G+[i2j2c. n-~ȱ_w>ɽwWR3>@U@ͭOܼpQyzzFXb*fyJ#_DgI0p^ZcriL Ќ5ai9 o^+B8:Q߷Pnzp&BБD(0L\lZDPhb$erkz/ Zꧼ$žhaD FZGXI}a>k_RFdtU`0af llZ7\v-mW7|"pM楩)f !AĮT'u_ʰF?T\Yx9e?`LyΡ.]>ToTfd"jW%}K4t-)=\$AMiRNti fuh}|u$._@ -K6YӘ~@9~' A7YΠnW!dugoedtHs(LΡ"r-@]ŷ 7 %XE#=FWo8'O?35$fFNz4H:ė rխzr %!򓮏W<ʽ}Yf8S>V 9eJ8zT4ˀ(@u{>dײ|=:_A`z<JZ糅kzs6*)`TV` w(7O} Z#?|yX,; y6 "EfV2GU q#/Ħjo; >|AH#/sYtW*0F-"U1q$qk7mo0LۃM \fc/yO҇[ 1X(kve;Ƌd|Y - -DRMhj5DL\U>ͻ%!Z̚s? |ВWI]Kr$<̠0U܆fp|Cx.1ؤ/Ϧf>jH5bexnzv(9mR"=XѻN ̄[k!TW3"pkp)IiT_-NWN*l,읾n}3aN~or~ Wi׫ν8t/ /#iq9bu: $aZ%G,ͦBOF -do ̮%+~3UoEKT -jg#m k2+@O0dzg|s:0Я͌f1šI^E$ -=k-j'ɢwnhk%* Z?E)t9TZ ls[Z{ꕱjGʚ"+ 买!SFYR M4uqBKOJW5Y5ؔ4#comԣr{^U@:|$?@Nc|CrݞPDyrf__3&9[5z===KD@՞xuk%ʔ\,,=4{?E%t}^u<6G{9AX /7L>kNkx7@&]3V!G W|dΰEX&bT? 72-*\:HpDQOIBNhϣ&o"#mv> -6w4X"{r$(5pGQ3h,o!Fafg娵*"bRmf.w!tَU#c긿PȂ\* 90 @y')3Z{8|TV`@춂Q[]gwĀޞvѳĔKu[HX|W ģK˒e?.R&+3>DsJ=e`%-;qf P&3"6iD|CV98]najWs!=jfmu L&*Cq|ĜҹA_CgHYi//d}]Y+- Ek$T3_8 :P&C~[eޔ /G|6ts2Tu_*);E9=1[*sjqJir~WTS>96ї0q{ ~ZZatFk11BwK!Ery%4u-;1l7GdYeBNT-{~ﲕPVO浘](OV]U<63t0>Wn[l\l2GB1LnڥhmjS-ObV35ϧBG. -52 [i!SF\uGSdV:ߖjXwb<(qVbPwkLYӽ}0R-ZgqGC =0,1$`;&8Qٳ\VФeiP /¥Ѵ3zO[L΂\kOZ[WalkAv |t -#5c[tLU%wUHCBCŇj]tfipiu4ɩsK`jp{DD@3 ߟH!S6)$+#^ D;aHP~j]xDЛG;ϒy( ;cTjoÛ&qi7>蓨1<>BqkhLִ uʝ4މZgžiTEG׿@17>iAD*yda_\AH06y2w@X-Tv> vZY_I9%p)"LE* )NQo=!)4x[뮻GG=Uzk l|~ז>C` -Z6CO!KE˖W/ )#Z/d>~.^COSQkhdxm2.%77˚FyM9tnf;դ\X[ߠR<П8K#-qN7R},C$m>H{^/b7+-JaJ.1-q{h}| q|$yvqոY,]e#-nRD:C{Cda54nuUQ~R##}z]-Vu)I 375RDXK6TmAD8NO#Q+,<`Μg{pw;Ȑõ =W,D|\i7MparO&+QcT9V9X.1c^5JѢõf_ ).Ht7+Dkeyj]2l_Jw]@y!wPʧN>;D`8l>T@s~w# -RrC`Dq~RAf |VUz[kDY -%~7r+Wuq޾eovJ W: "F3pd5{DDy+a[@x ۵?q!a |K|SdTg@pT @M+$6DHb?WdžMVߘ˥GR:$)##](|A9P!k98%@dgSoZS{(_O=A\ -*s3 nw㛉ͻґt}+B5 -czabQa иD"#aj r%<_UMhN>/v8JSg)mQ -$ qe1QvfF9434x[Nߟ kvxJ+W8lqj HNBh`O - -l(AW WY>?mBZWJ,^SD@grRy99{Vբm瞴ۭ"xWA\,nwlkNtw"m F!ruy%%4>{0%guw IDo_kr7=NQ8ݯ# ("Z=~%U<͹[&E jmy:dACLo>ɹ\̞;ShSL` '>7` A-Su$je^ aXB;#ZPb0L=w"h/d~?74`1W";Aj9,3~OZSzH?EmꓚCݎ?U}_hDq;9W8Sj40t; {!I2K*_g=>"Ji"kR1Ó7>Aݜ㎡9Ր|̌ '&~-sR FkqŰ;dp@!<`t"gGB!>”5D`Vm[қ2sWQS@*}=Df3%wpYf'U~lzHK3_JfwahdW1R}n &z*p)4xLehdw}h%XmXƏRR̵wÃӠtO _M;J-G7Xa?e %ErV`h"4IՖ񿫒I)6\OU8H`:4dĐ:}#\\n%+n`!Aҿ$ވc2pSF›7f:KpWд̚v`L=C|t*4KC jE +ҞH#h`1D1 2DTP>bҪe=J{ 8 F yBIv̵O"OAi2󘰄{T|:g^1zY&|8N977>Y>sb]U>nhH ^ߏSl+"yq MLCaN@J:hG}]Hۓ'i - -=NVG@WqbGr;GBMf)MYNT(Aߖ3PT.ˎt=^kzb.ok\CoBCċN$jl$@["v)ɢ'Iy2œیG1m j"@1J7W@^E^C~QķoK ;03!KRx8SMM$O%P9V0V>&.ڄוjTWae ؛#V|dd#n Ep9R>Byq H,9l vnF|hcmY鸄⎔5a{:* !C8:wGAs(O] {ia 9Fd3eVp y(*?lw|F'J?k hwAH4*68,4U2\i}:ڇ0AuТWOkzˣbAҰ,5;fB"_he0u;DDqGR^\1hwEw1uc߯mqg >2~vg5o7Ǻ7i<]uX]vW-bcPL<$WrԼk, ѥ -oKl~m8G|)nd36MYy{x --m{ ͋@ucP'ϦzO)b/)Q0$[IYWANv%D!L'=Bq`ɀ/449|j%Y7TduvnÕNx{X_{W$V67XR;! $aUSގQGCydG ?y?档͹pfY+TfB| -y9F-90D7gSmphk.έu'̩fHxSʤ\ͅb/ltnzҍ^~|SU5D%1j8*'Ƒd3K8eµţ mwy+hh U$o^%4yx5qt׊ )R2-,-qTGBATai@ۀny#@F/>Vɍmtko St#n[󦒝]ڈ%gكB.o¢T1 V%춘,GP]S&+M3@[NsjZuT光Һ-c0MJ\OEQ_j / -  f2>ɣ2?ߜje7fcHढ.^ýTM pA54Z)ڍj8ID M@IhDgk2$˴_D{9QMmd F+<2#!Y.F?\";GAZjq"y>6/KWȇx5g)͜'rҧȂW"j,M{8QA -dWQ ;3CGbFH)Rmclg2Nm~~M||&Sb81'㣟Or|^}/W\2dт&6?/@p4/aKhUf~Pȗٟ b5i6 ƻZQ5D`'Х:#"+8|H>XH Ӊywgޅf%t#)5m&?wbM[hnZ!\~ZqW, d@89aE,ђwa_qc dWBuӟ[[/egԕbѮXE~ dQV@J].GtDk']'5O˟NtٰFDBN.f8GaY h~sM*l0!17?\fiwCn{$4~jrڴw#D[h2z -Tf p6]>66]@JޒTaR1l'ǹ̋$M1+,@y#jkNYܸS6 "4))MZ5>akD3!OɖbrŶtp~ ؀hPj| -Dn"(橴ɝrMȁcw\K''d1ш7ƶ"b?Qm^Xxz?)vw'd*%Øb9ɑ~kICj8u>jmC2}V?z&#JF ;;ھn /@^"*O.^cSLά,} Rkqů gUg՛&`nh?Q¸*{WXm/Bהe*b9,'$֗4\U ;Oe]`)_@3LҎh裡$ɼŁ 730c" jc}3CQA -Fʕ3%HMOLS/=|d#j{To,mŤ8@?M/F -\y ^ z07E$ J𬬕

΃ʚE -e2[epVny# X;Ayl$̣8&exFGZ9Mif ­ :S[:_ W\(,Y$됣4.f+N5 VnDld!~~n>[JPo_mCHOW` svyOҬY?.`,=Vm&PkVQAU]!*>=7Q':EJW?aVnтdhkVYB/vW^U S7"QVi 4[ęJ;=4@i^Ka^QJ0`--_ˡMCӰR<`&۷W]۩TETzU{MK) qI6 ,5,1F5> $b)3˓ZsNt}K$Vl~3Mz⯥=s.Td'&Rd_+/9{8(z~B؈\ot f??ʔHv.)+2#ckgФ&) >,c)y|cL3oScesk.FɍmOyo[z`!0vb$Nϴ7ߨ͡7A^(RM:V:.sQKD?BXhJhK,hY29rn_-dX@k(랓 kԏ;tT+94LS]a TE@, ʫVt/]KImtF{kU[3M4f篧rGܭkOט=?m|DXL -[H_f7` o6xrWMh/S.:Tkf`kꠥo^cq* 7p8K@ ]$)|m^ ^:C@XCFK"m$/S>د Bك$ Lbdz~ ٲa9_BdPImXaWqsn1#vX)Ac4:bb5iJSwViGy|]Z% 2Q1J&Lԓtg I0OQ!w#UbTOjEG&|;C%lQPvFD~QRda´ -WThpnߺcNCz?;?F]*&G+j0$ReGI vw]=085#/Fpdsjj-Oï_VZ>ŖݨuT-ܖ59SaDgAw<7F%^()ZĔEQ]W_UfҋzJ;ʐz@&[Tum9gfj= M0F=8#]kiZp%6%g~A^IM aBw*"ּ̈́6*⥈P{ ;Toa|We+B!{9;@ -MH('pA<2MɧQ4(iWnAwV5!&߄;F<:u`m\ۭ*)n#՜=%2;q?JBY+mx oet}Xl9-{#ToXhW=y *l@?-C4L&?U/^8hApJ'yS\enAw݊_uL3j11ng]GPWVt]iS`{^B'u81m @i`)[87HR<@hgUk\tNg}W`iҘrO{(ϳ8ŖBΨi&/8d -vaW6a9?(ٵC?e`]Yknp'~mX`4c̲?7rYF_5QS;{d?}v'ų[ S:nsi.ix|Gj]kh(H=Ϯ -zY&HP* hnkP }iH,V"F^Vwݞ<dO%^dn7C|GkLd/t9+QGrԇ-oiTw;;j}0#(0IF_7X_h s&̮݈¦z#w%IZLGS(y -Kݔ Bas;+2J ?R9xONK]njn%e-/ - =ҥa#}쵼`mִâodMKؘP>1˛+jO\}]v`xsۛa[\q>dSA%+mɸrnuEizU@tv"pP׸c쩗|(|nlYH$9 y>8YgU;qeKPBeVt JRP#&5rP`~ -a"Kwm%|\;8q.Ejh~  iIPD$PΩl]SYZNx㦉 I Jen:B?V6PgV8or5uDH -|Ɏe')F~cpRpx hG P",n:e.X-|KйmrjY{H566+G-58oCԴ/BVE<3& YIP6:8;V{~)SD5vw|fj۾-I:qDd(ASXjM!\wMMUݾ[U~ﳐqۚ.|t$#ǕQ -z~w6.Q(EWj:kJաjXa&Į6>'0jت8G lhNvuoF5dC="xQ(+VE)c\,+} &S~87(*)[v $A/5y-ݭ5pB-)Ad5]$=t`!U9pjbF.0E禓`zզdX¦uiU2qv @r%lGFD2N ?[T/}}Lm+Y*-v2 ( a1mXal~Z[BEg>x66CܕHvJԴZc=?;A9ȨX]CA^<[=D獘 /!a !d,a*DgBYwXoYR"[z!ĥVl=BV)Lg2?]QV8+&] \ ^>]UJʏ`Tu Jr1xi] c"2h}kGIHd_p0ƽiji"0QD]seR"wcigV`"V(LuӜߊPFT<%5iȺEе}RSE E|j+~NoikIY{ QcɈ>>OPbkF I<JH0KMvv\-zjo9/c9\H]+ϞP&K[1gO: >m#(n~aȇAv;3@z g5osHX~D TG: iYt~ Ur^8zo /朜od*=V}IYd@*ߏBQEYlO\۟s^;wuȑ *+:^]F ;w.ޝR@AO=2=+%vAYd=Vhwύunӧ^ȭ=-;h &#C Xx3-[z.2$kV\^(~Xv Mb|'Ǝ׫Ú׳)P5If 8I#+34l-hB4 d:>ԔuIFlDe|Tqg*j7F~6vէ_,P D b0_kb񙦳JU4Ԥ:7|3rN`HprR\+oq4+ҷDĺ[&*/2κI2Z# >VvFѐY?JhDgèpRkж3 \X>'ä>dFZ8O7~)%zݵ(yU_MXia]WcPeųqAP*MǺѤ fFw-4ă:0^y" m>n+z1]Pn+syuםG2 -/s]P#4?bX5aޯQOKF"ocӊuO; H[%P̯D4q'Лr)☬"ê7Z`q9i?SnkQhh1s3|v}S؏WӱPaWJYaÎ}霮f[YGp*MXqjLs5Ma뿁lb꛰bY5!ǦDt0EJOyO-͋`3@"hX.Oɒ-oq^٦?Ozuh]/}Ax/~:u+5rQS2Ң|c-qn3PȂlHLLBa2_fۄws,%44 9op)/C bGoռ} -c3PoKH~^tt&c%fCv6*^* -F5TN;Yq7O{7[&-yi0)5Ռ<]T)b^$(vŸ Blv[: -]n*Vk'$0I5(҃|y#IЩi ی(j*7_35| `Ezҭ[dx5v1n<4B:428fX `87A5q,Y#>)=`YdcJ顄wrqr ˮö3H -]r4 -I,^A4/./sVIDCDƈk^YXY۝snmn𲕈Ǫܿ/DYn0E(Z=0+*+oRE@?@„gZUTUי|ojij쮑~æػ+þ@Uj,A$V9,'&'kNA<;' is 'from you', '<' 'to you'.", - ":':' is 'internal message'.", - ":Control-Arrows resizes window.", --- ":F5/F6 copies/pastes current line.", - ":", - ":", - ":Enter target server:port", - -- 14 -} ---++++++++++++++++++++++++++++++++++++++++ - --- sW must not go below 3. --- sH must not go below 2. -local sW, sH = 40, 15 -local inet = neo.requireAccess("c.internet", "internet").list()() -local windows = neo.requireAccess("x.neo.pub.window", "windows") -local window = windows(sW, sH) - -local tcp = nil -local dummyTcp = {read = function() return "" end, write = function() end, close = function() end} -local tcpBuf = "" - -local function fmtLine(s) - s = unicode.safeTextFormat(s) - local l = unicode.len(s) - return unicode.sub(s .. (" "):rep(sW - l), -sW) -end - -local function line(i) - if i ~= sH then - assert(console[i], "console" .. i) - window.span(1, i, fmtLine(console[i]), 0xFFFFFF, 0) - else - window.span(1, i, fmtLine(l15), 0, 0xFFFFFF) - end -end - -local function incoming(s) - local function shift(f) - for i = 1, #console - 1 do - console[i] = console[i + 1] - end - console[#console] = f - end - -- Need to break this safely. - shift("") - for i = 1, unicode.len(s) do - local ch = unicode.sub(s, i, i) - if unicode.wlen(console[#console] .. ch) > sW then - shift(" ") - end - console[#console] = console[#console] .. ch - end - for i = 1, #console do - line(i) - end -end - -local function submitLine() - incoming(">" .. l15) - if not tcp then - tcp = inet.connect(l15) - if not tcp then - incoming(":The connection could not be created.") - tcp = dummyTcp - else - if not tcp.finishConnect() then - incoming(":Warning: finishConnect = false") - end - neo.scheduleTimer(os.uptime() + 0.1) - end - else - -- PRJblackstar doesn't need \r but others might - tcp.write(l15 .. "\r\n") - end - l15 = "" - line(sH) -end - -if lcOverride then - submitLine() -end - -local function clipEnt(tx) - tx = tx:gsub("\r", "") - local ci = tx:find("\n") or (#tx + 1) - tx = tx:sub(1, ci - 1) - l15 = tx - line(sH) -end - -local control = false -while true do - local e = {coroutine.yield()} - if e[1] == "k.timer" then - while true do - local b, e = tcp.read(neo.readBufSize) - if not b then - if e then - incoming(":Warning: " .. e) - tcp.close() - tcp = dummyTcp - end - break - elseif b == "" then - break - else - tcpBuf = tcpBuf .. b:gsub("\r", "") - while true do - local nlp = tcpBuf:find("\n") - if nlp then - incoming("<" .. tcpBuf:sub(1, nlp - 1)) - tcpBuf = tcpBuf:sub(nlp + 1) - else - break - end - end - end - end - neo.scheduleTimer(os.uptime() + 0.1) - elseif e[1] == "x.neo.pub.window" then - if e[3] == "close" then - if tcp then - tcp.close() - end - return - elseif e[3] == "clipboard" then - clipEnt(e[4]) - elseif e[3] == "key" then - if e[5] == 29 or e[5] == 157 then - control = e[6] - elseif e[6] then - if not control then - if e[4] == 8 or e[4] == 127 then - l15 = unicode.sub(l15, 1, -2) - elseif e[4] == 13 then - submitLine() - elseif e[4] >= 32 then - l15 = l15 .. unicode.char(e[4]) - end - line(sH) - elseif e[5] == 203 and sW > 8 then - sW = sW - 1 - window.setSize(sW, sH) - elseif e[5] == 200 and sH > 2 then - sH = sH - 1 - table.remove(console, 1) - window.setSize(sW, sH) - elseif e[5] == 205 then - sW = sW + 1 - window.setSize(sW, sH) - elseif e[5] == 208 then - sH = sH + 1 - table.insert(console, 1, "") - window.setSize(sW, sH) - end - end - elseif e[3] == "line" then - line(e[4]) - end - end -end diff --git a/repository/apps/app-telnet.lua b/repository/apps/app-telnet.lua new file mode 100644 index 0000000..396f9a1 --- /dev/null +++ b/repository/apps/app-telnet.lua @@ -0,0 +1,96 @@ +-- This is released into the public domain. +-- No warranty is provided, implied or otherwise. + +-- app-telnet.lua : just a utility now +-- Authors: 20kdc + +local inet = neo.requireAccess("c.internet", "internet").list()() + +local _, _, termId = ... +local ok = pcall(function () + assert(string.sub(termId, 1, 12) == "x.neo.pub.t/") +end) + +local termClose + +if not ok then + termId = nil + assert(neo.executeAsync("svc-t", function (res) + termId = res.access + termClose = res.close + neo.scheduleTimer(0) + end, "kmt")) + while true do + if coroutine.yield() == "k.timer" then break end + end +end +local term = neo.requireAccess(termId, "terminal") + +term.write([[ +┎┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┒ +┋ ┃ ╱ ┃╲ ╱┃ ▀▀┃▀▀ ┋ +┋ ┃╳ ┃ ╲ ╱ ┃ ┃ ┋ +┋ ┃ ╲ ┃ ╲╱ ┃ ┃ ┋ +┋ ┋ +┋ KittenOS NEO MUD Terminal ┋ +┖┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┚ +export TERM=ansi.sys <- IMPORTANT!!! +Enter target server:port... +]]) + +local targetBuffer = "" + +neo.scheduleTimer(0) +while true do + local e = {coroutine.yield()} + if e[1] == "k.timer" then + while tcp do + local b, e = tcp.read(neo.readBufSize) + if not b then + if e then + term.write("\nkmt: " .. tostring(e) .. "\n") + tcp.close() + tcp = nil + end + elseif b == "" then + break + else + term.write(b) + end + end + neo.scheduleTimer(os.uptime() + 0.049) + elseif e[1] == "k.procdie" then + if e[3] == term.pid then + break + end + elseif e[1] == termId then + if targetBuffer and e[2] == "data" then + targetBuffer = targetBuffer .. e[3]:gsub("\r", "") + local p = targetBuffer:find("\n") + if p then + local ok, res, rer = pcall(inet.connect, targetBuffer:sub(1, p - 1)) + targetBuffer = targetBuffer:sub(p + 1):gsub("\n", "\r\n") + if not ok then + -- Likes to return this kind + term.write("kmt: " .. tostring(res) .. "\n") + elseif not res then + -- Could theoretically return this kind + term.write("kmt: " .. tostring(rer) .. "\n") + else + -- Hopefully this kind + term.write("kmt: Connecting...\n") + tcp = res + tcp.write(targetBuffer) + targetBuffer = nil + end + end + elseif tcp and e[2] == "data" or e[2] == "telnet" then + tcp.write(e[3]) + end + end +end + +if tcp then + tcp.close() +end + diff --git a/repository/docs/kn-perms b/repository/docs/kn-perms index b999e95..7364246 100644 --- a/repository/docs/kn-perms +++ b/repository/docs/kn-perms @@ -63,7 +63,8 @@ The security check may be aliased to "r.*": Registers a service's API for retrieval via the "x." mechanism. Returns a: - function (function (pkg, pid, send)) + function (function (pkg, pid, send), + secret) While the registration is locked on success, attempting to use it will fail, as no handler has been given. @@ -71,6 +72,11 @@ The security check may be aliased to registration with a callback used for when a process tries to use the registered API. + Unless 'secret' is truthy, a + k.registration event is sent to all + processes; using the secret flag is + useful for a more ad-hoc security + approach. What that API returns goes to the target process. The given "sendSig" function can be diff --git a/repository/docs/repoauthors/app-kmt b/repository/docs/repoauthors/app-kmt deleted file mode 100644 index c7a0318..0000000 --- a/repository/docs/repoauthors/app-kmt +++ /dev/null @@ -1,2 +0,0 @@ -repository/apps/app-kmt.lua: 20kdc, Public Domain - diff --git a/repository/docs/repoauthors/app-telnet b/repository/docs/repoauthors/app-telnet new file mode 100644 index 0000000..1ce8edf --- /dev/null +++ b/repository/docs/repoauthors/app-telnet @@ -0,0 +1,2 @@ +repository/apps/app-telnet.lua: 20kdc, Public Domain + diff --git a/repository/docs/repoauthors/neo-docs b/repository/docs/repoauthors/neo-docs index 2af57e5..e4f863e 100644 --- a/repository/docs/repoauthors/neo-docs +++ b/repository/docs/repoauthors/neo-docs @@ -16,4 +16,6 @@ repository/docs/us-nxapp: 20kdc, Public Domain repository/docs/us-perms: 20kdc, Public Domain repository/docs/us-setti: 20kdc, Public Domain repository/docs/us-clawf: 20kdc, Public Domain +repository/docs/ul-linee: 20kdc, Public Domain +repository/docs/us-termi: 20kdc, Public Domain diff --git a/repository/docs/ul-linee b/repository/docs/ul-linee new file mode 100644 index 0000000..32ab10a --- /dev/null +++ b/repository/docs/ul-linee @@ -0,0 +1,78 @@ +The "lineedit" library provides a + quick way to implement nice line + editing into applications. + +It requires the user to store the + line and the cursor position for the + line, which allows the user to + implement any additional logic for + the application. + +(To clarify, this implies that the + library's functions are stateless.) + +The functions are as follows: + +draw = function (sW, line, curX, rX): + Returns the spantext. + +sW is the width of the area. +line is the safeTextFormatted text. +curX is the cursor in screen units +(usually done as part of the text + formatting) +This is optional, and if not provided + the cursor will not be shown. +rX is an optional camera override. +If not provided, the cursor will be + used; if that is not provided, then + the left side will be shown. + +clamp = function (line, curX): + Returns curX clamped to the line's + length (does not check for < 1). + +key = function (ks, kc, line, curX): + Performs something. + +ks, if truthy, is the unicode.char + of the key event key character. +kc is the key event keycode. +(If feeding in clipboard characters, + use 0 here.) + +line is the text. + +This returns three values, any of + which or all of which may be nil for + a 'no effect' response: + +The new line text, 'lT'. +The new curX, 'lC'. +The extended action, 'lX'. + +The following extended actions exist: + +"l<": The cursor should be warped to + the end of the previous line, if one + exists (if not, do nothing) + +"l>": The cursor should be warped to + the start of the next line, if one + exists (if not, do nothing) + +"w<": This line should be welded to + the previous line, and the cursor + should be placed at the weld point. + +"w>": This line should be welded to + the next line, and the cursor + should be placed at the weld point. + +"nl": The Enter button was pressed. + +-- This is released into + the public domain. +-- No warranty is provided, + implied or otherwise. + diff --git a/repository/docs/us-termi b/repository/docs/us-termi new file mode 100644 index 0000000..2a0b80b --- /dev/null +++ b/repository/docs/us-termi @@ -0,0 +1,114 @@ +The "svc-t" program / "x.neo.pub.t" + permission makes up the terminal + subsystem for KittenOS NEO. + + --- THEORETICAL TERMINALS MODEL --- + +The theoretical model for terminals + in KittenOS NEO is a TELNET client, + using the non-standard behavior of + treating a lack of remote echo as + meaning 'local line editing'. + +To prevent code size going too far, + the shipped terminal supports: + +1. Built-in history as part of the + line editing functionality + +2. ANSI.SYS-compatible display, but + no support for attributes. + +If you really want full support, + write a better terminal application. + +A process starting another process + connected to the same terminal is + advised to wait for that process to + die before continuing reading input. + +The controlling process is whichever + process is supposed to be accepting + user input. This is contextual, and + there is no mechanism to control + this explicitly. + +The controlling process should show + text in response to any user input, + or at least provide some form of + acknowledgement that user input has + been received. + +For convenience, terminal echo is on + by default; this is easily remedied. + + --- ACTUAL USAGE OF TERMINALS --- + +Access control on terminals is looser + than for most permissions, as it has + to be able to be 'sublet' in some + cases, including events. + +As such, the secret flag is set for + terminal registration. + +A terminal program is given a string + argument for the ID of the terminal + to connect to. + +A terminal always has an ID beginning + with "x.neo.pub.t/". ALWAYS CHECK. + +Requiring the responsible access + connects to the terminal. All + terminal programs SHOULD check for + the death of their parent terminal + (via the k.procdie event) and + self-destruct accordingly. + +A program may start svc-t directly. + +In this case, it must pass a function + (resTbl) and may pass a title. + +When the terminal has shown, the + function provided is called with a + table as follows: + +access = "x.neo.pub.t/<...>" +close = function (): close terminal + +The k.kill permission and the close + function are the only ways for a + program to kill a terminal, and the + close function is only given to the + creating process. + +In either case, when the access has + been acquired, the following API is + presented: + +id = "x.neo.pub.t/<...>" +pid = +write = function (text): Writes the + TELNET data to the terminal. + +User input is provided in events: + , "data", + +TELNET commands are provided in: + , "telnet", + +There is a total of one TELNET + command per event, unpadded. + +Notably, intermixing the data part + of the data/telnet events in order + produces the full terminal-to-server + TELNET stream. + +-- This is released into + the public domain. +-- No warranty is provided, + implied or otherwise. +