From 415064480a1e3e7607049aad9eebbb835da6fdaf Mon Sep 17 00:00:00 2001 From: spsf Date: Tue, 24 Feb 2026 02:00:37 -0600 Subject: [PATCH 01/32] Patch the AsyncSyscall v4 exploit from working --- Src/Hyperion-bash/bin/hysh | 27 ++++++--------- Src/Hyperion-kernel/boot/kernel.lua | 2 +- .../lib/modules/hyperion/01_stdlib.kmod | 34 +++++++++++++------ .../lib/modules/hyperion/26_tty.kmod | 11 ++++-- .../lib/modules/hyperion/30_userspace.kmod | 15 ++++++++ .../lib/modules/hyperion/45_hypervisor.kmod | 31 +++++++++++++---- .../lib/modules/hyperion/91_login.kmod | 2 +- 7 files changed, 83 insertions(+), 39 deletions(-) diff --git a/Src/Hyperion-bash/bin/hysh b/Src/Hyperion-bash/bin/hysh index a1bc15a..f7edf76 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/Hyperion-bash/bin/hysh @@ -946,23 +946,16 @@ local function runCommand(command) return end - local text = fs.readAllText(cmdPath) - local program, err = load(text, progName) - if not program then - syscall.devctl(1,"sfgc",2) - local line, rest = tostring(err):match(":(%d+): (.+)$") - if line then printInline(progName..": load error on line "..line..": "); print(rest) - else print(progName..": load error: "..tostring(err)) end - syscall.devctl(1,"sfgc",1); return - end - - local proc = syscall.spawn(function(...) - syscall.open("/dev/tty/tty1","r") - syscall.open("/dev/tty/tty1","w") - syscall.open("/dev/null","w") - local ok2, msg = pcall(program, ...) - if not ok2 then printError(progName, msg) end - end, progName, nil, {table.unpack(args, 2)}) + local proc = syscall.spawn(function() + -- Open standard fds so programs that don't do it themselves work correctly. + syscall.open("/dev/tty/tty1", "r") -- fd 0 stdin + syscall.open("/dev/tty/tty1", "w") -- fd 1 stdout + syscall.open("/dev/null", "w") -- fd 2 stderr + -- exec replaces this coroutine's code with a fresh isolated environment + -- compiled from disk by the kernel (via loadExecutable -> freshUserEnv), + -- so the child cannot share any upvalue or syscall table state with hysh. + syscall.exec(cmdPath, {table.unpack(args, 2)}) + end, progName) while true do local exited, code = syscall.collect(proc) diff --git a/Src/Hyperion-kernel/boot/kernel.lua b/Src/Hyperion-kernel/boot/kernel.lua index 326b527..28a03ef 100644 --- a/Src/Hyperion-kernel/boot/kernel.lua +++ b/Src/Hyperion-kernel/boot/kernel.lua @@ -8,7 +8,7 @@ local computer = args[6] local ifs = args[7] local kernel = {} kernel.LOG_Text="" -kernel.version="HyperionOS V1.2.0" +kernel.version="HyperionOS V1.2.3" kernel.process = "Kernel" kernel.users={[0]="root",[1]="User"} kernel.hostname = "hyperion" diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod index 5fa01c0..997a38f 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod @@ -207,17 +207,29 @@ function toHex(num) return string.format("%X", num) end -syscall = setmetatable({}, { - __index = function(self, name) - return function(...) - local res = table.pack(coroutine.yield("syscall", name, ...)) - if res[1] then - return table.unpack(res, 2, res.n) - else - error(res[2], 2) +local function makeSyscallProxy() + local backing = {} + return setmetatable(backing, { + __index = function(self, name) + local raw = rawget(self, name) + if raw ~= nil then return raw end + return function(...) + local res = table.pack(coroutine.yield("syscall", name, ...)) + if res[1] then + return table.unpack(res, 2, res.n) + else + error(res[2], 2) + end end - end - end -}) + end, + __newindex = function(self, k, v) + rawset(self, k, v) + end, + }) +end + +syscall = makeSyscallProxy() + +_makeSyscallProxy = makeSyscallProxy table.serialize = serialize diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod index 0edd9ec..e2b3123 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod @@ -334,6 +334,13 @@ local function buildKeyMaps() end kernel.processes.cctmond = function() + local _getTasks = function() + local ret = {} + for _, v in pairs(kernel.tasks) do ret[#ret+1] = v.pid end + return ret + end + local _sigsend = kernel.signal.sigsend + local timeout = false while true do local event = {kernel.computer:getMachineEvent()} @@ -352,8 +359,8 @@ kernel.processes.cctmond = function() end if ctrl and charOrKey == apis.keys.c then - for _, task in ipairs(syscall.getTasks()) do - syscall.sigsend(task, 1) + for _, pid in ipairs(_getTasks()) do + _sigsend(pid, 1) end end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod index 2621138..4a12670 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod @@ -55,3 +55,18 @@ local origLoad = load kernel._U = readonly(kernel._G) kernel._U._G = kernel._U kernel._U.load = function(a,b,c,d) return origLoad(a,b,c,d or kernel._U) end + +function kernel.freshUserEnv() + local locals = {} + locals.syscall = _makeSyscallProxy() + + local env = setmetatable(locals, { + __index = kernel._U, + __newindex = function(_, k, v) rawset(locals, k, v) end, + }) + + locals._G = env + locals.load = function(a, b, c, d) return origLoad(a, b, c, d or env) end + + return env +end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod index c23be56..7f414e6 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/45_hypervisor.kmod @@ -11,14 +11,16 @@ local function bit_is_set(num, bit) return math.floor(num / (2 ^ bit)) % 2 == 1 end -local function loadExecutable(path, env) +local function loadExecutable(path) kernel.vfs.access(path, "rx") local fd = kernel.vfs.open(path, "r") local data = kernel.vfs.read(fd, 1024 * 1024 * 4) kernel.vfs.close(fd) - local func, err = load(data, "@" .. path, "t", env or kernel._U) + local env = kernel.freshUserEnv() + + local func, err = load(data, "@" .. path, "t", env) if not func then error("ENOEXEC: " .. tostring(err)) end local meta = kernel.vfs.lstat(path) @@ -92,7 +94,7 @@ local function createTask(func, name, envars, args, tgid, real_uid, eff_uid) end function sys.spawn(func, name, envars, args, tgid) - local caller = kernel.currentTask + local caller = kernel.currentTask local real_uid = caller and caller.uid or kernel.uid local eff_uid = caller and caller.euid or real_uid return createTask(func, name, envars, args, tgid, real_uid, eff_uid) @@ -357,10 +359,25 @@ function kernel.main() if task.sigq and #task.sigq ~= 0 and task.sigh then local coro = coroutine.create(task.sigh) - if kernel.config.preempt then - resumeWithTimeout(coro, task.timeSlice, table.remove(task.sigq, 1)) - else - coroutine.resume(coro, table.remove(task.sigq, 1)) + local sigret = { coroutine.resume(coro, table.remove(task.sigq, 1)) } + while coroutine.status(coro) ~= "dead" do + if sigret[1] == false then break end + if sigret[2] == "syscall" then + local scname = sigret[3] + local sysret + if kernel.syscalls[scname] then + sysret = { xpcall(kernel.syscalls[scname], debug.traceback, table.unpack(sigret, 4)) } + else + sysret = { false, "Unknown syscall: " .. tostring(scname) } + end + if not sysret[1] then + sigret = { coroutine.resume(coro, false, sysret[2]) } + else + sigret = { coroutine.resume(coro, true, table.unpack(sysret, 2)) } + end + else + sigret = { coroutine.resume(coro) } + end end end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod index 621f94b..bc54e12 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod @@ -2,7 +2,7 @@ local kernel = ... kernel.processes.login = function() - local ok, err = pcall(syscall.execspawn, "/bin/login", "login") + local ok, err = pcall(kernel.hpv.execspawn, "/bin/login", "login") if not ok then kernel.log("Failed to exec /bin/login: " .. tostring(err), "ERROR", 2) end From 5b2e5eac655730c6094c25e83445628c74dc6fee Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 24 Feb 2026 17:54:09 -0500 Subject: [PATCH 02/32] added more libs and fixed build script --- Src/Hyperion-core/lib/{store => }/LibDeflate | 0 Src/Hyperion-core/lib/{store => }/deflate | 0 Src/Hyperion-core/lib/http | 0 Src/Hyperion-core/lib/json | 388 +++++++++++++++++ .../lib/modules/hyperion/26_tty.kmod | 400 ------------------ Src/Hyperion-spm/bin/spm | 6 + build.py | 8 +- 7 files changed, 396 insertions(+), 406 deletions(-) rename Src/Hyperion-core/lib/{store => }/LibDeflate (100%) rename Src/Hyperion-core/lib/{store => }/deflate (100%) create mode 100644 Src/Hyperion-core/lib/http create mode 100644 Src/Hyperion-core/lib/json diff --git a/Src/Hyperion-core/lib/store/LibDeflate b/Src/Hyperion-core/lib/LibDeflate similarity index 100% rename from Src/Hyperion-core/lib/store/LibDeflate rename to Src/Hyperion-core/lib/LibDeflate diff --git a/Src/Hyperion-core/lib/store/deflate b/Src/Hyperion-core/lib/deflate similarity index 100% rename from Src/Hyperion-core/lib/store/deflate rename to Src/Hyperion-core/lib/deflate diff --git a/Src/Hyperion-core/lib/http b/Src/Hyperion-core/lib/http new file mode 100644 index 0000000..e69de29 diff --git a/Src/Hyperion-core/lib/json b/Src/Hyperion-core/lib/json new file mode 100644 index 0000000..484c997 --- /dev/null +++ b/Src/Hyperion-core/lib/json @@ -0,0 +1,388 @@ +--:Minify:-- +-- json.lua +-- +-- Copyright (c) 2020 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.2" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\", + [ "\"" ] = "\"", + [ "\b" ] = "b", + [ "\f" ] = "f", + [ "\n" ] = "n", + [ "\r" ] = "r", + [ "\t" ] = "t", +} + +local escape_char_map_inv = { [ "/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if rawget(val, 1) ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(1, 4), 16 ) + local n2 = tonumber( s:sub(7, 10), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local res = "" + local j = i + 1 + local k = j + + while j <= #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + + elseif x == 92 then -- `\`: Escape + res = res .. str:sub(k, j - 1) + j = j + 1 + local c = str:sub(j, j) + if c == "u" then + local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) + or str:match("^%x%x%x%x", j + 1) + or decode_error(str, j - 1, "invalid unicode escape in string") + res = res .. parse_unicode_escape(hex) + j = j + #hex + else + if not escape_chars[c] then + decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") + end + res = res .. escape_char_map_inv[c] + end + k = j + 1 + + elseif x == 34 then -- `"`: End of string + res = res .. str:sub(k, j - 1) + return res, j + 1 + end + + j = j + 1 + end + + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json \ No newline at end of file diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod index e2b3123..e6c07db 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod @@ -1,402 +1,2 @@ -- :Minify:-- local kernel = ... -local apis = kernel.apis -local native = apis.peripheral -local sides = {"top", "bottom", "left", "right", "front", "back"} -local peripheral={} - -function peripheral.getNames() - local results = {} - for n = 1, #sides do - local side = sides[n] - if native.isPresent(side) then - table.insert(results, side) - if native.hasType(side, "peripheral_hub") then - local remote = native.call(side, "getNamesRemote") - for _, name in ipairs(remote) do - table.insert(results, name) - end - end - end - end - return results -end - -function peripheral.isPresent(name) - if native.isPresent(name) then - return true - end - - for n = 1, #sides do - local side = sides[n] - if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then - return true - end - end - return false -end - -function peripheral.getType(peripheral) - if type(peripheral) == "string" then - if native.isPresent(peripheral) then - return native.getType(peripheral) - end - for n = 1, #sides do - local side = sides[n] - if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then - return native.call(side, "getTypeRemote", peripheral) - end - end - return nil - else - local mt = getmetatable(peripheral) - if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then - error("bad argument #1 (table is not a peripheral)", 2) - end - return table.unpack(mt.types) - end -end - -function peripheral.hasType(peripheral, peripheral_type) - if type(peripheral) == "string" then - if native.isPresent(peripheral) then - return native.hasType(peripheral, peripheral_type) - end - for n = 1, #sides do - local side = sides[n] - if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then - return native.call(side, "hasTypeRemote", peripheral, peripheral_type) - end - end - return nil - else - local mt = getmetatable(peripheral) - if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then - error("bad argument #1 (table is not a peripheral)", 2) - end - return mt.types[peripheral_type] ~= nil - end -end - -function peripheral.getMethods(name) - if native.isPresent(name) then - return native.getMethods(name) - end - for n = 1, #sides do - local side = sides[n] - if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then - return native.call(side, "getMethodsRemote", name) - end - end - return nil -end - -function peripheral.getName(peripheral) - local mt = getmetatable(peripheral) - if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then - error("bad argument #1 (table is not a peripheral)", 2) - end - return mt.name -end - -function peripheral.call(name, method, ...) - if native.isPresent(name) then - return native.call(name, method, ...) - end - - for n = 1, #sides do - local side = sides[n] - if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then - return native.call(side, "callRemote", name, method, ...) - end - end - return nil -end - -function peripheral.wrap(name) - local methods = peripheral.getMethods(name) - if not methods then - return nil - end - - local types = { peripheral.getType(name) } - for i = 1, #types do types[types[i]] = true end - local result = setmetatable({}, { - __name = "peripheral", - name = name, - type = types[1], - types = types, - }) - for _, method in ipairs(methods) do - result[method] = function(...) - return peripheral.call(name, method, ...) - end - end - return result -end - -function peripheral.find(ty, filter) - local results = {} - for _, name in ipairs(peripheral.getNames()) do - if peripheral.hasType(name, ty) then - local wrapped = peripheral.wrap(name) - if filter == nil or filter(name, wrapped) then - table.insert(results, wrapped) - end - end - end - return table.unpack(results) -end - -local icolors = { - [0x1] = 1, -- #000000 - [0x2] = 2, -- #FFFFFF - [0x4] = 3, -- #FF0000 - [0x8] = 4, -- #00FF00 - [0x10] = 5, -- #0000FF - [0x20] = 6, -- #00FFFF - [0x40] = 7, -- #FF00FF - [0x80] = 8, -- #FFFF00 - [0x100] = 9, -- #FF6D00 - [0x200] = 10, -- #6DFF55 - [0x400] = 11, -- #24FFFF - [0x800] = 12, -- #924900 - [0x1000] = 13, -- #6D6D55 - [0x2000] = 14, -- #DBDBAA - [0x4000] = 15, -- #6D00FF - [0x8000] = 16 -- #B6FF00 -} - -local colors = { - 0x0001, -- #000000 - 0x0002, -- #FFFFFF - 0x0004, -- #FF0000 - 0x0008, -- #00FF00 - 0x0010, -- #0000FF - 0x0020, -- #00FFFF - 0x0040, -- #FF00FF - 0x0080, -- #FFFF00 - 0x0100, -- #FF6D00 - 0x0200, -- #6DFF55 - 0x0400, -- #24FFFF - 0x0800, -- #924900 - 0x1000, -- #6D6D55 - 0x2000, -- #DBDBAA - 0x4000, -- #6D00FF - 0x8000 -- #B6FF00 -} - -local function write(text, term) - local x, y = term.getCursorPos() - local w, h = term.getSize() - - for i = 1, #text do - local c = text:sub(i, i) - - if c == "\n" then - y = y + 1 - x = 1 - elseif c == "\t" then - local tabSize = 4 - local spaces = tabSize - ((x - 1) % tabSize) - term.write(string.rep(" ", spaces)) - x = x + spaces - elseif c == "\b" then - if x > 1 then - x = x - 1 - term.setCursorPos(x, y) - term.write(" ") - term.setCursorPos(x, y) - end - else - if x <= w and y <= h then - term.setCursorPos(x, y) - term.write(c) - x = x + 1 - end - end - - if x > w then - x = 1 - y = y + 1 - end - - if y - 1 >= h then - term.scroll(1) - y = h - term.setCursorPos(x, y) - end - end - - term.setCursorPos(x, y) -end - -kernel.devfs.data.tty={} -local ctrl,alt = false, false - -local function serializeBool(bool) - if bool then - return "T" - else - return "F" - end -end - -local function newtty(obj, id, ev) - kernel.devfs.data["tty"][id] = function(op, mode) - if op=="type" then - return "character device" - elseif op=="open" then - local h = { - read=function(amount) - local rv="" - for i=1, amount or 1 do - local event = {ev()} - if event[1] then - rv=rv..event[1] - end - end - if rv=="" then rv=nil end - return rv - end, - write=function(content) - write(content, obj) - end, - size=function() - local s={obj.getSize()} - return table.concat(s,";") - end, - clear=function() - obj.clear() - obj.setCursorPos(1,1) - end, - gpos=function() - local s={obj.getCursorPos()} - return table.concat(s,";") - end, - spos=function(x,y) - return obj.setCursorPos(x,y) - end, - sfgc=function(c) - return obj.setTextColor(colors[c]) - end, - sbgc=function(c) - return obj.setBackgroundColor(colors[c]) - end, - gfgc=function() - return icolors[obj.getTextColor()] - end, - gbgc=function() - return icolors[obj.getBackgroundColor()] - end, - gctrl=function() - return serializeBool(ctrl)..";"..serializeBool(alt) - end - } - if mode=="rw" then - return h - elseif mode=="r" then - h["write"]=nil - return h - elseif mode=="w" then - h["read"]=nil - return h - end - end - end -end - -local fifo = kernel.newFifo() - -local ctrlLetterKeys = nil -local specialKeys = nil - -local function buildKeyMaps() - if ctrlLetterKeys then return end - local k = apis.keys - ctrlLetterKeys = {} - local letters = { - {k.a,1},{k.b,2},{k.c,3},{k.d,4},{k.e,5},{k.f,6},{k.g,7}, - {k.h,8}, {k.j,10},{k.k,11},{k.l,12},{k.m,13}, - {k.n,14},{k.o,15},{k.p,16}, - {k.u,21},{k.v,22},{k.w,23},{k.x,24},{k.y,25},{k.z,26}, - } - for _, pair in ipairs(letters) do - ctrlLetterKeys[pair[1]] = string.char(pair[2]) - end - specialKeys = { - [k.home] = "\1", - [k.delete] = "\4", - [k["end"]] = "\5", - [k.pageUp] = "\2", - [k.pageDown]= "\12", - } -end - -kernel.processes.cctmond = function() - local _getTasks = function() - local ret = {} - for _, v in pairs(kernel.tasks) do ret[#ret+1] = v.pid end - return ret - end - local _sigsend = kernel.signal.sigsend - - local timeout = false - while true do - local event = {kernel.computer:getMachineEvent()} - - if event[1] then - local eventType = event[1] - local charOrKey = event[3] - - buildKeyMaps() - - if eventType == "keyPressed" then - if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then - ctrl = true - elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then - alt = true - end - - if ctrl and charOrKey == apis.keys.c then - for _, pid in ipairs(_getTasks()) do - _sigsend(pid, 1) - end - end - - if ctrl and ctrlLetterKeys[charOrKey] then - fifo.push(ctrlLetterKeys[charOrKey]) - end - - if specialKeys[charOrKey] then - fifo.push(specialKeys[charOrKey]) - end - elseif eventType == "keyReleased" then - if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then - ctrl = false - elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then - alt = false - end - elseif eventType == "keyTyped" then - if charOrKey then fifo.push(charOrKey) end - end - - timeout = false - else - timeout = true - end - - if timeout then - sleep(0.05) - end - end -end - -newtty(apis.term, "tty1", fifo.pop) - - -for i,v in ipairs({peripheral.find("monitor")}) do - v.setTextScale(.5) - v.write("Initializing...") - newtty(v,"tty"..tostring(i+1),function () end) -end \ No newline at end of file diff --git a/Src/Hyperion-spm/bin/spm b/Src/Hyperion-spm/bin/spm index e69de29..9756ac9 100644 --- a/Src/Hyperion-spm/bin/spm +++ b/Src/Hyperion-spm/bin/spm @@ -0,0 +1,6 @@ +local args={...} + +local json=require("json") +local http=require("http") +local rootRepo="https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/spm/spm.src" + diff --git a/build.py b/build.py index f6e3b23..83af105 100644 --- a/build.py +++ b/build.py @@ -88,7 +88,6 @@ def process_root(src_root: Path, out_root: Path, minify: bool): def install_bootloader(arch: str, release: bool): boot_dir = BUILD_ROOT / "$" / ARCH_BOOT_DIR[arch] - boot_lua = boot_dir / "boot.lua" eeprom = boot_dir / "eeprom" for src in (boot_lua, eeprom): @@ -96,9 +95,6 @@ def install_bootloader(arch: str, release: bool): print(f" ! Bootloader file not found: {src}", file=sys.stderr) sys.exit(1) - print(f" Installing: boot.lua -> Build/boot.lua") - shutil.copy2(boot_lua, BUILD_ROOT / "boot.lua") - eeprom_dst_name = "startup.lua" if release else "eeprom" print(f" Installing: eeprom -> Build/{eeprom_dst_name}") shutil.copy2(eeprom, BUILD_ROOT / eeprom_dst_name) @@ -203,7 +199,7 @@ def _make_firstboot_kmod(users): lines.append("do") lines.append(" local ok, err = pcall(function()") - lines.append(" kernel.vfs.remove('/lib/modules/hyperion/50_firstboot_users.kmod')") + lines.append(" kernel.vfs.remove('/lib/modules/Hyperion/50_firstboot_users.kmod')") lines.append(" end)") lines.append(" if not ok then") lines.append(" kernel.log('FIRSTBOOT: could not self-delete: ' .. tostring(err), 'WARN')") @@ -215,7 +211,7 @@ def _make_firstboot_kmod(users): def inject_makeusers(users, arch): base = BUILD_ROOT / "$" if arch else BUILD_ROOT - kmod_path = base / "lib" / "modules" / "hyperion" / "50_firstboot_users.kmod" + kmod_path = base / "lib" / "modules" / "Hyperion" / "50_firstboot_users.kmod" kmod_path.parent.mkdir(parents=True, exist_ok=True) kmod_path.write_text(_make_firstboot_kmod(users), encoding="utf-8") print(" Wrote first-boot user setup -> " + str(kmod_path.relative_to(BUILD_ROOT))) From 0eabfebd0f3c0b6382a6e0eb583f24101a1e735b Mon Sep 17 00:00:00 2001 From: Astronand Date: Wed, 25 Feb 2026 08:01:28 -0500 Subject: [PATCH 03/32] fixed new ghxx exploit --- Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod | 1 + Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod | 1 + 2 files changed, 2 insertions(+) diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod index 997a38f..fc5f3e2 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod @@ -225,6 +225,7 @@ local function makeSyscallProxy() __newindex = function(self, k, v) rawset(self, k, v) end, + __metatable=false }) end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod index 4a12670..346f575 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod @@ -63,6 +63,7 @@ function kernel.freshUserEnv() local env = setmetatable(locals, { __index = kernel._U, __newindex = function(_, k, v) rawset(locals, k, v) end, + __metatable=false }) locals._G = env From 34b89a8e3498497e84b27aab4f05aae8b9fb7a83 Mon Sep 17 00:00:00 2001 From: Astronand Date: Wed, 25 Feb 2026 08:04:53 -0500 Subject: [PATCH 04/32] fixed build script --- build.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build.py b/build.py index 83af105..50e91f6 100644 --- a/build.py +++ b/build.py @@ -90,11 +90,6 @@ def install_bootloader(arch: str, release: bool): boot_dir = BUILD_ROOT / "$" / ARCH_BOOT_DIR[arch] eeprom = boot_dir / "eeprom" - for src in (boot_lua, eeprom): - if not src.exists(): - print(f" ! Bootloader file not found: {src}", file=sys.stderr) - sys.exit(1) - eeprom_dst_name = "startup.lua" if release else "eeprom" print(f" Installing: eeprom -> Build/{eeprom_dst_name}") shutil.copy2(eeprom, BUILD_ROOT / eeprom_dst_name) From 02e7b3897c18159eafaf8708e6a144b22e417d67 Mon Sep 17 00:00:00 2001 From: Astronand Date: Wed, 25 Feb 2026 09:03:32 -0500 Subject: [PATCH 05/32] fixed tty naming --- Src/Hyperion-bash/bin/hysh | 8 ++++---- Src/Hyperion-bash/bin/login | 4 ++-- .../lib/modules/CC-Tweaked/25_tty.kmod | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Src/Hyperion-bash/bin/hysh b/Src/Hyperion-bash/bin/hysh index f7edf76..0e1b051 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/Hyperion-bash/bin/hysh @@ -1,6 +1,6 @@ --:Minify:-- -syscall.open("/dev/tty/tty1","r") --stdin (Device 0) -syscall.open("/dev/tty/tty1","w") --stdout (Device 1) +syscall.open("/dev/tty/1","r") --stdin (Device 0) +syscall.open("/dev/tty/1","w") --stdout (Device 1) syscall.open("/dev/null","w") --stderr (device 2) local success, errorMsg = xpcall(function() @@ -948,8 +948,8 @@ local function runCommand(command) local proc = syscall.spawn(function() -- Open standard fds so programs that don't do it themselves work correctly. - syscall.open("/dev/tty/tty1", "r") -- fd 0 stdin - syscall.open("/dev/tty/tty1", "w") -- fd 1 stdout + syscall.open("/dev/tty/1", "r") -- fd 0 stdin + syscall.open("/dev/tty/1", "w") -- fd 1 stdout syscall.open("/dev/null", "w") -- fd 2 stderr -- exec replaces this coroutine's code with a fresh isolated environment -- compiled from disk by the kernel (via loadExecutable -> freshUserEnv), diff --git a/Src/Hyperion-bash/bin/login b/Src/Hyperion-bash/bin/login index a81c82e..50c3a2e 100644 --- a/Src/Hyperion-bash/bin/login +++ b/Src/Hyperion-bash/bin/login @@ -1,6 +1,6 @@ --:Minify:-- -syscall.open("/dev/tty/tty1", "r") --stdin (fd 0) -syscall.open("/dev/tty/tty1", "w") --stdout (fd 1) +syscall.open("/dev/tty/1", "r") --stdin (fd 0) +syscall.open("/dev/tty/1", "w") --stdout (fd 1) syscall.open("/dev/null", "w") --stderr (fd 2) diff --git a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod index 9c5e168..4a86f4a 100644 --- a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod +++ b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod @@ -354,10 +354,10 @@ kernel.processes.cctmond = function() end end -newtty(apis.term, "TTY1", fifo.pop) +newtty(apis.term, "1", fifo.pop) for i,v in ipairs({peripheral.find("monitor")}) do v.setTextScale(.5) v.write("Initializing...") - newtty(v,"TTY"..tostring(i+1),function () end) + newtty(v,tostring(i+1),function () end) end \ No newline at end of file From a6550aa069870a394f29455538a2a85c4523f7f9 Mon Sep 17 00:00:00 2001 From: Astronand Date: Wed, 25 Feb 2026 11:41:41 -0500 Subject: [PATCH 06/32] fixed minify header and build script --- .../lib/modules/hyperion/01_stdlib.kmod | 2 +- .../lib/modules/hyperion/10_vfs.kmod | 2 +- .../lib/modules/hyperion/11_require.kmod | 2 +- .../lib/modules/hyperion/12_tmpfs.kmod | 1 + .../lib/modules/hyperion/13_loopdev.kmod | 2 +- .../lib/modules/hyperion/14_keventd.kmod | 22 ------------------- .../lib/modules/hyperion/20_socket.kmod | 2 +- .../lib/modules/hyperion/26_tty.kmod | 5 ++++- .../lib/modules/hyperion/30_userspace.kmod | 2 +- .../lib/modules/hyperion/50_gpio.kmod | 1 + .../lib/modules/hyperion/70_stdlibadv.kmod | 2 +- .../lib/modules/hyperion/90_init.kmod | 2 +- .../lib/modules/hyperion/91_login.kmod | 2 +- .../lib/modules/hyperion/92_permissions.kmod | 2 +- .../lib/modules/hyperion/99_final.kmod | 1 + build.py | 2 +- 16 files changed, 18 insertions(+), 34 deletions(-) delete mode 100644 Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod index fc5f3e2..acec657 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... kernel.allowGlobalOverwrites = true diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod index 51f3301..e8d7307 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local vfs = {} kernel.vfs = vfs diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod index ebb7a40..7068837 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/11_require.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local cache = {} kernel.searchpaths = { diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod index 371409d..eecb14f 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_tmpfs.kmod @@ -1,3 +1,4 @@ +--:Minify:-- local kernel = ... local proxy = {} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod index f1dcf2c..5a50bcc 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/13_loopdev.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- -- Loop device driver: -- -- BIND (directory) - re-routes VFS calls into a host directory subtree. diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod deleted file mode 100644 index 1ee28c1..0000000 --- a/Src/Hyperion-kernel/lib/modules/hyperion/14_keventd.kmod +++ /dev/null @@ -1,22 +0,0 @@ ----- :Minify:-- ---local kernel = ... --- ---local timeout = false ---kernel.processes.keventd = function() --- while true do --- local event = {kernel.computer:getMachineEvent()} --- if event[1] then --- if event[1] == "keyTyped" then --- if event[3] == "\x1b^s" then --- kernel.shutdown() --- elseif event[3] == "\x1b^r" then --- kernel.reboot() --- end --- end --- timeout = false --- else --- timeout = true --- end --- if timeout then sleep(.05) end --- end ---end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod index c95e70b..f1a2380 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/20_socket.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- -- Supports: -- AF_UNIX - local IPC via /var/run/*.sock paths -- AF_INET - network sockets with three backends: diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod index e6c07db..9c82644 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod @@ -1,2 +1,5 @@ --- :Minify:-- +--:Minify:-- local kernel = ... +kernel.vfs.open("/dev/tty/1","r") +kernel.vfs.open("/dev/tty/1","w") +kernel.vfs.open("/dev/null","w") \ No newline at end of file diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod index 346f575..a7a2e90 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/30_userspace.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local args = {...} local kernel = args[1] kernel._G = _G diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod index fbb705c..1e43f63 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/50_gpio.kmod @@ -1,3 +1,4 @@ +--:Minify:-- local kernel=... local sysc=kernel.syscalls kernel.gpio={} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod index a8148cb..6f768bc 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/70_stdlibadv.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... function print(...) local args = {...} diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod index 2cfc4ef..0e94960 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/90_init.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... kernel.log("Loading init system...") kernel.log("InitPath: " .. kernel.config.initPath) diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod index bc54e12..229b9b2 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/91_login.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... kernel.processes.login = function() diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod index cb84c38..dd38e96 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local P = kernel.vfs.P diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod index 15ee521..5aba7bd 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod @@ -1,2 +1,3 @@ +--:Minify:-- local kernel = ... kernel.allowGlobalOverwrites = false \ No newline at end of file diff --git a/build.py b/build.py index 50e91f6..e04aeea 100644 --- a/build.py +++ b/build.py @@ -72,7 +72,7 @@ def process_root(src_root: Path, out_root: Path, minify: bool): if minify and has_minify_header(src): print(" > Minifying") result = subprocess.run( - ["luamin", "-f", str(src)], + ["luamin.cmd", "-f", str(src)], capture_output=True, text=True ) if result.returncode != 0: From 17453983adf73a5db2c3423d1f28334ecf8cbddb Mon Sep 17 00:00:00 2001 From: spsf Date: Sun, 1 Mar 2026 00:16:37 -0600 Subject: [PATCH 07/32] Path traversal fixes, 26_tty removal, ctrl+key fixes --- Src/Hyperion-bash/bin/hysh | 243 +++++++++++++- Src/Hyperion-bash/bin/micro | 28 +- .../lib/modules/CC-Tweaked/25_tty.kmod | 43 ++- .../lib/modules/hyperion/10_vfs.kmod | 305 ++++++++++++------ .../lib/modules/hyperion/26_tty.kmod | 5 - 5 files changed, 501 insertions(+), 123 deletions(-) delete mode 100644 Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod diff --git a/Src/Hyperion-bash/bin/hysh b/Src/Hyperion-bash/bin/hysh index 0e1b051..4e0d50d 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/Hyperion-bash/bin/hysh @@ -810,6 +810,160 @@ builtinCmds.df = function(...) end end +local function listDir(dir, prefix) + local ok, entries = pcall(syscall.listdir, dir) + if not ok or not entries then return {} end + local results = {} + for _, e in ipairs(entries) do + if prefix == "" or e:sub(1, #prefix) == prefix then + local fullpath = (dir == "/" and "/" or dir.."/")..e + local t = syscall.type(fullpath) + results[#results+1] = t == "directory" and (e.."/") or e + end + end + table.sort(results) + return results +end + +local function listCommands(prefix) + local results = {} + local seen = {} + for name in pairs(builtinCmds) do + if prefix == "" or name:sub(1, #prefix) == prefix then + if not seen[name] then results[#results+1] = name; seen[name] = true end + end + end + local paths = string.split(syscall.getEnviron("PATH") or "/bin/", ":") + for _, p in ipairs(paths) do + local ok, entries = pcall(syscall.listdir, p) + if ok and entries then + for _, e in ipairs(entries) do + local fullpath = (p:sub(-1)=="/" and p or p.."/")..e + local xok = pcall(syscall.access, fullpath, "x") + if xok and (prefix == "" or e:sub(1, #prefix) == prefix) then + if not seen[e] then results[#results+1] = e; seen[e] = true end + end + end + end + end + table.sort(results) + return results +end + +local function commonPrefix(list) + if #list == 0 then return "" end + local pre = list[1] + for i = 2, #list do + local s = list[i] + local j = 1 + while j <= #pre and j <= #s and pre:sub(j,j) == s:sub(j,j) do j = j+1 end + pre = pre:sub(1, j-1) + if pre == "" then return "" end + end + return pre +end + +local function showCompletions(completions) + syscall.write(1, "\n") + local W = 51 + local maxlen = 0 + for _, c in ipairs(completions) do if #c > maxlen then maxlen = #c end end + local colw = maxlen + 2 + local cols = math.max(1, math.floor(W / colw)) + local i = 0 + for _, c in ipairs(completions) do + local padded = c .. string.rep(" ", colw - #c) + syscall.write(1, padded) + i = i + 1 + if i % cols == 0 then syscall.write(1, "\n") end + end + if i % cols ~= 0 then syscall.write(1, "\n") end +end + +local function parseForCompletion(input, cursorPos) + local sofar = input:sub(1, cursorPos - 1) + local tokens = {} + for tok in sofar:gmatch("%S+") do tokens[#tokens+1] = tok end + local partial = sofar:match("%S+$") or "" + local isFirst = (#tokens == 0) or (sofar:sub(-1) ~= " " and #tokens == 1) + + local partialDir, partialBase + if partial:find("/") then + partialDir = partial:match("^(.*/)") or "/" + partialBase = partial:match("[^/]*$") or "" + else + partialDir = nil + partialBase = partial + end + + return tokens, partial, isFirst, partialDir, partialBase +end + +local tabState = { last = nil, idx = 0, list = {} } + +local function doTabComplete(input, cursorPos) + local tokens, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos) + + local candidates + if isFirst then + if partialDir then + local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + candidates = listDir(dir, partialBase) + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + else + candidates = listCommands(partialBase) + end + else + local dir, base + if partialDir then + dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + base = partialBase + else + dir = syscall.getcwd() + base = partialBase + end + candidates = listDir(dir, base) + if partialDir then + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + end + end + + if #candidates == 0 then + return input, cursorPos, false + end + + local context = input.."\0"..tostring(cursorPos) + if tabState.last ~= context then + tabState.last = context + tabState.idx = 0 + tabState.list = candidates + end + + if #candidates == 1 then + local completed = candidates[1] + local before = input:sub(1, cursorPos - 1 - #partial) + local after = input:sub(cursorPos) + local newInput = before .. completed .. after + local newCursor = #before + #completed + 1 + tabState.last = nil + return newInput, newCursor, true + end + + local pre = commonPrefix(candidates) + if #pre > #partial then + local before = input:sub(1, cursorPos - 1 - #partial) + local after = input:sub(cursorPos) + local newInput = before .. pre .. after + local newCursor = #before + #pre + 1 + tabState.last = newInput.."\0"..tostring(newCursor) + tabState.list = candidates + return newInput, newCursor, true + else + showCompletions(candidates) + return input, cursorPos, true + end +end + local function getUserInput() syscall.devctl(1,"sfgc",3) syscall.write(1, userhost) @@ -829,6 +983,41 @@ local function getUserInput() local history = 0 local dirty = true + local function getGhostSuffix() + if #input == 0 then return "" end + local _, partial, isFirst, partialDir, partialBase = parseForCompletion(input, cursorPos) + if cursorPos ~= #input + 1 then return "" end + local candidates + if isFirst then + if partialDir then + local dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + candidates = listDir(dir, partialBase) + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + else + candidates = listCommands(partialBase) + end + else + local dir, base + if partialDir then + dir = partialDir:sub(1,1) == "/" and partialDir or (syscall.getcwd().."/"..partialDir) + base = partialBase + else + dir = syscall.getcwd() + base = partialBase + end + candidates = listDir(dir, base) + if partialDir then + for i, c in ipairs(candidates) do candidates[i] = partialDir..c end + end + end + if #candidates == 0 then return "" end + local pre = commonPrefix(candidates) + if #pre > #partial then + return pre:sub(#partial + 1) + end + return "" + end + local function redraw() syscall.devctl(1,"spos",curOffsetX,curOffsetY) syscall.write(1, string.sub(input, 1, cursorPos-1)) @@ -841,21 +1030,31 @@ local function getUserInput() syscall.write(1, string.sub(input, cursorPos, cursorPos)) end syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16) - syscall.write(1, string.sub(input, cursorPos+1) .. " ") + local after = string.sub(input, cursorPos+1) + syscall.write(1, after) + local ghost = getGhostSuffix() + if #ghost > 0 then + syscall.devctl(1,"sfgc",14) + syscall.write(1, ghost) + syscall.devctl(1,"sfgc",1) + syscall.write(1, " ") + else + syscall.write(1, " ") + end end while true do local key = syscall.read(0) if key and key ~= "" then - if key=="\19" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end - elseif key=="\20" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end - elseif key=="\17" then + if key=="" then if cursorPos>1 then cursorPos=cursorPos-1;dirty=true end + elseif key=="" then if cursorPos<=#input then cursorPos=cursorPos+1;dirty=true end + elseif key=="" then if history<#commandHistory then history=history+1 input=commandHistory[#commandHistory-history+1] cursorPos=#input+1;dirty=true end - elseif key=="\18" then + elseif key=="" then if history>1 then history=history-1 input=commandHistory[#commandHistory-history+1] @@ -863,6 +1062,38 @@ local function getUserInput() elseif history==1 then history=0;input="";cursorPos=1;dirty=true end + elseif key=="" then cursorPos=1;dirty=true + elseif key=="" then cursorPos=#input+1;dirty=true + elseif key=="[3~" then + if cursorPos<=#input then + input=string.sub(input,1,cursorPos-1)..string.sub(input,cursorPos+1) + dirty=true + end + elseif key=="\t" then + local newInput, newCursor, needsRedraw = doTabComplete(input, cursorPos) + if needsRedraw then + input = newInput; cursorPos = newCursor + local posStr = syscall.devctl(1, "gpos") + local sep = posStr:find(";") + local px = tonumber(posStr:sub(1, sep-1)) + local py = tonumber(posStr:sub(sep+1)) + if px > 1 then + syscall.devctl(1,"spos",1,py) + local tsz = syscall.devctl(1,"size") or "51;19" + local tw = tonumber(tsz:match("^(%d+)")) or 51 + syscall.write(1, string.rep(" ", tw)) + syscall.devctl(1,"spos",1,py) + end + syscall.devctl(1,"sfgc",3); syscall.write(1, userhost) + syscall.devctl(1,"sfgc",1); syscall.write(1, ":") + syscall.devctl(1,"sfgc",10); syscall.write(1, syscall.getcwd()) + syscall.devctl(1,"sfgc",1); syscall.write(1, "$ ") + posStr = syscall.devctl(1, "gpos") + sep = posStr:find(";") + curOffsetX = tonumber(posStr:sub(1, sep-1)) + curOffsetY = tonumber(posStr:sub(sep+1)) + dirty = true + end elseif key=="\b" then if cursorPos>1 then input=string.sub(input,1,cursorPos-2)..string.sub(input,cursorPos) @@ -873,7 +1104,7 @@ local function getUserInput() syscall.devctl(1,"spos",curOffsetX,curOffsetY) syscall.write(1, input.." \n") return input - else + elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then input=string.sub(input,1,cursorPos-1)..key..string.sub(input,cursorPos) cursorPos=cursorPos+1;dirty=true end diff --git a/Src/Hyperion-bash/bin/micro b/Src/Hyperion-bash/bin/micro index 3a21d9d..c73d68c 100644 --- a/Src/Hyperion-bash/bin/micro +++ b/Src/Hyperion-bash/bin/micro @@ -223,7 +223,7 @@ local function prompt(label, default) tbg(16); tfg(1) local key = syscall.read(0) if not key or key == "" then sleep(0.02) - elseif key == "\27" then return nil + elseif key == "" then return nil elseif key == "\n" then return inp elseif key == "\b" then if #inp > 0 then inp = inp:sub(1,-2) end else @@ -359,31 +359,29 @@ while running do local key = syscall.read(0) if key and key ~= "" then local b = key:byte(1) - if key == "\17" then moveCursorUp(map); dirty=true - elseif key == "\18" then moveCursorDown(map); dirty=true - elseif key == "\19" then - if cx > 1 then cx=cx-1 - elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end - dirty=true - elseif key == "\20" then + if key == "" then moveCursorUp(map); dirty=true + elseif key == "" then moveCursorDown(map); dirty=true + elseif key == "" then if cx <= #lines[cy] then cx=cx+1 elseif cy < #lines then cy=cy+1; cx=1 end dirty=true + elseif key == "" then + if cx > 1 then cx=cx-1 + elseif cy > 1 then cy=cy-1; cx=#lines[cy]+1 end + dirty=true + elseif key == "" then cx=1; dirty=true + elseif key == "" then cx=#lines[cy]+1; dirty=true + elseif key == "[5~" then for _=1,ROWS do moveCursorUp(map) end; dirty=true + elseif key == "[6~" then for _=1,ROWS do moveCursorDown(map) end; dirty=true + elseif key == "[3~" then delRight() elseif key == "\n" then newline() elseif key == "\b" then delLeft() elseif key == "\t" then for _=1,4 do insChar(" ") end - elseif b == 1 then cx=1; dirty=true - elseif b == 2 then - for _=1,ROWS do moveCursorUp(map) end; dirty=true - elseif b == 4 then delRight() - elseif b == 5 then cx=#lines[cy]+1; dirty=true elseif b == 6 then local p=prompt("Find: ",sPat); dirty=true if p then sPat=p; sLine=0; findNext() end elseif b == 7 then goToLine() elseif b == 11 then cutLine() - elseif b == 12 then - for _=1,ROWS do moveCursorDown(map) end; dirty=true elseif b == 14 then if sPat=="" then local p=prompt("Find: ",""); dirty=true diff --git a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod index 4a86f4a..8b586e1 100644 --- a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod +++ b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod @@ -1,4 +1,4 @@ --- :Minify:-- +--:Minify:-- local kernel = ... local apis = kernel.apis local native = apis.peripheral @@ -317,7 +317,18 @@ kernel.processes.cctmond = function() local eventType = event[1] local charOrKey = event[3] - -- Update modifier keys + local ctrlKeyMap = { + [apis.keys.a]=1, [apis.keys.b]=2, [apis.keys.c]=3, + [apis.keys.d]=4, [apis.keys.e]=5, [apis.keys.f]=6, + [apis.keys.g]=7, [apis.keys.h]=8, [apis.keys.i]=9, + [apis.keys.j]=10, [apis.keys.k]=11, [apis.keys.l]=12, + [apis.keys.m]=13, [apis.keys.n]=14, [apis.keys.o]=15, + [apis.keys.p]=16, [apis.keys.q]=17, [apis.keys.r]=18, + [apis.keys.s]=19, [apis.keys.t]=20, [apis.keys.u]=21, + [apis.keys.v]=22, [apis.keys.w]=23, [apis.keys.x]=24, + [apis.keys.y]=25, [apis.keys.z]=26, + } + if eventType == "keyPressed" then if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then ctrl = true @@ -325,11 +336,31 @@ kernel.processes.cctmond = function() alt = true end - -- Handle Ctrl+C - if ctrl and charOrKey == apis.keys.c then - for _, task in ipairs(syscall.getTasks()) do - syscall.sigsend(task, 1) -- SIGINT + if ctrl then + local ctrlByte = ctrlKeyMap[charOrKey] + if ctrlByte then + if ctrlByte == 3 then + for _, task in ipairs(syscall.getTasks()) do + syscall.sigsend(task, 1) + end + else + fifo.push(string.char(ctrlByte)) + end end + else + local specialKeyMap = { + [apis.keys.up] = "", + [apis.keys.down] = "", + [apis.keys.right] = "", + [apis.keys.left] = "", + [apis.keys.home] = "", + [apis.keys["end"]] = "", + [apis.keys.pageUp] = "[5~", + [apis.keys.pageDown] = "[6~", + [apis.keys.delete] = "[3~", + } + local special = specialKeyMap[charOrKey] + if special then fifo.push(special) end end elseif eventType == "keyReleased" then diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod index e8d7307..fd63bc9 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod @@ -113,57 +113,23 @@ end local SAFE_COMPONENT_PATTERN = "^[A-Za-z0-9_.+%-%@%(%)%[%]]+$" -local function normalizePath(path) - local task = kernel.currentTask - local cwd = task.cwd or "/" - - if path:sub(1, 1) ~= "/" then - path = cwd .. "/" .. path +local function tokenizePath(path) + local isAbsolute = (path:sub(1,1) == "/") + local tokens = {} + for comp in (path .. "/"):gmatch("([^/]*)/") do + table.insert(tokens, comp) end + return isAbsolute, tokens +end - local stack = {} - local i = 1 - local len = #path - while i <= len do - local j = path:find("/", i, true) - local comp - if j then - comp = path:sub(i, j - 1) - i = j + 1 - else - comp = path:sub(i) - i = len + 1 - end - - comp = comp:match("^%s*(.-)%s*$") - - if comp == "" or comp == "." then - elseif comp == ".." then - if #stack > 0 then - table.remove(stack) - end - else - comp = comp:lower() - if not comp:match(SAFE_COMPONENT_PATTERN) then - error("EINVAL: illegal characters in path component: " .. comp, 2) - end - if comp == ".meta" then - error("EINVAL: reserved path component: " .. comp, 2) - end - table.insert(stack, comp) - end +local function validateComponent(comp) + local lower = comp:lower() + if not lower:match(SAFE_COMPONENT_PATTERN) then + error("EINVAL: illegal characters in path component: " .. comp, 3) end - - local result = "/" .. table.concat(stack, "/") - - local root = task and task.root - if root and root ~= "/" then - if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then - result = root - end + if lower == ".meta" then + error("EINVAL: reserved path component: .meta", 3) end - - return result end function vfs.splitPath(path) @@ -222,47 +188,205 @@ local function readMetaEntry(disk, parentDiskPath, filename) end local MAX_SYMLINK = 16 -local function resolveSymlinks(path, noFollow, _depth) - _depth = _depth or 0 - if _depth > MAX_SYMLINK then error("ELOOP") end - path = normalizePath(path) - local parts = {} - for p in path:gmatch("[^/]+") do table.insert(parts, p) end +local function namei(path, noFollow, symDepth) + symDepth = symDepth or 0 + if symDepth > MAX_SYMLINK then error("ELOOP") end - local resolved = "" + local task = kernel.currentTask + local euid = (task and (task.euid or task.uid)) or kernel.uid + local groups = (task and task.groups) or kernel.groups or {} + local root = (task and task.root) or "/" + local cwd = (task and task.cwd) or "/" - for i, part in ipairs(parts) do - local candidate = resolved == "" and ("/" .. part) or (resolved .. "/" .. part) + if root ~= "/" and root:sub(-1) == "/" then root = root:sub(1,-2) end - if noFollow and i == #parts then - resolved = candidate - break - end - - local disk, parentDisk = resolveMount(resolved == "" and "/" or resolved) - local entry = readMetaEntry(disk, parentDisk, part) - - if entry and entry.etype == 0x01 then - local target = entry.cmeta - if target:sub(1,1) ~= "/" then - target = (resolved == "" and "/" or resolved) .. "/" .. target + local function canTraverse(entry) + if euid == 0 then return true end + if not entry then return true end + local bits = entry.perms + if euid == entry.owner and bit_is_set(bits, 9) then return true end + if entry.group then + for _, gid in ipairs(groups) do + if gid == entry.group and bit_is_set(bits, 8) then return true end end - if i < #parts then - target = target .. "/" .. table.concat(parts, "/", i+1, #parts) - end - return resolveSymlinks(normalizePath(target), noFollow, _depth + 1) end - - resolved = candidate + return bit_is_set(bits, 7) end - if resolved == "" then resolved = "/" end - return resolved + local isAbsolute, tokens = tokenizePath(path) + + local stack = {} + + if isAbsolute then + stack = {} + else + for seg in cwd:gmatch("[^/]+") do table.insert(stack, seg) end + end + + local i = 1 + while i <= #tokens do + local comp = tokens[i] + i = i + 1 + + comp = comp:match("^%s*(.-)%s*$") + + if comp == "" or comp == "." then + elseif comp == ".." then + local currentPath = "/" .. table.concat(stack, "/") + + local jailStack = {} + if root ~= "/" then + for seg in root:gmatch("[^/]+") do table.insert(jailStack, seg) end + end + + if #stack <= #jailStack then + stack = {} + for _, seg in ipairs(jailStack) do table.insert(stack, seg) end + else + local exitName = stack[#stack] + local parentPath = "/" .. table.concat(stack, "/", 1, #stack - 1) + if parentPath == "/" then parentPath = "/" end + + local okM, diskM, dpM = pcall(resolveMount, parentPath == "" and "/" or parentPath) + if okM and diskM then + local entry = readMetaEntry(diskM, dpM, exitName) + if entry then + if entry.etype ~= 0x00 then + error("ENOTDIR: not a directory: " .. currentPath) + end + if not canTraverse(entry) then + error("EACCES: permission denied traversing " .. currentPath) + end + else + local okD, diskD, dpD = pcall(resolveMount, currentPath) + if okD and diskD then + local dtype = diskD:type(dpD) + if dtype ~= nil and dtype ~= "directory" then + error("ENOTDIR: not a directory: " .. currentPath) + end + end + end + end + + table.remove(stack) + end + + else + validateComponent(comp) + local lname = comp:lower() + + local curPath = "/" .. table.concat(stack, "/") + + local okM, diskM, dpM = pcall(resolveMount, curPath == "/" and "/" or curPath) + local entry = nil + if okM and diskM then + entry = readMetaEntry(diskM, dpM, lname) + end + + local isFinal = (i > #tokens) + + if entry and entry.etype == 0x01 then + if isFinal and noFollow then + table.insert(stack, lname) + else + symDepth = symDepth + 1 + if symDepth > MAX_SYMLINK then error("ELOOP") end + + local target = entry.cmeta + if not target or target == "" then + error("ENOENT: empty symlink target") + end + + local symIsAbs, symTokens = tokenizePath(target) + + if symIsAbs then + stack = {} + if root ~= "/" then + for seg in root:gmatch("[^/]+") do table.insert(stack, seg) end + end + end + + local fresh = {} + for j = 1, i - 2 do table.insert(fresh, tokens[j]) end + local insertAt = #fresh + 1 + for _, t in ipairs(symTokens) do table.insert(fresh, t) end + for j = i, #tokens do table.insert(fresh, tokens[j]) end + tokens = fresh + i = insertAt + end + else + table.insert(stack, lname) + + if not isFinal then + local newPath = "/" .. table.concat(stack, "/") + local okD, diskD, dpD = pcall(resolveMount, newPath) + if okD and diskD then + local dtype = diskD:type(dpD) + if dtype ~= nil and dtype ~= "directory" then + error("ENOTDIR: not a directory: " .. newPath) + end + end + if not canTraverse(entry) then + error("EACCES: permission denied traversing " .. newPath) + end + end + end + end + end + + local result = "/" .. table.concat(stack, "/") + + if root ~= "/" then + if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then + result = root + end + end + + return result +end + +local function normalizePath(path) + local task = kernel.currentTask + local cwd = (task and task.cwd) or "/" + local root = (task and task.root) or "/" + if root ~= "/" and root:sub(-1) == "/" then root = root:sub(1,-2) end + + local isAbsolute, tokens = tokenizePath(path) + local stack = {} + + if not isAbsolute then + for seg in cwd:gmatch("[^/]+") do table.insert(stack, seg) end + end + + local jailStack = {} + if root ~= "/" then + for seg in root:gmatch("[^/]+") do table.insert(jailStack, seg) end + end + + for _, comp in ipairs(tokens) do + comp = comp:match("^%s*(.-)%s*$") + if comp == "" or comp == "." then + elseif comp == ".." then + if #stack > #jailStack then + table.remove(stack) + end + else + table.insert(stack, comp:lower()) + end + end + + local result = "/" .. table.concat(stack, "/") + if root ~= "/" then + if result ~= root and result:sub(1, #root + 1) ~= root .. "/" then + result = root + end + end + return result end local function resolvePath(path, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) local disk, diskPath = resolveMount(real) if kernel.config.logPathResolution then kernel.log("resolvePath '"..path.."' -> '"..real.."' diskPath '"..diskPath.."'") @@ -271,24 +395,23 @@ local function resolvePath(path, noFollow) end local function getFileMeta(path, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) - local parts = {} - for p in real:gmatch("[^/]+") do table.insert(parts, p) end + if real == "/" then + return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } + end - local default = { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } - if #parts == 0 then return default end + local parent, name = real:match("^(.*)/([^/]+)$") + if not parent or parent == "" then parent = "/" end - local parentNorm = "/" .. table.concat(parts, "/", 1, #parts - 1) - if parentNorm == "" then parentNorm = "/" end - local disk, parentDiskPath = resolveMount(parentNorm) - local entry = readMetaEntry(disk, parentDiskPath, parts[#parts]) + local disk, parentDiskPath = resolveMount(parent) + local entry = readMetaEntry(disk, parentDiskPath, name) if entry then return entry end - return default + return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } end local function writeMetaEntry(path, name, entry, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) local disk, diskPath = resolveMount(real) local mp @@ -668,7 +791,7 @@ function vfs.mkdir(path) end function vfs.remove(path) - local norm = resolveSymlinks(path, true) + local norm = namei(path, true) local parent = norm:match("^(.*)/[^/]+$") or "/" if parent == "" then parent = "/" end local parentMeta = getFileMeta(parent) @@ -681,7 +804,7 @@ function vfs.remove(path) end if meta.etype == 0x01 then - local norm = resolveSymlinks(path, true) + local norm = namei(path, true) local parent = norm:match("^(.*)/[^/]+$") or "/" if parent == "" then parent = "/" end local name = norm:match("[^/]+$") @@ -747,7 +870,7 @@ function vfs.access(path, mode) end local function updateMeta(path, fn, noFollow) - local real = resolveSymlinks(path, noFollow) + local real = namei(path, noFollow) local norm = real local parent = norm:match("^(.*)/[^/]+$") or "/" if parent == "" then parent = "/" end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod deleted file mode 100644 index 9c82644..0000000 --- a/Src/Hyperion-kernel/lib/modules/hyperion/26_tty.kmod +++ /dev/null @@ -1,5 +0,0 @@ ---:Minify:-- -local kernel = ... -kernel.vfs.open("/dev/tty/1","r") -kernel.vfs.open("/dev/tty/1","w") -kernel.vfs.open("/dev/null","w") \ No newline at end of file From a0a0ac69d4403eeed6bc3e3052a66df0c7a1497b Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 07:27:45 -0500 Subject: [PATCH 08/32] remove old shell files --- Src/Hyperion-bash/bin/cat | 33 -------- Src/Hyperion-bash/bin/clear | 1 - Src/Hyperion-bash/bin/echo | 2 - Src/Hyperion-bash/bin/hyshex | 94 --------------------- Src/Hyperion-bash/bin/luaold | 50 ----------- Src/Hyperion-bash/bin/mkdir | 23 ----- Src/Hyperion-bash/bin/ps | 1 + Src/Hyperion-bash/bin/pwd | 1 - Src/Hyperion-bash/bin/sysdump | 1 + Src/Hyperion-bash/bin/whoami | 1 - Src/Hyperion-bash/bin/yes | 1 + Src/Hyperion-installer/{bin => @CD}/install | 0 12 files changed, 3 insertions(+), 205 deletions(-) delete mode 100644 Src/Hyperion-bash/bin/cat delete mode 100644 Src/Hyperion-bash/bin/clear delete mode 100644 Src/Hyperion-bash/bin/echo delete mode 100644 Src/Hyperion-bash/bin/hyshex delete mode 100644 Src/Hyperion-bash/bin/luaold delete mode 100644 Src/Hyperion-bash/bin/mkdir delete mode 100644 Src/Hyperion-bash/bin/pwd delete mode 100644 Src/Hyperion-bash/bin/whoami rename Src/Hyperion-installer/{bin => @CD}/install (100%) diff --git a/Src/Hyperion-bash/bin/cat b/Src/Hyperion-bash/bin/cat deleted file mode 100644 index 1156880..0000000 --- a/Src/Hyperion-bash/bin/cat +++ /dev/null @@ -1,33 +0,0 @@ -local args = {...} -local name = syscall.getTask(syscall.getpid()).name -local fs = require("sys.fs") - -if not args[1] then - while true do - local content = syscall.read(0, 1024) - if not content or content == "" then break end - printInline(content) - end - print("") - return -end - -for _, arg in ipairs(args) do - local filePath = arg - if filePath:sub(1,1) ~= "/" then - filePath = syscall.getcwd().."/"..filePath - end - - if not fs.exists(filePath) then - print(name..": Cannot access '"..arg.."': No such file.") - else - local fd = syscall.open(filePath, "r") - while true do - local content = syscall.read(fd, 1024) - if not content or content == "" then break end - printInline(content) - end - syscall.close(fd) - end -end -print("") diff --git a/Src/Hyperion-bash/bin/clear b/Src/Hyperion-bash/bin/clear deleted file mode 100644 index 8519315..0000000 --- a/Src/Hyperion-bash/bin/clear +++ /dev/null @@ -1 +0,0 @@ -syscall.devctl(1,"clear") \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/echo b/Src/Hyperion-bash/bin/echo deleted file mode 100644 index 7b5a803..0000000 --- a/Src/Hyperion-bash/bin/echo +++ /dev/null @@ -1,2 +0,0 @@ -local args = {...} -print(table.concat(args, " ")) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/hyshex b/Src/Hyperion-bash/bin/hyshex deleted file mode 100644 index b69309a..0000000 --- a/Src/Hyperion-bash/bin/hyshex +++ /dev/null @@ -1,94 +0,0 @@ ---:Minify:-- -syscall.open("/dev/tty/tty1","r") -syscall.open("/dev/tty/tty1","w") -syscall.open("/dev/null","r") -syscall.devctl(1,"clear") -syscall.devctl(1,"sfgc",1) -syscall.devctl(1,"spos",1,1) -print("HyperionOS hysh Shell") -local str="" -local stopInput=false -local proc=0 -local fs=require("sys.fs") -local timeout=false -syscall.setEnviron("SHELL","simpleshell") -printInline("> ") -syscall.sigcatch(function(sig) - if sig==1 then - syscall.kill(proc) - print("Terminated") - printInline("> ") - stopInput=false - end -end) - -while true do - if not stopInput then - local input=syscall.read(0) - if input then - if input=="\b" then - if #str>0 then - str=str:sub(1,#str-1) - printInline("\b") - end - elseif input=="\n" then - print("") - stopInput=true - if str == "" then - printInline("> ") - stopInput=false - else - local path=nil - local split=string.split(str, " ") - if fs.exists("/bin/"..split[1]) then - path="/bin/"..split[1] - elseif fs.exists("/bin/"..split[1]..".lua") then - path="/bin/"..split[1]..".lua" - end - if not path then - print("Program not found") - printInline("> ") - stopInput=false - else - local text = fs.readAllText(path) - local program, err = load(text, path) - if not program then - print(err) - printInline("> ") - end - proc = syscall.spawn(function(...) - syscall.open("/dev/tty/tty1","r") - syscall.open("/dev/tty/tty1","w") - syscall.open("/dev/null","w") - program(...) - end, path, nil, {table.unpack(split, 2)}) - end - str="" - end - else - str=str..input - printInline(input) - end - timeout=false - else - timeout=true - end - else - local exited, code = syscall.collect(proc) - if exited then - if code then - print("\nTask exited with code:\n"..tostring(code)) - end - printInline("> ") - stopInput=false - end - timeout=true - end - if timeout then - if stopInput then - sleep(.5) - else - sleep(.05) - end - end -end diff --git a/Src/Hyperion-bash/bin/luaold b/Src/Hyperion-bash/bin/luaold deleted file mode 100644 index 6226c17..0000000 --- a/Src/Hyperion-bash/bin/luaold +++ /dev/null @@ -1,50 +0,0 @@ ---:Minify:-- -print("HyperionOS lua") -local str="" -local stopInput=false -local timeout=false -local luaEnv=setmetatable({},{__index=_ENV}) -printInline("> ") -while true do - local input=syscall.read(0) - if input then - if input=="\b" then - if #str>0 then - str=str:sub(1,#str-1) - printInline("\b") - end - elseif input=="\n" then - print("") - stopInput=true - if str == "" then - printInline("> ") - stopInput=false - elseif str == "exit()" then - break - else - local func=load(str,"@Lua","t",luaEnv) - local ok,err = xpcall(func, debug.traceback) - if not ok then - print(err) - end - printInline("\n> ") - str="" - end - str="" - else - str=str..input - printInline(input) - end - timeout=false - else - timeout=true - end - - if timeout then - if stopInput then - sleep(.5) - else - sleep(.05) - end - end -end \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/mkdir b/Src/Hyperion-bash/bin/mkdir deleted file mode 100644 index 025a830..0000000 --- a/Src/Hyperion-bash/bin/mkdir +++ /dev/null @@ -1,23 +0,0 @@ -local args = {...} -local name = syscall.getTask(syscall.getpid()).name -if #args == 0 then - print(name..": Missing operand.") - return -end - -local fs = require("sys.fs") -local newDir = args[1] -if newDir:sub(1, 1) ~= "/" then - newDir = syscall.getcwd().."/"..newDir -end - -if newDir:sub(#newDir, #newDir) ~= "/" then - newDir = newDir.."/" -end - -if fs.isDir(newDir) then - print(name..": Cannot create directory '"..args[1].."': Directory already exists.") - return -end - -fs.mkdir(newDir) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/ps b/Src/Hyperion-bash/bin/ps index 97a99ea..4004fc1 100644 --- a/Src/Hyperion-bash/bin/ps +++ b/Src/Hyperion-bash/bin/ps @@ -1,3 +1,4 @@ +--:Minify:-- for i,v in ipairs(syscall.getTasks()) do local task = syscall.getTask(v) print(task.pid,task.username,task.name,task.status) diff --git a/Src/Hyperion-bash/bin/pwd b/Src/Hyperion-bash/bin/pwd deleted file mode 100644 index f583060..0000000 --- a/Src/Hyperion-bash/bin/pwd +++ /dev/null @@ -1 +0,0 @@ -print(syscall.getcwd()) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/sysdump b/Src/Hyperion-bash/bin/sysdump index e532fbc..224d47f 100644 --- a/Src/Hyperion-bash/bin/sysdump +++ b/Src/Hyperion-bash/bin/sysdump @@ -1,3 +1,4 @@ +--:Minify:-- local syscalls=syscall.sysdump() for i=1, #syscalls do print(syscalls[i]) diff --git a/Src/Hyperion-bash/bin/whoami b/Src/Hyperion-bash/bin/whoami deleted file mode 100644 index 3d832ca..0000000 --- a/Src/Hyperion-bash/bin/whoami +++ /dev/null @@ -1 +0,0 @@ -print((syscall.getUsername() or "Unknown")) \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/yes b/Src/Hyperion-bash/bin/yes index 9490c89..bf0d571 100644 --- a/Src/Hyperion-bash/bin/yes +++ b/Src/Hyperion-bash/bin/yes @@ -1,3 +1,4 @@ +--:Minify:-- local args = {...} while true do if #args == 0 then diff --git a/Src/Hyperion-installer/bin/install b/Src/Hyperion-installer/@CD/install similarity index 100% rename from Src/Hyperion-installer/bin/install rename to Src/Hyperion-installer/@CD/install From 16c900de848ae221f1fbd2497f4ca55d39ca5152 Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 21:23:35 -0500 Subject: [PATCH 09/32] fixed ls links, modules writeable --- .vscode/settings.json | 3 +- Src/Hyperion-firmware-ccpc/boot/cct/boot.lua | 316 ++++++++++++++ Src/Hyperion-firmware-ccpc/boot/cct/eeprom | 119 ++++++ Src/Hyperion-firmware-ccpc/boot/cct/initdisks | 155 +++++++ .../lib/modules/CC-Tweaked/25_tty.kmod | 394 ++++++++++++++++++ .../lib/modules/CC-Tweaked/40_http.kmod} | 0 .../lib/modules/CC-Tweaked/40_modem.kmod | 0 .../lib/modules/CC-Tweaked/40_redstone.kmod | 26 ++ .../lib/modules/CC-Tweaked/25_tty.kmod | 16 + Src/Hyperion-kernel/boot/kernel.lua | 18 +- .../lib/modules/hyperion/10_vfs.kmod | 30 +- .../lib/modules/hyperion/12_devfs.kmod | 2 +- .../lib/modules/hyperion/92_permissions.kmod | 118 +----- build.py | 158 ++++--- building.md | 73 ++-- 15 files changed, 1198 insertions(+), 230 deletions(-) create mode 100644 Src/Hyperion-firmware-ccpc/boot/cct/boot.lua create mode 100644 Src/Hyperion-firmware-ccpc/boot/cct/eeprom create mode 100644 Src/Hyperion-firmware-ccpc/boot/cct/initdisks create mode 100644 Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod rename Src/{Hyperion-installer/@CD/install => Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_http.kmod} (100%) create mode 100644 Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_modem.kmod create mode 100644 Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod diff --git a/.vscode/settings.json b/.vscode/settings.json index 81d26b7..138447a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "syscall", "printf", "printInline", - "toHex" + "toHex", + "loadcstr" ] } \ No newline at end of file diff --git a/Src/Hyperion-firmware-ccpc/boot/cct/boot.lua b/Src/Hyperion-firmware-ccpc/boot/cct/boot.lua new file mode 100644 index 0000000..7bdae02 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/boot/cct/boot.lua @@ -0,0 +1,316 @@ +-- :Minify:-- +local BOOT_DRIVE_PATH = ({...})[1] or "/$" +---@diagnostic disable-next-line: undefined-global +local term = term +local os = os +local function write(text) + local x, y = term.getCursorPos() + local w, h = term.getSize() + + for i = 1, #text do + local c = text:sub(i, i) + + if c == "\n" then + y = y + 1 + x = 1 + elseif c == "\t" then + local tabSize = 4 + local spaces = tabSize - ((x - 1) % tabSize) + term.write(string.rep(" ", spaces)) + x = x + spaces + elseif c == "\b" then + if x > 1 then + x = x - 1 + term.setCursorPos(x, y) + term.write(" ") + term.setCursorPos(x, y) + end + else + if x <= w and y <= h then + term.setCursorPos(x, y) + term.write(c) + x = x + 1 + end + end + + if x > w then + x = 1 + y = y + 1 + end + + if y - 1 >= h then + term.scroll(1) + y = h + term.setCursorPos(x, y) + end + end + + term.setCursorPos(x, y) +end + +local function displaySuperBadError(err) + term.setBackgroundColor(0x1) + term.setTextColor(0x4) + term.clear() + term.setCursorPos(1, 1) + term.write("A critical error occurred while loading the system:") + term.setCursorPos(1, 3) + write(err) + while true do end +end + +term.setCursorBlink(false) +local ok, err = xpcall(function() + local apis = {BOOT_DRIVE_PATH = BOOT_DRIVE_PATH} + + local lua = { + coroutine = true, + debug = true, + _VERSION = true, + assert = true, + collectgarbage = true, + error = true, + gcinfo = true, + getfenv = true, + getmetatable = true, + ipairs = true, + __inext = true, + load = true, + math = true, + next = true, + pairs = true, + pcall = true, + rawequal = true, + rawget = true, + rawlen = true, + rawset = true, + select = true, + setfenv = true, + setmetatable = true, + string = true, + table = true, + tonumber = true, + tostring = true, + type = true, + xpcall = true, + _G = true + } + + local debug = debug + for i, v in pairs(_G) do + if not lua[i] or lua[i] == nil then + apis[i] = v + _G[i] = nil + end + end + + local acekeys={ + [apis.keys.enter]="\n", + [apis.keys.tab]="\t", + [apis.keys.backspace]="\b", + [apis.keys.up]="\17", + [apis.keys.down]="\18", + [apis.keys.left]="\19", + [apis.keys.right]="\20", + } + + function sleep(time) + local stoptime = apis.os.clock() + (time) + while stoptime > apis.os.clock() do end + end + + apis.term.setPaletteColor(0x1, 0xFFFFFF) -- #000000 + apis.term.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF + apis.term.setPaletteColor(0x4, 0x00FF00) -- #FF0000 + apis.term.setPaletteColor(0x8, 0x0000FF) -- #00FF00 + apis.term.setPaletteColor(0x10, 0x00FFFF) -- #0000FF + apis.term.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF + apis.term.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF + apis.term.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00 + apis.term.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00 + apis.term.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55 + apis.term.setPaletteColor(0x400, 0x924900) -- #24FFFF + apis.term.setPaletteColor(0x800, 0x6D6D55) -- #924900 + apis.term.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55 + apis.term.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA + apis.term.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF + apis.term.setPaletteColor(0x8000, 0x000000) -- #B6FF00 + + local function getFile(path) + local file = apis.fs.open(path, "r") + if not file then + displaySuperBadError("Could not open file: " .. path) + end + local content = file.readAll() + file.close() + return content + end + + local Kernel = load(getFile(BOOT_DRIVE_PATH .. "/boot/kernel.lua"),"@Kernel") + local initFs = load(getFile(BOOT_DRIVE_PATH .. "/boot/cct/initdisks"),"@Init_disks")(apis) + local fs = load(getFile(BOOT_DRIVE_PATH .. "/boot/initfs"), "@InitFs")() + + if not Kernel then displaySuperBadError("Could not load kernel.") end + if not initFs then displaySuperBadError("Could not load initdisks.") end + if not fs then displaySuperBadError("Could not load initfs.") end + + local eventQueue = {} + + local function queueEvent(event, ...) + table.insert(eventQueue, {event, ...}) + end + + local computer = { + time = function() return apis.os.epoch("utc") end, + clock = function() return apis.os.clock() * 1000 end, + shutdown = apis.os.shutdown, + reboot = apis.os.reboot, + getMachineEvent = function() + if #eventQueue > 0 then + return table.unpack(table.remove(eventQueue, 1)) + else + return nil + end + end, + getEEPROM = function() return getFile("/startup.lua") end, + setEEPROM = function(_, text) + local h = apis.fs.open("/startup.lua", "w") + h.write(text) + h.close() + end + } + + local icolors = { + [0x1] = 1, -- #000000 + [0x2] = 2, -- #FFFFFF + [0x4] = 3, -- #FF0000 + [0x8] = 4, -- #00FF00 + [0x10] = 5, -- #0000FF + [0x20] = 6, -- #00FFFF + [0x40] = 7, -- #FF00FF + [0x80] = 8, -- #FFFF00 + [0x100] = 9, -- #FF6D00 + [0x200] = 10, -- #6DFF55 + [0x400] = 11, -- #24FFFF + [0x800] = 12, -- #924900 + [0x1000] = 13, -- #6D6D55 + [0x2000] = 14, -- #DBDBAA + [0x4000] = 15, -- #6D00FF + [0x8000] = 16 -- #B6FF00 + } + + local colors = { + 0x0001, -- #000000 + 0x0002, -- #FFFFFF + 0x0004, -- #FF0000 + 0x0008, -- #00FF00 + 0x0010, -- #0000FF + 0x0020, -- #00FFFF + 0x0040, -- #FF00FF + 0x0080, -- #FFFF00 + 0x0100, -- #FF6D00 + 0x0200, -- #6DFF55 + 0x0400, -- #24FFFF + 0x0800, -- #924900 + 0x1000, -- #6D6D55 + 0x2000, -- #DBDBAA + 0x4000, -- #6D00FF + 0x8000 -- #B6FF00 + } + + apis.term.setBackgroundColor(0x8000) + apis.term.setTextColor(0x1000) + apis.term.clear() + apis.term.setCursorPos(1, 1) + + local kernelCoro = coroutine.create(function() + ---@diagnostic disable-next-line: param-type-mismatch + local ok, err = xpcall(Kernel, debug.traceback, apis, initFs, "cct", "/sbin/init", + { + print = function(_, text) write(text .. "\n") end, + printInline = function(_, text) write(text) end, + clear = function() + apis.term.clear() + apis.term.setCursorPos(1, 1) + end, + setCursorPos = function(_, x, y) + apis.term.setCursorPos(x, y) + end, + getCursorPos = function() return apis.term.getCursorPos() end, + getSize = function() return apis.term.getSize() end, + setBackgroundColor = function(_, color) + apis.term.setBackgroundColor(colors[color]) + end, + setTextColor = function(_, color) + apis.term.setTextColor(colors[color]) + end, + getBackgroundColor = function() + return icolors[apis.term.getBackgroundColor()] + end, + getTextColor = function() + return icolors[apis.term.getTextColor()] + end + }, computer, fs, "$") + if not ok then displaySuperBadError(err) end + end) + + function coroutine.resumeWithTimeout(co, timeout, ...) + local startTime = computer.time() + debug.sethook(co, function() + if computer.time() > startTime + timeout then + return coroutine.yield("timeout") + end + end, "", 1000) + local ret = {coroutine.resume(co, ...)} + if ret[1] and ret[2] == "timeout" then + return "timeout" + elseif ret[1] == false then + return "error", ret[2] + else + debug.sethook(co) + return "success", table.unpack(ret, 2) + end + end + + write("Loaded in " .. tostring(apis.os.clock()) .. " seconds.\n") + + while true do + local status, err = coroutine.resumeWithTimeout(kernelCoro, 50) + apis.os.queueEvent("NoSleep") + local exit = false + while not exit do + local event = {coroutine.yield()} + if event[1] == "key" then + queueEvent("keyPressed", 1, event[2]) + if acekeys[event[2]] then + queueEvent("keyTyped", 1, acekeys[event[2]]) + end + elseif event[1] == "char" then + queueEvent("keyTyped", 1, event[2]) + elseif event[1] == "key_up" then + queueEvent("keyReleased", 1, event[2]) + elseif event[1] == "disk" then + queueEvent("componentAdded", "disk") + elseif event[1] == "disk_eject" then + queueEvent("componentRemoved", "disk") + elseif event[1] == "modem_message" then + queueEvent("modem_message", table.unpack(event, 2)) + elseif event[1] == "rednet_message" then + queueEvent("rednet_message", table.unpack(event, 2)) + elseif event[1] == "http_success" then + queueEvent("http_success", table.unpack(event, 2)) + elseif event[1] == "http_failure" then + queueEvent("http_failure", table.unpack(event, 2)) + elseif event[1] == "NoSleep" then + exit = true + end + end + if status == "error" or coroutine.status(kernelCoro) == "dead" then + displaySuperBadError("Kernel error: " .. tostring(err)) + coroutine.yield("key") + end + end +end, debug.traceback) + +if not ok then displaySuperBadError("Fatal error during boot: " .. err) end +while true do coroutine.yield() end diff --git a/Src/Hyperion-firmware-ccpc/boot/cct/eeprom b/Src/Hyperion-firmware-ccpc/boot/cct/eeprom new file mode 100644 index 0000000..e3bb2a4 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/boot/cct/eeprom @@ -0,0 +1,119 @@ +--:Minify:-- +local BOOT_DRIVE_PATH=({...})[1] or "/$" +-- UnBIOS by JackMacWindows +-- This will undo most of the changes/additions made in the BIOS, but some things may remain wrapped if `debug` is unavailable +-- To use, just place a `bios.lua` in the root of the drive, and run this program +-- Here's a list of things that are irreversibly changed: +-- * both `bit` and `bit32` are kept for compatibility +-- * string metatable blocking (on old versions of CC) +-- In addition, if `debug` is not available these things are also irreversibly changed: +-- * old Lua 5.1 `load` function (for loading from a function) +-- * `loadstring` prefixing (before CC:T 1.96.0) +-- * `http.request` +-- * `os.shutdown` and `os.reboot` +-- * `peripheral` +-- * `turtle.equip[Left|Right]` +-- Licensed under the MIT license +local args = {...} +local keptAPIs = {keys=true, bit32 = true, bit = true, ccemux = true, config = true, coroutine = true, debug = true, fs = true, http = true, mounter = true, os = true, periphemu = true, peripheral = true, redstone = true, rs = true, term = true, utf8 = true, _HOST = true, _CC_DEFAULT_SETTINGS = true, _CC_DISABLE_LUA51_FEATURES = true, _VERSION = true, assert = true, collectgarbage = true, error = true, gcinfo = true, getfenv = true, getmetatable = true, ipairs = true, __inext = true,load = true, loadstring = true, math = true, newproxy = true, next = true, pairs = true, pcall = true, rawequal = true, rawget = true, rawlen = true, rawset = true, select = true, setfenv = true, setmetatable = true, string = true, table = true, tonumber = true, tostring = true, type = true, unpack = true, xpcall = true, turtle = true, pocket = true, commands = true, _G = true} +local t = {} +for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end +for _,k in ipairs(t) do _G[k] = nil end +local native = _G.term.native() +for _, method in ipairs {"nativePaletteColor", "nativePaletteColour", "screenshot"} do native[method] = _G.term[method] end +_G.term = native +if _G.http then + _G.http.checkURL = _G.http.checkURLAsync + _G.http.websocket = _G.http.websocketAsync +end +if _G.commands then _G.commands = _G.commands.native end +if _G.turtle then _G.turtle.native, _G.turtle.craft = nil end +local delete = {os = {"version", "pullEventRaw", "pullEvent", "run", "loadAPI", "unloadAPI", "sleep"}, http = _G.http and {"get", "post", "put", "delete", "patch", "options", "head", "trace", "listen", "checkURLAsync", "websocketAsync"}, fs = {"complete", "isDriveRoot"}} +for k,v in pairs(delete) do for _,a in ipairs(v) do _G[k][a] = nil end end +-- Set up TLCO +-- This functions by crashing `rednet.run` by removing `os.pullEventRaw`. Normally +-- this would cause `parallel` to throw an error, but we replace `error` with an +-- empty placeholder to let it continue and return without throwing. This results +-- in the `pcall` returning successfully, preventing the error-displaying code +-- from running - essentially making it so that `os.shutdown` is called immediately +-- after the new BIOS exits. +-- +-- From there, the setup code is placed in `term.native` since it's the first +-- thing called after `parallel` exits. This loads the new BIOS and prepares it +-- for execution. Finally, it overwrites `os.shutdown` with the new function to +-- allow it to be the last function called in the original BIOS, and returns. +-- From there execution continues, calling the `term.redirect` dummy, skipping +-- over the error-handling code (since `pcall` returned ok), and calling +-- `os.shutdown()`. The real `os.shutdown` is re-added, and the new BIOS is tail +-- called, which effectively makes it run as the main chunk. +local olderror = error +_G.error = function() end +_G.term.redirect = function() end +function _G.term.native() + _G.term.native = nil + _G.term.redirect = nil + _G.error = olderror + term.setBackgroundColor(32768) + term.setTextColor(1) + term.setCursorPos(1, 1) + term.setCursorBlink(true) + term.clear() + local file = fs.open(BOOT_DRIVE_PATH.."/boot/cct/boot.lua", "r") + if file == nil then + term.setCursorBlink(false) + term.setTextColor(16384) + term.write("Could not find /boot/cct/boot.lua. UnBIOS cannot continue.") + term.setCursorPos(1, 2) + term.write("Press any key to continue") + coroutine.yield("key") + os.shutdown() + end + local fn, err = loadstring(file.readAll(), "@bootloader") + file.close() + if fn == nil then + term.setCursorBlink(false) + term.setTextColor(16384) + term.write("Could not load /boot/cc/boot.lua. UnBIOS cannot continue.") + term.setCursorPos(1, 2) + term.write(err) + term.setCursorPos(1, 3) + term.write("Press any key to continue") + coroutine.yield("key") + os.shutdown() + end + setfenv(fn, _G) + local oldshutdown = os.shutdown + os.shutdown = function() + os.shutdown = oldshutdown + return fn(BOOT_DRIVE_PATH) + end +end +if debug then + -- Restore functions that were overwritten in the BIOS + -- Apparently this has to be done *after* redefining term.native + local function restoreValue(tab, idx, name, hint) + local i, key, value = 1, debug.getupvalue(tab[idx], hint) + while key ~= name and key ~= nil do + key, value = debug.getupvalue(tab[idx], i) + i=i+1 + end + tab[idx] = value or tab[idx] + end + restoreValue(_G, "loadstring", "nativeloadstring", 1) + restoreValue(_G, "load", "nativeload", 5) + if http then restoreValue(http, "request", "nativeHTTPRequest", 3) end + restoreValue(os, "shutdown", "nativeShutdown", 1) + restoreValue(os, "reboot", "nativeReboot", 1) + if turtle then + restoreValue(turtle, "equipLeft", "v", 1) + restoreValue(turtle, "equipRight", "v", 1) + end + do + local i, key, value = 1, debug.getupvalue(peripheral.isPresent, 2) + while key ~= "native" and key ~= nil do + key, value = debug.getupvalue(peripheral.isPresent, i) + i=i+1 + end + _G.peripheral = value or peripheral + end +end \ No newline at end of file diff --git a/Src/Hyperion-firmware-ccpc/boot/cct/initdisks b/Src/Hyperion-firmware-ccpc/boot/cct/initdisks new file mode 100644 index 0000000..3669757 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/boot/cct/initdisks @@ -0,0 +1,155 @@ +-- :Minify:-- +local apis = ({...})[1] +local BOOT_DRIVE_PATH = apis.BOOT_DRIVE_PATH or "/$" +local fs = apis.fs +local native = apis.peripheral +local peripheral = {} +local sides = {"top", "bottom", "left", "right", "front", "back"} + +function peripheral.getType(name) + if native.isPresent(name) then return native.getType(name) end + for n = 1, #sides do + local side = sides[n] + if native.hasType(side, "peripheral_hub") and + native.call(side, "isPresentRemote", name) then + return native.call(side, "getTypeRemote", name) + end + end + return nil +end + +function peripheral.getNames() + local names = {} + for n = 1, #sides do + local side = sides[n] + if native.isPresent(side) then table.insert(names, side) end + if native.hasType(side, "peripheral_hub") then + local hubSides = native.call(side, "getConnectedSides") + for _, hubSide in ipairs(hubSides) do + table.insert(names, hubSide) + end + end + end + return names +end + +local disks = {} +local internal = {} + +local function norm(path) + if not path or path == "" then return "/" end + return fs.combine("/", path) +end + +local function createDisk(id, basePath, readonly, periph) + basePath = norm(basePath) + + local disk = {address = id, isReadOnly = function() return readonly end} + + function disk:spaceUsed() + return fs.getCapacity(basePath) - fs.getFreeSpace(basePath) + end + + function disk:spaceTotal() return fs.getCapacity(basePath) end + + function disk:list(path) + local p = fs.combine(basePath, path) + if not fs.exists(p) or not fs.isDir(p) then + return nil, "not directory" + end + return fs.list(p) + end + + function disk:fileExists(path) + local p = fs.combine(basePath, path) + return fs.exists(p) and not fs.isDir(p) + end + + function disk:directoryExists(path) + local p = fs.combine(basePath, path) + return fs.exists(p) and fs.isDir(p) + end + + function disk:type(path) + local p = fs.combine(basePath, path) + if not fs.exists(p) then + return nil + elseif fs.isDir(p) then + return "directory" + else + return "file" + end + end + + function disk:makeDirectory(path) + local p = fs.combine(basePath, path) + fs.makeDir(p) + return true + end + + function disk:remove(path) + local p = fs.combine(basePath, path) + if fs.exists(p) then fs.delete(p) end + return true + end + + function disk:setLabel(label) periph.setLabel(label) end + + function disk:getLabel(label) return periph.getLabel() end + + function disk:attributes(path) + local p = fs.combine(basePath, path) + return fs.attributes(p) + end + + function disk:open(path, mode) + local p = fs.combine(basePath, path) + return fs.open(p, mode) + end + + return disk +end + +internal["$"] = createDisk("$", BOOT_DRIVE_PATH, false, { + setLabel = function(label) + local h = fs.open("/.label", "w") + h.write(label) + h.close() + end, + getLabel = function() + local h = fs.open("/.label", "r") + if not h then return "$" end + local label = h.readAll() + h.close() + return label + end +}) + +local function refresh() + for id, _ in pairs(disks) do + if not peripheral.getType(id) then disks[id] = nil end + end + + for _, name in ipairs(peripheral.getNames()) do + if peripheral.getType(name) == "disk" then + if not disks[name] then + local mount = disk.getMountPath(name) + if mount then + disks[name] = createDisk(name, mount, false, disk) + end + end + end + end +end + +local function iter() + refresh() + local combined = {} + + for id, obj in pairs(internal) do combined[id] = obj end + for id, obj in pairs(disks) do combined[id] = obj end + + return pairs(combined) +end + +return {refresh = refresh, list = iter} diff --git a/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod new file mode 100644 index 0000000..8b586e1 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/25_tty.kmod @@ -0,0 +1,394 @@ +--:Minify:-- +local kernel = ... +local apis = kernel.apis +local native = apis.peripheral +local sides = {"top", "bottom", "left", "right", "front", "back"} +local peripheral={} + +function peripheral.getNames() + local results = {} + for n = 1, #sides do + local side = sides[n] + if native.isPresent(side) then + table.insert(results, side) + if native.hasType(side, "peripheral_hub") then + local remote = native.call(side, "getNamesRemote") + for _, name in ipairs(remote) do + table.insert(results, name) + end + end + end + end + return results +end + +function peripheral.isPresent(name) + if native.isPresent(name) then + return true + end + + for n = 1, #sides do + local side = sides[n] + if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then + return true + end + end + return false +end + +function peripheral.getType(peripheral) + if type(peripheral) == "string" then + if native.isPresent(peripheral) then + return native.getType(peripheral) + end + for n = 1, #sides do + local side = sides[n] + if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then + return native.call(side, "getTypeRemote", peripheral) + end + end + return nil + else + local mt = getmetatable(peripheral) + if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then + error("bad argument #1 (table is not a peripheral)", 2) + end + return table.unpack(mt.types) + end +end + +function peripheral.hasType(peripheral, peripheral_type) + if type(peripheral) == "string" then + if native.isPresent(peripheral) then + return native.hasType(peripheral, peripheral_type) + end + for n = 1, #sides do + local side = sides[n] + if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", peripheral) then + return native.call(side, "hasTypeRemote", peripheral, peripheral_type) + end + end + return nil + else + local mt = getmetatable(peripheral) + if not mt or mt.__name ~= "peripheral" or type(mt.types) ~= "table" then + error("bad argument #1 (table is not a peripheral)", 2) + end + return mt.types[peripheral_type] ~= nil + end +end + +function peripheral.getMethods(name) + if native.isPresent(name) then + return native.getMethods(name) + end + for n = 1, #sides do + local side = sides[n] + if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then + return native.call(side, "getMethodsRemote", name) + end + end + return nil +end + +function peripheral.getName(peripheral) + local mt = getmetatable(peripheral) + if not mt or mt.__name ~= "peripheral" or type(mt.name) ~= "string" then + error("bad argument #1 (table is not a peripheral)", 2) + end + return mt.name +end + +function peripheral.call(name, method, ...) + if native.isPresent(name) then + return native.call(name, method, ...) + end + + for n = 1, #sides do + local side = sides[n] + if native.hasType(side, "peripheral_hub") and native.call(side, "isPresentRemote", name) then + return native.call(side, "callRemote", name, method, ...) + end + end + return nil +end + +function peripheral.wrap(name) + local methods = peripheral.getMethods(name) + if not methods then + return nil + end + + local types = { peripheral.getType(name) } + for i = 1, #types do types[types[i]] = true end + local result = setmetatable({}, { + __name = "peripheral", + name = name, + type = types[1], + types = types, + }) + for _, method in ipairs(methods) do + result[method] = function(...) + return peripheral.call(name, method, ...) + end + end + return result +end + +function peripheral.find(ty, filter) + local results = {} + for _, name in ipairs(peripheral.getNames()) do + if peripheral.hasType(name, ty) then + local wrapped = peripheral.wrap(name) + if filter == nil or filter(name, wrapped) then + table.insert(results, wrapped) + end + end + end + return table.unpack(results) +end + +local icolors = { + [0x1] = 1, -- #000000 + [0x2] = 2, -- #FFFFFF + [0x4] = 3, -- #FF0000 + [0x8] = 4, -- #00FF00 + [0x10] = 5, -- #0000FF + [0x20] = 6, -- #00FFFF + [0x40] = 7, -- #FF00FF + [0x80] = 8, -- #FFFF00 + [0x100] = 9, -- #FF6D00 + [0x200] = 10, -- #6DFF55 + [0x400] = 11, -- #24FFFF + [0x800] = 12, -- #924900 + [0x1000] = 13, -- #6D6D55 + [0x2000] = 14, -- #DBDBAA + [0x4000] = 15, -- #6D00FF + [0x8000] = 16 -- #B6FF00 +} + +local colors = { + 0x0001, -- #000000 + 0x0002, -- #FFFFFF + 0x0004, -- #FF0000 + 0x0008, -- #00FF00 + 0x0010, -- #0000FF + 0x0020, -- #00FFFF + 0x0040, -- #FF00FF + 0x0080, -- #FFFF00 + 0x0100, -- #FF6D00 + 0x0200, -- #6DFF55 + 0x0400, -- #24FFFF + 0x0800, -- #924900 + 0x1000, -- #6D6D55 + 0x2000, -- #DBDBAA + 0x4000, -- #6D00FF + 0x8000 -- #B6FF00 +} + +local function write(text, term) + local x, y = term.getCursorPos() + local w, h = term.getSize() + + for i = 1, #text do + local c = text:sub(i, i) + + if c == "\n" then + y = y + 1 + x = 1 + elseif c == "\t" then + local tabSize = 4 + local spaces = tabSize - ((x - 1) % tabSize) + term.write(string.rep(" ", spaces)) + x = x + spaces + elseif c == "\b" then + if x > 1 then + x = x - 1 + term.setCursorPos(x, y) + term.write(" ") + term.setCursorPos(x, y) + end + else + if x <= w and y <= h then + term.setCursorPos(x, y) + term.write(c) + x = x + 1 + end + end + + if x > w then + x = 1 + y = y + 1 + end + + if y - 1 >= h then + term.scroll(1) + y = h + term.setCursorPos(x, y) + end + end + + term.setCursorPos(x, y) +end + +kernel.devfs.data.tty={} +local ctrl,alt = false, false + +local function serializeBool(bool) + if bool then + return "T" + else + return "F" + end +end + +local function newtty(obj, id, ev) + kernel.devfs.data["tty"][id] = function(op, mode) + if op=="type" then + return "character device" + elseif op=="open" then + local h = { + read=function(amount) + local rv="" + for i=1, amount or 1 do + local event = {ev()} + if event[1] then + rv=rv..event[1] + end + end + if rv=="" then rv=nil end + return rv + end, + write=function(content) + write(content, obj) + end, + size=function() + local s={obj.getSize()} + return table.concat(s,";") + end, + clear=function() + obj.clear() + obj.setCursorPos(1,1) + end, + gpos=function() + local s={obj.getCursorPos()} + return table.concat(s,";") + end, + spos=function(x,y) + return obj.setCursorPos(x,y) + end, + sfgc=function(c) + return obj.setTextColor(colors[c]) + end, + sbgc=function(c) + return obj.setBackgroundColor(colors[c]) + end, + gfgc=function() + return icolors[obj.getTextColor()] + end, + gbgc=function() + return icolors[obj.getBackgroundColor()] + end, + gctrl=function() + return serializeBool(ctrl)..";"..serializeBool(alt) + end + } + if mode=="rw" then + return h + elseif mode=="r" then + h["write"]=nil + return h + elseif mode=="w" then + h["read"]=nil + return h + end + end + end +end + +local fifo = kernel.newFifo() + +kernel.processes.cctmond = function() + local timeout = false + while true do + local event = {kernel.computer:getMachineEvent()} + + if event[1] then + local eventType = event[1] + local charOrKey = event[3] + + local ctrlKeyMap = { + [apis.keys.a]=1, [apis.keys.b]=2, [apis.keys.c]=3, + [apis.keys.d]=4, [apis.keys.e]=5, [apis.keys.f]=6, + [apis.keys.g]=7, [apis.keys.h]=8, [apis.keys.i]=9, + [apis.keys.j]=10, [apis.keys.k]=11, [apis.keys.l]=12, + [apis.keys.m]=13, [apis.keys.n]=14, [apis.keys.o]=15, + [apis.keys.p]=16, [apis.keys.q]=17, [apis.keys.r]=18, + [apis.keys.s]=19, [apis.keys.t]=20, [apis.keys.u]=21, + [apis.keys.v]=22, [apis.keys.w]=23, [apis.keys.x]=24, + [apis.keys.y]=25, [apis.keys.z]=26, + } + + if eventType == "keyPressed" then + if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then + ctrl = true + elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then + alt = true + end + + if ctrl then + local ctrlByte = ctrlKeyMap[charOrKey] + if ctrlByte then + if ctrlByte == 3 then + for _, task in ipairs(syscall.getTasks()) do + syscall.sigsend(task, 1) + end + else + fifo.push(string.char(ctrlByte)) + end + end + else + local specialKeyMap = { + [apis.keys.up] = "", + [apis.keys.down] = "", + [apis.keys.right] = "", + [apis.keys.left] = "", + [apis.keys.home] = "", + [apis.keys["end"]] = "", + [apis.keys.pageUp] = "[5~", + [apis.keys.pageDown] = "[6~", + [apis.keys.delete] = "[3~", + } + local special = specialKeyMap[charOrKey] + if special then fifo.push(special) end + end + + elseif eventType == "keyReleased" then + if charOrKey == apis.keys.leftCtrl or charOrKey == apis.keys.rightCtrl then + ctrl = false + elseif charOrKey == apis.keys.leftAlt or charOrKey == apis.keys.rightAlt then + alt = false + end + + elseif eventType == "keyTyped" then + if charOrKey then fifo.push(charOrKey) end + end + + timeout = false + else + timeout = true + end + + if timeout then + sleep(0.05) + end + end +end + +newtty(apis.term, "1", fifo.pop) + +for i,v in ipairs({peripheral.find("monitor")}) do + v.setTextScale(.5) + v.write("Initializing...") + newtty(v,tostring(i+1),function () end) +end \ No newline at end of file diff --git a/Src/Hyperion-installer/@CD/install b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_http.kmod similarity index 100% rename from Src/Hyperion-installer/@CD/install rename to Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_http.kmod diff --git a/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_modem.kmod b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_modem.kmod new file mode 100644 index 0000000..e69de29 diff --git a/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod new file mode 100644 index 0000000..8e06ca6 --- /dev/null +++ b/Src/Hyperion-firmware-ccpc/lib/modules/CC-Tweaked/40_redstone.kmod @@ -0,0 +1,26 @@ +local args={...} +local kernel=args[1] +local driver={} + +driver.name="CCT Term Module" +driver.version="0.1.0" +driver.type="gpio" +driver.description="CCT redstone Module Kernel Module" +driver.arch="cct" +driver.author="HyperionOS Dev Team" +driver.license="MIT" +driver.api={} + +function driver.load() + -- will +end + +function driver.unload() + -- Nothing to unload +end + +function driver.main() + -- Nothing to run +end + +-- kernel.drivers.register(driver) \ No newline at end of file diff --git a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod index 8b586e1..8163ad2 100644 --- a/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod +++ b/Src/Hyperion-firmware-cct/lib/modules/CC-Tweaked/25_tty.kmod @@ -243,6 +243,22 @@ local function serializeBool(bool) end local function newtty(obj, id, ev) + obj.setPaletteColor(0x1, 0xFFFFFF) -- #000000 + obj.setPaletteColor(0x2, 0xFF0000) -- #FFFFFF + obj.setPaletteColor(0x4, 0x00FF00) -- #FF0000 + obj.setPaletteColor(0x8, 0x0000FF) -- #00FF00 + obj.setPaletteColor(0x10, 0x00FFFF) -- #0000FF + obj.setPaletteColor(0x20, 0xFF00FF) -- #00FFFF + obj.setPaletteColor(0x40, 0xFFFF00) -- #FF00FF + obj.setPaletteColor(0x80, 0xFF6D00) -- #FFFF00 + obj.setPaletteColor(0x100, 0x6DFF55) -- #FF6D00 + obj.setPaletteColor(0x200, 0x24FFFF) -- #6DFF55 + obj.setPaletteColor(0x400, 0x924900) -- #24FFFF + obj.setPaletteColor(0x800, 0x6D6D55) -- #924900 + obj.setPaletteColor(0x1000, 0xDBDBAA) -- #6D6D55 + obj.setPaletteColor(0x2000, 0x6D00FF) -- #DBDBAA + obj.setPaletteColor(0x4000, 0xB6FF00) -- #6D00FF + obj.setPaletteColor(0x8000, 0x000000) -- #B6FF00 kernel.devfs.data["tty"][id] = function(op, mode) if op=="type" then return "character device" diff --git a/Src/Hyperion-kernel/boot/kernel.lua b/Src/Hyperion-kernel/boot/kernel.lua index 28a03ef..19a380d 100644 --- a/Src/Hyperion-kernel/boot/kernel.lua +++ b/Src/Hyperion-kernel/boot/kernel.lua @@ -27,15 +27,15 @@ local windowsExp = false function kernel.log(msg, level, c) c=c or 12 - kernel.LOG_Text = kernel.LOG_Text..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n" + kernel.LOG_Text = kernel.LOG_Text..string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n" if kernel.status == "start" then screen:setTextColor(c) - screen:print(string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg) + screen:print(tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg) elseif kernel.status == "term" then kernel.standbyTask=kernel.currentTask kernel.currentTask=kernel.kernelTask kernel.vfs.devctl(1,"sfgc",c) - kernel.vfs.write(1,string.format("%X",c-1).." "..tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n") + kernel.vfs.write(1,tostring(computer:time()).." "..kernel.users[kernel.uid].." "..kernel.process.."["..tostring(level or "INFO").."]: "..msg.."\n") kernel.currentTask=kernel.standbyTask end end @@ -149,6 +149,10 @@ function kernel.saveLog() ifs.writeAllText("/var/log/syslog.log", kernel.LOG_Text) end +function loadcstr(string) + +end + function kernel.newFifo() local fifo = {} fifo.push=function(data) @@ -251,7 +255,12 @@ kernel.syscalls["sysdump"]=function() end return rv end -kernel.syscalls["test"]=function() return true end +kernel.syscalls["reboot"]=function() + kernel.computer:reboot() +end +kernel.syscalls["shutdown"]=function() + kernel.computer:reboot() +end kernel.log("Running modules") for _,p in ipairs(modules) do @@ -272,6 +281,7 @@ for _,p in ipairs(modules) do end kernel.log("Kernel initialized successfully.") +kernel.saveLog() kernel.status="running" kernel.main() if kernel.status=="panic" then diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod index fd63bc9..07b6617 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod @@ -401,12 +401,27 @@ local function getFileMeta(path, noFollow) return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } end - local parent, name = real:match("^(.*)/([^/]+)$") - if not parent or parent == "" then parent = "/" end + local cur = real + + -- FML i hated implementing this - Astronand + while true do + local parent, name = cur:match("^(.*)/([^/]+)$") + if not parent or parent == "" then parent = "/" end + + local disk, parentDiskPath = resolveMount(parent) + local entry = readMetaEntry(disk, parentDiskPath, name) + + if entry then + return entry + end + + if parent == "/" or cur == "/" then + break + end + + cur = parent + end - local disk, parentDiskPath = resolveMount(parent) - local entry = readMetaEntry(disk, parentDiskPath, name) - if entry then return entry end return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } end @@ -610,6 +625,7 @@ function vfs.open(path, mode) if mode == "r" and bit_is_set(meta.perms, 6) then fobj.suid_owner = meta.owner end + if disk.isvirt then fobj.isvirt=true end task.fd[fd] = fobj if not disk.isvirt then total = total + 1 end return fd @@ -658,8 +674,10 @@ function vfs.close(fd) local task = kernel.currentTask local file = task.fd[fd] if not file then error("EBADF") end + if not task.fd[fd].isvirt then + total = total - 1 + end task.fd[fd] = nil - total = total - 1 file.refcount = file.refcount - 1 if file.refcount <= 0 and file.handle and file.handle.close then file.handle.close() diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod index 50d705b..6ca94fa 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_devfs.kmod @@ -16,7 +16,7 @@ proxy.getLabel = function() return "devfs" end proxy.attributes = function(path) return { size = 0, modified = 0, - created = 0, + created = 0 } end function proxy:open(path, mode) diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod index dd38e96..17f59da 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod @@ -87,7 +87,7 @@ kernel.log("Seeding filesystem permissions...", "INFO") mergeMeta("/", { {"bin", REG, 0, 0, RWX_RX_RX}, {"boot", REG, 0, 0, RWX_RX_RX}, - {"dev", REG, 0, 0, RWX_RX_RX}, + {"dev", REG, 0, 0, RWXRWXRWX}, {"etc", REG, 0, 0, RWX_RX_RX}, {"home", REG, 0, 0, RWX_RX_RX}, {"lib", REG, 0, 0, RWX_RX_RX}, @@ -98,126 +98,10 @@ mergeMeta("/", { {"var", REG, 0, 0, RWX_RX_RX}, }) -mergeMeta("/boot", { - {"kernel.lua", REG, 0, 0, RW_R_R }, - {"boot.cfg", REG, 0, 0, RW_R_R }, - {"safeboot.cfg", REG, 0, 0, RW_R_R }, - {"fstab", REG, 0, 0, RW_R_R }, - {"initfs", REG, 0, 0, RW_R_R }, - {"cct", REG, 0, 0, RWX_RX_RX}, - {"oc", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/boot/cct", { - {"boot.lua", REG, 0, 0, RW_R_R}, - {"initdisks", REG, 0, 0, RW_R_R}, - {"eeprom", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/boot/oc", { - {"boot.lua", REG, 0, 0, RW_R_R}, - {"initfs.lua",REG, 0, 0, RW_R_R}, - {"eeprom", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/sbin", { - {"init.lua", REG, 0, 0, RWX_RX_RX}, -}) - mergeMeta("/bin", { - {"cat", REG, 0, 0, RWX_RX_RX}, - {"chattr", REG, 0, 0, RWX_RX_RX}, - {"chgrp", REG, 0, 0, RWX_RX_RX}, - {"chmod", REG, 0, 0, RWX_RX_RX}, - {"chown", REG, 0, 0, RWX_RX_RX}, - {"chroot", REG, 0, 0, RWX_RX_RX}, - {"clear", REG, 0, 0, RWX_RX_RX}, - {"echo", REG, 0, 0, RWX_RX_RX}, - {"hfetch", REG, 0, 0, RWX_RX_RX}, - {"help", REG, 0, 0, RWX_RX_RX}, - {"hysh", REG, 0, 0, RWX_RX_RX}, - {"hyshex", REG, 0, 0, RWX_RX_RX}, - {"id", REG, 0, 0, RWX_RX_RX}, - {"install", REG, 0, 0, RWX_RX_RX}, - {"ln", REG, 0, 0, RWX_RX_RX}, {"login", REG, 0, 0, SUID_755 }, - {"loimgcreate", REG, 0, 0, RWX_RX_RX}, - {"looptest", REG, 0, 0, RWX_RX_RX}, - {"losetup", REG, 0, 0, RWX_RX_RX}, - {"ls", REG, 0, 0, RWX_RX_RX}, - {"lsusers", REG, 0, 0, RWX_RX_RX}, - {"lua", REG, 0, 0, RWX_RX_RX}, - {"luaold", REG, 0, 0, RWX_RX_RX}, - {"micro", REG, 0, 0, RWX_RX_RX}, - {"mkdir", REG, 0, 0, RWX_RX_RX}, - {"mount", REG, 0, 0, RWX_RX_RX}, - {"passwd", REG, 0, 0, RWX_RX_RX}, - {"ps", REG, 0, 0, RWX_RX_RX}, - {"pwd", REG, 0, 0, RWX_RX_RX}, - {"readlink", REG, 0, 0, RWX_RX_RX}, - {"sed", REG, 0, 0, RWX_RX_RX}, - {"socktest", REG, 0, 0, RWX_RX_RX}, - {"spm", REG, 0, 0, RWX_RX_RX}, - {"startup", REG, 0, 0, RWX_RX_RX}, {"su", REG, 0, 0, SUID_755 }, {"sudo", REG, 0, 0, SUID_755 }, - {"sysdump", REG, 0, 0, RWX_RX_RX}, - {"umount", REG, 0, 0, RWX_RX_RX}, - {"useradd", REG, 0, 0, RWX_RX_RX}, - {"userdel", REG, 0, 0, RWX_RX_RX}, - {"usermod", REG, 0, 0, RWX_RX_RX}, - {"whoami", REG, 0, 0, RWX_RX_RX}, - {"yes", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/bin/startup", { - {"test.lua", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/lib", { - {"sys", REG, 0, 0, RWX_RX_RX}, - {"modules", REG, 0, 0, RWX_RX_RX}, - {"crypto", REG, 0, 0, RWX_RX_RX}, - {"store", REG, 0, 0, RWX_RX_RX}, - {"snip", REG, 0, 0, RW_R_R }, - {"io", REG, 0, 0, RW_R_R }, - {"bit32", REG, 0, 0, RW_R_R }, -}) - -mergeMeta("/lib/sys", { - {"fs", REG, 0, 0, RW_R_R}, - {"hpv", REG, 0, 0, RW_R_R}, - {"ipc", REG, 0, 0, RW_R_R}, - {"term", REG, 0, 0, RW_R_R}, - {"init", REG, 0, 0, RW_R_R}, -}) - -mergeMeta("/lib/modules", { - {"hyperion", REG, 0, 0, RWX_RX_RX}, -}) - -mergeMeta("/lib/modules/hyperion", { - {"01_stdlib.kmod", REG, 0, 0, RW_R_R}, - {"10_vfs.kmod", REG, 0, 0, RW_R_R}, - {"11_require.kmod", REG, 0, 0, RW_R_R}, - {"12_devfs.kmod", REG, 0, 0, RW_R_R}, - {"12_tmpfs.kmod", REG, 0, 0, RW_R_R}, - {"13_loopdev.kmod", REG, 0, 0, RW_R_R}, - {"14_keventd.kmod", REG, 0, 0, RW_R_R}, - {"19_fstab.kmod", REG, 0, 0, RW_R_R}, - {"20_signals.kmod", REG, 0, 0, RW_R_R}, - {"20_socket.kmod", REG, 0, 0, RW_R_R}, - {"26_tty.kmod", REG, 0, 0, RW_R_R}, - {"30_userspace.kmod", REG, 0, 0, RW_R_R}, - {"40_auth.kmod", REG, 0, 0, RW_R_R}, - {"45_hypervisor.kmod", REG, 0, 0, RW_R_R}, - {"47_dbg.kmod", REG, 0, 0, RW_R_R}, - {"50_gpio.kmod", REG, 0, 0, RW_R_R}, - {"70_stdlibadv.kmod", REG, 0, 0, RW_R_R}, - {"90_init.kmod", REG, 0, 0, RW_R_R}, - {"91_login.kmod", REG, 0, 0, RW_R_R}, - {"92_permissions.kmod", REG, 0, 0, RW_R_R}, - {"99_final.kmod", REG, 0, 0, RW_R_R}, }) mergeMeta("/etc", { diff --git a/build.py b/build.py index e04aeea..aee6698 100644 --- a/build.py +++ b/build.py @@ -6,8 +6,10 @@ Usage: Targets: build build-mini + build-micro build-test build-mini-test + build-micro-test clean Arch flags: @@ -23,11 +25,9 @@ import sys import shutil import argparse import subprocess -import hashlib -import random -import string from pathlib import Path from typing import Union +import lz4.frame PROJECT_ROOT = Path(__file__).resolve().parent SRC_ROOT = PROJECT_ROOT / "Src" @@ -48,7 +48,34 @@ def clean(): print("Nothing to clean.") -def process_root(src_root: Path, out_root: Path, minify: bool): +def has_minify_header(path: Path) -> bool: + try: + with path.open("r", encoding="utf-8", errors="ignore") as f: + for _ in range(3): + if "--:Minify:--" in f.readline(): + return True + except OSError: + pass + return False + + +def minify_file(src: Path) -> str: + result = subprocess.run( + ["luamin.cmd", "-f", str(src)], + capture_output=True, + text=True + ) + if result.returncode != 0: + print(f" ! luamin failed: {result.stderr.strip()}", file=sys.stderr) + sys.exit(1) + return result.stdout + + +def compress_lz4(data: bytes) -> bytes: + return lz4.frame.compress(data) + + +def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): print(f"Building from {src_root}") print(f"Output to {out_root}") print() @@ -69,16 +96,20 @@ def process_root(src_root: Path, out_root: Path, minify: bool): print(f" Processing: {src.relative_to(src_root)}") - if minify and has_minify_header(src): + if has_minify_header(src): print(" > Minifying") - result = subprocess.run( - ["luamin.cmd", "-f", str(src)], - capture_output=True, text=True - ) - if result.returncode != 0: - print(f" ! luamin failed: {result.stderr.strip()}", file=sys.stderr) - sys.exit(1) - dst.write_text(result.stdout, encoding="utf-8") + content = minify_file(src) + if micro: + print(" > LZ4 compressing") + compressed = compress_lz4(content.encode("utf-8")) + # wrap in kernel.unpack if in hyperion-kernel + if pkg_dir.name == "hyperion-kernel" and dst.suffix == ".lua": + content_str = f"kernel.unpack([=[{compressed.hex()}]=])" + dst.write_text(content_str, encoding="utf-8") + else: + dst.write_bytes(compressed) + else: + dst.write_text(content, encoding="utf-8") else: print(" > Copying") shutil.copy2(src, dst) @@ -95,26 +126,15 @@ def install_bootloader(arch: str, release: bool): shutil.copy2(eeprom, BUILD_ROOT / eeprom_dst_name) -def has_minify_header(path: Path) -> bool: - try: - with path.open("r", encoding="utf-8", errors="ignore") as f: - for _ in range(3): - if "--:Minify:--" in f.readline(): - return True - except OSError: - pass - return False - - -def run_build(minify: bool, include_test: bool, arch: Union[str, None], release: bool): +def run_build(minify: bool, micro: bool, include_test: bool, arch: Union[str, None], release: bool): clean() BUILD_ROOT.mkdir() out_root = BUILD_ROOT / "$" if arch else BUILD_ROOT - process_root(SRC_ROOT, out_root, minify) + process_root(SRC_ROOT, out_root, minify, micro) if include_test: - process_root(TEST_ROOT, out_root, minify) + process_root(TEST_ROOT, out_root, minify, micro) if arch: print("Installing bootloader files ...") @@ -122,46 +142,6 @@ def run_build(minify: bool, include_test: bool, arch: Union[str, None], release: print() -def main(): - parser = argparse.ArgumentParser(description="HyperionOS build script") - parser.add_argument("target", choices=["build", "build-mini", "build-test", "build-mini-test", "clean"]) - parser.add_argument("--arch", choices=["cct", "oc"], default=None, - help="Target architecture (cct or oc)") - parser.add_argument("--release", dest="release", action="store_true", default=True, - help="Release build: eeprom placed as startup.lua (default)") - parser.add_argument("--dev", dest="release", action="store_false", - help="Dev build: boot.lua and eeprom copied unchanged") - parser.add_argument( - "--makeuser", metavar=("USERNAME", "PASSWORD"), nargs=2, action="append", - default=[], - help=( - "Pre-create a user on first boot (dev builds only). " - "May be specified multiple times. " - "Example: --makeuser root secretpass --makeuser alice alicepass" - ), - ) - - args = parser.parse_args() - - if args.makeuser and args.release: - parser.error("--makeuser is only allowed with --dev builds") - - if args.target == "clean": - clean() - return - - minify = "mini" in args.target - include_test = "test" in args.target - - run_build(minify=minify, include_test=include_test, arch=args.arch, release=args.release) - - if args.makeuser: - print("Injecting first-boot user setup ...") - inject_makeusers(args.makeuser, args.arch) - print() - - print("Build complete.") - def _make_firstboot_kmod(users): lines = [] lines.append("local kernel = ...") @@ -212,5 +192,47 @@ def inject_makeusers(users, arch): print(" Wrote first-boot user setup -> " + str(kmod_path.relative_to(BUILD_ROOT))) +def main(): + parser = argparse.ArgumentParser(description="HyperionOS build script") + parser.add_argument("target", choices=["build", "build-mini", "build-micro", "build-test", "build-mini-test", "build-micro-test", "clean"]) + parser.add_argument("--arch", choices=["cct", "oc"], default=None, + help="Target architecture (cct or oc)") + parser.add_argument("--release", dest="release", action="store_true", default=True, + help="Release build: eeprom placed as startup.lua (default)") + parser.add_argument("--dev", dest="release", action="store_false", + help="Dev build: boot.lua and eeprom copied unchanged") + parser.add_argument( + "--makeuser", metavar=("USERNAME", "PASSWORD"), nargs=2, action="append", + default=[], + help=( + "Pre-create a user on first boot (dev builds only). " + "May be specified multiple times. " + "Example: --makeuser root secretpass --makeuser alice alicepass" + ), + ) + + args = parser.parse_args() + + if args.makeuser and args.release: + parser.error("--makeuser is only allowed with --dev builds") + + if args.target == "clean": + clean() + return + + minify = "mini" in args.target or "micro" in args.target + micro = "micro" in args.target + include_test = "test" in args.target + + run_build(minify=minify, micro=micro, include_test=include_test, arch=args.arch, release=args.release) + + if args.makeuser: + print("Injecting first-boot user setup ...") + inject_makeusers(args.makeuser, args.arch) + print() + + print("Build complete.") + + if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/building.md b/building.md index 585f45e..f26e86c 100644 --- a/building.md +++ b/building.md @@ -8,24 +8,23 @@ Run: make build ``` -Optional variables: +**Optional variables:** -* **`ARCH=`** +* **`ARCH=`** – Select bootloader type: - * `cct` Build using the cct bootloader - * `oc` Build using the oc bootloader + * `cct` – Build using the CCT bootloader + * `oc` – Build using the OC bootloader -* **`DEV=1`** +* **`DEV=1`** – Enable development mode: - * Builds in development mode - * Bootloader does not start automatically on system startup + * Bootloader does **not** start automatically on system startup -If `DEV` is not specified: +**Default behavior (if `DEV` is not specified):** -* Default is release mode -* Bootloader starts automatically on system startup +* Builds in **release mode** +* Bootloader starts automatically on system startup -**Examples** +**Examples:** ```bash make build ARCH=cct @@ -42,38 +41,46 @@ Run: python build.py build ``` -Optional arguments: +**Optional arguments:** -* **`--arch {cct|oc}`** - Select bootloader +* **`--arch {cct|oc}`** – Select bootloader: - * `cct` Use the cct bootloader - * `oc` Use the oc bootloader + * `cct` – Use the CCT bootloader + * `oc` – Use the OC bootloader -* **`--dev`** +* **`--dev`** – Development mode: - * Development mode - * Bootloader does not start automatically. You must run `eeprom` in CraftOS to start Hyperion. + * Bootloader does **not** start automatically + * You must run `eeprom` in CraftOS to start Hyperion -* **`--release`** (default) +* **`--release`** (default) – Release mode: - * Release mode * Bootloader starts automatically - -* **`--makeuser username password`** - Makes a username upon startup. Only works for `--dev` builds. - - * `--makeuser root rootpass` - - Makes the root account already exist on first boot with rootpass as password - - * `--makeuser root rootpass --makeuser alice alicepass` - - Makes the root account and alice account already exist on first boot with defined passwords -**Examples** +* **`--makeuser username password`** – Pre-create user accounts (only works with `--dev` builds): + + ```bash + --makeuser root rootpass + --makeuser root rootpass --makeuser alice alicepass + ``` + + * Example: The first command creates the `root` account with the given password on first boot + * Example: The second command creates both `root` and `alice` accounts with defined passwords on first boot + +**Examples:** ```bash python build.py build --arch cct python build.py build --arch oc --dev ``` + +--- + +### Build Requirements + +* **`build`** – No additional requirements +* **`build-mini`** – Requires [`luamin`](https://www.npmjs.com/package/luamin) +* **`build-micro`** – Requires: + + * [`luamin`](https://www.npmjs.com/package/luamin) + * [`LZ4 binaries`](https://github.com/lz4/lz4/releases) \ No newline at end of file From 31ce894fda2ac8041418cf256541e52c4bac101a Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 21:26:50 -0500 Subject: [PATCH 10/32] fixed build script --- build.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.py b/build.py index aee6698..a08a62b 100644 --- a/build.py +++ b/build.py @@ -27,7 +27,6 @@ import argparse import subprocess from pathlib import Path from typing import Union -import lz4.frame PROJECT_ROOT = Path(__file__).resolve().parent SRC_ROOT = PROJECT_ROOT / "Src" @@ -224,6 +223,9 @@ def main(): micro = "micro" in args.target include_test = "test" in args.target + if micro: + import lz4.block + run_build(minify=minify, micro=micro, include_test=include_test, arch=args.arch, release=args.release) if args.makeuser: From 4e5a4172bfb9219a49fba0f084d81313c8167acc Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 21:29:46 -0500 Subject: [PATCH 11/32] hopfully fixed it omfg --- build.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/build.py b/build.py index a08a62b..986ceb8 100644 --- a/build.py +++ b/build.py @@ -96,19 +96,20 @@ def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): print(f" Processing: {src.relative_to(src_root)}") if has_minify_header(src): - print(" > Minifying") - content = minify_file(src) - if micro: - print(" > LZ4 compressing") - compressed = compress_lz4(content.encode("utf-8")) - # wrap in kernel.unpack if in hyperion-kernel - if pkg_dir.name == "hyperion-kernel" and dst.suffix == ".lua": - content_str = f"kernel.unpack([=[{compressed.hex()}]=])" - dst.write_text(content_str, encoding="utf-8") + if minify: + print(" > Minifying") + content = minify_file(src) + if micro: + print(" > LZ4 compressing") + compressed = compress_lz4(content.encode("utf-8")) + # wrap in kernel.unpack if in hyperion-kernel + if pkg_dir.name == "hyperion-kernel" and dst.suffix == ".lua": + content_str = f"kernel.unpack([=[{compressed.hex()}]=])" + dst.write_text(content_str, encoding="utf-8") + else: + dst.write_bytes(compressed) else: - dst.write_bytes(compressed) - else: - dst.write_text(content, encoding="utf-8") + dst.write_text(content, encoding="utf-8") else: print(" > Copying") shutil.copy2(src, dst) From b532a63fc67075099c1bb581d13e08dc607eb945 Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 21:32:56 -0500 Subject: [PATCH 12/32] fixed stupid dumbass mistake i made me dumb --- build.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.py b/build.py index 986ceb8..c3bd5e4 100644 --- a/build.py +++ b/build.py @@ -110,6 +110,9 @@ def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): dst.write_bytes(compressed) else: dst.write_text(content, encoding="utf-8") + else: + print(" > Copying") + shutil.copy2(src, dst) else: print(" > Copying") shutil.copy2(src, dst) From 1827a463ebd66489cacb60125608c94a084eb167 Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 21:56:05 -0500 Subject: [PATCH 13/32] added ll for ghxx --- Src/Hyperion-bash/bin/ll | 3 +++ Src/Hyperion-bash/bin/sysdump | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 Src/Hyperion-bash/bin/ll diff --git a/Src/Hyperion-bash/bin/ll b/Src/Hyperion-bash/bin/ll new file mode 100644 index 0000000..fe29656 --- /dev/null +++ b/Src/Hyperion-bash/bin/ll @@ -0,0 +1,3 @@ +local args={...} +table.insert(args, "-lah") +syscall.exec("/bin/ls", args)2 \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/sysdump b/Src/Hyperion-bash/bin/sysdump index 224d47f..a19fedc 100644 --- a/Src/Hyperion-bash/bin/sysdump +++ b/Src/Hyperion-bash/bin/sysdump @@ -1,6 +1,9 @@ --:Minify:-- +local path=... +path=path or "/dev/tty/1" local syscalls=syscall.sysdump() +local fd=syscall.open(path,"w") for i=1, #syscalls do - print(syscalls[i]) + syscall.write(fd,syscalls[i].."\n) end -print("Total # of syscalls: "..tostring(#syscalls)) \ No newline at end of file +syscall.write(fd,"Total # of syscalls: "..tostring(#syscalls)) \ No newline at end of file From eb5bed0f093a90c7b6ab01f09c8b60c40bd45909 Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 21:58:51 -0500 Subject: [PATCH 14/32] fixed asyncsyscall5 --- Src/Hyperion-firmware-cct/boot/cct/boot.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/Src/Hyperion-firmware-cct/boot/cct/boot.lua b/Src/Hyperion-firmware-cct/boot/cct/boot.lua index 7bdae02..b902780 100644 --- a/Src/Hyperion-firmware-cct/boot/cct/boot.lua +++ b/Src/Hyperion-firmware-cct/boot/cct/boot.lua @@ -71,7 +71,6 @@ local ok, err = xpcall(function() collectgarbage = true, error = true, gcinfo = true, - getfenv = true, getmetatable = true, ipairs = true, __inext = true, @@ -85,7 +84,6 @@ local ok, err = xpcall(function() rawlen = true, rawset = true, select = true, - setfenv = true, setmetatable = true, string = true, table = true, From b7f52dd17bdfee6a69801f7030d104ad6f5b03b2 Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 2 Mar 2026 22:25:37 -0500 Subject: [PATCH 15/32] working on syscall manifest and fixed anther exploit --- Src/Hyperion-bash/bin/sysdump | 5 +- .../lib/modules/hyperion/10_vfs.kmod | 2 +- Src/syscallautofill.lua | 101 ++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 Src/syscallautofill.lua diff --git a/Src/Hyperion-bash/bin/sysdump b/Src/Hyperion-bash/bin/sysdump index a19fedc..d027d7d 100644 --- a/Src/Hyperion-bash/bin/sysdump +++ b/Src/Hyperion-bash/bin/sysdump @@ -4,6 +4,7 @@ path=path or "/dev/tty/1" local syscalls=syscall.sysdump() local fd=syscall.open(path,"w") for i=1, #syscalls do - syscall.write(fd,syscalls[i].."\n) + syscall.write(fd,syscalls[i].."\n") end -syscall.write(fd,"Total # of syscalls: "..tostring(#syscalls)) \ No newline at end of file +syscall.write(fd,"Total # of syscalls: "..tostring(#syscalls)) +syscall.close(fd) \ No newline at end of file diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod index 07b6617..156cc71 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/10_vfs.kmod @@ -398,7 +398,7 @@ local function getFileMeta(path, noFollow) local real = namei(path, noFollow) if real == "/" then - return { etype = 0x00, owner = 0, group = 0, perms = 63, cmeta = "" } + return { etype = 0x00, owner = 0, group = 0, perms = 62, cmeta = "" } end local cur = real diff --git a/Src/syscallautofill.lua b/Src/syscallautofill.lua new file mode 100644 index 0000000..b753f83 --- /dev/null +++ b/Src/syscallautofill.lua @@ -0,0 +1,101 @@ +syscall={} +syscall.sethomedir=function(uid, homedir) end +syscall.read=function(fd, amount) end +syscall.getTask=function(pid) end +syscall.connect=function(fd, address) end +syscall.getcwd=function() end +syscall.lodetach=function(id) end +syscall.stop=function(pid) end +syscall.recv +syscall.write=function(fd, data) end +syscall.getppid=function() end +syscall.lstat +syscall.open=function(path, mode) end +syscall.lseek=function(fd, offset, whence) end +syscall.setHostname=function(hostname) end +syscall.chroot +syscall.dup2 +syscall.getpid=function() end +syscall.fchown +syscall.close=function(fd) end +syscall.umount +syscall.getTasks=function() end +syscall.sysdump=function() end +syscall.fchmod +syscall.getHostname=function() end +syscall.listen +syscall.dup +syscall.gpio_read +syscall.fget_suid +syscall.gpio_write +syscall.setpassword +syscall.setEnviron=function(key, value) end +syscall.losetup +syscall.reboot=function() end +syscall.getuid=function() end +syscall.sigsend +syscall.sleep=function(time) end +syscall.exit=function(code) end +syscall.getEnviron=function(key) end +syscall.continue=function(pid) end +syscall.socket +syscall.log=function(text, tag, color) end +syscall.loimgwrite +syscall.exists=function(path) end +syscall.setuid=function(uid) end +syscall.exec=function(path, args, envars) end +syscall.execspawn=function(path, name, envars, args, tgid) end +syscall.loimgcreate +syscall.time=function() end +syscall.newuser +syscall.spawn=function(func, name, envars, args, tgid) end +syscall.collect=function(pid) end +syscall.setshell +syscall.devctl=function(fd, funcname) end +syscall.listusers +syscall.unlockuser +syscall.mount +syscall.accept +syscall.lolist +syscall.readlink +syscall.deleteuser +syscall.remove=function(path) end +syscall.type=function(path) end +syscall.elevate +syscall.mkdir=function(path) end +syscall.getuidbyname +syscall.whoami=function() end +syscall.sendfile +syscall.setusername +syscall.geteuid +syscall.login +syscall.getHost +syscall.getUptime +syscall.httpget +syscall.stat +syscall.symlink +syscall.pread +syscall.chdir +syscall.arch=function() end +syscall.pwrite +syscall.sockshutdown +syscall.resolve +syscall.send +syscall.fstat +syscall.chown +syscall.fsync +syscall.lockuser +syscall.getUsername +syscall.getsockname +syscall.bind +syscall.kill=function(pid) end +syscall.setgid +syscall.getpeername +syscall.sigcatch +syscall.shutdown +syscall.access +syscall.sigignore +syscall.getpasswd +syscall.version +syscall.chmod=function(path, perms) end +syscall.listdir=function(path) end \ No newline at end of file From c7545e6947fa1c61825d75caf353d882fde541bc Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 07:31:59 -0500 Subject: [PATCH 16/32] Update build.py --- build.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/build.py b/build.py index c3bd5e4..43a9177 100644 --- a/build.py +++ b/build.py @@ -102,12 +102,7 @@ def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): if micro: print(" > LZ4 compressing") compressed = compress_lz4(content.encode("utf-8")) - # wrap in kernel.unpack if in hyperion-kernel - if pkg_dir.name == "hyperion-kernel" and dst.suffix == ".lua": - content_str = f"kernel.unpack([=[{compressed.hex()}]=])" - dst.write_text(content_str, encoding="utf-8") - else: - dst.write_bytes(compressed) + dst.write_bytes(compressed) else: dst.write_text(content, encoding="utf-8") else: From 9a7db6c243c08780edb67e4d941743898d04ed1e Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 08:02:05 -0500 Subject: [PATCH 17/32] syscalls now autocomplete is vsc --- Src/syscallautofill.lua | 118 ++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/Src/syscallautofill.lua b/Src/syscallautofill.lua index b753f83..1a37930 100644 --- a/Src/syscallautofill.lua +++ b/Src/syscallautofill.lua @@ -6,96 +6,96 @@ syscall.connect=function(fd, address) end syscall.getcwd=function() end syscall.lodetach=function(id) end syscall.stop=function(pid) end -syscall.recv +syscall.recv=function(fd, amount) end syscall.write=function(fd, data) end syscall.getppid=function() end -syscall.lstat +syscall.lstat=function(path) end syscall.open=function(path, mode) end syscall.lseek=function(fd, offset, whence) end syscall.setHostname=function(hostname) end -syscall.chroot -syscall.dup2 +syscall.chroot=function(path) end +syscall.dup2=function(src, dst) end syscall.getpid=function() end -syscall.fchown +syscall.fchown=function(fd, uid, gid) end syscall.close=function(fd) end -syscall.umount +syscall.umount=function(target) end syscall.getTasks=function() end syscall.sysdump=function() end -syscall.fchmod +syscall.fchmod=function(fd, perms) end syscall.getHostname=function() end -syscall.listen -syscall.dup -syscall.gpio_read -syscall.fget_suid -syscall.gpio_write -syscall.setpassword +syscall.listen=function(fd, backlog) end +syscall.dup=function(fd) end +syscall.gpio_read=function(pin) end +syscall.fget_suid=function(fd) end +syscall.gpio_write=function(pin, data) end +syscall.setpassword=function(uid, newPassword) end syscall.setEnviron=function(key, value) end -syscall.losetup +syscall.losetup=function(filePath, forceImage) end syscall.reboot=function() end syscall.getuid=function() end -syscall.sigsend +syscall.sigsend=function(pid, sigid) end syscall.sleep=function(time) end syscall.exit=function(code) end syscall.getEnviron=function(key) end syscall.continue=function(pid) end -syscall.socket +syscall.socket=function(domain, socktype) end syscall.log=function(text, tag, color) end -syscall.loimgwrite +syscall.loimgwrite=function(imgStr, destPath) end syscall.exists=function(path) end syscall.setuid=function(uid) end syscall.exec=function(path, args, envars) end syscall.execspawn=function(path, name, envars, args, tgid) end -syscall.loimgcreate +syscall.loimgcreate=function(srcPath) end syscall.time=function() end -syscall.newuser +syscall.newuser=function(username, password, gid, homedir, shell) end syscall.spawn=function(func, name, envars, args, tgid) end syscall.collect=function(pid) end -syscall.setshell +syscall.setshell=function(uid, shell) end syscall.devctl=function(fd, funcname) end -syscall.listusers -syscall.unlockuser -syscall.mount -syscall.accept -syscall.lolist -syscall.readlink -syscall.deleteuser +syscall.listusers=function() end +syscall.unlockuser=function(uid) end +syscall.mount=function(target, diskOrId) end +syscall.accept=function(fd) end +syscall.lolist=function() end +syscall.readlink=function(path) end +syscall.deleteuser=function(uid) end syscall.remove=function(path) end syscall.type=function(path) end -syscall.elevate +syscall.elevate=function(targetUsername, password) end syscall.mkdir=function(path) end -syscall.getuidbyname +syscall.getuidbyname=function(username) end syscall.whoami=function() end -syscall.sendfile -syscall.setusername -syscall.geteuid -syscall.login -syscall.getHost -syscall.getUptime -syscall.httpget -syscall.stat -syscall.symlink -syscall.pread -syscall.chdir +syscall.sendfile=function(src, dest, amount) end +syscall.setusername=function(uid, newUsername) end +syscall.geteuid=function() end +syscall.login=function(username, password) end +syscall.getHost=function() end +syscall.getUptime=function() end +syscall.httpget=function(url, headers) end +syscall.stat=function(path) end +syscall.symlink=function(target, linkPath) end +syscall.pread=function(fd, count, offset) end +syscall.chdir=function(path) end syscall.arch=function() end -syscall.pwrite -syscall.sockshutdown -syscall.resolve -syscall.send -syscall.fstat -syscall.chown -syscall.fsync -syscall.lockuser -syscall.getUsername -syscall.getsockname -syscall.bind +syscall.pwrite=function(fd, data, offset) end +syscall.sockshutdown=function(fd) end +syscall.resolve=function(hostname) end +syscall.send=function(fd, data) end +syscall.fstat=function(fd) end +syscall.chown=function(path, uid, gid) end +syscall.fsync=function(fd) end +syscall.lockuser=function(uid) end +syscall.getUsername=function(uid) end +syscall.getsockname=function(fd) end +syscall.bind=function(fd, address) end syscall.kill=function(pid) end -syscall.setgid -syscall.getpeername -syscall.sigcatch -syscall.shutdown -syscall.access -syscall.sigignore -syscall.getpasswd -syscall.version +syscall.setgid=function(uid, gid) end +syscall.getpeername=function(fd) end +syscall.sigcatch=function(handler) end +syscall.shutdown=function() end +syscall.access=function(path, mode) end +syscall.sigignore=function() end +syscall.getpasswd=function(uid) end +syscall.version=function() end syscall.chmod=function(path, perms) end syscall.listdir=function(path) end \ No newline at end of file From e2e1d5b8a5b91c5134a93d6cde50ed4f347fa860 Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 08:52:55 -0500 Subject: [PATCH 18/32] making descriptions for syscalls --- Src/syscallautofill.lua | 42 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Src/syscallautofill.lua b/Src/syscallautofill.lua index 1a37930..abca076 100644 --- a/Src/syscallautofill.lua +++ b/Src/syscallautofill.lua @@ -1,12 +1,48 @@ syscall={} + +--- Sets home directory of User with corosponding uid to homedir +--- @param uid number +--- @param homedir string +--- @return true|nil, nil|string syscall.sethomedir=function(uid, homedir) end + +--- Reads amount from fd and returns content or nil +--- @param fd integer +--- @param amount integer +--- @return string|nil syscall.read=function(fd, amount) end + +--- Gets information of task with id of pid +---@param pid integer +---@return table|nil syscall.getTask=function(pid) end + +--- client: connect to server address +---@param fd integer +---@param address string +---@return nil syscall.connect=function(fd, address) end -syscall.getcwd=function() end + +--- Get current working directory +--- @return string +syscall.getcwd=function() return "string" end + +--- detach loop device (must be unmounted first) +--- @param id string +--- @return nil syscall.lodetach=function(id) end -syscall.stop=function(pid) end -syscall.recv=function(fd, amount) end + +--- Stops task with id of pid +--- @param pid integer +--- @return boolean, string|nil +syscall.stop=function(pid) return true end + +--- Receive bytes from socket (blocking poll, returns "" on nothing) +--- @param fd integer +--- @param amount integer +--- @return string +syscall.recv=function(fd, amount) return "string" end + syscall.write=function(fd, data) end syscall.getppid=function() end syscall.lstat=function(path) end From 82c3e2b3466e39701f067a1297dc5c2da0d2ae12 Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 08:57:45 -0500 Subject: [PATCH 19/32] fix ll --- Src/Hyperion-bash/bin/hysh | 4 ++-- Src/Hyperion-bash/bin/ll | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/Hyperion-bash/bin/hysh b/Src/Hyperion-bash/bin/hysh index 4e0d50d..6038361 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/Hyperion-bash/bin/hysh @@ -1,7 +1,7 @@ --:Minify:-- syscall.open("/dev/tty/1","r") --stdin (Device 0) syscall.open("/dev/tty/1","w") --stdout (Device 1) -syscall.open("/dev/null","w") --stderr (device 2) +syscall.open("/dev/null","w") --stderr (device 2) local success, errorMsg = xpcall(function() @@ -15,7 +15,7 @@ print("HyperionOS hysh Shell") local userhost = (syscall.getUsername() or "Unknown").."@"..(syscall.getHostname() or "Unknown") local commandHistory = {} local terminate = false -syscall.setEnviron("SHELL","rtbash") +syscall.setEnviron("SHELL","hysh") syscall.setEnviron("PATH","/bin/") local _home = syscall.getEnviron("HOME") if _home and _home ~= "" then diff --git a/Src/Hyperion-bash/bin/ll b/Src/Hyperion-bash/bin/ll index fe29656..5cefbfd 100644 --- a/Src/Hyperion-bash/bin/ll +++ b/Src/Hyperion-bash/bin/ll @@ -1,3 +1,3 @@ local args={...} table.insert(args, "-lah") -syscall.exec("/bin/ls", args)2 \ No newline at end of file +syscall.exec("/bin/ls", args) \ No newline at end of file From fabc061731788f01faf5a5e4ddc32d4b6e083f1f Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 10:06:04 -0500 Subject: [PATCH 20/32] made lua not clear screen --- Src/Hyperion-bash/bin/lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Src/Hyperion-bash/bin/lua b/Src/Hyperion-bash/bin/lua index e10d3ff..b6f42bb 100644 --- a/Src/Hyperion-bash/bin/lua +++ b/Src/Hyperion-bash/bin/lua @@ -260,17 +260,17 @@ local function getUserInput(prompt, history) while true do local key = syscall.read(0) if key and key ~= "" then - if key == "\19" then + if key == "" then if cursor > 1 then cursor = cursor - 1; dirty = true end - elseif key == "\20" then + elseif key == "" then if cursor <= #input then cursor = cursor + 1; dirty = true end - elseif key == "\17" then + elseif key == "" then if history and histIdx < #history then histIdx = histIdx + 1 input = history[#history - histIdx + 1] cursor = #input + 1; dirty = true end - elseif key == "\18" then + elseif key == "" then if histIdx > 1 then histIdx = histIdx - 1 input = history[#history - histIdx + 1] @@ -299,8 +299,6 @@ local function getUserInput(prompt, history) end end -syscall.devctl(1, "clear") -syscall.devctl(1, "spos", 1, 1) c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n") c(C_NIL) w("Interactive Lua REPL. exit() to quit.\n\n") From bb354cc70679348e6f600527dbde73d2a6cdc708 Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 11:45:05 -0500 Subject: [PATCH 21/32] mv syscallautofill --- Src/syscallautofill.lua => syscallautofill.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Src/syscallautofill.lua => syscallautofill.lua (100%) diff --git a/Src/syscallautofill.lua b/syscallautofill.lua similarity index 100% rename from Src/syscallautofill.lua rename to syscallautofill.lua From 6fefa2d9ff59fede68fcec04d6608d9cc0a66b2a Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 3 Mar 2026 12:25:25 -0500 Subject: [PATCH 22/32] cp should not copy perms --- Src/Hyperion-bash/bin/hysh | 2 -- 1 file changed, 2 deletions(-) diff --git a/Src/Hyperion-bash/bin/hysh b/Src/Hyperion-bash/bin/hysh index 6038361..0f41b2f 100644 --- a/Src/Hyperion-bash/bin/hysh +++ b/Src/Hyperion-bash/bin/hysh @@ -133,8 +133,6 @@ local function copyfile(src, dst) if not data then return false, err end local ok, err2 = writefile(dst, data) if not ok then return false, err2 end - local ok2, stat = pcall(syscall.stat, src) - if ok2 and stat and stat.perms then pcall(syscall.chmod, dst, stat.perms) end return true end From 62e032e4c5b63e7bdce128a451cbc6e20315431b Mon Sep 17 00:00:00 2001 From: Astronand Date: Thu, 5 Mar 2026 09:53:30 -0500 Subject: [PATCH 23/32] made sysinit in /usr/lib/sysinit and sped up micro added kernel.firstBoot in kernel table --- Src/Hyperion-bash/bin/micro | 2 - Src/Hyperion-core/sbin/init | 4 ++ .../init.lua => usr/lib/sysinit/sysinit} | 0 Src/Hyperion-kernel/boot/kernel.lua | 9 +-- Src/Hyperion-kernel/boot/safeboot.cfg | 2 +- .../{92_permissions.kmod => 92_setup.kmod} | 58 ++++++++++--------- .../lib/modules/hyperion/99_final.kmod | 2 +- syscallautofill.lua | 4 +- 8 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 Src/Hyperion-core/sbin/init rename Src/Hyperion-core/{sbin/init.lua => usr/lib/sysinit/sysinit} (100%) rename Src/Hyperion-kernel/lib/modules/hyperion/{92_permissions.kmod => 92_setup.kmod} (71%) diff --git a/Src/Hyperion-bash/bin/micro b/Src/Hyperion-bash/bin/micro index c73d68c..63b49ff 100644 --- a/Src/Hyperion-bash/bin/micro +++ b/Src/Hyperion-bash/bin/micro @@ -413,8 +413,6 @@ while running do redraw() dirty = false end - - sleep(0.05) end tclear(); tfg(1); tbg(16); tpos(1,1) diff --git a/Src/Hyperion-core/sbin/init b/Src/Hyperion-core/sbin/init new file mode 100644 index 0000000..5071168 --- /dev/null +++ b/Src/Hyperion-core/sbin/init @@ -0,0 +1,4 @@ +local args={...} +syscall.remove("/sbin/init") +syscall.symlink("/usr/lib/sysinit/sysinit", "/sbin/init") +syscall.exec("/sbin/init", args) \ No newline at end of file diff --git a/Src/Hyperion-core/sbin/init.lua b/Src/Hyperion-core/usr/lib/sysinit/sysinit similarity index 100% rename from Src/Hyperion-core/sbin/init.lua rename to Src/Hyperion-core/usr/lib/sysinit/sysinit diff --git a/Src/Hyperion-kernel/boot/kernel.lua b/Src/Hyperion-kernel/boot/kernel.lua index 19a380d..3a70aba 100644 --- a/Src/Hyperion-kernel/boot/kernel.lua +++ b/Src/Hyperion-kernel/boot/kernel.lua @@ -113,13 +113,14 @@ local split = function(str, delim, maxResultCountOrNil) end if not ifs.isFile("/boot/boot.cfg") then - kernel.log("boot.cfg missing or corrupted!, Attempting to write recovery boot.cfg", "ERROR", 2) + kernel.log("First boot detected writing boot.cfg", "INFO", 3) ifs.writeAllText("/boot/boot.cfg",ifs.readAllText("/boot/safeboot.cfg")) + kernel.firstBoot=true end local initCfgFunc, err = load(ifs.readAllText("/boot/boot.cfg"), "@boot.cfg") if not initCfgFunc then - kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err)) + kernel.LOG_Text("Failed to load /boot/boot.cfg: "..tostring(err)) end ---@diagnostic disable-next-line: param-type-mismatch @@ -149,10 +150,6 @@ function kernel.saveLog() ifs.writeAllText("/var/log/syslog.log", kernel.LOG_Text) end -function loadcstr(string) - -end - function kernel.newFifo() local fifo = {} fifo.push=function(data) diff --git a/Src/Hyperion-kernel/boot/safeboot.cfg b/Src/Hyperion-kernel/boot/safeboot.cfg index 78bea9d..8005543 100644 --- a/Src/Hyperion-kernel/boot/safeboot.cfg +++ b/Src/Hyperion-kernel/boot/safeboot.cfg @@ -4,7 +4,7 @@ -- This file is auto-generated during the build process. -- DEFAULT BOOT CONFIGURATION FILE return { - initPath = "/sbin/init.lua", + initPath = "/sbin/init", maxOpenFiles = 128, maxFilesPerTask = 16, preempt=true diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod similarity index 71% rename from Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod rename to Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod index 17f59da..367f9d8 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/92_permissions.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod @@ -82,36 +82,38 @@ local function mergeMeta(dir, entries) end end -kernel.log("Seeding filesystem permissions...", "INFO") +if kernel.firstBoot then + kernel.log("Seeding filesystem permissions...") -mergeMeta("/", { - {"bin", REG, 0, 0, RWX_RX_RX}, - {"boot", REG, 0, 0, RWX_RX_RX}, - {"dev", REG, 0, 0, RWXRWXRWX}, - {"etc", REG, 0, 0, RWX_RX_RX}, - {"home", REG, 0, 0, RWX_RX_RX}, - {"lib", REG, 0, 0, RWX_RX_RX}, - {"root", REG, 0, 0, RW____ }, - {"sbin", REG, 0, 0, RWX_RX_RX}, - {"tmp", REG, 0, 0, RWXRWXRWX}, - {"usr", REG, 0, 0, RWX_RX_RX}, - {"var", REG, 0, 0, RWX_RX_RX}, -}) + mergeMeta("/", { + {"bin", REG, 0, 0, RWX_RX_RX}, + {"boot", REG, 0, 0, RWX_RX_RX}, + {"dev", REG, 0, 0, RWXRWXRWX}, + {"etc", REG, 0, 0, RWX_RX_RX}, + {"home", REG, 0, 0, RWX_RX_RX}, + {"lib", REG, 0, 0, RWX_RX_RX}, + {"root", REG, 0, 0, RW____ }, + {"sbin", REG, 0, 0, RWX_RX_RX}, + {"tmp", REG, 0, 0, RWXRWXRWX}, + {"usr", REG, 0, 0, RWX_RX_RX}, + {"var", REG, 0, 0, RWX_RX_RX}, + }) -mergeMeta("/bin", { - {"login", REG, 0, 0, SUID_755 }, - {"su", REG, 0, 0, SUID_755 }, - {"sudo", REG, 0, 0, SUID_755 }, -}) + mergeMeta("/bin", { + {"login", REG, 0, 0, SUID_755 }, + {"su", REG, 0, 0, SUID_755 }, + {"sudo", REG, 0, 0, SUID_755 }, + }) -mergeMeta("/etc", { - {"passwd", REG, 0, 0, RW_R_R }, - {"shadow", REG, 0, 0, RW____ }, - {"pam.d", REG, 0, 0, RWX_RX_RX}, -}) + mergeMeta("/etc", { + {"passwd", REG, 0, 0, RW_R_R }, + {"shadow", REG, 0, 0, RW____ }, + {"pam.d", REG, 0, 0, RW____ }, + }) -mergeMeta("/etc/pam.d", { - {"secret", REG, 0, 0, RW____}, -}) + mergeMeta("/etc/pam.d", { + {"secret", REG, 0, 0, RW____}, + }) -kernel.log("Filesystem permissions seeded.", "INFO") + kernel.log("Filesystem permissions seeded.") +end diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod index 5aba7bd..d592766 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/99_final.kmod @@ -1,3 +1,3 @@ --:Minify:-- local kernel = ... -kernel.allowGlobalOverwrites = false \ No newline at end of file +kernel.allowGlobalOverwrites = false diff --git a/syscallautofill.lua b/syscallautofill.lua index abca076..29c9649 100644 --- a/syscallautofill.lua +++ b/syscallautofill.lua @@ -1,7 +1,7 @@ syscall={} --- Sets home directory of User with corosponding uid to homedir ---- @param uid number +--- @param uid integer --- @param homedir string --- @return true|nil, nil|string syscall.sethomedir=function(uid, homedir) end @@ -87,7 +87,7 @@ syscall.newuser=function(username, password, gid, homedir, shell) end syscall.spawn=function(func, name, envars, args, tgid) end syscall.collect=function(pid) end syscall.setshell=function(uid, shell) end -syscall.devctl=function(fd, funcname) end +syscall.devctl=function(fd, funcname, ...) end syscall.listusers=function() end syscall.unlockuser=function(uid) end syscall.mount=function(target, diskOrId) end From 7da67899dbf5d7c4d0a14a669e4d3f8134e7698a Mon Sep 17 00:00:00 2001 From: Astronand Date: Thu, 5 Mar 2026 10:36:43 -0500 Subject: [PATCH 24/32] finished hyperion manifest :D --- manifest.lua | 657 ++++++++++++++++++++++++++++++++++++++++++++ syscallautofill.lua | 137 --------- 2 files changed, 657 insertions(+), 137 deletions(-) create mode 100644 manifest.lua delete mode 100644 syscallautofill.lua diff --git a/manifest.lua b/manifest.lua new file mode 100644 index 0000000..819fe61 --- /dev/null +++ b/manifest.lua @@ -0,0 +1,657 @@ +--- @version 1.2.3 +--- @diagnostic disable: missing-return +--- @diagnostic disable: duplicate-set-field +syscall={} + +--- Sets home directory of User with corresponding uid to homedir +--- @param uid integer +--- @param homedir string +--- @return true|nil, nil|string +syscall.sethomedir=function(uid, homedir) end + +--- Reads amount from fd and returns content or nil +--- @param fd integer +--- @param amount integer +--- @return string|nil +syscall.read=function(fd, amount) end + +--- Gets information of task with id of pid +---@param pid integer +---@return table|nil +syscall.getTask=function(pid) end + +--- Connects a client socket to a server address +---@param fd integer +---@param address string +---@return boolean, string|nil +syscall.connect=function(fd, address) end + +--- Get current working directory +--- @return string +syscall.getcwd=function() end + +--- Detach loop device (must be unmounted first) +--- @param id string +--- @return boolean, string|nil +syscall.lodetach=function(id) end + +--- Stops task with id of pid +--- @param pid integer +--- @return boolean, string|nil +syscall.stop=function(pid) return true end + +--- Receive bytes from socket (blocking poll, returns "" if no data) +--- @param fd integer +--- @param amount integer +--- @return string +syscall.recv=function(fd, amount) end + +--- Write data to file descriptor +--- @param fd integer +--- @param data string +--- @return boolean, string|nil +syscall.write=function(fd, data) end + +--- Get parent process ID +--- @return integer +syscall.getppid=function() end + +--- Get file information (metadata) +--- @param path string +--- @return table|nil +syscall.lstat=function(path) end + +--- Open a file with mode ("r", "w", etc.) +--- @param path string +--- @param mode string +--- @return integer|nil, string|nil +syscall.open=function(path, mode) end + +--- Seek in a file descriptor +--- @param fd integer +--- @param offset integer +--- @param whence integer +--- @return integer|nil +syscall.lseek=function(fd, offset, whence) end + +--- Set system hostname +--- @param hostname string +--- @return boolean +syscall.setHostname=function(hostname) end + +--- Change root directory +--- @param path string +--- @return boolean, string|nil +syscall.chroot=function(path) end + +--- Duplicate file descriptor +--- @param src integer +--- @param dst integer +--- @return integer|nil, string|nil +syscall.dup2=function(src, dst) end + +--- Get current process ID +--- @return integer +syscall.getpid=function() end + +--- Change ownership of a file descriptor +--- @param fd integer +--- @param uid integer +--- @param gid integer +--- @return boolean, string|nil +syscall.fchown=function(fd, uid, gid) end + +--- Close a file descriptor +--- @param fd integer +--- @return boolean, string|nil +syscall.close=function(fd) end + +--- Unmount a target +--- @param target string +--- @return boolean, string|nil +syscall.umount=function(target) end + +--- Get all task IDs +--- @return integer[] +syscall.getTasks=function() end + +--- Dump system state for debugging +--- @return table +syscall.sysdump=function() end + +--- Change permissions of a file descriptor +--- @param fd integer +--- @param perms integer +--- @return boolean, string|nil +syscall.fchmod=function(fd, perms) end + +--- Get system hostname +--- @return string +syscall.getHostname=function() end + +--- Listen for incoming connections +--- @param fd integer +--- @param backlog integer +--- @return boolean, string|nil +syscall.listen=function(fd, backlog) end + +--- Duplicate a file descriptor +--- @param fd integer +--- @return integer|nil +syscall.dup=function(fd) end + +--- Read GPIO pin +--- @param pin integer +--- @return number|nil +syscall.gpio_read=function(pin) end + +--- Get SUID bit from fd +--- @param fd integer +--- @return boolean +syscall.fget_suid=function(fd) end + +--- Write GPIO pin +--- @param pin integer +--- @param data number +--- @return boolean +syscall.gpio_write=function(pin, data) end + +--- Set password for user +--- @param uid integer +--- @param newPassword string +--- @return boolean, string|nil +syscall.setpassword=function(uid, newPassword) end + +--- Set environment variable +--- @param key string +--- @param value string +--- @return boolean +syscall.setEnviron=function(key, value) end + +--- Setup a loop device with filePath +--- @param filePath string +--- @param forceImage boolean +--- @return string|nil, string|nil +syscall.losetup=function(filePath, forceImage) end + +--- Reboot the system +syscall.reboot=function() end + +--- Get current user ID +--- @return integer +syscall.getuid=function() end + +--- Send signal to task +--- @param pid integer +--- @param sigid integer|string +--- @return boolean, string|nil +syscall.sigsend=function(pid, sigid) end + +--- Sleep current task for time seconds +--- @param time number +syscall.sleep=function(time) end + +--- Exit current task +--- @param code integer|nil +syscall.exit=function(code) end + +--- Get environment variable +--- @param key string +--- @return string|nil +syscall.getEnviron=function(key) end + +--- Continue a stopped task +--- @param pid integer +--- @return boolean, string|nil +syscall.continue=function(pid) end + +--- Create a socket +--- @param domain integer +--- @param socktype integer +--- @return integer|nil +syscall.socket=function(domain, socktype) end + +--- Log a message +--- @param text string +--- @param tag string +--- @param color integer +syscall.log=function(text, tag, color) end + +--- Write an image to disk +--- @param imgStr string +--- @param destPath string +--- @return boolean, string|nil +syscall.loimgwrite=function(imgStr, destPath) end + +--- Check if file exists +--- @param path string +--- @return boolean +syscall.exists=function(path) end + +--- Set user ID of current task +--- @param uid integer +--- @return boolean, string|nil +syscall.setuid=function(uid) end + +--- Replace current task with executable +--- @param path string +--- @param args table +--- @param envars table +syscall.exec=function(path, args, envars) end + +--- Spawn a new task from executable +--- @param path string +--- @param name string +--- @param envars table +--- @param args table +--- @param tgid integer +--- @return integer +syscall.execspawn=function(path, name, envars, args, tgid) end + +--- Create image from file +--- @param srcPath string +--- @return string|nil +syscall.loimgcreate=function(srcPath) end + +--- Get system time in ms +--- @return number +syscall.time=function() end + +--- Create a new user +--- @param username string +--- @param password string +--- @param gid integer +--- @param homedir string +--- @param shell string +--- @return integer|nil +syscall.newuser=function(username, password, gid, homedir, shell) end + +--- Spawn a new task from function +--- @param func function +--- @param name string +--- @param envars table +--- @param args table +--- @param tgid integer +--- @return integer +syscall.spawn=function(func, name, envars, args, tgid) end + +--- Collect exit code of a dead child task +--- @param pid integer +--- @return boolean, integer|string +syscall.collect=function(pid) end + +--- Set shell of user +--- @param uid integer +--- @param shell string +--- @return boolean +syscall.setshell=function(uid, shell) end + +--- Device control +--- @param fd integer +--- @param funcname string +--- @param ... any +--- @return any +syscall.devctl=function(fd, funcname, ...) end + +--- List all users +--- @return table +syscall.listusers=function() end + +--- Unlock a user account +--- @param uid integer +--- @return boolean +syscall.unlockuser=function(uid) end + +--- Mount a disk or loop device +--- @param target string +--- @param diskOrId string +--- @return boolean, string|nil +syscall.mount=function(target, diskOrId) end + +--- Accept a client connection on a socket +--- @param fd integer +--- @return integer|nil +syscall.accept=function(fd) end + +--- List loop devices +--- @return table +syscall.lolist=function() end + +--- Read a symbolic link +--- @param path string +--- @return string|nil +syscall.readlink=function(path) end + +--- Delete a user +--- @param uid integer +--- @return boolean +syscall.deleteuser=function(uid) end + +--- Remove a file +--- @param path string +--- @return boolean, string|nil +syscall.remove=function(path) end + +--- Get type of a path (file, dir, link) +--- @param path string +--- @return string|nil +syscall.type=function(path) end + +--- Elevate to another user with password +--- @param targetUsername string +--- @param password string +--- @return boolean +syscall.elevate=function(targetUsername, password) end + +--- Make a directory +--- @param path string +--- @return boolean, string|nil +syscall.mkdir=function(path) end + +--- Get UID by username +--- @param username string +--- @return integer|nil +syscall.getuidbyname=function(username) end + +--- Get current user name +--- @return string +syscall.whoami=function() end + +--- Send file content +--- @param src string +--- @param dest string +--- @param amount integer +--- @return boolean, string|nil +syscall.sendfile=function(src, dest, amount) end + +--- Change username of user +--- @param uid integer +--- @param newUsername string +--- @return boolean +syscall.setusername=function(uid, newUsername) end + +--- Get effective UID +--- @return integer +syscall.geteuid=function() end + +--- Login user +--- @param username string +--- @param password string +--- @return boolean +syscall.login=function(username, password) end + +--- Get system hostname +--- @return string +syscall.getHost=function() end + +--- Get system uptime in ms +--- @return number +syscall.getUptime=function() end + +--- HTTP GET request +--- @param url string +--- @param headers table|nil +--- @return string|nil +syscall.httpget=function(url, headers) end + +--- Get file metadata +--- @param path string +--- @return table|nil +syscall.stat=function(path) end + +--- Create symbolic link +--- @param target string +--- @param linkPath string +--- @return boolean, string|nil +syscall.symlink=function(target, linkPath) end + +--- Read from fd at offset +--- @param fd integer +--- @param count integer +--- @param offset integer +--- @return string|nil +syscall.pread=function(fd, count, offset) end + +--- Change current working directory +--- @param path string +--- @return boolean, string|nil +syscall.chdir=function(path) end + +--- Get system architecture +--- @return string +syscall.arch=function() end + +--- Write to fd at offset +--- @param fd integer +--- @param data string +--- @param offset integer +--- @return boolean, string|nil +syscall.pwrite=function(fd, data, offset) end + +--- Shutdown socket +--- @param fd integer +--- @return boolean, string|nil +syscall.sockshutdown=function(fd) end + +--- Resolve hostname to IP +--- @param hostname string +--- @return string|nil +syscall.resolve=function(hostname) end + +--- Send data over socket +--- @param fd integer +--- @param data string +--- @return boolean, string|nil +syscall.send=function(fd, data) end + +--- Get file descriptor info +--- @param fd integer +--- @return table|nil +syscall.fstat=function(fd) end + +--- Change ownership of path +--- @param path string +--- @param uid integer +--- @param gid integer +--- @return boolean, string|nil +syscall.chown=function(path, uid, gid) end + +--- Flush file descriptor +--- @param fd integer +--- @return boolean, string|nil +syscall.fsync=function(fd) end + +--- Lock user account +--- @param uid integer +--- @return boolean +syscall.lockuser=function(uid) end + +--- Get username by UID +--- @param uid integer +--- @return string|nil +syscall.getUsername=function(uid) end + +--- Get socket name +--- @param fd integer +--- @return string|nil +syscall.getsockname=function(fd) end + +--- Bind socket to address +--- @param fd integer +--- @param address string +--- @return boolean, string|nil +syscall.bind=function(fd, address) end + +--- Kill a task +--- @param pid integer +--- @return boolean, string|nil +syscall.kill=function(pid) end + +--- Set GID for user +--- @param uid integer +--- @param gid integer +--- @return boolean +syscall.setgid=function(uid, gid) end + +--- Get peer name of socket +--- @param fd integer +--- @return string|nil +syscall.getpeername=function(fd) end + +--- Set signal handler +--- @param handler function +syscall.sigcatch=function(handler) end + +--- Shutdown the system +syscall.shutdown=function() end + +--- Check file access mode +--- @param path string +--- @param mode string +--- @return boolean +syscall.access=function(path, mode) end + +--- Ignore current signal +syscall.sigignore=function() end + +--- Get user password hash +--- @param uid integer +--- @return string|nil +syscall.getpasswd=function(uid) end + +--- Get OS version +--- @return string +syscall.version=function() end + +--- Change file permissions +--- @param path string +--- @param perms integer +--- @return boolean +syscall.chmod=function(path, perms) end + +--- List directory contents +--- @param path string +--- @return table +syscall.listdir=function(path) end + + +---------------------------------------------- +--- STDLib manifest +---------------------------------------------- + +--- Gets the index of value or -1 +--- @param tabl table +--- @param value string|integer +--- @return integer +table.indexOf=function(tabl, value) end + +-- Returns true if tabl has key else false +--- @param tabl table +--- @param query string +--- @return boolean +table.hasKey=function(tabl, query) end + +--- Returns true if tabl has value else false +--- @param tabl table +--- @param query any +--- @return boolean +table.hasVal=function(tabl, query) end + +--- Creates a deepcopy of tabl +--- @param tabl table +--- @return table +table.deepcopy=function(tabl) end + +--- Returns the keys of tabl +--- @param tabl table +--- @return table +table.keys=function(tabl) end + +--- Returns the values of tabl +--- @param tabl table +--- @return table +table.values=function(tabl) end + +--- Returns a serialized version of tabl +--- @param tabl table +--- @return string +table.serialize=function(tabl) end + +--- Gets prefix of string with suffix +--- @param str string +--- @param suffix string +--- @return string +string.getPrefix=function(str, suffix) end + +--- Gets suffix of string with prefix +--- @param str string +--- @param prefix string +--- @return string +string.getSuffix=function(str, prefix) end + +--- Returns if sting has prefix +--- @param str string +--- @param prefix string +--- @return boolean +string.hasPrefix=function(str, prefix) end + +--- Returns if sting has suffix +--- @param str string +--- @param suffix string +--- @return boolean +string.hasSuffix=function(str, suffix) end + +--- Joins all args +--- @param str string +--- @param ... string +--- @return string +string.join=function(str, ...) end + +--- Joins all strings with delim +--- @param delim string +--- @param ... string +--- @return string +string.delim=function(delim, ...) end + +--- Splits a string by delim +--- @param str string +--- @param delim string +--- @return table +string.split=function(str, delim) end + +--- Replaces all instances of target with repl +--- @param str string +--- @param target string +--- @param repl string +--- @return string +string.replace=function(str, target, repl) end + +--- Converts a number to hex +--- @param num integer +--- @return string +toHex=function(num) end + +--- Returns if obj is equal to all in ... +--- @param obj any +--- @param ... any +--- @return boolean +isEqualToAll=function(obj, ...) end + +--- Returns if obj is equal to any in ... +--- @param obj any +--- @param ... any +--- @return boolean +isEqualToAny=function(obj, ...) end + +--- Prints text to stdout +--- @param ... any +print=function(...) end + +--- Prints text to stdout but with no trailing newline +--- @param ... any +printInline=function(...) end + +--- Prints text to stdout with format +--- @param fmt string +--- @param ... any +printf=function(fmt, ...) end \ No newline at end of file diff --git a/syscallautofill.lua b/syscallautofill.lua deleted file mode 100644 index 29c9649..0000000 --- a/syscallautofill.lua +++ /dev/null @@ -1,137 +0,0 @@ -syscall={} - ---- Sets home directory of User with corosponding uid to homedir ---- @param uid integer ---- @param homedir string ---- @return true|nil, nil|string -syscall.sethomedir=function(uid, homedir) end - ---- Reads amount from fd and returns content or nil ---- @param fd integer ---- @param amount integer ---- @return string|nil -syscall.read=function(fd, amount) end - ---- Gets information of task with id of pid ----@param pid integer ----@return table|nil -syscall.getTask=function(pid) end - ---- client: connect to server address ----@param fd integer ----@param address string ----@return nil -syscall.connect=function(fd, address) end - ---- Get current working directory ---- @return string -syscall.getcwd=function() return "string" end - ---- detach loop device (must be unmounted first) ---- @param id string ---- @return nil -syscall.lodetach=function(id) end - ---- Stops task with id of pid ---- @param pid integer ---- @return boolean, string|nil -syscall.stop=function(pid) return true end - ---- Receive bytes from socket (blocking poll, returns "" on nothing) ---- @param fd integer ---- @param amount integer ---- @return string -syscall.recv=function(fd, amount) return "string" end - -syscall.write=function(fd, data) end -syscall.getppid=function() end -syscall.lstat=function(path) end -syscall.open=function(path, mode) end -syscall.lseek=function(fd, offset, whence) end -syscall.setHostname=function(hostname) end -syscall.chroot=function(path) end -syscall.dup2=function(src, dst) end -syscall.getpid=function() end -syscall.fchown=function(fd, uid, gid) end -syscall.close=function(fd) end -syscall.umount=function(target) end -syscall.getTasks=function() end -syscall.sysdump=function() end -syscall.fchmod=function(fd, perms) end -syscall.getHostname=function() end -syscall.listen=function(fd, backlog) end -syscall.dup=function(fd) end -syscall.gpio_read=function(pin) end -syscall.fget_suid=function(fd) end -syscall.gpio_write=function(pin, data) end -syscall.setpassword=function(uid, newPassword) end -syscall.setEnviron=function(key, value) end -syscall.losetup=function(filePath, forceImage) end -syscall.reboot=function() end -syscall.getuid=function() end -syscall.sigsend=function(pid, sigid) end -syscall.sleep=function(time) end -syscall.exit=function(code) end -syscall.getEnviron=function(key) end -syscall.continue=function(pid) end -syscall.socket=function(domain, socktype) end -syscall.log=function(text, tag, color) end -syscall.loimgwrite=function(imgStr, destPath) end -syscall.exists=function(path) end -syscall.setuid=function(uid) end -syscall.exec=function(path, args, envars) end -syscall.execspawn=function(path, name, envars, args, tgid) end -syscall.loimgcreate=function(srcPath) end -syscall.time=function() end -syscall.newuser=function(username, password, gid, homedir, shell) end -syscall.spawn=function(func, name, envars, args, tgid) end -syscall.collect=function(pid) end -syscall.setshell=function(uid, shell) end -syscall.devctl=function(fd, funcname, ...) end -syscall.listusers=function() end -syscall.unlockuser=function(uid) end -syscall.mount=function(target, diskOrId) end -syscall.accept=function(fd) end -syscall.lolist=function() end -syscall.readlink=function(path) end -syscall.deleteuser=function(uid) end -syscall.remove=function(path) end -syscall.type=function(path) end -syscall.elevate=function(targetUsername, password) end -syscall.mkdir=function(path) end -syscall.getuidbyname=function(username) end -syscall.whoami=function() end -syscall.sendfile=function(src, dest, amount) end -syscall.setusername=function(uid, newUsername) end -syscall.geteuid=function() end -syscall.login=function(username, password) end -syscall.getHost=function() end -syscall.getUptime=function() end -syscall.httpget=function(url, headers) end -syscall.stat=function(path) end -syscall.symlink=function(target, linkPath) end -syscall.pread=function(fd, count, offset) end -syscall.chdir=function(path) end -syscall.arch=function() end -syscall.pwrite=function(fd, data, offset) end -syscall.sockshutdown=function(fd) end -syscall.resolve=function(hostname) end -syscall.send=function(fd, data) end -syscall.fstat=function(fd) end -syscall.chown=function(path, uid, gid) end -syscall.fsync=function(fd) end -syscall.lockuser=function(uid) end -syscall.getUsername=function(uid) end -syscall.getsockname=function(fd) end -syscall.bind=function(fd, address) end -syscall.kill=function(pid) end -syscall.setgid=function(uid, gid) end -syscall.getpeername=function(fd) end -syscall.sigcatch=function(handler) end -syscall.shutdown=function() end -syscall.access=function(path, mode) end -syscall.sigignore=function() end -syscall.getpasswd=function(uid) end -syscall.version=function() end -syscall.chmod=function(path, perms) end -syscall.listdir=function(path) end \ No newline at end of file From a69f945b91005f5b060de67ae776bf7053424e73 Mon Sep 17 00:00:00 2001 From: Astronand Date: Fri, 6 Mar 2026 09:57:45 -0500 Subject: [PATCH 25/32] did some reorganizing --- Src/Hyperion-kernel/boot/kernel.lua | 2 +- .../lib/modules/hyperion/92_setup.kmod | 1 + Src/{Hyperion-bash => hysh}/bin/chattr | 0 Src/{Hyperion-bash => hysh}/bin/chgrp | 0 Src/{Hyperion-bash => hysh}/bin/chmod | 0 Src/{Hyperion-bash => hysh}/bin/chown | 0 Src/{Hyperion-bash => hysh}/bin/chroot | 0 Src/{Hyperion-bash => hysh}/bin/help | 0 Src/{Hyperion-bash => hysh}/bin/hfetch | 0 Src/{Hyperion-bash => hysh}/bin/hysh | 0 Src/{Hyperion-bash => hysh}/bin/id | 0 Src/{Hyperion-bash => hysh}/bin/ll | 0 Src/{Hyperion-bash => hysh}/bin/ln | 0 Src/{Hyperion-bash => hysh}/bin/login | 0 Src/{Hyperion-bash => hysh}/bin/loimgcreate | 0 Src/{Hyperion-bash => hysh}/bin/losetup | 0 Src/{Hyperion-bash => hysh}/bin/ls | 0 Src/{Hyperion-bash => hysh}/bin/lsusers | 0 Src/{Hyperion-bash => hysh}/bin/mount | 0 Src/{Hyperion-bash => hysh}/bin/passwd | 0 Src/{Hyperion-bash => hysh}/bin/ps | 0 Src/{Hyperion-bash => hysh}/bin/readlink | 0 Src/{Hyperion-bash => hysh}/bin/su | 0 Src/{Hyperion-bash => hysh}/bin/sudo | 0 Src/{Hyperion-bash => hysh}/bin/sysdump | 0 Src/{Hyperion-bash => hysh}/bin/umount | 0 Src/{Hyperion-bash => hysh}/bin/useradd | 0 Src/{Hyperion-bash => hysh}/bin/userdel | 0 Src/{Hyperion-bash => hysh}/bin/usermod | 0 Src/{Hyperion-bash => hysh}/bin/yes | 0 Src/{Hyperion-bash => lua}/bin/lua | 6 ++--- Src/{Hyperion-bash => micro}/bin/micro | 0 Src/{Hyperion-bash => sed}/bin/sed | 0 Src/{Hyperion-spm => spm}/bin/spm | 0 Src/{Hyperion-core => sysinit}/sbin/init | 0 .../usr/lib/sysinit/sysinit | 2 +- .../HyperionOS-units}/bin/looptest | 0 .../HyperionOS-units}/bin/socktest | 0 manifest.lua | 24 +++++++++---------- 39 files changed, 18 insertions(+), 17 deletions(-) rename Src/{Hyperion-bash => hysh}/bin/chattr (100%) rename Src/{Hyperion-bash => hysh}/bin/chgrp (100%) rename Src/{Hyperion-bash => hysh}/bin/chmod (100%) rename Src/{Hyperion-bash => hysh}/bin/chown (100%) rename Src/{Hyperion-bash => hysh}/bin/chroot (100%) rename Src/{Hyperion-bash => hysh}/bin/help (100%) rename Src/{Hyperion-bash => hysh}/bin/hfetch (100%) rename Src/{Hyperion-bash => hysh}/bin/hysh (100%) rename Src/{Hyperion-bash => hysh}/bin/id (100%) rename Src/{Hyperion-bash => hysh}/bin/ll (100%) rename Src/{Hyperion-bash => hysh}/bin/ln (100%) rename Src/{Hyperion-bash => hysh}/bin/login (100%) rename Src/{Hyperion-bash => hysh}/bin/loimgcreate (100%) rename Src/{Hyperion-bash => hysh}/bin/losetup (100%) rename Src/{Hyperion-bash => hysh}/bin/ls (100%) rename Src/{Hyperion-bash => hysh}/bin/lsusers (100%) rename Src/{Hyperion-bash => hysh}/bin/mount (100%) rename Src/{Hyperion-bash => hysh}/bin/passwd (100%) rename Src/{Hyperion-bash => hysh}/bin/ps (100%) rename Src/{Hyperion-bash => hysh}/bin/readlink (100%) rename Src/{Hyperion-bash => hysh}/bin/su (100%) rename Src/{Hyperion-bash => hysh}/bin/sudo (100%) rename Src/{Hyperion-bash => hysh}/bin/sysdump (100%) rename Src/{Hyperion-bash => hysh}/bin/umount (100%) rename Src/{Hyperion-bash => hysh}/bin/useradd (100%) rename Src/{Hyperion-bash => hysh}/bin/userdel (100%) rename Src/{Hyperion-bash => hysh}/bin/usermod (100%) rename Src/{Hyperion-bash => hysh}/bin/yes (100%) rename Src/{Hyperion-bash => lua}/bin/lua (97%) rename Src/{Hyperion-bash => micro}/bin/micro (100%) rename Src/{Hyperion-bash => sed}/bin/sed (100%) rename Src/{Hyperion-spm => spm}/bin/spm (100%) rename Src/{Hyperion-core => sysinit}/sbin/init (100%) rename Src/{Hyperion-core => sysinit}/usr/lib/sysinit/sysinit (99%) rename {Src/Hyperion-bash => Test/HyperionOS-units}/bin/looptest (100%) rename {Src/Hyperion-bash => Test/HyperionOS-units}/bin/socktest (100%) diff --git a/Src/Hyperion-kernel/boot/kernel.lua b/Src/Hyperion-kernel/boot/kernel.lua index 3a70aba..518b2e4 100644 --- a/Src/Hyperion-kernel/boot/kernel.lua +++ b/Src/Hyperion-kernel/boot/kernel.lua @@ -120,7 +120,7 @@ end local initCfgFunc, err = load(ifs.readAllText("/boot/boot.cfg"), "@boot.cfg") if not initCfgFunc then - kernel.LOG_Text("Failed to load /boot/boot.cfg: "..tostring(err)) + kernel.PANIC("Failed to load /boot/boot.cfg: "..tostring(err)) end ---@diagnostic disable-next-line: param-type-mismatch diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod index 367f9d8..926eb32 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/92_setup.kmod @@ -97,6 +97,7 @@ if kernel.firstBoot then {"tmp", REG, 0, 0, RWXRWXRWX}, {"usr", REG, 0, 0, RWX_RX_RX}, {"var", REG, 0, 0, RWX_RX_RX}, + {"opt", REG, 0, 0, RWXRWXRWX}, }) mergeMeta("/bin", { diff --git a/Src/Hyperion-bash/bin/chattr b/Src/hysh/bin/chattr similarity index 100% rename from Src/Hyperion-bash/bin/chattr rename to Src/hysh/bin/chattr diff --git a/Src/Hyperion-bash/bin/chgrp b/Src/hysh/bin/chgrp similarity index 100% rename from Src/Hyperion-bash/bin/chgrp rename to Src/hysh/bin/chgrp diff --git a/Src/Hyperion-bash/bin/chmod b/Src/hysh/bin/chmod similarity index 100% rename from Src/Hyperion-bash/bin/chmod rename to Src/hysh/bin/chmod diff --git a/Src/Hyperion-bash/bin/chown b/Src/hysh/bin/chown similarity index 100% rename from Src/Hyperion-bash/bin/chown rename to Src/hysh/bin/chown diff --git a/Src/Hyperion-bash/bin/chroot b/Src/hysh/bin/chroot similarity index 100% rename from Src/Hyperion-bash/bin/chroot rename to Src/hysh/bin/chroot diff --git a/Src/Hyperion-bash/bin/help b/Src/hysh/bin/help similarity index 100% rename from Src/Hyperion-bash/bin/help rename to Src/hysh/bin/help diff --git a/Src/Hyperion-bash/bin/hfetch b/Src/hysh/bin/hfetch similarity index 100% rename from Src/Hyperion-bash/bin/hfetch rename to Src/hysh/bin/hfetch diff --git a/Src/Hyperion-bash/bin/hysh b/Src/hysh/bin/hysh similarity index 100% rename from Src/Hyperion-bash/bin/hysh rename to Src/hysh/bin/hysh diff --git a/Src/Hyperion-bash/bin/id b/Src/hysh/bin/id similarity index 100% rename from Src/Hyperion-bash/bin/id rename to Src/hysh/bin/id diff --git a/Src/Hyperion-bash/bin/ll b/Src/hysh/bin/ll similarity index 100% rename from Src/Hyperion-bash/bin/ll rename to Src/hysh/bin/ll diff --git a/Src/Hyperion-bash/bin/ln b/Src/hysh/bin/ln similarity index 100% rename from Src/Hyperion-bash/bin/ln rename to Src/hysh/bin/ln diff --git a/Src/Hyperion-bash/bin/login b/Src/hysh/bin/login similarity index 100% rename from Src/Hyperion-bash/bin/login rename to Src/hysh/bin/login diff --git a/Src/Hyperion-bash/bin/loimgcreate b/Src/hysh/bin/loimgcreate similarity index 100% rename from Src/Hyperion-bash/bin/loimgcreate rename to Src/hysh/bin/loimgcreate diff --git a/Src/Hyperion-bash/bin/losetup b/Src/hysh/bin/losetup similarity index 100% rename from Src/Hyperion-bash/bin/losetup rename to Src/hysh/bin/losetup diff --git a/Src/Hyperion-bash/bin/ls b/Src/hysh/bin/ls similarity index 100% rename from Src/Hyperion-bash/bin/ls rename to Src/hysh/bin/ls diff --git a/Src/Hyperion-bash/bin/lsusers b/Src/hysh/bin/lsusers similarity index 100% rename from Src/Hyperion-bash/bin/lsusers rename to Src/hysh/bin/lsusers diff --git a/Src/Hyperion-bash/bin/mount b/Src/hysh/bin/mount similarity index 100% rename from Src/Hyperion-bash/bin/mount rename to Src/hysh/bin/mount diff --git a/Src/Hyperion-bash/bin/passwd b/Src/hysh/bin/passwd similarity index 100% rename from Src/Hyperion-bash/bin/passwd rename to Src/hysh/bin/passwd diff --git a/Src/Hyperion-bash/bin/ps b/Src/hysh/bin/ps similarity index 100% rename from Src/Hyperion-bash/bin/ps rename to Src/hysh/bin/ps diff --git a/Src/Hyperion-bash/bin/readlink b/Src/hysh/bin/readlink similarity index 100% rename from Src/Hyperion-bash/bin/readlink rename to Src/hysh/bin/readlink diff --git a/Src/Hyperion-bash/bin/su b/Src/hysh/bin/su similarity index 100% rename from Src/Hyperion-bash/bin/su rename to Src/hysh/bin/su diff --git a/Src/Hyperion-bash/bin/sudo b/Src/hysh/bin/sudo similarity index 100% rename from Src/Hyperion-bash/bin/sudo rename to Src/hysh/bin/sudo diff --git a/Src/Hyperion-bash/bin/sysdump b/Src/hysh/bin/sysdump similarity index 100% rename from Src/Hyperion-bash/bin/sysdump rename to Src/hysh/bin/sysdump diff --git a/Src/Hyperion-bash/bin/umount b/Src/hysh/bin/umount similarity index 100% rename from Src/Hyperion-bash/bin/umount rename to Src/hysh/bin/umount diff --git a/Src/Hyperion-bash/bin/useradd b/Src/hysh/bin/useradd similarity index 100% rename from Src/Hyperion-bash/bin/useradd rename to Src/hysh/bin/useradd diff --git a/Src/Hyperion-bash/bin/userdel b/Src/hysh/bin/userdel similarity index 100% rename from Src/Hyperion-bash/bin/userdel rename to Src/hysh/bin/userdel diff --git a/Src/Hyperion-bash/bin/usermod b/Src/hysh/bin/usermod similarity index 100% rename from Src/Hyperion-bash/bin/usermod rename to Src/hysh/bin/usermod diff --git a/Src/Hyperion-bash/bin/yes b/Src/hysh/bin/yes similarity index 100% rename from Src/Hyperion-bash/bin/yes rename to Src/hysh/bin/yes diff --git a/Src/Hyperion-bash/bin/lua b/Src/lua/bin/lua similarity index 97% rename from Src/Hyperion-bash/bin/lua rename to Src/lua/bin/lua index b6f42bb..d7a1a7b 100644 --- a/Src/Hyperion-bash/bin/lua +++ b/Src/lua/bin/lua @@ -288,9 +288,9 @@ local function getUserInput(prompt, history) syscall.devctl(1,"spos",ox,oy) w(input .. " \n") return input - else - input = input:sub(1, cursor-1) .. key .. input:sub(cursor) - cursor = cursor + 1; dirty = true + elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then + input=string.sub(input,1,cursor-1)..key..string.sub(input,cursor) + cursor=cursor+1;dirty=true end end local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0 diff --git a/Src/Hyperion-bash/bin/micro b/Src/micro/bin/micro similarity index 100% rename from Src/Hyperion-bash/bin/micro rename to Src/micro/bin/micro diff --git a/Src/Hyperion-bash/bin/sed b/Src/sed/bin/sed similarity index 100% rename from Src/Hyperion-bash/bin/sed rename to Src/sed/bin/sed diff --git a/Src/Hyperion-spm/bin/spm b/Src/spm/bin/spm similarity index 100% rename from Src/Hyperion-spm/bin/spm rename to Src/spm/bin/spm diff --git a/Src/Hyperion-core/sbin/init b/Src/sysinit/sbin/init similarity index 100% rename from Src/Hyperion-core/sbin/init rename to Src/sysinit/sbin/init diff --git a/Src/Hyperion-core/usr/lib/sysinit/sysinit b/Src/sysinit/usr/lib/sysinit/sysinit similarity index 99% rename from Src/Hyperion-core/usr/lib/sysinit/sysinit rename to Src/sysinit/usr/lib/sysinit/sysinit index 574c93a..518774c 100644 --- a/Src/Hyperion-core/usr/lib/sysinit/sysinit +++ b/Src/sysinit/usr/lib/sysinit/sysinit @@ -41,6 +41,6 @@ for i,v in ipairs(files) do end while true do - sleep(1) + sleep(5) kernel.saveLog() end \ No newline at end of file diff --git a/Src/Hyperion-bash/bin/looptest b/Test/HyperionOS-units/bin/looptest similarity index 100% rename from Src/Hyperion-bash/bin/looptest rename to Test/HyperionOS-units/bin/looptest diff --git a/Src/Hyperion-bash/bin/socktest b/Test/HyperionOS-units/bin/socktest similarity index 100% rename from Src/Hyperion-bash/bin/socktest rename to Test/HyperionOS-units/bin/socktest diff --git a/manifest.lua b/manifest.lua index 819fe61..9fa2a2e 100644 --- a/manifest.lua +++ b/manifest.lua @@ -11,7 +11,7 @@ syscall.sethomedir=function(uid, homedir) end --- Reads amount from fd and returns content or nil --- @param fd integer ---- @param amount integer +--- @param amount? integer --- @return string|nil syscall.read=function(fd, amount) end @@ -115,7 +115,7 @@ syscall.umount=function(target) end --- @return integer[] syscall.getTasks=function() end ---- Dump system state for debugging +--- Dump all syscalls for debugging --- @return table syscall.sysdump=function() end @@ -235,16 +235,16 @@ syscall.setuid=function(uid) end --- Replace current task with executable --- @param path string ---- @param args table ---- @param envars table +--- @param args? table +--- @param envars? table syscall.exec=function(path, args, envars) end --- Spawn a new task from executable --- @param path string ---- @param name string ---- @param envars table ---- @param args table ---- @param tgid integer +--- @param name? string +--- @param envars? table +--- @param args? table +--- @param tgid? integer --- @return integer syscall.execspawn=function(path, name, envars, args, tgid) end @@ -268,10 +268,10 @@ syscall.newuser=function(username, password, gid, homedir, shell) end --- Spawn a new task from function --- @param func function ---- @param name string ---- @param envars table ---- @param args table ---- @param tgid integer +--- @param name? string +--- @param envars? table +--- @param args? table +--- @param tgid? integer --- @return integer syscall.spawn=function(func, name, envars, args, tgid) end From 1590e1f3f7a77dad16a2d028b14fb356ecb69cad Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 9 Mar 2026 11:28:09 -0400 Subject: [PATCH 26/32] more reorganizeing and $PKGCONFIG.ini files added B to ls -lh for "Bytes" --- Src/Hyperion-core/lib/{sys => }/fs | 0 Src/Hyperion-core/lib/io | 2 +- Src/Hyperion-core/lib/snip | 0 Src/Hyperion-core/lib/sys/hpv | 6 -- Src/Hyperion-core/lib/sys/init | 5 -- Src/Hyperion-core/lib/sys/ipc | 3 - Src/Hyperion-core/lib/sys/term | 71 ---------------- Src/Hyperion-kernel/boot/fstab | 3 +- .../lib/modules/hyperion/01_stdlib.kmod | 1 + .../lib/modules/hyperion/12_procfs.kmod | 83 +++++++++++++++++++ Src/{Hyperion-core => bit32}/lib/bit32 | 0 .../lib/crypto/blake2s | 0 Src/{Hyperion-core => deflate}/lib/LibDeflate | 0 Src/{Hyperion-core => deflate}/lib/deflate | 0 Src/hysh/bin/hysh | 2 +- Src/hysh/bin/loimgcreate | 2 +- Src/hysh/bin/ls | 4 +- Src/hysh/bin/sudo | 2 +- Src/hysh/bin/userdel | 2 +- Src/{Hyperion-core => json}/lib/json | 0 Src/sysinit/$PKGCONFIG.ini | 1 + Src/sysinit/usr/lib/sysinit/sysinit | 2 +- Test/HyperionOS-units/bin/hunit | 2 +- Test/HyperionOS-units/usr/lib/hunit/dir.unit | 2 +- build.py | 3 + 25 files changed, 100 insertions(+), 96 deletions(-) rename Src/Hyperion-core/lib/{sys => }/fs (100%) delete mode 100644 Src/Hyperion-core/lib/snip delete mode 100644 Src/Hyperion-core/lib/sys/hpv delete mode 100644 Src/Hyperion-core/lib/sys/init delete mode 100644 Src/Hyperion-core/lib/sys/ipc delete mode 100644 Src/Hyperion-core/lib/sys/term create mode 100644 Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod rename Src/{Hyperion-core => bit32}/lib/bit32 (100%) rename Src/{Hyperion-core => blake2s}/lib/crypto/blake2s (100%) rename Src/{Hyperion-core => deflate}/lib/LibDeflate (100%) rename Src/{Hyperion-core => deflate}/lib/deflate (100%) rename Src/{Hyperion-core => json}/lib/json (100%) create mode 100644 Src/sysinit/$PKGCONFIG.ini diff --git a/Src/Hyperion-core/lib/sys/fs b/Src/Hyperion-core/lib/fs similarity index 100% rename from Src/Hyperion-core/lib/sys/fs rename to Src/Hyperion-core/lib/fs diff --git a/Src/Hyperion-core/lib/io b/Src/Hyperion-core/lib/io index 9a94a48..e0eb470 100644 --- a/Src/Hyperion-core/lib/io +++ b/Src/Hyperion-core/lib/io @@ -1,5 +1,5 @@ local io = {} -local fs = require("sys.fs") +local fs = require("fs") function io.open(path, mode) return fs.open(path, mode) diff --git a/Src/Hyperion-core/lib/snip b/Src/Hyperion-core/lib/snip deleted file mode 100644 index e69de29..0000000 diff --git a/Src/Hyperion-core/lib/sys/hpv b/Src/Hyperion-core/lib/sys/hpv deleted file mode 100644 index b6ad280..0000000 --- a/Src/Hyperion-core/lib/sys/hpv +++ /dev/null @@ -1,6 +0,0 @@ -local sys = {} -local fs = require("sys.fs") - - - -return sys \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/init b/Src/Hyperion-core/lib/sys/init deleted file mode 100644 index 3ce5639..0000000 --- a/Src/Hyperion-core/lib/sys/init +++ /dev/null @@ -1,5 +0,0 @@ -local sys = {} -sys.fs = require("sys.fs") -sys.hpv = require("sys.hpv") -sys.ipc = require("sys.ipc") -return sys \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/ipc b/Src/Hyperion-core/lib/sys/ipc deleted file mode 100644 index bcc01b9..0000000 --- a/Src/Hyperion-core/lib/sys/ipc +++ /dev/null @@ -1,3 +0,0 @@ -local ipc = {} - -return ipc \ No newline at end of file diff --git a/Src/Hyperion-core/lib/sys/term b/Src/Hyperion-core/lib/sys/term deleted file mode 100644 index 88db99e..0000000 --- a/Src/Hyperion-core/lib/sys/term +++ /dev/null @@ -1,71 +0,0 @@ -local term = {} - -function term.clear() - coroutine.yield("VFS_write", 1, "\27C\25") -end - -function term.setCursorPos(x, y) - coroutine.yield("VFS_write", 1, "\27cs"..tostring(y)..";"..tostring(x).."\25") -end - -function term.size() - coroutine.yield("VFS_write", 1, "\27ts\25") - local ok, data = coroutine.yield("VFS_read", 0, 16) -- read response - if not ok then error("Failed to get terminal size") end - local x, y = string.match(data, "%R(%d+);(%d+)\25") - return tonumber(x), tonumber(y) -end - -function term.getCursorPos() - coroutine.yield("VFS_write", 1, "\27gc\25") - local ok, data = coroutine.yield("VFS_read", 0, 16) -- read response - if not ok then error("Failed to get cursor position") end - local y, x = string.match(data, "%R(%d+);(%d+)\25") - return tonumber(x), tonumber(y) -end - -function term.write(data) - coroutine.yield("VFS_write", 1, data) -end - -function term.setTextColor(color) - local ok, err = coroutine.yield("VFS_type", 1) - if not ok then error(err) end - if ok ~= "tty" then return end - coroutine.yield("VFS_write", 1, "\27f"..tostring(color).."\25") -end - -function term.setBackgroundColor(color) - local ok, err = coroutine.yield("VFS_type", 1) - if not ok then error(err) end - if ok ~= "tty" then return end - coroutine.yield("VFS_write", 1, "\27b"..tostring(color).."\25") -end - -function term.isColor() - local ok, err = coroutine.yield("VFS_type", 1) - if not ok then error(err) end - return ok == "tty" -end - -function term.scroll(n) - coroutine.yield("VFS_write", 1, "\27S"..tostring(n).."\25") -end - -function term.setDefault(color, layer) - if layer then - coroutine.yield("VFS_write", 1, "\27F"..tostring(color).."\25") - else - coroutine.yield("VFS_write", 1, "\27B"..tostring(color).."\25") - end -end - -function term.showCursor(show) - if show then - coroutine.yield("VFS_write", 1, "\27sc\25") - else - coroutine.yield("VFS_write", 1, "\27hc\25") - end -end - -return term \ No newline at end of file diff --git a/Src/Hyperion-kernel/boot/fstab b/Src/Hyperion-kernel/boot/fstab index ba670ab..0f7573d 100644 --- a/Src/Hyperion-kernel/boot/fstab +++ b/Src/Hyperion-kernel/boot/fstab @@ -1,3 +1,4 @@ U $;/ U devfs0000;/dev/ -U tmpfs0000;/tmp/ \ No newline at end of file +U tmpfs0000;/tmp/ +U procfs0000;/proc/ \ No newline at end of file diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod index acec657..36fec1a 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod @@ -1,4 +1,5 @@ --:Minify:-- +--- @diagnostic disable: duplicate-set-field local kernel = ... kernel.allowGlobalOverwrites = true diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod new file mode 100644 index 0000000..9690ba2 --- /dev/null +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod @@ -0,0 +1,83 @@ +--:Minify:-- +local kernel = ... + +local proxy = {} +local data = {} + +proxy.address = "procfs0000" +proxy.isvirt = true +proxy.isReadOnly = function() return false end +proxy.spaceUsed = function() return 0 end +proxy.spaceTotal = function() return 0 end +proxy.makeDirectory = function() error("EACCES") end +proxy.remove = function() error("EACCES") end +proxy.setLabel = function() error("EACCES") end +proxy.getLabel = function() return "procfs" end +proxy.attributes = function(path) return { + size = 0, + modified = 0, + created = 0 +} end + +function proxy:open(path, mode) + local steps = kernel.vfs.splitPath(path) + local step = data + for i=1, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENFILE") end + step=dat + end + if type(step[steps[#steps]]) == "function" then + return step[steps[#steps]]("open", mode) + end + error("ENFILE") +end + +function proxy:type(path, mode) + local steps = kernel.vfs.splitPath(path) + local step = data + if #steps == 0 then + return "directory" + end + for i=1, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENFILE") end + step=dat + end + if type(step[steps[#steps]]) == "function" then + return step[steps[#steps]]("type", mode) + end + if type(step[steps[#steps]]) == "table" then + return "directory" + end + error("ENOENT") +end + +function proxy:list(path) + local steps = kernel.vfs.splitPath(path) + local step = data + if #steps == 0 then + return table.keys(data) + end + for i=1, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENOENT") end + step=dat + end + if type(step[steps[#steps]]) == "table" then + return table.keys(step[steps[#steps]]) + end + error("ENOENT") +end + +function proxy:fileExists(path) + local ok = pcall(function() + return self:type(path) + end) + return ok +end + +kernel.procfs={} +kernel.procfs.data=data +kernel.procfs.proxy=proxy +kernel.disks["procfs0000"]=proxy \ No newline at end of file diff --git a/Src/Hyperion-core/lib/bit32 b/Src/bit32/lib/bit32 similarity index 100% rename from Src/Hyperion-core/lib/bit32 rename to Src/bit32/lib/bit32 diff --git a/Src/Hyperion-core/lib/crypto/blake2s b/Src/blake2s/lib/crypto/blake2s similarity index 100% rename from Src/Hyperion-core/lib/crypto/blake2s rename to Src/blake2s/lib/crypto/blake2s diff --git a/Src/Hyperion-core/lib/LibDeflate b/Src/deflate/lib/LibDeflate similarity index 100% rename from Src/Hyperion-core/lib/LibDeflate rename to Src/deflate/lib/LibDeflate diff --git a/Src/Hyperion-core/lib/deflate b/Src/deflate/lib/deflate similarity index 100% rename from Src/Hyperion-core/lib/deflate rename to Src/deflate/lib/deflate diff --git a/Src/hysh/bin/hysh b/Src/hysh/bin/hysh index 0f41b2f..23ce7a5 100644 --- a/Src/hysh/bin/hysh +++ b/Src/hysh/bin/hysh @@ -5,7 +5,7 @@ syscall.open("/dev/null","w") --stderr (device 2) local success, errorMsg = xpcall(function() -local fs = require("sys.fs") +local fs = require("fs") syscall.devctl(1,"clear") syscall.devctl(1,"sfgc",1) diff --git a/Src/hysh/bin/loimgcreate b/Src/hysh/bin/loimgcreate index 76d9699..6fca0dc 100644 --- a/Src/hysh/bin/loimgcreate +++ b/Src/hysh/bin/loimgcreate @@ -43,7 +43,7 @@ if opts.help then return end -local fs = require("sys.fs") +local fs = require("fs") if opts.x then if #args < 2 then diff --git a/Src/hysh/bin/ls b/Src/hysh/bin/ls index 97c7126..be98f0e 100644 --- a/Src/hysh/bin/ls +++ b/Src/hysh/bin/ls @@ -45,7 +45,7 @@ if cloptions.help then return end -local fs = require("sys.fs") +local fs = require("fs") local dir = args[1] or "" if dir:sub(1, 1) ~= "/" then dir = syscall.getcwd() .. "/" .. dir @@ -84,7 +84,7 @@ local function humanSize(size) size = size / 1024 scale = scale + 1 end - if scale == 0 then return tostring(size) end + if scale == 0 then return tostring(size).."B" end if size < 10 then return string.format("%.1f%s", size, sizePrefixes[scale]) end diff --git a/Src/hysh/bin/sudo b/Src/hysh/bin/sudo index a843cba..6fc29eb 100644 --- a/Src/hysh/bin/sudo +++ b/Src/hysh/bin/sudo @@ -1,5 +1,5 @@ --:Minify:-- -local fs = require("sys.fs") +local fs = require("fs") local cmdArgs = {...} local targetUser = "root" diff --git a/Src/hysh/bin/userdel b/Src/hysh/bin/userdel index 8f4e798..e4d5842 100644 --- a/Src/hysh/bin/userdel +++ b/Src/hysh/bin/userdel @@ -29,7 +29,7 @@ if not ok then end if removeHome and pwent and pwent.homedir then - local fs = require("sys.fs") + local fs = require("fs") local ok2, err2 = pcall(function() local function rmdir(path) for _, f in ipairs(fs.list(path) or {}) do diff --git a/Src/Hyperion-core/lib/json b/Src/json/lib/json similarity index 100% rename from Src/Hyperion-core/lib/json rename to Src/json/lib/json diff --git a/Src/sysinit/$PKGCONFIG.ini b/Src/sysinit/$PKGCONFIG.ini new file mode 100644 index 0000000..c5153d4 --- /dev/null +++ b/Src/sysinit/$PKGCONFIG.ini @@ -0,0 +1 @@ +[symlinks] diff --git a/Src/sysinit/usr/lib/sysinit/sysinit b/Src/sysinit/usr/lib/sysinit/sysinit index 518774c..58be0c9 100644 --- a/Src/sysinit/usr/lib/sysinit/sysinit +++ b/Src/sysinit/usr/lib/sysinit/sysinit @@ -1,6 +1,6 @@ --:Minify:-- local kernel=... -local fs=require("sys.fs") +local fs=require("fs") for i,v in pairs(kernel.processes) do kernel.log("Spawning kernel task "..i) diff --git a/Test/HyperionOS-units/bin/hunit b/Test/HyperionOS-units/bin/hunit index 7e96c8d..cf6e040 100644 --- a/Test/HyperionOS-units/bin/hunit +++ b/Test/HyperionOS-units/bin/hunit @@ -1,4 +1,4 @@ -local fs=require("sys.fs") +local fs=require("fs") local units=fs.list("/usr/lib/hunit/") fs.mkdir("/tmp/hunit/") local errors={} diff --git a/Test/HyperionOS-units/usr/lib/hunit/dir.unit b/Test/HyperionOS-units/usr/lib/hunit/dir.unit index 78c5a3f..1d3c79e 100644 --- a/Test/HyperionOS-units/usr/lib/hunit/dir.unit +++ b/Test/HyperionOS-units/usr/lib/hunit/dir.unit @@ -1,3 +1,3 @@ -local fs = require("sys.fs") +local fs = require("fs") assert(fs.mkdir("/tmp/hunit/testdir"), "failed to make directory") assert(fs.isDir("/tmp/hunit/testdir"), "directory does not exist") \ No newline at end of file diff --git a/build.py b/build.py index 43a9177..c110788 100644 --- a/build.py +++ b/build.py @@ -90,6 +90,9 @@ def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): continue rel = src.relative_to(pkg_dir) + if rel=="$PKGCONFIG.ini": + continue + dst = out_root / rel dst.parent.mkdir(parents=True, exist_ok=True) From f12159bfb9ee9e3ff3c85b191c8366e90d49eda8 Mon Sep 17 00:00:00 2001 From: Astronand Date: Mon, 9 Mar 2026 11:31:31 -0400 Subject: [PATCH 27/32] fixed build script --- build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index c110788..d3b0765 100644 --- a/build.py +++ b/build.py @@ -90,9 +90,10 @@ def process_root(src_root: Path, out_root: Path, minify: bool, micro: bool): continue rel = src.relative_to(pkg_dir) - if rel=="$PKGCONFIG.ini": - continue + if rel.name=="$PKGCONFIG.ini": + continue + dst = out_root / rel dst.parent.mkdir(parents=True, exist_ok=True) From 12669d9f820fd18796794e851e8ea7e8834b6103 Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 10 Mar 2026 09:17:21 -0400 Subject: [PATCH 28/32] adde /proc fs and working on install --- .../lib/modules/hyperion/01_stdlib.kmod | 21 ++ .../lib/modules/hyperion/12_procfs.kmod | 194 +++++++++++++++--- Src/hysh/bin/ls | 4 +- install/installcc.lua | 2 + manifest.lua | 5 + 5 files changed, 199 insertions(+), 27 deletions(-) create mode 100644 install/installcc.lua diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod index 36fec1a..0c0420a 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/01_stdlib.kmod @@ -173,6 +173,27 @@ function table.indexOf(t, value) return -1 end +function table.merge(...) + local args={...} + local out = {} + local outi = {} + + for _,t in ipairs(args) do + for i,v in pairs(t) do + out[i]=v + end + for i,v in ipairs(t) do + outi[#outi+1]=v + end + end + + for i,v in ipairs(outi) do + out[i]=v + end + + return out +end + function string.replace(s, target, repl) local result = {} local i = 1 diff --git a/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod b/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod index 9690ba2..36ffcff 100644 --- a/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod +++ b/Src/Hyperion-kernel/lib/modules/hyperion/12_procfs.kmod @@ -19,16 +19,128 @@ proxy.attributes = function(path) return { created = 0 } end +local function buildMeta(entries, opts) + opts = opts or {} + local uid = opts.uid or 0 + local gid = opts.gid or 0 + local perms = opts.perms or 0x3F -- default read/write for owner/group/world + + local chunks = {} + table.insert(chunks, string.char(0x02)) -- version header + + for path, target in pairs(entries) do + local name = path + local nameLen = #name + if nameLen > 255 then + error("Filename too long (>255 bytes): "..name) + end + + -- Determine entry type: 0x01 = symlink if target ~= nil and target ~= "" + local entryType = 0x00 + local cmeta = "" + if target and target ~= "" then + entryType = 0x01 + cmeta = target + end + local cmetaLen = #cmeta + if cmetaLen > 255 then + error("cmeta too long (>255 bytes) for "..name) + end + + -- Build entry as bytes + table.insert(chunks, string.char(nameLen)) -- name length + table.insert(chunks, name) -- name + table.insert(chunks, string.char(entryType)) -- entry type + table.insert(chunks, string.char(uid % 256, math.floor(uid/256) % 256)) -- uid + table.insert(chunks, string.char(gid % 256, math.floor(gid/256) % 256)) -- gid + table.insert(chunks, string.char(perms % 256, math.floor(perms/256) % 256)) -- perms + table.insert(chunks, string.char(cmetaLen)) -- cmeta length + if cmetaLen > 0 then + table.insert(chunks, cmeta) + end + end + + return table.concat(chunks) +end + +local function simpleFile(r,w) + return function(op, mode) + if op=="type" then + return "character device" + elseif op=="open" then + if mode=="r" then + return { + read=r + } + elseif mode=="w" then + return { + write=w + } + end + end + end +end + +local function strFile(str) + local dat=tostring(str) + local pos=1 + return simpleFile(function(amount) + pos=pos+amount + return dat:sub(pos-amount, pos) + end,function() error("EACCES") end) +end + +local function newtaskproxy(task) + local files,siblings,children={},{},{} + if task.fd[0] then files["0"]=task.fd[0].path end + for i,v in ipairs(task.fd) do + files[tostring(i)]=tostring(v.path) + end + for i,v in ipairs(task.siblings) do + siblings[tostring(v.pid)]="/proc/"..tostring(v.pid) + end + for i,v in ipairs(task.children) do + children[tostring(v.pid)]="/proc/"..tostring(v.pid) + end + return { + [".meta"]=strFile(buildMeta({cwd=task.cwd,parent="/proc/"..tostring(task.parent.pid)})), + uid=strFile(task.uid), + comm=strFile(task.name), + fd={ + [".meta"]=strFile(buildMeta(files)) + }, + siblings={ + [".meta"]=strFile(buildMeta(siblings)) + }, + children={ + [".meta"]=strFile(buildMeta(children)) + } + } +end + function proxy:open(path, mode) local steps = kernel.vfs.splitPath(path) local step = data - for i=1, #steps-1 do - local dat = step[steps[i]] - if type(dat) ~= "table" then error("ENFILE") end - step=dat - end - if type(step[steps[#steps]]) == "function" then - return step[steps[#steps]]("open", mode) + if tonumber(steps[1]) then + local task=kernel.tasks[tostring(steps[1])] + local step = newtaskproxy(task) + for i=2, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENFILE") end + step=dat + end + if type(step[steps[#steps]]) == "function" then + return step[steps[#steps]]("open", mode) + end + else + for i=1, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENFILE") end + step=dat + end + if type(step[steps[#steps]]) == "function" then + return step[steps[#steps]]("open", mode) + end end error("ENFILE") end @@ -39,16 +151,33 @@ function proxy:type(path, mode) if #steps == 0 then return "directory" end - for i=1, #steps-1 do - local dat = step[steps[i]] - if type(dat) ~= "table" then error("ENFILE") end - step=dat - end - if type(step[steps[#steps]]) == "function" then - return step[steps[#steps]]("type", mode) - end - if type(step[steps[#steps]]) == "table" then - return "directory" + if tonumber(steps[1]) then + local task=kernel.tasks[steps[1]] + if #steps==1 then return "directory" end + local step = newtaskproxy(task) + for i=2, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENFILE") end + step=dat + end + if type(step[steps[#steps]]) == "function" then + return step[steps[#steps]]("type", mode) + end + if type(step[steps[#steps]]) == "table" then + return "directory" + end + else + for i=1, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENFILE") end + step=dat + end + if type(step[steps[#steps]]) == "function" then + return step[steps[#steps]]("type", mode) + end + if type(step[steps[#steps]]) == "table" then + return "directory" + end end error("ENOENT") end @@ -57,15 +186,29 @@ function proxy:list(path) local steps = kernel.vfs.splitPath(path) local step = data if #steps == 0 then - return table.keys(data) + return table.merge(table.keys(data),table.keys(kernel.tasks)) end - for i=1, #steps-1 do - local dat = step[steps[i]] - if type(dat) ~= "table" then error("ENOENT") end - step=dat - end - if type(step[steps[#steps]]) == "table" then - return table.keys(step[steps[#steps]]) + if tonumber(steps[1]) then + local task=kernel.tasks[steps[1]] + local step = newtaskproxy(task) + if #steps==1 then return table.keys(step) end + for i=2, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENOENT") end + step=dat + end + if type(step[steps[#steps]]) == "table" then + return table.keys(step[steps[#steps]]) + end + else + for i=1, #steps-1 do + local dat = step[steps[i]] + if type(dat) ~= "table" then error("ENOENT") end + step=dat + end + if type(step[steps[#steps]]) == "table" then + return table.keys(step[steps[#steps]]) + end end error("ENOENT") end @@ -77,6 +220,7 @@ function proxy:fileExists(path) return ok end +data.uptime=simpleFile(function()return tostring(kernel.computer:getUptime())end,function()error("EACCES")end) kernel.procfs={} kernel.procfs.data=data kernel.procfs.proxy=proxy diff --git a/Src/hysh/bin/ls b/Src/hysh/bin/ls index be98f0e..cf54cc4 100644 --- a/Src/hysh/bin/ls +++ b/Src/hysh/bin/ls @@ -148,7 +148,7 @@ if cloptions.l then syscall.devctl(1, "sfgc", 1) end elseif isDir then - syscall.devctl(1, "sfgc", 4) + syscall.devctl(1, "sfgc", 14) printInline(v) syscall.devctl(1, "sfgc", 1) else @@ -177,7 +177,7 @@ for i, v in ipairs(list) do if isSym then syscall.devctl(1, "sfgc", 6) elseif isDir then - syscall.devctl(1, "sfgc", 4) + syscall.devctl(1, "sfgc", 14) else local isExec = stat and stat.perms and (math.floor(stat.perms / (2^9)) % 2 == 1) syscall.devctl(1, "sfgc", isExec and 3 or 1) diff --git a/install/installcc.lua b/install/installcc.lua new file mode 100644 index 0000000..1ee515b --- /dev/null +++ b/install/installcc.lua @@ -0,0 +1,2 @@ +print("Hello, World!") +sleep() \ No newline at end of file diff --git a/manifest.lua b/manifest.lua index 9fa2a2e..f6c8716 100644 --- a/manifest.lua +++ b/manifest.lua @@ -577,6 +577,11 @@ table.values=function(tabl) end --- @return string table.serialize=function(tabl) end +--- Returns a merged table with a and b +--- @param ... table +--- @return table +table.merge=function(...) end + --- Gets prefix of string with suffix --- @param str string --- @param suffix string From be0fe5dc5a25d11b6bcbe812933191564575db84 Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 10 Mar 2026 10:32:32 -0400 Subject: [PATCH 29/32] install works (i think) --- Src/iniparse/lib/iniparse | 38 +++++++++ install/data/Build.tar | Bin 0 -> 323584 bytes install/data/tarbad | 157 ++++++++++++++++++++++++++++++++++++++ install/installcc.lua | 25 +++++- 4 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 Src/iniparse/lib/iniparse create mode 100644 install/data/Build.tar create mode 100644 install/data/tarbad diff --git a/Src/iniparse/lib/iniparse b/Src/iniparse/lib/iniparse new file mode 100644 index 0000000..f6b0e4b --- /dev/null +++ b/Src/iniparse/lib/iniparse @@ -0,0 +1,38 @@ +local ini = {} + +function ini.parse(str) + local config = {} + local section = nil + + for line in str:gmatch("[^\r\n]+") do + -- trim whitespace + line = line:match("^%s*(.-)%s*$") + + -- skip empty lines and comments + if line ~= "" and not line:match("^[;#]") then + -- section + local sec = line:match("^%[(.-)%]$") + if sec then + section = sec + config[section] = config[section] or {} + else + -- key=value + local key, value = line:match("^(.-)=(.*)$") + if key then + key = key:match("^%s*(.-)%s*$") + value = value:match("^%s*(.-)%s*$") + + if section then + config[section][key] = value + else + config[key] = value + end + end + end + end + end + + return config +end + +return ini \ No newline at end of file diff --git a/install/data/Build.tar b/install/data/Build.tar new file mode 100644 index 0000000000000000000000000000000000000000..b33dd6a38b1c6553abfa9bc1895f158733fae30a GIT binary patch literal 323584 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 literal 0 HcmV?d00001 diff --git a/install/data/tarbad b/install/data/tarbad new file mode 100644 index 0000000..caa6983 --- /dev/null +++ b/install/data/tarbad @@ -0,0 +1,157 @@ + +local function octal_to_number(str) + str = str:gsub("%z", ""):match("^%s*(.-)%s*$") + return tonumber(str, 8) or 0 +end + +local function dedupe_path(path) + + local parts = {} + for p in path:gmatch("[^/]+") do table.insert(parts, p) end + + for prefix_len = 1, math.floor(#parts / 2) do + local ok = true + for i = 1, prefix_len do + if parts[i] ~= parts[i + prefix_len] then + ok = false + break + end + end + if ok then + local cleaned = {} + for i = 1, #parts - prefix_len do + cleaned[#cleaned + 1] = parts[i + prefix_len] + end + return table.concat(cleaned, "/") + end + end + + return path +end + + +local function make_dirs(root, path) + local cur = root + for part in path:gmatch("([^/]+)/") do + if not cur[part] then + cur[part] = { __type = "dir", __entries = {} } + end + cur = cur[part].__entries + end + return cur +end + +local function flatten(node, prefix) + local out = {} + prefix = prefix or "" + + for name, obj in pairs(node) do + local full = prefix .. name + if obj.__type == "file" then + out[#out+1] = { + name = full, + type = "file", + contents = obj.__contents + } + elseif obj.__type == "dir" then + out[#out+1] = { + name = full .. "/", + type = "dir", + contents = flatten(obj.__entries, full .. "/") + } + end + end + + return out +end + +local function unpack_tar(tarstr) + local i = 1 + local len = #tarstr + local root = {} + + while i + 512 <= len do + local header = tarstr:sub(i, i + 511) + + if header:match("^\0+$") then break end + + local name_raw = header:sub(1, 100):gsub("%z.*", "") + local prefix_raw = header:sub(346, 500):gsub("%z.*", "") + + local name + if prefix_raw ~= "" then + name = prefix_raw .. "/" .. name_raw + else + name = name_raw + end + + name = name:gsub("^%./", ""):gsub("/+", "/") + + name = dedupe_path(name) + + local size = octal_to_number(header:sub(125,136)) + local typeflag = header:sub(157,157) + + i = i + 512 + + local contents = tarstr:sub(i, i + size - 1) + local pad = (512 - (size % 512)) % 512 + i = i + size + pad + + if name == "" then goto continue end + + local is_dir = typeflag == "5" or name:sub(-1) == "/" + + local clean_name = name:gsub("/$", "") + if clean_name == "" then goto continue end + + local parent_path = clean_name:match("(.+)/") + local fname = clean_name:match("([^/]+)$") + if not fname then goto continue end + + local parent = root + if parent_path then + parent = make_dirs(root, parent_path .. "/") + end + + if is_dir then + parent[fname] = parent[fname] or { __type = "dir", __entries = {} } + else + parent[fname] = { __type = "file", __contents = contents } + end + + ::continue:: + end + + return flatten(root) +end + +local function write_directory(prefix, items) + for _, v in ipairs(items) do + if v.type == "dir" then + fs.makeDir(prefix..v.name) + write_directory(prefix, v.contents) + elseif v.type == "file" then + local file = fs.open(prefix..v.name, "w") + file.write(v.contents) + file.close() + end + end +end + +local in_tar = ({...})[1] +local out_dir = ({...})[2] + +if not in_tar or not out_dir then + print("Usage: unpack_tar ") + return +end + +local f = fs.open(in_tar, "r") +local tarstr = f.readAll() +f.close() + +local list = unpack_tar(tarstr) +write_directory(out_dir, list) + +print("TAR extracted into: " .. out_dir) diff --git a/install/installcc.lua b/install/installcc.lua index 1ee515b..dd61b57 100644 --- a/install/installcc.lua +++ b/install/installcc.lua @@ -1,2 +1,25 @@ print("Hello, World!") -sleep() \ No newline at end of file +sleep(1) +term.clear() +print("Do you want to install HyperionOS? [Y/n]") +local input=read() +if input=="y" or input=="Y" or input=="" then + goto install +else + goto exit +end + +::install:: +print("Installing tar but bad...") +shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/install/data/tarbad /tar.lua") +print("Installing HyperionOS...") +print("Installing precompiled tar") +shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/install/data/Build.tar /Build.tar") +shell.run("tar Build.tar /") +shell.run("rm $") +shell.run("cp Build $") +shell.run("rm Build") +shell.run("rm Build.tar") +fs.copy("/$/boot/cct/eeprom","/startup.lua") +dofile("startup.lua") +::exit:: \ No newline at end of file From ea3a7e99a75b6f1c2eb1f44accd190b69143f71a Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 10 Mar 2026 10:34:36 -0400 Subject: [PATCH 30/32] Update install/installcc.lua --- install/installcc.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/installcc.lua b/install/installcc.lua index dd61b57..117b4a3 100644 --- a/install/installcc.lua +++ b/install/installcc.lua @@ -11,10 +11,10 @@ end ::install:: print("Installing tar but bad...") -shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/install/data/tarbad /tar.lua") +shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/1.2-dev/install/data/tarbad /tar.lua") print("Installing HyperionOS...") print("Installing precompiled tar") -shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/install/data/Build.tar /Build.tar") +shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/1.2-dev/install/data/Build.tar /Build.tar") shell.run("tar Build.tar /") shell.run("rm $") shell.run("cp Build $") From 2d4ea1bbf41557a6dad09dd5ec6c76e28b91765d Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 10 Mar 2026 10:39:01 -0400 Subject: [PATCH 31/32] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c96f442..f4f2e14 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Download on PineStore](https://raster.shields.io/badge/dynamic/json?url=https%3A%2F%2Fpinestore.cc%2Fapi%2Fproject%2F225&query=%24.project.downloads&suffix=%20downloads&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNzYuOTA0IiBoZWlnaHQ9Ijg5LjI5NSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDc2OS4wNCA4OTIuOTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTQuNzQgLTQuNjgyNikiIGZpbGw9IiM5YWIyZjIiPgogIDxwYXRoIGQ9Im00MTAgODUxYzAtMTIgMjYtMjEgNTgtMjEgMTUgMCAyMiA0IDE3IDktMTQgMTItNzUgMjItNzUgMTJ6Ii8%2BCiAgPHBhdGggZD0ibTU4NSA3NDJjLTEtNDkgNC03MiAxNi04NSAyMi0yNCAzMC02OCAxNi04Ni0xMi0xNC0yNy0zOS00OC03OC0xMC0xOS05LTI2IDQtNDEgMjItMjQgMjEtNjctMi0xNDQtMjEtNjktMzktMTQ0LTQ4LTE5NS00LTI2LTItMzMgMTEtMzMgMzEgMCAxMTIgMzMgMTQxIDU4IDI4IDIzIDgxIDkyIDcxIDkyLTIgMCA1IDI2IDE2IDU3IDI4IDc5IDI5IDIyNCAzIDMwOC0xMCAzMy0xOSA2Mi0xOSA2NS00IDI2LTEzMiAxNTAtMTU1IDE1MC0zIDAtNi0zMC02LTY4eiIvPgogIDxwYXRoIGQ9Im02OCA2NzNjLTcyLTEwOS03MS0yNzggMy00MjMgMzYtNzEgNjItMTAwIDEyOC0xNDAgNDMtMjcgNjUtMzQgMTE4LTM2IDEwMC00IDk4IDExLTE5IDEzNi0zNCAzNy03OCA4OC05NiAxMTMtMjggMzktMzEgNDgtMjEgNjUgMTEgMTcgNiAyNy0zMyA3OS00MCA1My00NCA2Mi0zMiA3OCAxNyAyMyAxOCA1NyAyIDczLTYgNi0xNCAzMS0xNyA1NC02IDQyLTYgNDItMzMgMXoiLz4KIDwvZz4KIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNC43NCAtNC42ODI2KSIgZmlsbD0iIzU5YTY0ZiI%2BCiAgPHBhdGggZD0ibTM2NSA4MTNjLTUzLTYtMTM5LTMzLTE5Mi02MS02OC0zNS04My02Ny01OC0xMjIgMjYtNTkgNDAtNjcgNzgtNDkgNjggMzMgMTY3IDU4IDI2NiA2OSA1OCA1IDEwNiAxMiAxMDkgMTQgMiAzIDYgMzIgOSA2NSA4IDg1IDAgOTEtMTAxIDkwLTQ0LTEtOTQtNC0xMTEtNnoiLz4KICA8cGF0aCBkPSJtNDEwIDQ1OWMtNjctNy0xNjAtMjktMTk5LTQ4LTI3LTE0LTM0LTM2LTIwLTYzIDIxLTM4IDk3LTEzNiAxNTAtMTkzIDI1LTI3IDU4LTcxIDczLTk3IDI1LTQzIDMxLTQ3IDU0LTQyIDQwIDEwIDQyIDEyIDQyIDUyIDAgMjAgNiA1NyAxNCA4MiAyNCA3MyA1NCAxOTIgNjIgMjM2IDUgMzUgMyA0NS0xNSA2My0yMyAyMy0zNiAyNC0xNjEgMTB6Ii8%2BCiA8L2c%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTQuNzQgLTQuNjgyNikiIGZpbGw9IiM3ZWNiMjUiPgogIDxwYXRoIGQ9Im01NTggNjc0Yy0yLTItNTEtOS0xMDktMTQtMTAyLTExLTIwNC0zNy0yNjQtNjktMTYtOC0zMi0xNC0zNC0xMi00IDMtMzEtNDgtMzEtNjEgMC01IDIxLTMxIDQ2LTU4IDUxLTU0IDcxLTYwIDEzMC0zNSAxOSA4IDgzIDE5IDE0MiAyNSA1OCA2IDEwNyAxMiAxMDcgMTNzMTUgMjYgMzMgNTZjMjcgNDMgMzIgNjMgMzAgOTktMiAzNS04IDQ3LTI1IDUzLTExIDQtMjMgNi0yNSAzeiIvPgogPC9nPgogPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE0Ljc0IC00LjY4MjYpIiBmaWxsPSIjZWNlZGVmIj4KICA8cGF0aCBkPSJtMjYwIDg5MGMtMzQtOC03MC00MS03MC02NSAwLTYtOS0yMC0yMC0zMHMtMjAtMjItMjAtMjctMTMtMjEtMzAtMzVjLTM1LTI5LTQxLTgzLTEzLTEyMiAxNS0yMiAxNS0yNi0xLTU2LTE4LTMzLTE4LTMzIDI3LTkxIDI4LTM2IDQyLTYzIDM2LTY4LTIzLTI1IDktNzggMTIwLTE5NyAzNi0zOCA3Mi04MSA4Mi05NiAxMC0xNCAyNS0zMCAzMy0zNSAzNi0yMCA3IDMyLTUzIDk3LTQ4IDUxLTEyNiAxNTAtMTQ5IDE4OS0xMCAxOC05IDI0IDEwIDQwIDIzIDE5IDIzIDE5LTI5IDcxLTUzIDUyLTUzIDUyLTM4IDgyIDE0IDI4IDE0IDMzLTEwIDc2LTMyIDU3LTIzIDgxIDQ2IDEyMCAzNCAxOSA0OSAzMyA0NSA0Mi0xNCAzNyAzNiA3NSA5OCA3NSAyNSAwIDQwLTcgNTQtMjUgMTgtMjMgMjctMjUgOTUtMjUgOTQgMCAxMDItOCA5My04OS02LTUzLTUtNTkgMTQtNjQgMzItOCAyNi02NC0xNS0xMzItMzUtNTgtMzUtNTgtOS04MiAyMS0xOSAyNC0yOSAxOS01Ni0xMC00Ny00NC0xNzUtNjEtMjI3LTgtMjUtMTQtNjItMTQtODMgMC0yNy01LTM5LTE3LTQzLTEwLTMtMjUtOC0zMy0xMC0xMi00LTEyLTYtMS0xNCAyNy0xNiA1NiA1IDY5IDUxIDM1IDExNyA0MyAxNDggNDYgMTcwIDIgMTMgMTEgNTEgMjEgODQgMjEgNzEgMjEgMTIxIDAgMTQ1LTE0IDE1LTEzIDE5IDUgNDMgMTEgMTQgMjAgMzAgMjAgMzVzNyAxNSAxNSAyMmMyMSAxNyAxNiA3NS0xMCAxMDItMTggMTktMjAgMzItMTcgNzkgNCA1MCAyIDU4LTE5IDcyLTEyIDktNTAgMTktODMgMjMtNDUgNS02NSAxMy04MyAzMi0yNiAyOC05MiAzOC0xNTMgMjJ6Ii8%2BCiA8L2c%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTQuNzQgLTQuNjgyNikiIGZpbGw9IiM3ZTY3NGQiPgogIDxwYXRoIGQ9Im0yNDggODU0Yy0zMC0xNi00Ny01OS0zMC03NiA4LTggMjMtNyA1NCAyIDI0IDcgNjEgMTQgODMgMTcgNTQgNyA1OSAxNSAzNSA0Ni0xOCAyMy0yOSAyNy02OCAyNy0yNi0xLTU5LTctNzQtMTZ6Ii8%2BCiA8L2c%2BCjwvc3ZnPgo%3D&label=PineStore)](https://pinestore.cc/projects/225/hyperionos) # HyperionOS HyperionOS is a modular, hybrid kernel operating system written entirely in Lua. It features a custom task scheduler, virtual filesystem, syscall interface, and separates core functionality from user-space services. From beebf01223fccbfb2b5a607e38afac373e907851 Mon Sep 17 00:00:00 2001 From: Astronand Date: Tue, 10 Mar 2026 10:57:10 -0400 Subject: [PATCH 32/32] remove tar during install --- install/installcc.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/install/installcc.lua b/install/installcc.lua index dd61b57..8c9b7c7 100644 --- a/install/installcc.lua +++ b/install/installcc.lua @@ -16,6 +16,8 @@ print("Installing HyperionOS...") print("Installing precompiled tar") shell.run("wget https://git.astronand.dev/Hyperion/HyperionOS/raw/branch/main/install/data/Build.tar /Build.tar") shell.run("tar Build.tar /") +print("Removing tar but bad...") +shell.run("rm /tar.lua") shell.run("rm $") shell.run("cp Build $") shell.run("rm Build")