├── .gitignore ├── OCNetFS ├── conf.lua ├── lfs-server.lua ├── main.lua └── ocnetfs.lua ├── README.md ├── ccemu ├── ccbios.lua └── ccemu.lua ├── chip8.lua ├── debug.lua ├── dfpwm ├── dfpwm.lua └── lib │ └── dfpwm.lua ├── enhanced ├── components.lua ├── df.lua ├── format.lua ├── index.txt ├── less.lua ├── ls.lua ├── more.lua └── resolution.lua ├── filesystems ├── fslib.lua ├── index.txt ├── mksocfs.lua ├── mountfs.lua ├── msdos.lua ├── socfs.lua ├── socfs.txt ├── tapefs.lua └── tapefsd.lua ├── immibis-compress ├── compress.lua ├── decompress.lua ├── index.txt └── ipack.lua ├── index.txt ├── nano └── nano.lua ├── ocwe.lua ├── ooci ├── ctif2ooci.lua ├── index.txt ├── ooci-view.lua ├── ooci.txt └── ooci2ctif.lua ├── programs.cfg ├── soundcard.lua ├── tapeutils ├── dumptape.lua ├── formattape.lua ├── index.txt ├── loadtape.lua └── setspeed.lua ├── unicode-more.lua ├── unrequire.lua ├── utilset ├── free.lua └── index.txt ├── vcomponent ├── index.txt ├── vcmanage.lua ├── vcomponent.lua └── vtest.lua └── wocchat ├── index.txt ├── wocchat.cfg └── wocchat.lua /.gitignore: -------------------------------------------------------------------------------- 1 | KairosOS/build 2 | OCNetFS/host 3 | -------------------------------------------------------------------------------- /OCNetFS/conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | t.identity = "OCNetFS" 3 | t.console = true 4 | t.window = false 5 | t.modules.audio = false 6 | t.modules.graphics = false 7 | t.modules.image = false 8 | t.modules.joystick = false 9 | t.modules.keyboard = false 10 | t.modules.math = false 11 | t.modules.mouse = false 12 | t.modules.physics = false 13 | t.modules.sound = false 14 | t.modules.touch = false 15 | t.modules.video = false 16 | t.modules.window = false 17 | end 18 | -------------------------------------------------------------------------------- /OCNetFS/lfs-server.lua: -------------------------------------------------------------------------------- 1 | -- Warning, given a bug in this program, the client could potentially access files outside the folder 2 | -- Best to chroot/limit permissions for this server 3 | 4 | -- Configuration 5 | local totalspace = math.huge 6 | local curspace = 0 7 | local port = 14948 8 | local label = "netfs" 9 | local change = false 10 | local debug = false 11 | -- End of Configuration 12 | 13 | local socket = require("socket") 14 | local lfs = require("lfs") 15 | 16 | local function sanitizePath(path) 17 | local currentdir = lfs.currentdir() 18 | if currentdir:sub(-1,-1):find("[\\/]") then 19 | currentdir = currentdir:sub(1,-2) 20 | end 21 | path = ("/" .. path):gsub("\\", "/") 22 | local tPath = {} 23 | for part in path:gmatch("[^/]+") do 24 | if part ~= "" and part ~= "." then 25 | if part == ".." then 26 | table.remove(tPath) 27 | else 28 | table.insert(tPath, part) 29 | end 30 | end 31 | end 32 | local newpath = currentdir .. "/" .. table.concat(tPath, "/") 33 | return newpath 34 | end 35 | 36 | local function getDirectoryItems(path) 37 | local stat,iter,obj = pcall(lfs.dir,path) 38 | if not stat then 39 | print(iter) 40 | return {} 41 | end 42 | local output = {} 43 | for entry in function() return iter(obj) end do 44 | if entry ~= "." and entry ~= ".." then 45 | output[#output + 1] = entry 46 | end 47 | end 48 | return output 49 | end 50 | 51 | local recurseCount 52 | function recurseCount(path) 53 | local count = 0 54 | local list = getDirectoryItems(path) 55 | for i = 1,#list do 56 | if lfs.attributes(path .. "/" .. list[i],"mode") == "directory" then 57 | count = count + 512 + recurseCount(path .. "/" .. list[i]) 58 | else 59 | local size = lfs.attributes(path .. "/" .. list[i],"size") 60 | if size == nil then 61 | print(path .. "/" .. list[i]) 62 | print(lfs.attributes(path .. "/" .. list[i],"mode")) 63 | end 64 | count = count + (size or 0) 65 | end 66 | end 67 | return count 68 | end 69 | 70 | print("Warning, I take no responsibility if a bug in this program eats your computer\nIt's your fault for running it under such a permission\nThough, bug reports and fixes are welcomed ;)\n") 71 | 72 | if change then 73 | print("Warning, modification enabled on potentially dangerous program\n") 74 | end 75 | 76 | print("Calculating current space usage ...") 77 | curspace = recurseCount(sanitizePath("/")) 78 | 79 | local stat, server = pcall(assert,socket.bind("*", port)) 80 | if not stat then 81 | print("Failed to get default port " .. port .. ": " .. server) 82 | server = assert(socket.bind("*", 0)) 83 | end 84 | local sID, sPort = server:getsockname() 85 | server:settimeout(0) 86 | 87 | print("Listening on " .. sID .. ":" .. sPort) 88 | 89 | local ots = tostring 90 | function tostring(obj) 91 | if obj == math.huge then 92 | return "math.huge" 93 | elseif obj == -math.huge then 94 | return "-math.huge" 95 | elseif obj ~= obj then 96 | return "0/0" 97 | else 98 | return ots(obj) 99 | end 100 | end 101 | 102 | -- Unserialize without loadstring, for security purposes. 103 | -- Not very robust but gets the job done. 104 | local unserialize 105 | function unserialize(str) 106 | if type(str) ~= "string" then 107 | error("bad argument #1: string expected, got " .. type(str),2) 108 | end 109 | if str:sub(1,1) == "{" and str:sub(-1,-1) == "}" then 110 | local i = 1 111 | local gen = {} 112 | local block = str:sub(2,-2) .. "," 113 | local piece = "" 114 | for part in block:gmatch("(.-),") do 115 | piece = piece .. part 116 | if (piece:sub(1,1) == "\"" and piece:sub(-1,-1) == "\"") or piece:sub(1,1) ~= "\"" then 117 | if piece:find("^%[.-%]=.*") then 118 | local key, value = piece:match("^%[(.-)%]=(.*)") 119 | gen[unserialize(key)] = unserialize(value) 120 | else 121 | gen[i] = unserialize(piece) 122 | i = i + 1 123 | end 124 | piece = "" 125 | else 126 | piece = piece .. "," 127 | end 128 | end 129 | if piece ~= "" then 130 | error("Cannot unserialize " .. piece,2) 131 | end 132 | return gen 133 | elseif str:sub(1,1) == "\"" and str:sub(-1,-1) == "\"" then -- string 134 | return str:sub(2,-2):gsub("\\a","\a"):gsub("\\b","\b"):gsub("\\f","\f"):gsub("\\n","\n"):gsub("\\r","\r"):gsub("\\t","\t"):gsub("\\v","\v"):gsub("\\\"","\""):gsub("\\'","'"):gsub("\\\n","\n"):gsub("\\0","\0"):gsub("\\(%d%d?%d?)",string.char):gsub("\\\\","\\") 135 | elseif tonumber(str) then 136 | return tonumber(str) 137 | elseif str == "0/0" then 138 | return 0/0 139 | elseif str == "math.huge" then 140 | return math.huge 141 | elseif str == "-math.huge" then 142 | return -math.huge 143 | elseif str == "true" then 144 | return true 145 | elseif str == "false" then 146 | return false 147 | elseif str == "nil" or str == "" then 148 | return nil 149 | else 150 | error("Cannot unserialize " .. str,2) 151 | end 152 | end 153 | 154 | local curclient 155 | local function sendData(msg) 156 | if debug then 157 | local ip,port = curclient:getpeername() 158 | print(ip .. ":" .. port .. " < " .. msg) 159 | end 160 | curclient:send(msg .. "\n") 161 | end 162 | 163 | local function checkArg(pos,obj,what) 164 | if type(obj) ~= what then 165 | sendData("bad argument #" .. pos .. " (" .. what .. " expected, got " .. type(obj) .. ")") 166 | return false 167 | end 168 | return true 169 | end 170 | 171 | local function dprint(ctrl, line) 172 | print(" > " .. ctrl .. "," .. line:gsub("[^\32-\126]", function(a) return "\\"..a:byte() end)) 173 | end 174 | 175 | -- do not change order 176 | local ops={"size","seek","read","isDirectory","open","spaceTotal","setLabel","lastModified","close","rename","isReadOnly","exists","getLabel","spaceUsed","makeDirectory","list","write","remove"} 177 | 178 | local sockets = {server} 179 | local hndls = {} 180 | local function update() 181 | -- Check for new data or new clients 182 | local ready, _, err = socket.select(sockets,nil) 183 | if not ready then 184 | print("select gave " .. tostring(err)) 185 | return 186 | end 187 | for _, client in ipairs(ready) do 188 | if client == server then 189 | client = server:accept() 190 | if client ~= nil then 191 | local ci,cp = client:getpeername() 192 | print("User connected from: " .. ci .. ":" .. cp) 193 | sockets[#sockets + 1] = client 194 | client:settimeout(0) 195 | end 196 | break 197 | end 198 | curclient = client 199 | local line, err = client:receive() 200 | if not line then 201 | print("socket receive gave: " .. err) 202 | if err ~= "closed" then 203 | pcall(client.close,client) 204 | end 205 | for i = 1,#sockets do 206 | if sockets[i] == client then 207 | table.remove(sockets, i) 208 | break 209 | end 210 | end 211 | break 212 | end 213 | local ctrl = line:byte(1,1) - 31 214 | ctrl = ops[ctrl] or ctrl 215 | local line = line:sub(2) 216 | if debug then 217 | dprint(ctrl, line) 218 | end 219 | local stat,ret = pcall(unserialize, line) 220 | if not stat then 221 | if not debug then 222 | dprint(ctrl, line) 223 | end 224 | print("Bad Input: " .. ret) 225 | sendData("{nil,\"bad input\"}") 226 | return 227 | end 228 | if type(ret) ~= "table" then 229 | if not debug then 230 | dprint(ctrl, line) 231 | end 232 | print("Bad Input (exec): " .. type(ret)) 233 | sendData("{nil,\"bad input\"}") 234 | return 235 | end 236 | if ctrl == "size" then 237 | if not checkArg(1,ret[1],"string") then return end 238 | local size = lfs.attributes(sanitizePath(ret[1]),"size") 239 | sendData("{" .. (size or 0) .. "}") 240 | elseif ctrl == "seek" then 241 | if not checkArg(1,ret[1],"number") then return end 242 | if not checkArg(2,ret[2],"string") then return end 243 | if not checkArg(3,ret[3],"number") then return end 244 | local fd = ret[1] 245 | if hndls[fd] == nil then 246 | sendData("{nil, \"bad file descriptor\"}") 247 | else 248 | local new = hndls[fd]:seek(ret[2],ret[3]) 249 | sendData("{" .. new .. "}") 250 | end 251 | elseif ctrl == "read" then 252 | if not checkArg(1,ret[1],"number") then return end 253 | if not checkArg(2,ret[2],"number") then return end 254 | local fd = ret[1] 255 | if hndls[fd] == nil then 256 | sendData("{nil, \"bad file descriptor\"}") 257 | else 258 | local data = hndls[fd]:read(ret[2]) 259 | if type(data) == "string" and #data > 0 then 260 | sendData("{" .. string.format("%q",data):gsub("\\\n","\\n") .. "}") 261 | else 262 | sendData("{nil}") 263 | end 264 | end 265 | elseif ctrl == "isDirectory" then 266 | if not checkArg(1,ret[1],"string") then return end 267 | sendData("{" .. tostring(lfs.attributes(sanitizePath(ret[1]),"mode") == "directory") .. "}") 268 | elseif ctrl == "open" then 269 | if not checkArg(1,ret[1],"string") then return end 270 | if not checkArg(2,ret[2],"string") then return end 271 | local mode = ret[2]:sub(1,1) 272 | if (mode == "w" or mode == "a") and not change then 273 | sendData("{nil,\"file not found\"}") -- Yes, this is what it returns 274 | else 275 | local file, errorstr = io.open(ret[1], ret[2]) 276 | if not file then 277 | sendData("{nil," .. string.format("%q",errorstr):gsub("\\\n","\\n") .. "}") 278 | else 279 | local randhand 280 | while true do 281 | randhand = math.random(1000000000,9999999999) 282 | if not hndls[randhand] then 283 | hndls[randhand] = file 284 | break 285 | end 286 | end 287 | sendData("{" .. randhand .. "}") 288 | end 289 | end 290 | elseif ctrl == "spaceTotal" then 291 | sendData("{" .. tostring(totalspace) .. "}") 292 | elseif ctrl == "setLabel" then 293 | if not checkArg(1,ret[1],"string") then return end 294 | if change then 295 | label = ret[1] 296 | sendData("{\"" .. label .. "\"}") 297 | else 298 | sendData("label is read only") 299 | end 300 | elseif ctrl == "lastModified" then 301 | if not checkArg(1,ret[1],"string") then return end 302 | local modtime = lfs.attributes(sanitizePath(ret[1]),"modification") 303 | sendData("{" .. (modtime or 0) .. "}") 304 | elseif ctrl == "close" then 305 | if not checkArg(1,ret[1],"number") then return end 306 | local fd = ret[1] 307 | if hndls[fd] == nil then 308 | sendData("{nil, \"bad file descriptor\"}") 309 | else 310 | hndls[fd]:close() 311 | hndls[fd] = nil 312 | sendData("{}") 313 | end 314 | elseif ctrl == "rename" then 315 | if not checkArg(1,ret[1],"string") then return end 316 | if not checkArg(2,ret[2],"string") then return end 317 | if change then 318 | sendData("{" .. tostring(os.rename(sanitizePath(ret[1]),sanitizePath(ret[2])) == true) .. "}") 319 | else 320 | sendData("{false}") 321 | end 322 | elseif ctrl == "isReadOnly" then 323 | sendData("{" .. tostring(not change) .. "}") 324 | elseif ctrl == "exists" then 325 | if not checkArg(1,ret[1],"string") then return end 326 | sendData("{" .. tostring(lfs.attributes(sanitizePath(ret[1]),"mode") ~= nil) .. "}") 327 | elseif ctrl == "getLabel" then 328 | sendData("{\"" .. label .. "\"}") 329 | elseif ctrl == "spaceUsed" then 330 | -- TODO: Need to update this 331 | sendData("{" .. curspace .. "}") 332 | elseif ctrl == "makeDirectory" then 333 | if not checkArg(1,ret[1],"string") then return end 334 | if change then 335 | sendData("{" .. tostring(lfs.mkdir(sanitizePath(ret[1]))) .. "}") 336 | else 337 | sendData("{false}") 338 | end 339 | elseif ctrl == "list" then 340 | if not checkArg(1,ret[1],"string") then return end 341 | ret[1] = sanitizePath(ret[1]) 342 | local list = getDirectoryItems(ret[1]) 343 | local out = "" 344 | for i = 1,#list do 345 | if lfs.attributes(ret[1] .. "/" .. list[i],"mode") == "directory" then 346 | list[i] = list[i] .. "/" 347 | end 348 | out = out .. string.format("%q",list[i]):gsub("\\\n","\\n") 349 | if i < #list then 350 | out = out .. "," 351 | end 352 | end 353 | sendData("{{" .. out .. "}}") 354 | elseif ctrl == "write" then 355 | if not checkArg(1,ret[1],"number") then return end 356 | if not checkArg(2,ret[2],"string") then return end 357 | local fd = ret[1] 358 | if hndls[fd] == nil then 359 | sendData("{nil, \"bad file descriptor\"}") 360 | else 361 | local success = hndls[fd]:write(ret[2]) 362 | sendData("{" .. tostring(success) .. "}") 363 | end 364 | elseif ctrl == "remove" then 365 | -- TODO: Recursive remove 366 | if not checkArg(1,ret[1],"string") then return end 367 | if change then 368 | if lfs.attributes(sanitizePath(ret[1]),"mode") == "directory" then 369 | sendData("{" .. tostring(lfs.rmdir(sanitizePath(ret[1]))) .. "}") 370 | else 371 | os.remove(sanitizePath(ret[1])) 372 | end 373 | else 374 | sendData("{false}") 375 | end 376 | else 377 | print("Unknown control: " .. ctrl) 378 | end 379 | end 380 | end 381 | 382 | while true do 383 | update() 384 | end 385 | -------------------------------------------------------------------------------- /OCNetFS/main.lua: -------------------------------------------------------------------------------- 1 | -- Configuration 2 | local totalspace = math.huge 3 | local curspace = 0 4 | local port = 14948 5 | local label = "netfs" 6 | local change = false 7 | local debug = false 8 | -- End of Configuration 9 | 10 | local socket = require("socket") 11 | 12 | local recurseCount 13 | function recurseCount(path) 14 | local count = 0 15 | local list = love.filesystem.getDirectoryItems(path) 16 | for i = 1,#list do 17 | if love.filesystem.isDirectory(path .. "/" .. list[i]) then 18 | count = count + 512 + recurseCount(path .. "/" .. list[i]) 19 | else 20 | count = count + love.filesystem.getSize(path .. "/" .. list[i]) 21 | end 22 | end 23 | return count 24 | end 25 | 26 | local recursiveDestroy 27 | function recursiveDestroy(path) 28 | local state = true 29 | local list = love.filesystem.getDirectoryItems(path) 30 | for i = 1,#list do 31 | if love.filesystem.isDirectory(path .. "/" .. list[i]) then 32 | state = state and recursiveDestroy(path .. "/" .. list[i]) 33 | else 34 | state = state and love.filesystem.remove(path .. "/" .. list[i]) 35 | end 36 | end 37 | return state 38 | end 39 | 40 | 41 | if change then 42 | print("Modification enabled\n") 43 | end 44 | 45 | print("Calculating current space usage ...") 46 | curspace = recurseCount("/") 47 | 48 | local stat, server = pcall(assert,socket.bind("*", port)) 49 | if not stat then 50 | print("Failed to get default port " .. port .. ": " .. server) 51 | server = assert(socket.bind("*", 0)) 52 | end 53 | local sID, sPort = server:getsockname() 54 | server:settimeout(0) 55 | 56 | print("Listening on " .. sID .. ":" .. sPort) 57 | 58 | local ots = tostring 59 | function tostring(obj) 60 | if obj == math.huge then 61 | return "math.huge" 62 | elseif obj == -math.huge then 63 | return "-math.huge" 64 | elseif obj ~= obj then 65 | return "0/0" 66 | else 67 | return ots(obj) 68 | end 69 | end 70 | 71 | -- Unserialize without loadstring, for security purposes. 72 | -- Not very robust but gets the job done. 73 | local unserialize 74 | function unserialize(str) 75 | if type(str) ~= "string" then 76 | error("bad argument #1: string expected, got " .. type(str),2) 77 | end 78 | if str:sub(1,1) == "{" and str:sub(-1,-1) == "}" then 79 | local i = 1 80 | local gen = {} 81 | local block = str:sub(2,-2) .. "," 82 | local piece = "" 83 | for part in block:gmatch("(.-),") do 84 | piece = piece .. part 85 | if (piece:sub(1,1) == "\"" and piece:sub(-1,-1) == "\"") or piece:sub(1,1) ~= "\"" then 86 | if piece:find("^%[.-%]=.*") then 87 | local key, value = piece:match("^%[(.-)%]=(.*)") 88 | gen[unserialize(key)] = unserialize(value) 89 | else 90 | gen[i] = unserialize(piece) 91 | i = i + 1 92 | end 93 | piece = "" 94 | else 95 | piece = piece .. "," 96 | end 97 | end 98 | if piece ~= "" then 99 | error("Cannot unserialize " .. piece,2) 100 | end 101 | return gen 102 | elseif str:sub(1,1) == "\"" and str:sub(-1,-1) == "\"" then -- string 103 | return str:sub(2,-2):gsub("\\a","\a"):gsub("\\b","\b"):gsub("\\f","\f"):gsub("\\n","\n"):gsub("\\r","\r"):gsub("\\t","\t"):gsub("\\v","\v"):gsub("\\\"","\""):gsub("\\'","'"):gsub("\\\n","\n"):gsub("\\0","\0"):gsub("\\(%d%d?%d?)",string.char):gsub("\\\\","\\") 104 | elseif tonumber(str) then 105 | return tonumber(str) 106 | elseif str == "0/0" then 107 | return 0/0 108 | elseif str == "math.huge" then 109 | return math.huge 110 | elseif str == "-math.huge" then 111 | return -math.huge 112 | elseif str == "true" then 113 | return true 114 | elseif str == "false" then 115 | return false 116 | elseif str == "nil" or str == "" then 117 | return nil 118 | else 119 | error("Cannot unserialize " .. str,2) 120 | end 121 | end 122 | 123 | local curclient 124 | local function sendData(msg) 125 | if debug then 126 | local ip,port = curclient:getpeername() 127 | print(ip .. ":" .. port .. " < " .. msg) 128 | end 129 | curclient:send(msg .. "\n") 130 | end 131 | 132 | local function checkArg(pos,obj,what) 133 | if type(obj) ~= what then 134 | sendData("bad argument #" .. pos .. " (" .. what .. " expected, got " .. type(obj) .. ")") 135 | return false 136 | end 137 | return true 138 | end 139 | 140 | local function dprint(ctrl, line) 141 | print(" > " .. ctrl .. "," .. line:gsub("[^\32-\126]", function(a) return "\\"..a:byte() end)) 142 | end 143 | 144 | -- do not change order 145 | local ops={"size","seek","read","isDirectory","open","spaceTotal","setLabel","lastModified","close","rename","isReadOnly","exists","getLabel","spaceUsed","makeDirectory","list","write","remove"} 146 | 147 | local sockets = {server} 148 | local hndls = {} 149 | function love.update() 150 | -- Check for new data or new clients 151 | local ready, _, err = socket.select(sockets,nil) 152 | if not ready then 153 | print("select gave " .. tostring(err)) 154 | return 155 | end 156 | for _, client in ipairs(ready) do 157 | if client == server then 158 | client = server:accept() 159 | if client ~= nil then 160 | local ci,cp = client:getpeername() 161 | print("User connected from: " .. ci .. ":" .. cp) 162 | sockets[#sockets + 1] = client 163 | client:settimeout(0) 164 | end 165 | break 166 | end 167 | curclient = client 168 | local line, err = client:receive() 169 | if not line then 170 | print("socket receive gave: " .. err) 171 | if err ~= "closed" then 172 | pcall(client.close,client) 173 | end 174 | for i = 1,#sockets do 175 | if sockets[i] == client then 176 | table.remove(sockets, i) 177 | break 178 | end 179 | end 180 | break 181 | end 182 | local ctrl = line:byte(1,1) - 31 183 | ctrl = ops[ctrl] or ctrl 184 | local line = line:sub(2) 185 | if debug then 186 | dprint(ctrl, line) 187 | end 188 | local stat,ret = pcall(unserialize, line) 189 | if not stat then 190 | if not debug then 191 | dprint(ctrl, line) 192 | end 193 | print("Bad Input: " .. ret) 194 | sendData("{nil,\"bad input\"}") 195 | return 196 | end 197 | if type(ret) ~= "table" then 198 | if not debug then 199 | dprint(ctrl, line) 200 | end 201 | print("Bad Input (exec): " .. type(ret)) 202 | sendData("{nil,\"bad input\"}") 203 | return 204 | end 205 | if ctrl == "size" then 206 | if not checkArg(1,ret[1],"string") then return end 207 | local size = love.filesystem.getSize(ret[1]) 208 | sendData("{" .. (size or 0) .. "}") 209 | elseif ctrl == "seek" then 210 | if not checkArg(1,ret[1],"number") then return end 211 | if not checkArg(2,ret[2],"string") then return end 212 | if not checkArg(3,ret[3],"number") then return end 213 | local fd = ret[1] 214 | if hndls[fd] == nil then 215 | sendData("{nil, \"bad file descriptor\"}") 216 | else 217 | if ret[2] == "set" then 218 | hndls[fd]:seek(ret[3]) 219 | elseif ret[2] == "cur" then 220 | hndls[fd]:seek(hndls[fd]:tell() + ret[3]) 221 | elseif ret[2] == "end" then 222 | hndls[fd]:seek(hndls[fd]:getSize() + ret[3]) 223 | end 224 | sendData("{" .. hndls[fd]:tell() .. "}") 225 | end 226 | elseif ctrl == "read" then 227 | if not checkArg(1,ret[1],"number") then return end 228 | if not checkArg(2,ret[2],"number") then return end 229 | local fd = ret[1] 230 | if hndls[fd] == nil then 231 | sendData("{nil, \"bad file descriptor\"}") 232 | else 233 | local data = hndls[fd]:read(ret[2]) 234 | if type(data) == "string" and #data > 0 then 235 | sendData("{" .. string.format("%q",data):gsub("\\\n","\\n") .. "}") 236 | else 237 | sendData("{nil}") 238 | end 239 | end 240 | elseif ctrl == "isDirectory" then 241 | if not checkArg(1,ret[1],"string") then return end 242 | sendData("{" .. tostring(love.filesystem.isDirectory(ret[1])) .. "}") 243 | elseif ctrl == "open" then 244 | if not checkArg(1,ret[1],"string") then return end 245 | if not checkArg(2,ret[2],"string") then return end 246 | local mode = ret[2]:sub(1,1) 247 | if (mode == "w" or mode == "a") and not change then 248 | sendData("{nil,\"file not found\"}") -- Yes, this is what it returns 249 | else 250 | local file, errorstr = love.filesystem.newFile(ret[1], mode) 251 | if not file then 252 | sendData("{nil," .. string.format("%q",errorstr):gsub("\\\n","\\n") .. "}") 253 | else 254 | local randhand 255 | while true do 256 | randhand = math.random(1000000000,9999999999) 257 | if not hndls[randhand] then 258 | hndls[randhand] = file 259 | break 260 | end 261 | end 262 | sendData("{" .. randhand .. "}") 263 | end 264 | end 265 | elseif ctrl == "spaceTotal" then 266 | sendData("{" .. tostring(totalspace) .. "}") 267 | elseif ctrl == "setLabel" then 268 | if not checkArg(1,ret[1],"string") then return end 269 | if change then 270 | label = ret[1] 271 | sendData("{\"" .. label .. "\"}") 272 | else 273 | sendData("label is read only") 274 | end 275 | elseif ctrl == "lastModified" then 276 | if not checkArg(1,ret[1],"string") then return end 277 | local modtime = love.filesystem.getLastModified(ret[1]) 278 | sendData("{" .. (modtime or 0) .. "}") 279 | elseif ctrl == "close" then 280 | if not checkArg(1,ret[1],"number") then return end 281 | local fd = ret[1] 282 | if hndls[fd] == nil then 283 | sendData("{nil, \"bad file descriptor\"}") 284 | else 285 | hndls[fd]:close() 286 | hndls[fd] = nil 287 | sendData("{}") 288 | end 289 | elseif ctrl == "rename" then 290 | if not checkArg(1,ret[1],"string") then return end 291 | if not checkArg(2,ret[2],"string") then return end 292 | if change then 293 | local data = love.filesystem.read(ret[1]) 294 | if not data then 295 | sendData("{false}") 296 | else 297 | local succ = love.filesystem.write(ret[2],data) 298 | if not succ then 299 | sendData("{false}") 300 | else 301 | local succ = love.filesystem.remove(ret[1]) 302 | if not succ then 303 | local succ = love.filesystem.remove(ret[2]) 304 | if not succ then 305 | print("WARNING: two copies of " .. ret[1] .. " now exist") 306 | end 307 | sendData("{false}") 308 | else 309 | sendData("{true}") 310 | end 311 | end 312 | end 313 | else 314 | sendData("{false}") 315 | end 316 | elseif ctrl == "isReadOnly" then 317 | sendData("{" .. tostring(not change) .. "}") 318 | elseif ctrl == "exists" then 319 | if not checkArg(1,ret[1],"string") then return end 320 | sendData("{" .. tostring(love.filesystem.exists(ret[1])) .. "}") 321 | elseif ctrl == "getLabel" then 322 | sendData("{\"" .. label .. "\"}") 323 | elseif ctrl == "spaceUsed" then 324 | -- TODO: Need to update this 325 | sendData("{" .. curspace .. "}") 326 | elseif ctrl == "makeDirectory" then 327 | if not checkArg(1,ret[1],"string") then return end 328 | if change then 329 | sendData("{" .. tostring(love.filesystem.createDirectory(ret[1])) .. "}") 330 | else 331 | sendData("{false}") 332 | end 333 | elseif ctrl == "list" then 334 | if not checkArg(1,ret[1],"string") then return end 335 | local list = love.filesystem.getDirectoryItems(ret[1]) 336 | local out = "" 337 | for i = 1,#list do 338 | if love.filesystem.isDirectory(ret[1] .. "/" .. list[i]) then 339 | list[i] = list[i] .. "/" 340 | end 341 | out = out .. string.format("%q",list[i]):gsub("\\\n","\\n") 342 | if i < #list then 343 | out = out .. "," 344 | end 345 | end 346 | sendData("{{" .. out .. "}}") 347 | elseif ctrl == "write" then 348 | if not checkArg(1,ret[1],"number") then return end 349 | if not checkArg(2,ret[2],"string") then return end 350 | local fd = ret[1] 351 | if hndls[fd] == nil then 352 | sendData("{nil, \"bad file descriptor\"}") 353 | else 354 | local success = hndls[fd]:write(ret[2]) 355 | sendData("{" .. tostring(success) .. "}") 356 | end 357 | elseif ctrl == "remove" then 358 | if not checkArg(1,ret[1],"string") then return end 359 | if change then 360 | sendData("{" .. tostring(recursiveDestroy(ret[1])) .. "}") 361 | else 362 | sendData("{false}") 363 | end 364 | else 365 | print("Unknown control: " .. ctrl) 366 | end 367 | end 368 | end 369 | -------------------------------------------------------------------------------- /OCNetFS/ocnetfs.lua: -------------------------------------------------------------------------------- 1 | local function errprint(msg) 2 | io.stderr:write(msg) 3 | end 4 | 5 | local component = require("component") 6 | if not component.isAvailable("internet") then 7 | errprint("Internet card is required") 8 | return 9 | end 10 | 11 | local shell = require("shell") 12 | local args,opts = shell.parse(...) 13 | if #args < 1 or #args > 2 then 14 | print("Usage: ocnetfs ip [port]") 15 | return 16 | end 17 | if #args < 2 then 18 | args[2] = "14948" 19 | end 20 | 21 | local ip,port = args[1],tonumber(args[2]) 22 | if port == nil then 23 | errprint("Non numerical port",0) 24 | return 25 | end 26 | 27 | local internet = require("internet") 28 | local serialization = require("serialization") 29 | local vcomp = require("vcomponent") 30 | 31 | local socket = internet.open(ip,port) 32 | socket:setTimeout(3) 33 | 34 | local vnetfs = {} 35 | 36 | local function sendMessage(ctrl,data) 37 | socket:write(string.char(ctrl + 31) .. data .. "\n") 38 | socket:flush() 39 | end 40 | 41 | local function getData() 42 | local stat, line, err = pcall(socket.read, socket, "*l") 43 | if not stat then 44 | pcall(socket.close,socket) 45 | socket = {read = function() return nil, "non open socket" end, write = function() end, flush = function() end, close = function() end} 46 | vcomp.unregister(vnetfs.address) 47 | print("ocnetfs: " .. (line or "unknown error")) 48 | return {} 49 | elseif not line then 50 | pcall(socket.close,socket) 51 | socket = {read = function() return nil, "non open socket" end, write = function() end, flush = function() end, close = function() end} 52 | vcomp.unregister(vnetfs.address) 53 | print("ocnetfs: " .. (err or "unknown error")) 54 | return {} 55 | elseif line:sub(1,1) ~= "{" then 56 | error(line,3) 57 | else 58 | local fn, err = load("return " .. line,"ocnetfs-server","t",{math={huge=math.huge}}) 59 | if fn then 60 | return fn() 61 | else 62 | return nil, err 63 | end 64 | end 65 | end 66 | 67 | vnetfs.type = "filesystem" 68 | vnetfs.address = ip .. ":" .. port 69 | function vnetfs.size(path) 70 | checkArg(1,path,"string") 71 | sendMessage(1,serialization.serialize({path})) 72 | local ret = getData() 73 | return table.unpack(ret) 74 | end 75 | function vnetfs.seek(handle, whence, offset) 76 | checkArg(1,handle,"number") 77 | checkArg(2,whence,"string") 78 | checkArg(3,offset,"number") 79 | sendMessage(2,serialization.serialize({handle, whence, offset})) 80 | local ret = getData() 81 | return table.unpack(ret) 82 | end 83 | function vnetfs.read(handle, count) 84 | checkArg(1,handle,"number") 85 | checkArg(2,count,"number") 86 | sendMessage(3,serialization.serialize({handle, count})) 87 | local ret = getData() 88 | return table.unpack(ret) 89 | end 90 | function vnetfs.isDirectory(path) 91 | checkArg(1,path,"string") 92 | sendMessage(4,serialization.serialize({path})) 93 | local ret = getData() 94 | return table.unpack(ret) 95 | end 96 | function vnetfs.open(path, mode) 97 | checkArg(1,path,"string") 98 | checkArg(2,mode,"string") 99 | sendMessage(5,serialization.serialize({path, mode})) 100 | local ret = getData() 101 | return table.unpack(ret) 102 | end 103 | function vnetfs.spaceTotal() 104 | sendMessage(6,serialization.serialize({})) 105 | local ret = getData() 106 | return table.unpack(ret) 107 | end 108 | function vnetfs.setLabel(value) 109 | checkArg(1,value,"string") 110 | sendMessage(7,serialization.serialize({value})) 111 | local ret = getData() 112 | return table.unpack(ret) 113 | end 114 | function vnetfs.lastModified(path) 115 | checkArg(1,path,"string") 116 | sendMessage(8,serialization.serialize({path})) 117 | local ret = getData() 118 | return table.unpack(ret) 119 | end 120 | function vnetfs.close(handle) 121 | checkArg(1,handle,"number") 122 | sendMessage(9,serialization.serialize({handle})) 123 | local ret = getData() 124 | return table.unpack(ret) 125 | end 126 | function vnetfs.rename(from, to) 127 | checkArg(1,from,"string") 128 | checkArg(2,to,"string") 129 | sendMessage(10,serialization.serialize({from, to})) 130 | local ret = getData() 131 | return table.unpack(ret) 132 | end 133 | function vnetfs.isReadOnly() 134 | sendMessage(11,serialization.serialize({})) 135 | local ret = getData() 136 | return table.unpack(ret) 137 | end 138 | function vnetfs.exists(path) 139 | checkArg(1,path,"string") 140 | sendMessage(12,serialization.serialize({path})) 141 | local ret = getData() 142 | return table.unpack(ret) 143 | end 144 | function vnetfs.getLabel() 145 | sendMessage(13,serialization.serialize({})) 146 | local ret = getData() 147 | return table.unpack(ret) 148 | end 149 | function vnetfs.spaceUsed() 150 | sendMessage(14,serialization.serialize({})) 151 | local ret = getData() 152 | return table.unpack(ret) 153 | end 154 | function vnetfs.makeDirectory(path) 155 | checkArg(1,path,"string") 156 | sendMessage(15,serialization.serialize({path})) 157 | local ret = getData() 158 | return table.unpack(ret) 159 | end 160 | function vnetfs.list(path) 161 | checkArg(1,path,"string") 162 | sendMessage(16,serialization.serialize({path})) 163 | local ret = getData() 164 | return table.unpack(ret) 165 | end 166 | function vnetfs.write(handle, value) 167 | checkArg(1,handle,"number") 168 | checkArg(2,value,"string") 169 | sendMessage(17,serialization.serialize({handle, value})) 170 | local ret = getData() 171 | return table.unpack(ret) 172 | end 173 | function vnetfs.remove(path) 174 | checkArg(1,path,"string") 175 | sendMessage(18,serialization.serialize({path})) 176 | local ret = getData() 177 | return table.unpack(ret) 178 | end 179 | 180 | vcomp.register(vnetfs.address, vnetfs.type, vnetfs) 181 | print("Added component at " .. vnetfs.address) 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gams-OCProgs 2 | ============ 3 | 4 | Programs for OpenComputers, by Gamax92 5 | 6 | View index.txt for information regarding programs 7 | -------------------------------------------------------------------------------- /ccemu/ccbios.lua: -------------------------------------------------------------------------------- 1 | -- Bios entries for CCEmu 2 | -- A good majority of this is copied from ComputerCraft's bios.lua 3 | 4 | local term = require("term") 5 | local text = require("text") 6 | local fs = require("filesystem") 7 | local component = require("component") 8 | local computer = require("computer") 9 | local unicode = require("unicode") 10 | 11 | local env = ccemu.env 12 | local config = ccemu.config 13 | 14 | local function tablecopy(orig) 15 | local orig_type = type(orig) 16 | local copy 17 | if orig_type == 'table' then 18 | copy = {} 19 | for orig_key, orig_value in pairs(orig) do 20 | copy[orig_key] = orig_value 21 | end 22 | else 23 | copy = orig 24 | end 25 | return copy 26 | end 27 | 28 | function env.os.version() 29 | return "CCEmu 1.1" 30 | end 31 | function env.os.pullEventRaw(filter) 32 | return coroutine.yield(filter) 33 | end 34 | function env.os.pullEvent(filter) 35 | local e = table.pack(env.os.pullEventRaw(filter)) 36 | if e[1] == "terminate" then 37 | error("interrupted", 0) 38 | end 39 | return table.unpack(e) 40 | end 41 | env.sleep = os.sleep 42 | env.write = function(data) 43 | local count = 0 44 | local otw = text.wrap 45 | function text.wrap(...) 46 | local a, b, c = otw(...) 47 | if c then count = count + 1 end 48 | return a, b, c 49 | end 50 | local x = term.getCursor() 51 | local w = component.gpu.getResolution() 52 | term.write(data, unicode.len(data) + x - 1 > w) 53 | text.wrap = otw 54 | return count 55 | end 56 | env.print = function(...) 57 | local args = {...} 58 | for i = 1, #args do 59 | args[i] = tostring(args[i]) 60 | end 61 | return env.write(table.concat(args, "\t") .. "\n") 62 | end 63 | env.printError = function(...) io.stderr:write(table.concat({...}, "\t") .. "\n") end 64 | env.read = function(pwchar, hist) 65 | local line = term.read(tablecopy(hist), nil, nil, pwchar) 66 | if line == nil then 67 | return "" 68 | end 69 | return line:gsub("\n", "") 70 | end 71 | env.loadfile = function(file, env) 72 | return loadfile(file, "t", env) 73 | end 74 | env.dofile = dofile 75 | env.os.run = function(newenv, name, ...) 76 | local args = {...} 77 | setmetatable(newenv, {__index=env}) 78 | local fn, err = loadfile(name, nil, newenv) 79 | if fn then 80 | local ok, err = pcall(function() fn(table.unpack(args)) end) 81 | if not ok then 82 | if err and err ~= "" then 83 | env.printError(err) 84 | end 85 | return false 86 | end 87 | return true 88 | end 89 | if err and err ~= "" then 90 | env.printError(err) 91 | end 92 | return false 93 | end 94 | 95 | local tAPIsLoading = {} 96 | env.os.loadAPI = function(path) 97 | local sName = fs.name(path) 98 | if tAPIsLoading[sName] == true then 99 | env.printError("API " .. sName .. " is already being loaded") 100 | return false 101 | end 102 | tAPIsLoading[sName] = true 103 | 104 | local env2 105 | env2 = { 106 | getfenv = function() return env2 end 107 | } 108 | setmetatable(env2, {__index = env}) 109 | local fn, err = loadfile(path, nil, env2) 110 | if fn then 111 | fn() 112 | else 113 | env.printError(err) 114 | tAPIsLoading[sName] = nil 115 | return false 116 | end 117 | 118 | local tmpcopy = {} 119 | for k, v in pairs(env2) do 120 | tmpcopy[k] = v 121 | end 122 | 123 | env[sName] = tmpcopy 124 | tAPIsLoading[sName] = nil 125 | return true 126 | end 127 | env.os.unloadAPI = function(name) 128 | if _name ~= "_G" and type(env[name]) == "table" then 129 | env[name] = nil 130 | end 131 | end 132 | env.os.sleep = os.sleep 133 | if env.http ~= nil then 134 | -- TODO: http.get 135 | -- TODO: http.post 136 | end 137 | 138 | -- Install the lua part of the FS api 139 | local empty = {} 140 | env.fs.complete = function(path, location, includeFiles, includeDirs) 141 | includeFiles = (includeFiles ~= false) 142 | includeDirs = (includeDirs ~= false) 143 | local dir = location 144 | local start = 1 145 | local slash = string.find(path, "[/\\]", start) 146 | if slash == 1 then 147 | dir = "" 148 | start = 2 149 | end 150 | local name 151 | while not name do 152 | local slash = string.find(path, "[/\\]", start) 153 | if slash then 154 | local part = string.sub(path, start, slash - 1) 155 | dir = env.fs.combine(dir, part) 156 | start = slash + 1 157 | else 158 | name = string.sub(path, start) 159 | end 160 | end 161 | 162 | if env.fs.isDir(dir) then 163 | local results = {} 164 | if includeDirs and path == "" then 165 | table.insert(results, ".") 166 | end 167 | if dir ~= "" then 168 | if path == "" then 169 | table.insert(results, (includeDirs and "..") or "../") 170 | elseif path == "." then 171 | table.insert(results, (includeDirs and ".") or "./") 172 | end 173 | end 174 | local tFiles = env.fs.list(dir) 175 | for n=1,#tFiles do 176 | local sFile = tFiles[n] 177 | if #sFile >= #name and string.sub(sFile, 1, #name) == name then 178 | local bIdir = env.fs.isDir(env.fs.combine(dir, sFile)) 179 | local result = string.sub(sFile, #name + 1) 180 | if bIdir then 181 | table.insert(results, result .. "/") 182 | if includeDirs and #result > 0 then 183 | table.insert(results, result) 184 | end 185 | else 186 | if includeFiles and #result > 0 then 187 | table.insert(results, result) 188 | end 189 | end 190 | end 191 | end 192 | return results 193 | end 194 | return empty 195 | end 196 | -------------------------------------------------------------------------------- /chip8.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This requires OCLights2. 3 | Sadly, I don't have compiled builds of this, so go here and build this: 4 | https://github.com/gamax92/CCLights2/tree/opencomputers 5 | --]] 6 | local component = require("component") 7 | local computer = require("computer") 8 | local fs = require("filesystem") 9 | local keyboard = require("keyboard") 10 | local event = require("event") 11 | local shell = require("shell") 12 | 13 | -- You can setup the keylayout here: 14 | local keylayout = { 15 | {"1","2","3","4"}, -- 1,2,3,C 16 | {"Q","W","E","R"}, -- 4,5,6,D 17 | {"A","S","D","F"}, -- 7,8,9,E 18 | {"Z","X","C","V"}, -- A,0,B,F 19 | } 20 | 21 | local function errprint(...) 22 | local args = {...} 23 | for i = 1,#args do 24 | io.stderr:write(tostring(args[i])) 25 | if i < #args then 26 | io.stderr:write("\t") 27 | end 28 | end 29 | io.stderr:write("\n") 30 | end 31 | 32 | local args,opt = shell.parse(...) 33 | 34 | if #args ~= 1 then 35 | print("Usage: chip8 [OPTIONS] rom") 36 | print(" -ipf=%d Instructions per frame") 37 | return 38 | end 39 | 40 | local romfile = shell.resolve(args[1]) 41 | if not fs.exists(romfile) then 42 | errprint("No such file") 43 | return 44 | end 45 | 46 | if component.isAvailable("ocl_gpu") == nil then 47 | errprint("Could not find a OCLights2 GPU") 48 | return 49 | end 50 | 51 | local gpu = component.ocl_gpu 52 | 53 | local playSound 54 | if component.isAvailable("masssound") then 55 | print("Detected a MassSound card") 56 | playSound = function() component.masssound.playSound("note.pling") end 57 | else 58 | print("Falling back to computer.beep") 59 | playSound = function() computer.beep(300,0) end 60 | end 61 | 62 | -- Initialize the GPU 63 | print("Initializing GPU ...") 64 | gpu.bindTexture(0) -- Bind screen 65 | local gW,gH = gpu.getSize() 66 | if gW < 64 or gH < 32 then 67 | errprint("Your Monitor attached to the GPU is too small (" .. gW .. "," .. gH .. ")") 68 | return 69 | end 70 | local gpu_scale = math.floor(math.min(gW/64,gH/32)) 71 | gpu.setColor(64,64,64,255) 72 | gpu.fill() -- Fill in blank areas 73 | gpu.setColor(0,0,0,255) 74 | gpu.filledRectangle(0, 0, 64 * gpu_scale, 32 * gpu_scale) -- Black out Actual Display 75 | 76 | -- Initialize Emulator 77 | print("Initializing Emulator ...") 78 | local chip8 = { 79 | mem = {}, 80 | stack = {}, 81 | display = {}, 82 | ghost = {}, 83 | keycode = {}, 84 | keyflip = {}, 85 | keystate = {}, 86 | PC = 512, 87 | I = 512, 88 | REG = {}, 89 | delay = 0, 90 | sound = 0, 91 | IPF = 10, -- Instructions Per Frame 92 | PDS = 50, -- Pixel Decrement Speed, set to 255 to disable. 93 | running = true, 94 | } 95 | for i = 0,4095 do 96 | chip8.mem[i] = 0 97 | end 98 | local fontset = { 99 | 0xF0, 0x90, 0x90, 0x90, 0xF0, 100 | 0x20, 0x60, 0x20, 0x20, 0x70, 101 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, 102 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, 103 | 0x90, 0x90, 0xF0, 0x10, 0x10, 104 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, 105 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, 106 | 0xF0, 0x10, 0x20, 0x40, 0x40, 107 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, 108 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, 109 | 0xF0, 0x90, 0xF0, 0x90, 0x90, 110 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, 111 | 0xF0, 0x80, 0x80, 0x80, 0xF0, 112 | 0xE0, 0x90, 0x90, 0x90, 0xE0, 113 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, 114 | 0xF0, 0x80, 0xF0, 0x80, 0x80} 115 | print("Loading fontset ...") 116 | for i = 1,80 do 117 | chip8.mem[i-1] = fontset[i] 118 | end 119 | for y = 0,31 do 120 | chip8.display[y] = {} 121 | for x = 0,63 do 122 | chip8.display[y][x] = false 123 | end 124 | end 125 | for i = 0,15 do 126 | chip8.REG[i] = 0 127 | end 128 | chip8.keycode[00] = keyboard.keys[keylayout[4][2]:lower()] 129 | chip8.keycode[01] = keyboard.keys[keylayout[1][1]:lower()] 130 | chip8.keycode[02] = keyboard.keys[keylayout[1][2]:lower()] 131 | chip8.keycode[03] = keyboard.keys[keylayout[1][3]:lower()] 132 | chip8.keycode[04] = keyboard.keys[keylayout[2][1]:lower()] 133 | chip8.keycode[05] = keyboard.keys[keylayout[2][2]:lower()] 134 | chip8.keycode[06] = keyboard.keys[keylayout[2][3]:lower()] 135 | chip8.keycode[07] = keyboard.keys[keylayout[3][1]:lower()] 136 | chip8.keycode[08] = keyboard.keys[keylayout[3][2]:lower()] 137 | chip8.keycode[09] = keyboard.keys[keylayout[3][3]:lower()] 138 | chip8.keycode[10] = keyboard.keys[keylayout[4][1]:lower()] 139 | chip8.keycode[11] = keyboard.keys[keylayout[4][3]:lower()] 140 | chip8.keycode[12] = keyboard.keys[keylayout[1][4]:lower()] 141 | chip8.keycode[13] = keyboard.keys[keylayout[2][4]:lower()] 142 | chip8.keycode[14] = keyboard.keys[keylayout[3][4]:lower()] 143 | chip8.keycode[15] = keyboard.keys[keylayout[4][4]:lower()] 144 | for i = 0,15 do 145 | chip8.keyflip[chip8.keycode[i]] = i 146 | chip8.keystate[i] = false 147 | end 148 | 149 | os.sleep(0.05) -- Above may be LVM intensive. 150 | 151 | -- Load ROM 152 | print("Loading ROM ...") 153 | local file = io.open(romfile,"rb") 154 | if file == nil then 155 | errprint("Could not open file") 156 | end 157 | local index = 512 158 | while true do 159 | local data = file:read(1) 160 | if data == nil then break end 161 | chip8.mem[index] = data:byte() 162 | index = index + 1 163 | end 164 | file:close() 165 | 166 | os.sleep(0.05) -- Above may be LVM intensive. 167 | 168 | local keywait = { false, -1 } 169 | 170 | event.listen("key_down",function(name,uuid,char,key,who) 171 | if key == keyboard.keys["f1"] then chip8.running = false end 172 | if chip8.keyflip[key] ~= nil then 173 | if keywait[1] == true and chip8.keystate[chip8.keyflip[key]] == false then 174 | keywait[1] = false 175 | chip8.REG[keywait[2]] = chip8.keyflip[key] 176 | end 177 | chip8.keystate[chip8.keyflip[key]] = true 178 | end 179 | end) 180 | event.listen("key_up",function(name,uuid,char,key,who) 181 | if chip8.keyflip[key] ~= nil then 182 | chip8.keystate[chip8.keyflip[key]] = false 183 | end 184 | end) 185 | 186 | --Start emulating 187 | print("Beginning Emulation ...") 188 | while chip8.running do 189 | if keywait[1] ~= true then 190 | for i = 1,chip8.IPF do 191 | local opcode = chip8.mem[chip8.PC] * 256 + chip8.mem[chip8.PC + 1] -- Opcodes are two bytes 192 | chip8.PC = (chip8.PC + 2) % 4096 -- Advance PC 193 | -- Pull appart the opcode for easier processing. 194 | local base = math.floor(opcode/4096) 195 | local address = opcode % 4096 196 | local pX = math.floor(opcode/256)%16 197 | local pY = math.floor(opcode/16)%16 198 | local subbase = opcode%16 199 | local value = opcode%256 200 | if base == 0 then -- Call RCA 1802 program 201 | if address == 0x0E0 then 202 | for y = 0,31 do 203 | for x = 0,63 do 204 | chip8.display[y][x] = false 205 | end 206 | end 207 | gpu.setColor(0,0,0,255) 208 | gpu.filledRectangle(0, 0, 64 * gpu_scale, 32 * gpu_scale) 209 | elseif address == 0x0EE then 210 | -- Pop stack and jump 211 | chip8.PC = chip8.stack[#chip8.stack] 212 | chip8.stack[#chip8.stack] = nil 213 | else 214 | print("Attempted to run RCA program: " .. string.format("%03X", address)) 215 | end 216 | elseif base == 1 then -- Jumps to address 217 | chip8.PC = address 218 | elseif base == 2 then -- Push current address to stack and jump 219 | chip8.stack[#chip8.stack + 1] = chip8.PC 220 | chip8.PC = address 221 | elseif base == 3 then -- Skips the next instruction if REG[pX] equals value 222 | if chip8.REG[pX] == value then 223 | chip8.PC = chip8.PC + 2 224 | end 225 | elseif base == 4 then -- Skips the next instruction if REG[pX] doesn't equal value 226 | if chip8.REG[pX] ~= value then 227 | chip8.PC = chip8.PC + 2 228 | end 229 | elseif base == 5 and subbase == 0 then -- Skips the next instruction if REG[pX] equals REG[pY] 230 | if chip8.REG[pX] == chip8.REG[pY] then 231 | chip8.PC = chip8.PC + 2 232 | end 233 | elseif base == 6 then -- Sets REG[pX] to value 234 | chip8.REG[pX] = value 235 | elseif base == 7 then -- Adds value to REG[pX] 236 | chip8.REG[pX] = (chip8.REG[pX] + value) % 256 237 | elseif base == 8 and (subbase == 14 or (subbase >= 0 and subbase <= 7)) then -- Bit Operations 238 | if subbase == 0 then -- Sets REG[pX] to the value of REG[pY] 239 | chip8.REG[pX] = chip8.REG[pY] 240 | elseif subbase == 1 then -- Sets REG[pX] to REG[pX] BitWise OR REG[pY] 241 | chip8.REG[pX] = bit32.bor(chip8.REG[pX],chip8.REG[pY]) 242 | elseif subbase == 2 then -- Sets REG[pX] to REG[pX] BitWise AND REG[pY] 243 | chip8.REG[pX] = bit32.band(chip8.REG[pX],chip8.REG[pY]) 244 | elseif subbase == 3 then -- Sets REG[pX] to REG[pX] BitWise XOR REG[pY] 245 | chip8.REG[pX] = bit32.bxor(chip8.REG[pX],chip8.REG[pY]) 246 | elseif subbase == 4 then -- Adds REG[pY] to REG[pX]. REG[F] is set to 1 when there's a carry, and to 0 when there isn't 247 | local tmp = chip8.REG[pX] + chip8.REG[pY] 248 | chip8.REG[15] = tmp > 255 and 1 or 0 249 | chip8.REG[pX] = tmp % 256 250 | elseif subbase == 5 then -- REG[pY] is subtracted from REG[pX]. REG[F] is set to 0 when there's a borrow, and 1 when there isn't 251 | local tmp = chip8.REG[pX] - chip8.REG[pY] 252 | chip8.REG[15] = tmp >= 0 and 1 or 0 253 | chip8.REG[pX] = (tmp + 256) % 256 254 | elseif subbase == 6 then -- Shifts REG[pY] right by one and stored in REG[pX]. REG[F] is set to the value of the least significant bit of REG[pY] before the shift 255 | chip8.REG[15] = chip8.REG[pY] % 2 256 | chip8.REG[pX] = math.floor(chip8.REG[pY] / 2) 257 | elseif subbase == 7 then -- Sets REG[pX] to REG[pY] minus REG[pX]. REG[F] is set to 0 when there's a borrow, and 1 when there isn't 258 | local tmp = chip8.REG[pY] - chip8.REG[pX] 259 | chip8.REG[15] = tmp >= 0 and 1 or 0 260 | chip8.REG[pX] = (tmp + 256) % 256 261 | elseif subbase == 14 then -- Shifts REG[pY] left by one and stored in REG[pX]. REG[F] is set to the value of the most significant bit of REG[pY] before the shift 262 | chip8.REG[15] = math.floor(chip8.REG[pY] / 128) % 2 263 | chip8.REG[pX] = (chip8.REG[pY] * 2) % 256 264 | end 265 | elseif base == 9 and subbase == 0 then -- Skips the next instruction if REG[pX] doesn't equal REG[pY] 266 | if chip8.REG[pX] ~= chip8.REG[pY] then 267 | chip8.PC = chip8.PC + 2 268 | end 269 | elseif base == 10 then -- Sets I to address 270 | chip8.I = address 271 | elseif base == 11 then -- Jumps to address plus REG[0] 272 | chip8.PC = (address + chip8.REG[0]) % 4096 273 | elseif base == 12 then -- Sets REG[pX] to a random number BitWise AND value 274 | chip8.REG[pX] = bit32.band(math.random(0,255),value) 275 | elseif base == 13 then -- Draw Sprite 276 | chip8.REG[15] = 0 277 | local x = chip8.REG[pX] 278 | local y = chip8.REG[pY] 279 | for i = 0,subbase - 1 do 280 | local data = chip8.mem[chip8.I + i] 281 | for j = 0,7 do 282 | local bit = (math.floor(data/(2^j))%2) == 1 283 | local lx = (7 - j + x) % 64 284 | local ly = (y + i) % 32 285 | if bit then 286 | if chip8.display[ly][lx] == true then 287 | chip8.REG[15] = 1 288 | end 289 | local CC = not chip8.display[ly][lx] 290 | chip8.display[ly][lx] = not chip8.display[ly][lx] 291 | if CC then 292 | chip8.ghost[ly*64 + lx] = nil 293 | gpu.setColor(0,255,0,255) 294 | gpu.filledRectangle(lx * gpu_scale, ly * gpu_scale, gpu_scale, gpu_scale) 295 | else 296 | chip8.ghost[ly*64 + lx] = 255 297 | end 298 | end 299 | end 300 | end 301 | elseif base == 14 and value == 0x9E then -- Skips the next instruction if the key stored in REG[pX] is pressed 302 | if chip8.keystate[chip8.REG[pX]] then 303 | chip8.PC = chip8.PC + 2 304 | end 305 | elseif base == 14 and value == 0xA1 then -- Skips the next instruction if the key stored in REG[pX] isn't pressed 306 | if not chip8.keystate[chip8.REG[pX]] then 307 | chip8.PC = chip8.PC + 2 308 | end 309 | elseif base == 15 and value == 0x07 then -- Sets REG[pX] to the value of the delay timer 310 | chip8.REG[pX] = chip8.delay 311 | elseif base == 15 and value == 0x0A then -- A key press is awaited, and then stored in REG[pX] 312 | keywait[1],keywait[2] = true, pX 313 | break 314 | elseif base == 15 and value == 0x15 then -- Sets the delay timer to REG[pX] 315 | chip8.delay = chip8.REG[pX] 316 | elseif base == 15 and value == 0x18 then -- Sets the sound timer to REG[pX] 317 | chip8.sound = chip8.REG[pX] 318 | elseif base == 15 and value == 0x1E then -- Adds REG[pX] to I 319 | local tmp = chip8.I + chip8.REG[pX] 320 | chip8.REG[15] = tmp >= 4096 and 1 or 0 321 | chip8.I = tmp % 4096 322 | elseif base == 15 and value == 0x29 then -- Sets I to the location of the sprite for the character in REG[pX] 323 | chip8.I = chip8.REG[pX] * 5 324 | elseif base == 15 and value == 0x33 then -- Stores the Binary-coded decimal representation of REG[pX] at the address in I 325 | local BCD = string.format("%03d",chip8.REG[pX]) 326 | chip8.mem[chip8.I ] = tonumber(BCD:sub(1,1)) 327 | chip8.mem[chip8.I+1] = tonumber(BCD:sub(2,2)) 328 | chip8.mem[chip8.I+2] = tonumber(BCD:sub(3,3)) 329 | elseif base == 15 and value == 0x55 then -- Stores REG[0] to REG[pX] in memory starting at address I 330 | for i = 0,pX do 331 | chip8.mem[chip8.I + i] = chip8.REG[i] 332 | end 333 | chip8.I = chip8.I + pX + 1 334 | elseif base == 15 and value == 0x65 then -- Fills REG[0] to REG[pX] with values from memory starting at address I 335 | for i = 0,pX do 336 | chip8.REG[i] = chip8.mem[chip8.I + i] 337 | end 338 | chip8.I = chip8.I + pX + 1 339 | else 340 | print("Unknown opcode: " .. string.format("%04X",opcode)) 341 | end 342 | end 343 | end 344 | -- Decrement ghost pixels 345 | for k,v in pairs(chip8.ghost) do 346 | local x = k%64 347 | local y = math.floor(k/64) 348 | chip8.ghost[k] = chip8.ghost[k] - chip8.PDS 349 | gpu.setColor(0,math.max(chip8.ghost[k],0),0) 350 | gpu.filledRectangle(x * gpu_scale, y * gpu_scale, gpu_scale, gpu_scale) 351 | if chip8.ghost[k] <= 0 then chip8.ghost[k] = nil end 352 | end 353 | -- Decrement timers 354 | local oldsound = chip8.sound 355 | if chip8.delay >= 0 then chip8.delay = chip8.delay - 3 end 356 | if chip8.sound >= 0 then chip8.sound = chip8.sound - 3 end 357 | -- Play sound if zero 358 | if oldsound > 0 and chip8.sound <= 0 then 359 | playSound() 360 | end 361 | os.sleep(0.05) 362 | end 363 | gpu.setColor(0,0,0,50) 364 | for i = 1,20 do 365 | gpu.filledRectangle(0,0,gW,gH) 366 | os.sleep(0.05) 367 | end 368 | -------------------------------------------------------------------------------- /debug.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local fs = require("filesystem") 3 | local args = {...} 4 | if #args < 1 then 5 | print("Usage: debug file (arguments)") 6 | return 7 | end 8 | args[1] = shell.resolve(args[1]) 9 | if not fs.exists(args[1]) then 10 | error("No such file",0) 11 | end 12 | print(xpcall(function() return loadfile(args[1])(table.unpack(args,2)) end,debug.traceback)) 13 | -------------------------------------------------------------------------------- /dfpwm/dfpwm.lua: -------------------------------------------------------------------------------- 1 | local shell=require("shell") 2 | 3 | local args, options=shell.parse(...) 4 | if options.h or options.help then 5 | print([=[Usage: dfpwm [infile [outfile]] 6 | 7 | Options: 8 | -h --help This message 9 | -d --decode Decode from dfpwm 10 | -e --encode Encode to dfpwm (default) 11 | -o --old Use old DFPWM codec 12 | -n --new Use new DFPWM1a codec (default) 13 | --bsize=n Buffer size 14 | ]=]) 15 | return 16 | end 17 | 18 | local function errprint(msg) 19 | io.stderr:write(msg.."\n") 20 | io.stderr:flush() 21 | end 22 | 23 | local encode=true 24 | local new=true 25 | local bsize=8192 26 | 27 | if options.d or options.decode then 28 | encode=false 29 | end 30 | if options.o or options.old then 31 | new=false 32 | end 33 | if options.bsize then 34 | bsize=tonumber(options.bsize, 10) 35 | if not bsize then 36 | errprint("Error: '"..tostring(options.bsize).."' is not a valid number") 37 | return 38 | end 39 | end 40 | 41 | local dfpwm=require("dfpwm") 42 | local codec=dfpwm.new(new) 43 | 44 | local file, err 45 | if #args >= 1 then 46 | file, err=io.open(args[1], "rb") 47 | if not file then 48 | errprint(err) 49 | return 50 | end 51 | else 52 | file=io.stdin 53 | end 54 | 55 | local outfile 56 | if #args >= 2 then 57 | outfile, err=io.open(args[2], "wb") 58 | if not outfile then 59 | file:close() 60 | errprint(err) 61 | return 62 | end 63 | else 64 | outfile=io.stdout 65 | end 66 | 67 | while true do 68 | local data=file:read(bsize) 69 | if not data then break end 70 | data=table.pack(data:byte(1,-1)) 71 | 72 | local odata 73 | if encode then 74 | for i=1, #data do 75 | data[i]=bit32.bxor(data[i], 0x80) 76 | end 77 | odata=codec:compress(data) 78 | else 79 | odata=codec:decompress(data) 80 | for i=1, #odata do 81 | odata[i]=bit32.bxor(odata[i], 0x80) 82 | end 83 | end 84 | 85 | for i=1, #odata do 86 | odata[i]=string.char(odata[i]) 87 | end 88 | outfile:write(table.concat(odata)) 89 | outfile:flush() 90 | 91 | os.sleep(0) 92 | end 93 | 94 | if #args >= 1 then 95 | file:close() 96 | if #args >= 2 then 97 | outfile:close() 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /dfpwm/lib/dfpwm.lua: -------------------------------------------------------------------------------- 1 | local function reset(state) 2 | state.response=0 3 | state.level=0 4 | state.lastbit=false 5 | state.flastlevel=0 6 | state.lpflevel=0 7 | end 8 | 9 | local function ctx_update(state, curbit) 10 | local target=(curbit and 127 or -128) 11 | local nlevel=state.level+math.floor((state.response*(target-state.level)+state.RESP_PREC_HALF)/state.RESP_PREC_POWER) 12 | if nlevel == state.level and state.level ~= target then 13 | nlevel=nlevel+(curbit and 1 or -1) 14 | end 15 | 16 | local rtarget, rdelta 17 | if curbit == state.lastbit then 18 | rtarget=state.RESP_PREC_MAX 19 | rdelta=state.RESP_INC 20 | else 21 | rtarget=0 22 | rdelta=state.RESP_DEC 23 | end 24 | 25 | local nresponse=state.response+(state.new and 0 or math.floor((rdelta*(rtarget-state.response))/256)) 26 | if nresponse == state.response and state.response ~= rtarget then 27 | nresponse=nresponse+((curbit == state.lastbit) and 1 or -1) 28 | end 29 | 30 | if state.RESP_PREC > 8 then 31 | if nresponse < state.RESP_PREC_8 then 32 | nresponse=state.RESP_PREC_8 33 | end 34 | end 35 | 36 | state.response=nresponse 37 | state.lastbit=curbit 38 | state.level=nlevel 39 | end 40 | 41 | local function decompress(state, src) 42 | local dest={} 43 | for i=1, #src do 44 | local d=src[i] 45 | for j=0, 7 do 46 | -- apply context 47 | local curbit=(bit32.band(d, bit32.lshift(1, j)) ~= 0) 48 | local lastbit=state.lastbit 49 | ctx_update(state, curbit) 50 | 51 | -- apply noise shaping 52 | local blevel = bit32.band((curbit == lastbit) and state.level or math.floor((state.flastlevel+state.level+1)/2), 0xFF) 53 | if blevel >= 128 then 54 | blevel=blevel-256 55 | end 56 | state.flastlevel=state.level 57 | 58 | -- apply low-pass filter 59 | state.lpflevel=state.lpflevel+math.floor((state.LPF_STRENGTH*(blevel-state.lpflevel)+0x80)/256) 60 | dest[#dest+1]=bit32.band(state.lpflevel, 0xFF) 61 | end 62 | end 63 | return dest 64 | end 65 | 66 | local function compress(state, src) 67 | local dest={} 68 | for i=1, #src, 8 do 69 | local d=0 70 | for j=0, 7 do 71 | local inlevel=(src[i+j] or 0) 72 | if inlevel >= 128 then 73 | inlevel=inlevel-256 74 | end 75 | local curbit=(inlevel > state.level or (inlevel == state.level and state.level == 127)) 76 | d=bit32.bor(d/2, curbit and 128 or 0) 77 | ctx_update(state, curbit) 78 | end 79 | dest[#dest+1]=d 80 | end 81 | return dest 82 | end 83 | 84 | local dfpwm={} 85 | 86 | function dfpwm.new(newdfpwm) 87 | local state={ 88 | new=newdfpwm, 89 | reset=reset, 90 | decompress=decompress, 91 | compress=compress 92 | } 93 | if newdfpwm then 94 | state.RESP_INC=1 95 | state.RESP_DEC=1 96 | state.RESP_PREC=10 97 | state.LPF_STRENGTH=140 98 | else 99 | state.RESP_INC=7 100 | state.RESP_DEC=20 101 | state.RESP_PREC=8 102 | state.LPF_STRENGTH=100 103 | end 104 | state:reset() 105 | -- precompute and cache some stuff 106 | state.RESP_PREC_HALF=bit32.lshift(1, state.RESP_PREC-1) 107 | state.RESP_PREC_POWER=2^state.RESP_PREC 108 | state.RESP_PREC_MAX=bit32.lshift(1, state.RESP_PREC)-1 109 | state.RESP_PREC_8=bit32.lshift(2, state.RESP_PREC-8) 110 | return state 111 | end 112 | 113 | return dfpwm 114 | -------------------------------------------------------------------------------- /enhanced/components.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local shell = require("shell") 3 | local format = require("format") 4 | 5 | local args = shell.parse(...) 6 | local test = {} 7 | for _,arg in ipairs(args) do 8 | test[arg] = true 9 | end 10 | 11 | local result = {{"Type", "Address"}} 12 | for address, name in component.list() do 13 | if #args == 0 or test[name] then 14 | table.insert(result, {name, address}) 15 | end 16 | end 17 | 18 | format.tabulate(result) 19 | for _, entry in ipairs(result) do 20 | io.write(table.concat(entry, " "), "\n") 21 | end -------------------------------------------------------------------------------- /enhanced/df.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | local shell = require("shell") 3 | local format = require("format") 4 | 5 | local args, options = shell.parse(...) 6 | 7 | local mounts = {} 8 | if #args == 0 then 9 | for proxy, path in fs.mounts() do 10 | mounts[path] = proxy 11 | end 12 | else 13 | for i = 1, #args do 14 | local proxy, path = fs.get(args[i]) 15 | if not proxy then 16 | io.stderr:write(args[i], ": no such file or directory\n") 17 | else 18 | mounts[path] = proxy 19 | end 20 | end 21 | end 22 | 23 | local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}} 24 | for path, proxy in pairs(mounts) do 25 | local label = proxy.getLabel() or proxy.address 26 | local used, total = proxy.spaceUsed(), proxy.spaceTotal() 27 | local available, percent 28 | if total == "unlimited" then 29 | used = used or "N/A" 30 | available = "unlimited" 31 | percent = "0%" 32 | else 33 | available = total - used 34 | percent = used / total 35 | if percent ~= percent then -- NaN 36 | available = "N/A" 37 | percent = "N/A" 38 | else 39 | percent = math.ceil(percent * 100) .. "%" 40 | end 41 | end 42 | table.insert(result, {label, used, available, percent, path}) 43 | end 44 | 45 | format.tabulate(result, {0,1,1,1,0}) 46 | for _, entry in ipairs(result) do 47 | io.write(table.concat(entry, " "), "\n") 48 | end 49 | -------------------------------------------------------------------------------- /enhanced/format.lua: -------------------------------------------------------------------------------- 1 | local format = {} 2 | local component = require("component") 3 | local term = require("term") 4 | 5 | function format.padStringLeft(str, size) 6 | return str .. string.rep(" ", size - #str) 7 | end 8 | 9 | function format.padStringRight(str, size) 10 | return string.rep(" ", size - #str) .. str 11 | end 12 | 13 | function format.tabulate(input, justify) 14 | if #input == 0 then 15 | return 16 | end 17 | justify = justify or {} 18 | local tabsize = {} 19 | for i = 1, #input[1] do 20 | tabsize[i] = 0 21 | end 22 | for _, entry in ipairs(input) do 23 | for i, value in ipairs(entry) do 24 | if tostring(value):len() > tabsize[i] then 25 | tabsize[i] = tostring(value):len() 26 | end 27 | end 28 | end 29 | for _, entry in ipairs(input) do 30 | for i, value in ipairs(entry) do 31 | if (justify[i] or 0) == 0 then 32 | entry[i] = format.padStringLeft(tostring(entry[i]), tabsize[i]) 33 | elseif justify[i] == 1 then 34 | entry[i] = format.padStringRight(tostring(entry[i]), tabsize[i]) 35 | end 36 | end 37 | end 38 | end 39 | 40 | function format.tabulateList(input, justify) 41 | justify = justify or 0 42 | local tabsize = 0 43 | for i, value in ipairs(input) do 44 | if tostring(value):len() > tabsize then 45 | tabsize = tostring(value):len() 46 | end 47 | end 48 | for i, value in ipairs(input) do 49 | if justify == 0 then 50 | input[i] = format.padStringLeft(tostring(input[i]), tabsize) 51 | else 52 | input[i] = format.padStringRight(tostring(input[i]), tabsize) 53 | end 54 | end 55 | end 56 | 57 | function format.tabulateWidth(tAll, seperator) 58 | local w, h = component.gpu.getResolution() 59 | local nMaxLen = seperator 60 | for n, sItem in pairs(tAll) do 61 | if type(sItem) ~= "number" then 62 | nMaxLen = math.max(string.len(sItem) + seperator, nMaxLen) 63 | end 64 | end 65 | local nCols = math.floor(w/nMaxLen) 66 | local nCol = 1 67 | for n, s in ipairs(tAll) do 68 | if nCol > nCols then 69 | nCol = 1 70 | io.write("\n") 71 | end 72 | if type(s) == "number" then 73 | component.gpu.setForeground(s) 74 | else 75 | local cx, cy = term.getCursor() 76 | cx = 1 + ((nCol - 1) * nMaxLen) 77 | term.setCursor(cx, cy) 78 | term.write(s) 79 | nCol = nCol + 1 80 | end 81 | end 82 | end 83 | 84 | ------------------------------------------------------------------------------- 85 | 86 | return format 87 | -------------------------------------------------------------------------------- /enhanced/index.txt: -------------------------------------------------------------------------------- 1 | Modified versions of standard programs for improved or enhanced features. 2 | 3 | components.lua 4 | Version of components.lua that formats its output. 5 | Supports filtering output with multiple arguments 6 | Requires format.lua 7 | 8 | df.lua 9 | Version of df.lua that formats its output. 10 | Requires format.lua 11 | 12 | format.lua 13 | Format tables to consistent width. 14 | Install in /lib 15 | 16 | less.lua 17 | Version of more.lua that supports arrow keys and enter key. 18 | 19 | ls.lua 20 | Version of ls.lua that formats its output. 21 | option -l enhanced to show date and flags as well as print a total. 22 | Requires format.lua 23 | 24 | more.lua 25 | Version of more.lua that supports multiple files and enter key. 26 | Status line now says --MORE-- instead of : 27 | Screen is also not cleared 28 | 29 | resolution.lua 30 | Version of resolution.lua that separates width and height via an "x" instead 31 | of a space. 32 | -------------------------------------------------------------------------------- /enhanced/less.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local keyboard = require("keyboard") 3 | local shell = require("shell") 4 | local term = require("term") 5 | local text = require("text") 6 | local unicode = require("unicode") 7 | 8 | local args = shell.parse(...) 9 | if #args == 0 then 10 | io.write("Usage: less ") 11 | return 12 | end 13 | 14 | local file, reason = io.open(shell.resolve(args[1])) 15 | if not file then 16 | io.stderr:write(reason) 17 | return 18 | end 19 | 20 | local lines = {} 21 | local line = nil 22 | local w, h = component.gpu.getResolution() 23 | term.clear() 24 | term.setCursorBlink(false) 25 | while true do 26 | if not line then 27 | line = file:read("*l") 28 | if not line then 29 | break 30 | end 31 | end 32 | local wrapped 33 | wrapped, line = text.wrap(text.detab(line), w, w) 34 | table.insert(lines, wrapped) 35 | end 36 | 37 | local i = 1 38 | local operation = "none" 39 | local function drawPage() 40 | term.setCursor(1, 1) 41 | local buffer = "" 42 | for j = i, i + h - 2 do 43 | buffer = buffer .. (lines[j] or "~") .. "\n" 44 | end 45 | term.clear() 46 | io.write(buffer) 47 | end 48 | while true do 49 | w, h = component.gpu.getResolution() 50 | if operation == "none" then 51 | drawPage() 52 | elseif operation == "page" then 53 | local old = i 54 | i = math.min(i + h - 1, #lines - h + 2) 55 | if i ~= old then 56 | drawPage() 57 | end 58 | elseif operation == "down" then 59 | if i < #lines - h + 2 then 60 | i = i + 1 61 | component.gpu.copy(0, 1, w, h - 1, 0, -1) 62 | term.setCursor(1, h - 1) 63 | term.clearLine() 64 | io.write((lines[i + h - 2] or "~") .. "\n") 65 | end 66 | elseif operation == "up" then 67 | if i > 1 then 68 | i = i - 1 69 | component.gpu.copy(0, 0, w, h - 1, 0, 1) 70 | term.setCursor(1, 1) 71 | term.clearLine() 72 | io.write((lines[i] or "~") .. "\n") 73 | end 74 | end 75 | term.setCursor(1, h) 76 | term.write(":") 77 | term.setCursorBlink(true) 78 | while true do 79 | local event, address, char, code = coroutine.yield("key_down") 80 | if component.isPrimary(address) then 81 | if code == keyboard.keys.q then 82 | term.setCursorBlink(false) 83 | term.clearLine() 84 | return 85 | elseif code == keyboard.keys.space then 86 | operation = "page" 87 | break 88 | elseif code == keyboard.keys.up then 89 | operation = "up" 90 | break 91 | elseif code == keyboard.keys.down or code == keyboard.keys.enter then 92 | operation = "down" 93 | break 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /enhanced/ls.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local fs = require("filesystem") 3 | local shell = require("shell") 4 | local format = require("format") 5 | 6 | local dirs, options = shell.parse(...) 7 | if #dirs == 0 then 8 | table.insert(dirs, ".") 9 | end 10 | 11 | io.output():setvbuf("line") 12 | for i = 1, #dirs do 13 | local path = shell.resolve(dirs[i]) 14 | if #dirs > 1 then 15 | if i > 1 then 16 | io.write("\n") 17 | end 18 | io.write(path, ":\n") 19 | end 20 | local fslist, reason = fs.list(path) 21 | if not fslist then 22 | io.write(reason .. "\n") 23 | else 24 | local function setColor(c) 25 | if component.gpu.getForeground() ~= c then 26 | io.stdout:flush() 27 | component.gpu.setForeground(c) 28 | end 29 | end 30 | local list = {} 31 | for f in fslist do 32 | if options.a or f:sub(1, 1) ~= "." then 33 | table.insert(list, f) 34 | end 35 | end 36 | table.sort(list) 37 | local toOutput = {} 38 | local function getFileColor(file) 39 | if fs.isLink(fs.concat(path, file)) then 40 | return 0xFFAA00 41 | elseif fs.isDirectory(fs.concat(path, file)) then 42 | return 0x66CCFF 43 | elseif file:sub(-4) == ".lua" then 44 | return 0x00FF00 45 | else 46 | return 0xFFFFFF 47 | end 48 | end 49 | if options.l then 50 | local objColor = {} 51 | local totalSize = 0 52 | for _, f in ipairs(list) do 53 | local type 54 | if fs.isLink(fs.concat(path, f)) then 55 | type = "l" 56 | elseif fs.isDirectory(fs.concat(path, f)) then 57 | type = "d" 58 | else 59 | type = "-" 60 | end 61 | local flags = "rw" .. (f:sub(-4) == ".lua" and "x" or "-") 62 | local modificationTime = fs.lastModified(fs.concat(path, f)) / 1000 63 | local testdate = os.date("*t", modificationTime) 64 | local now = os.date("*t") 65 | local date 66 | if testdate.year ~= now.year then 67 | date = os.date("%b %d %Y", modificationTime) 68 | else 69 | date = os.date("%b %d %H:%M", modificationTime) 70 | end 71 | local filesize = fs.size(fs.concat(path, f)) 72 | totalSize = totalSize + filesize 73 | table.insert(toOutput, {type .. flags .. flags .. flags, filesize, date, f}) 74 | table.insert(objColor, getFileColor(f)) 75 | end 76 | format.tabulate(toOutput, {0,1,0,0}) 77 | io.write("total " .. math.ceil(totalSize / 1024) .. "\n") 78 | for j, entry in ipairs(toOutput) do 79 | for i = 1, 4 do 80 | if i == 4 then 81 | setColor(objColor[j]) 82 | end 83 | io.write(entry[i]) 84 | if i == 4 then 85 | setColor(0xFFFFFF) 86 | else 87 | io.write(" ") 88 | end 89 | end 90 | io.write("\n") 91 | end 92 | else 93 | for _, f in ipairs(list) do 94 | table.insert(toOutput, getFileColor(f)) 95 | table.insert(toOutput, f) 96 | end 97 | format.tabulateWidth(toOutput, 2) 98 | end 99 | setColor(0xFFFFFF) 100 | if not options.l then 101 | io.write("\n") 102 | end 103 | end 104 | end 105 | io.output():setvbuf("no") 106 | -------------------------------------------------------------------------------- /enhanced/more.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local keyboard = require("keyboard") 3 | local shell = require("shell") 4 | local term = require("term") 5 | local text = require("text") 6 | local unicode = require("unicode") 7 | 8 | local args = shell.parse(...) 9 | if #args == 0 then 10 | io.write("Usage: more ") 11 | return 12 | end 13 | 14 | local file, reason = io.open(shell.resolve(args[1])) 15 | if not file then 16 | io.stderr:write(reason) 17 | return 18 | end 19 | 20 | local line = nil 21 | local fullPage = true 22 | while true do 23 | local w, h = component.gpu.getResolution() 24 | term.setCursorBlink(false) 25 | local function getLine() 26 | if not line then 27 | line = file:read("*l") 28 | if not line then -- eof 29 | return 30 | end 31 | end 32 | local wrapped 33 | wrapped, line = text.wrap(text.detab(line), w, w) 34 | io.write(wrapped .. "\n") 35 | end 36 | if fullPage == true then 37 | for i = 1, h - 1 do 38 | getLine() 39 | end 40 | else 41 | getLine() 42 | end 43 | term.setCursor(1, h) 44 | component.gpu.setBackground(0xFFFFFF) 45 | component.gpu.setForeground(0x000000) 46 | term.write("--More--") 47 | component.gpu.setBackground(0x000000) 48 | component.gpu.setForeground(0xFFFFFF) 49 | term.setCursorBlink(true) 50 | while true do 51 | local event, address, char, code = coroutine.yield("key_down") 52 | if component.isPrimary(address) then 53 | if code == keyboard.keys.q then 54 | term.setCursorBlink(false) 55 | term.clearLine() 56 | return 57 | elseif code == keyboard.keys.space then 58 | term.clearLine() 59 | fullPage = true 60 | break 61 | elseif code == keyboard.keys.enter then 62 | term.clearLine() 63 | fullPage = false 64 | break 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /enhanced/resolution.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local shell = require("shell") 3 | local term = require("term") 4 | 5 | local args = shell.parse(...) 6 | if #args == 0 then 7 | local w, h = component.gpu.getResolution() 8 | io.write(w .. "x" .. h) 9 | return 10 | end 11 | 12 | if #args < 2 then 13 | io.write("Usage: resolution [ ]") 14 | return 15 | end 16 | 17 | local w = tonumber(args[1]) 18 | local h = tonumber(args[2]) 19 | if not w or not h then 20 | io.stderr:write("invalid width or height") 21 | return 22 | end 23 | 24 | local result, reason = component.gpu.setResolution(w, h) 25 | if not result then 26 | if reason then -- otherwise we didn't change anything 27 | io.stderr:write(reason) 28 | end 29 | return 30 | end 31 | term.clear() 32 | -------------------------------------------------------------------------------- /filesystems/fslib.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | 3 | local fslib = {} 4 | 5 | function fslib.readRawString(file, size) 6 | local str = "" 7 | while #str < size do 8 | str = str .. file:read(size - #str) 9 | end 10 | return str 11 | end 12 | 13 | if string.pack then 14 | function fslib.str2num(data) 15 | return string.unpack(" 255 or math.floor(opts.blocklog) ~= opts.blocklog then 54 | errprint("mksocfs: Invalid blocksize") 55 | elseif opts.blocklog == 1 and opts.reserved < 2 then 56 | errprint("mksocfs: Invalid reserved count") 57 | elseif opts.indexsize%64 ~= 0 then 58 | errprint("mksocfs: Invalid index area size") 59 | end 60 | 61 | if not fs.exists(args[1]) then 62 | errprint("mksocfs: " .. args[1] .. ": No such file") 63 | elseif fs.isDirectory(args[1]) then 64 | errprint("mksocfs: " .. args[1] .. ": Cannot format a directory") 65 | end 66 | 67 | local size = fs.size(args[1]) 68 | if size == 0 then 69 | local file, err = io.open(args[1], "rb") 70 | if not file then 71 | errprint("mksocfs: " .. args[1] .. ": " .. err:sub(1, 1):upper() .. err:sub(2)) 72 | end 73 | size, err = file:seek("end") 74 | file:close() 75 | if not size then 76 | errprint("mksocfs: " .. args[1] .. ": " .. err:sub(1, 1):upper() .. err:sub(2)) 77 | end 78 | end 79 | if size == 0 then 80 | error("mksocfs: " .. args[1] .. ": Cannot get size of file") 81 | end 82 | 83 | local file, err = io.open(args[1], "wb") 84 | if not file then 85 | errprint("mksocfs: " .. args[1] .. ": " .. err:sub(1, 1):upper() .. err:sub(2)) 86 | end 87 | local seek, err = file:seek("set", 0x0194) 88 | if not seek then 89 | file:close() 90 | errprint("mksocfs: " .. args[1] .. ": " .. err:sub(1, 1):upper() .. err:sub(2)) 91 | end 92 | 93 | print("Writing SOCFS Super Block ...") 94 | 95 | local creation = os.time() 96 | file:write(table.concat({ 97 | fslib.num2str(creation, 8), 98 | fslib.num2str(opts.datasize, 8), 99 | fslib.num2str(opts.indexsize, 8), 100 | "SOC", 101 | "\x10", 102 | fslib.num2str(math.floor(size/opts.blocksize), 8), 103 | fslib.num2str(opts.reserved, 4), 104 | fslib.num2str(opts.blocklog, 1) 105 | })) 106 | file:seek("set", math.floor(size/opts.blocksize)*opts.blocksize-opts.indexsize) 107 | local indexsize = opts.indexsize/64 108 | local empty = string.rep("\0", 64) 109 | 110 | local r = function(a, b) return string.char(math.random(a or 0, b or 255)) end 111 | local uuid = table.concat({r(), r(), r(), r(), r(), r(), r(64, 79), r(), r(128, 191), r(), r(), r(), r(), r(), r(), r()}) 112 | 113 | print("Writing SOCFS Index Area ...") 114 | 115 | for i = 1, indexsize do 116 | if i == 1 then 117 | file:write("\2" .. string.rep("\0", 63)) 118 | elseif i == indexsize then 119 | local label = opts.label:sub(1, 39) 120 | label = label .. string.rep("\0", 39-#label) 121 | file:write(table.concat({ 122 | "\1", 123 | fslib.num2str(creation, 8), 124 | uuid, 125 | label, 126 | })) 127 | else 128 | file:write(empty) 129 | end 130 | end 131 | file:close() 132 | 133 | print("Wrote SOCFS filesystem structures") 134 | -------------------------------------------------------------------------------- /filesystems/mountfs.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | local shell = require("shell") 3 | 4 | local args, opts = shell.parse(...) 5 | args[1] = args[1] or "/mnt/tape/data.raw" 6 | opts.t = opts.t or "msdos" 7 | if not fs.exists(args[1]) then 8 | error("mountfs: '" .. args[1] .. ": No such file", 2) 9 | end 10 | local ok, fsdrive = pcall(require, opts.t) 11 | if not ok then 12 | error("mountfs: Filesystem driver '" .. opts.t .. "' is invalid", 2) 13 | end 14 | if fsdrive.proxy == nil then 15 | error("mountfs: Filesystem driver '" .. opts.t .. "' is incompatible", 2) 16 | end 17 | local z = fsdrive.proxy(args[1]) 18 | if opts.d then 19 | local special 20 | if opts.t == "msdos" then 21 | special = "fat" 22 | elseif opts.t == "socfs" then 23 | special = "soc" 24 | end 25 | if special == nil then 26 | io.stderr:write("mountfs: No debugging available for '" .. opts.t .. "'\n") 27 | else 28 | for k, v in pairs(z[special]) do 29 | print(k, v) 30 | end 31 | end 32 | end 33 | print("mountfs: Mounted at /mnt/" .. z.address:sub(1, 3)) 34 | -------------------------------------------------------------------------------- /filesystems/socfs.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Simple OpenComputers FileSystem. 3 | Refer to socfs.txt for details 4 | 5 | This is probably not the best implementation that there could be. But the style 6 | of the index area makes it so I have to cache and generate representions out of 7 | it so that I'm not looping through the entire index every time. 8 | --]] 9 | local vcomp = require("vcomponent") 10 | local fs = require("filesystem") 11 | local io = require("io") 12 | local fslib = require("fslib") 13 | 14 | local socfs = {} 15 | local _socfs = {} 16 | 17 | local function dprint(...) 18 | print("[socfs] " .. select(1, ...), select(2, ...)) 19 | end 20 | 21 | function _socfs.readStr(str) 22 | return (str .. "\0"):match("(.-)\0") 23 | end 24 | 25 | function _socfs.vfs2index(socdat, path) 26 | local parent, name = fslib.getParent(path), fs.name(path) 27 | local dir = socdat.vfs[parent] 28 | if dir == nil then return end 29 | for i = 1, #dir do 30 | if dir[i][2] == name then 31 | return socdat.index[dir[i][1]] 32 | end 33 | end 34 | end 35 | 36 | function _socfs.writeIndex(socdat, index) 37 | -- TODO: Implement 38 | -- Note: Cache/Queue? 39 | end 40 | 41 | function _socfs.findFreeIndex(socdat) 42 | local free 43 | for i = 1, socdat.indexcount do 44 | local type = socdat.index[i].type 45 | if type == 0x00 or type == 0x18 or type == 0x19 or type == 0x1A then 46 | free = i 47 | break 48 | end 49 | end 50 | if free ~= nil then 51 | return free 52 | end 53 | -- Try to expand index area 54 | if (socdat.reserve+socdat.datasize)*socdat.blocksize+socdat.indexsize+64 <= socdat.total*socdat.blocksize then 55 | socdat.indexcount = socdat.indexcount + 1 56 | socdat.indexsize = socdat.indexsize + 64 57 | socdat.index[socdat.indexcount] = socdat.index[socdat.indexcount-1] 58 | socdat.index[socdat.indexcount-1] = {type=0} 59 | _socfs.writeIndex(socdat, socdat.indexcount) 60 | _socfs.writeIndex(socdat, socdat.indexcount-1) 61 | return socdat.indexcount-1 62 | end 63 | end 64 | 65 | function socfs.proxy(socfile) 66 | checkArg(1, socfile, "string") 67 | 68 | if not fs.exists(socfile) then 69 | error("No such file", 2) 70 | end 71 | local file = io.open(socfile, "rb") 72 | if file == nil then 73 | error("Failed to open file") 74 | end 75 | local pos, err = file:seek("set", 0x36) 76 | if pos == nil then 77 | error("Seeking failed: " .. err) 78 | end 79 | 80 | local socdat = {} 81 | file:seek("set", 0x194) 82 | local superblock = fslib.readRawString(file, 41) 83 | socdat.modtime = fslib.str2num(superblock:sub(0x01, 0x08)) 84 | socdat.datasize = fslib.str2num(superblock:sub(0x09, 0x10)) 85 | socdat.indexsize = fslib.str2num(superblock:sub(0x11, 0x18)) 86 | socdat.magic = fslib.str2num(superblock:sub(0x19, 0x1B)) 87 | socdat.version = fslib.str2num(superblock:sub(0x1C, 0x1C)) 88 | socdat.total = fslib.str2num(superblock:sub(0x1D, 0x24)) 89 | socdat.reserve = fslib.str2num(superblock:sub(0x25, 0x28)) 90 | socdat.logblock = fslib.str2num(superblock:sub(0x29, 0x29)) 91 | 92 | socdat.filesize = fs.size(socfile) 93 | socdat.blocksize = 2^(socdat.logblock+7) 94 | socdat.indexcount = socdat.indexsize/64 95 | superblock = nil 96 | 97 | if socdat.magic ~= 0x434F53 then 98 | error("SOCFS filesystem not detected") 99 | elseif socdat.version ~= 0x10 then 100 | error(string.format("Unknown SOCFS version: %d.%02d", math.floor(socdat.version/16), socdat.version%16)) 101 | elseif socdat.indexsize%64 ~= 0 then 102 | error("Index Area is not a multiple of 64") 103 | end 104 | 105 | if socdat.total*socdat.blocksize ~= socdat.filesize then 106 | dprint("Warning: Size of filesystem differs from media size") 107 | dprint(socdat.total*socdat.blocksize, socdat.filesize) 108 | socdat.total = math.min(socdat.total, math.floor(socdat.filesize/socdat.blocksize)) 109 | dprint("New size: " .. socdat.total*socdat.blocksize) 110 | end 111 | 112 | if socdat.datasize > socdat.total then 113 | error("Size of data area is larger than the filesystem") 114 | elseif socdat.indexsize > socdat.total*socdat.blocksize then 115 | error("Size of index area is larger than the filesystem") 116 | elseif socdat.reserve > socdat.total then 117 | error("Size of reserved area is larger than the filesystem") 118 | elseif (socdat.reserve+socdat.datasize)*socdat.blocksize+socdat.indexsize > socdat.total*socdat.blocksize then 119 | error("Size of all areas are larger than the filesystem") 120 | end 121 | 122 | local proxyObj = {} 123 | 124 | -- Cache the Index Area 125 | socdat.index = {} 126 | file:seek("set", socdat.total*socdat.blocksize - socdat.indexsize) 127 | local indexdata = fslib.readRawString(file, socdat.indexsize) 128 | for i = 1, socdat.indexcount do 129 | local entrydat = indexdata:sub(socdat.indexsize-i*64+1, socdat.indexsize-i*64+64) 130 | local entry = {type = entrydat:byte(1, 1)} 131 | if entry.type == 0x00 or entry.type == 0x02 then 132 | elseif entry.type == 0x01 then 133 | entry.time = fslib.str2num(entrydat:sub(0x02, 0x09)) 134 | local uuid = entrydat:sub(0x0A, 0x19):gsub(".", function(a) return string.format("%02x", a:byte()) end) 135 | entry.uuid = uuid:sub(1, 8) .. "-" .. uuid:sub(9, 12) .. "-" .. uuid:sub(13, 16) .. "-" .. uuid:sub(17, 20) .. "-" .. uuid:sub(21, 32) 136 | proxyObj.address = entry.uuid 137 | entry.label = _socfs.readStr(entrydat:sub(0x1A)) 138 | elseif entry.type == 0x10 or entry.type == 0x18 then 139 | entry.part = _socfs.readStr(entrydat:sub(0x02, 0x3C)) 140 | entry.next = fslib.str2num(entrydat:sub(0x3D, 0x40)) 141 | elseif entry.type == 0x11 or entry.type == 0x19 then 142 | entry.next = fslib.str2num(entrydat:sub(0x02, 0x05)) 143 | entry.time = fslib.str2num(entrydat:sub(0x06, 0x0D)) 144 | entry.part = _socfs.readStr(entrydat:sub(0x0E)) 145 | elseif entry.type == 0x12 or entry.type == 0x1A then 146 | entry.next = fslib.str2num(entrydat:sub(0x02, 0x05)) 147 | entry.time = fslib.str2num(entrydat:sub(0x06, 0x0D)) 148 | entry.block = fslib.str2num(entrydat:sub(0x0E, 0x15)) 149 | entry.length = fslib.str2num(entrydat:sub(0x16, 0x1D)) 150 | entry.part = _socfs.readStr(entrydat:sub(0x1E)) 151 | elseif entry.type == 0x17 then 152 | entry.start = fslib.str2num(entrydat:sub(0x0B, 0x12)) 153 | entry.last = fslib.str2num(entrydat:sub(0x13, 0x1A)) 154 | else 155 | error(string.format("Index " .. i .. " has unknown type: 0x%02X", entry.type)) 156 | end 157 | socdat.index[i] = entry 158 | end 159 | indexdata = nil 160 | if socdat.index[1].type ~= 0x01 then 161 | error("First Index Entry not Volume Entry") 162 | elseif socdat.index[socdat.indexcount].type ~= 0x02 then 163 | error("Last Index Entry not Starting Marker Entry") 164 | end 165 | for i = 1, socdat.indexcount do 166 | local entry = socdat.index[i] 167 | if entry.type == 0x11 or entry.type == 0x12 then 168 | entry.name = entry.part 169 | local next = entry.next 170 | while next ~= 0 do 171 | if socdat.index[next].type ~= 0x10 then 172 | error("Continuation chain points to " .. (socdat.index[next].type == 0x18 and "Deleted" or "non") .. " Continuation Entry") 173 | end 174 | entry.name = entry.name .. socdat.index[next].part 175 | next = socdat.index[next].next 176 | end 177 | entry.name = fslib.cleanPath(entry.name) 178 | if entry.name == "" then 179 | error("Index " .. i .. " has an empty name") 180 | end 181 | end 182 | end 183 | 184 | -- Build a VFS representation 185 | socdat.vfs = {[""]={}} 186 | for i = 1, socdat.indexcount do 187 | local entry = socdat.index[i] 188 | if entry.type == 0x11 then 189 | if socdat.vfs[entry.name] == nil then 190 | socdat.vfs[entry.name] = {} 191 | end 192 | elseif entry.type == 0x12 then 193 | local parent = fslib.getParent(entry.name) 194 | if socdat.vfs[parent] == nil then 195 | socdat.vfs[parent] = {} 196 | end 197 | end 198 | end 199 | for i = 1, socdat.indexcount do 200 | local entry = socdat.index[i] 201 | if entry.type == 0x11 or entry.type == 0x12 then 202 | local parent = fslib.getParent(entry.name) 203 | table.insert(socdat.vfs[parent], {i, fs.name(entry.name)}) 204 | end 205 | end 206 | 207 | local handlelist = {} 208 | 209 | proxyObj.type = "filesystem" 210 | function proxyObj.isReadOnly() 211 | return fs.get(socfile).isReadOnly() 212 | end 213 | function proxyObj.spaceUsed() 214 | local used = 0 215 | for i = 1, socdat.indexcount do 216 | local entry = socdat.index[i] 217 | if entry.type ~= 0 and entry.type ~= 0x18 and entry.type ~= 0x19 and entry.type ~= 0x1A then 218 | used = used + 64 219 | if entry.type == 0x12 then 220 | used = used + math.ceil(entry.length/socdat.blocksize)*socdat.blocksize 221 | end 222 | end 223 | end 224 | return socdat.reserve*socdat.blocksize + used 225 | end 226 | function proxyObj.spaceTotal() 227 | return socdat.total*socdat.blocksize 228 | end 229 | function proxyObj.getLabel() 230 | return socdat.index[1].label 231 | end 232 | function proxyObj.setLabel(value) 233 | checkArg(1, value, "string") 234 | socdat.index[1].label = value:sub(1, 39) 235 | _socfs.writeIndex(socdat, 1) 236 | end 237 | 238 | function proxyObj.list(path) 239 | checkArg(1, path, "string") 240 | path = fslib.cleanPath(path) 241 | local dir = socdat.vfs[path] 242 | if dir == nil then 243 | return nil, "no such file or directory" 244 | end 245 | local list = {} 246 | for i = 1, #dir do 247 | if socdat.index[dir[i][1]].type == 0x11 then 248 | list[#list + 1] = dir[i][2] .. "/" 249 | else 250 | list[#list + 1] = dir[i][2] 251 | end 252 | end 253 | list.n = #list 254 | return list 255 | end 256 | function proxyObj.exists(path) 257 | checkArg(1, path, "string") 258 | return _socfs.vfs2index(socdat, fslib.cleanPath(path)) ~= nil 259 | end 260 | function proxyObj.isDirectory(path) 261 | checkArg(1, path, "string") 262 | return socdat.vfs[fslib.cleanPath(path)] ~= nil 263 | end 264 | function proxyObj.size(path) 265 | checkArg(1, path, "string") 266 | local index = _socfs.vfs2index(socdat, fslib.cleanPath(path)) 267 | if index == nil or (index.type ~= 0x12 and index.type ~= 0x1A) then 268 | return 0 269 | end 270 | return index.length 271 | end 272 | function proxyObj.lastModified(path) 273 | checkArg(1, path, "string") 274 | path = fslib.cleanPath(path) 275 | local index = _socfs.vfs2index(socdat, fslib.cleanPath(path)) 276 | if index == nil then 277 | return 0 278 | end 279 | return index.time*1000 280 | end 281 | 282 | function proxyObj.makeDirectory(path) 283 | -- TODO: Recursive 284 | checkArg(1, path, "string") 285 | path = fslib.cleanPath(path) 286 | local parent = fslib.getParent(path) 287 | if socdat.vfs[path] ~= nil then 288 | return false 289 | end 290 | -- TODO: Remove when recursive is supported: 291 | if socdat.vfs[parent] == nil then 292 | return false 293 | end 294 | local icount = math.ceil((#path-51)/59)+1 295 | local ilist = {} 296 | for i = 1, icount do 297 | ilist[i] = _socfs.findFreeIndex(socdat) 298 | if ilist[i] == nil then 299 | return nil, "not enough space" 300 | end 301 | end 302 | socdat.index[ilist[1]] = { 303 | type = 0x11, 304 | next = ilist[2] or 0, 305 | time = os.time(), 306 | part = path:sub(1, 51), 307 | name = path, 308 | } 309 | for i = 2, icount do 310 | socdat.index[ilist[i]] = { 311 | type = 0x10, 312 | part = path:sub((i-1)*59-7, (i-1)*59+51), 313 | next = ilist[i+1] or 0, 314 | } 315 | end 316 | for i = 1, icount do 317 | _socfs.writeIndex(socdat, ilist[i]) 318 | end 319 | socdat.vfs[path] = {} 320 | -- TODO: Replace with segment logic when recursive is supported: 321 | table.insert(socdat.vfs[parent], {ilist[1], fs.name(path)}) 322 | return true 323 | end 324 | function proxyObj.rename(from, to) 325 | checkArg(1, from, "string") 326 | checkArg(2, to, "string") 327 | from = fslib.cleanPath(from) 328 | to = fslib.cleanPath(to) 329 | -- TODO: Stub 330 | return false 331 | end 332 | function proxyObj.remove(path) 333 | checkArg(1, path, "string") 334 | path = fslib.cleanPath(path) 335 | -- TODO: Stub 336 | return false 337 | end 338 | 339 | function proxyObj.open(path, mode) 340 | checkArg(1, path, "string") 341 | if mode ~= "r" and mode ~= "rb" and mode ~= "w" and mode ~= "wb" and mode ~= "a" and mode ~= "ab" then 342 | error("unsupported mode", 2) 343 | end 344 | path = fslib.cleanPath(path) 345 | -- TODO: Stub 346 | return nil, "file not found" 347 | end 348 | function proxyObj.read(handle, count) 349 | checkArg(1, handle, "number") 350 | checkArg(2, count, "number") 351 | if handlelist[handle] == nil or handlelist[handle].mode ~= "r" then 352 | return nil, "bad file descriptor" 353 | end 354 | end 355 | function proxyObj.write(handle, value) 356 | checkArg(1, handle, "number") 357 | checkArg(2, value, "string") 358 | if handlelist[handle] == nil or handlelist[handle].mode ~= "w" then 359 | return nil, "bad file descriptor" 360 | end 361 | end 362 | function proxyObj.seek(handle, whence, offset) 363 | checkArg(1, handle, "number") 364 | checkArg(2, whence, "string") 365 | checkArg(3, offset, "number") 366 | if handlelist[handle] == nil then 367 | return nil, "bad file descriptor" 368 | end 369 | if whence ~= "set" and whence ~= "cur" and whence ~= "end" then 370 | error("invalid mode", 2) 371 | end 372 | if offset < 0 then 373 | return nil, "Negative seek offset" 374 | end 375 | end 376 | function proxyObj.close(handle) 377 | checkArg(1, handle, "number") 378 | if handlelist[handle] == nil then 379 | return nil, "bad file descriptor" 380 | end 381 | handlelist[handle] = nil 382 | end 383 | proxyObj.soc = socdat 384 | vcomp.register(proxyObj.address, proxyObj.type, proxyObj) 385 | return proxyObj 386 | end 387 | return socfs 388 | -------------------------------------------------------------------------------- /filesystems/socfs.txt: -------------------------------------------------------------------------------- 1 | Simple OpenComputers FileSystem. 2 | Based on this: http://www.d-rift.nl/combuster/vdisk/sfs.html 3 | 4 | Changes: 5 | Timestamps are based in seconds and not 1/65536ths of a second 6 | Magic ID is now 0x434F53 'SOC' 7 | Unused Entry moved to 0x00 8 | Unusable Entry moved to 0x17 9 | Versions are represented as #.## and are decimal, but otherwise the same. 10 | Unsure if already stated or not, but blocks are indexed from beginning of drive 11 | (0) to the end of drive. Usable blocks are everything after the reserved. 12 | 13 | Volume Identifier format: 14 | Offset Size Description 15 | 0x00 1 Entry type (0x01 for the Volume Identifier Entry) 16 | 0x01 8 Time stamp 17 | 0x09 16 Filesystem UUID 18 | 0x19 39 Volume name in UTF-8, including zero terminator 19 | 20 | (Deleted) Continuation Entry: 21 | Offset Size Description 22 | 0x00 1 Entry type (0x10 for Continuation Entries, 0x18 if deleted) 23 | 0x01 59 Entry name in UTF8 24 | 0x3C 4 Index of next entry (0 for no more entries) 25 | 26 | (Deleted) Directory Entry 27 | Offset Size Description 28 | 0x00 1 Entry type (0x11 for Directory Entries, 0x19 if deleted) 29 | 0x01 4 Index of first Continuation Entry (0 if unneeded) 30 | 0x05 8 Time stamp 31 | 0x0D 51 Directory name in UTF-8 32 | 33 | (Deleted) File Entry 34 | Offset Size Description 35 | 0x00 1 Entry type (0x12 for File Entries, 0x1A if deleted) 36 | 0x01 4 Index of first Continuation Entry (0 if unneeded) 37 | 0x05 8 Time stamp 38 | 0x0D 8 Starting block number in the data area 39 | 0x15 8 File length in bytes 40 | 0x1D 35 File name in UTF-8 41 | -------------------------------------------------------------------------------- /filesystems/tapefs.lua: -------------------------------------------------------------------------------- 1 | -- WARNING: Severly untested. 2 | local component = require("component") 3 | local vcomp = require("vcomponent") 4 | local fs = require("filesystem") 5 | local io = require("io") 6 | 7 | local filedescript = {} 8 | 9 | local tapefs = {} 10 | function tapefs.proxy(address) 11 | local found = false 12 | for k,v in component.list("tape_drive") do 13 | if k == address and v == "tape_drive" then 14 | found = true 15 | break 16 | end 17 | end 18 | if not found then 19 | error("No such component",2) 20 | end 21 | local label 22 | component.invoke(address, "seek", -math.huge) 23 | local proxyObj = {} 24 | proxyObj.type = "filesystem" 25 | proxyObj.address = "tfs-" .. address:gsub("-","") 26 | proxyObj.isDirectory = function(path) 27 | checkArg(1,path,"string") 28 | path = fs.canonical(path) 29 | if path == "" then 30 | return true 31 | elseif path == "data.raw" and component.invoke(address, "isReady") then 32 | return false 33 | else 34 | return nil, "no such file or directory" 35 | end 36 | end 37 | proxyObj.lastModified = function(path) 38 | checkArg(1,path,"string") 39 | -- Unsupported 40 | return 0 41 | end 42 | proxyObj.list = function(path) 43 | checkArg(1,path,"string") 44 | path = fs.canonical(path) 45 | if path ~= "" then 46 | return nil, "no such file or directory" 47 | end 48 | local list = {} 49 | if path == "" and component.invoke(address, "isReady") then 50 | table.insert(list, "data.raw") 51 | end 52 | list.n = #list 53 | local iterator = 0 54 | return list 55 | end 56 | proxyObj.spaceTotal = function() 57 | return component.invoke(address, "getSize") 58 | end 59 | proxyObj.open = function(path,mode) 60 | checkArg(1,path,"string") 61 | checkArg(2,mode,"string") 62 | if path ~= "data.raw" or not component.invoke(address, "isReady") then 63 | return nil, "file not found" 64 | end 65 | if mode ~= "r" and mode ~= "rb" and mode ~= "w" and mode ~= "wb" and mode ~= "a" and mode ~= "ab" then 66 | error("unsupported mode",2) 67 | end 68 | while true do 69 | local rnddescrpt = math.random(1000000000,9999999999) 70 | if filedescript[rnddescrpt] == nil then 71 | filedescript[rnddescrpt] = { 72 | seek = 0, 73 | mode = mode:sub(1,1) == "r" and "r" or "w" 74 | } 75 | return rnddescrpt 76 | end 77 | end 78 | end 79 | proxyObj.remove = function(path) 80 | checkArg(1,path,"string") 81 | return false 82 | end 83 | proxyObj.rename = function(path, newpath) 84 | checkArg(1,path,"string") 85 | checkArg(1,newpath,"string") 86 | return false 87 | end 88 | proxyObj.read = function(fd, count) 89 | count = count or 1 90 | checkArg(1,fd,"number") 91 | checkArg(2,count,"number") 92 | if filedescript[fd] == nil or filedescript[fd].mode ~= "r" then 93 | return nil, "bad file descriptor" 94 | end 95 | 96 | component.invoke(address, "seek", -math.huge) 97 | component.invoke(address, "seek", filedescript[fd].seek) 98 | 99 | local data = component.invoke(address, "read", count) 100 | filedescript[fd].seek = filedescript[fd].seek + #data 101 | return data 102 | end 103 | proxyObj.close = function(fd) 104 | checkArg(1,fd,"number") 105 | if filedescript[fd] == nil then 106 | return nil, "bad file descriptor" 107 | end 108 | filedescript[fd] = nil 109 | end 110 | proxyObj.getLabel = function() 111 | return component.invoke(address, "getLabel") 112 | end 113 | proxyObj.seek = function(fd,kind,offset) 114 | checkArg(1,fd,"number") 115 | checkArg(2,kind,"string") 116 | checkArg(3,offset,"number") 117 | if filedescript[fd] == nil then 118 | return nil, "bad file descriptor" 119 | end 120 | if kind ~= "set" and kind ~= "cur" and kind ~= "end" then 121 | error("invalid mode",2) 122 | end 123 | if offset < 0 then 124 | return nil, "Negative seek offset" 125 | end 126 | local newpos 127 | if kind == "set" then 128 | newpos = offset 129 | elseif kind == "cur" then 130 | newpos = filedescript[fd].seek + offset 131 | elseif kind == "end" then 132 | newpos = component.invoke(address, "getSize") + offset - 1 133 | end 134 | filedescript[fd].seek = math.min(math.max(newpos, 0), component.invoke(address, "getSize") - 1) 135 | return filedescript[fd].seek 136 | end 137 | proxyObj.size = function(path) 138 | checkArg(1,path,"string") 139 | path = fs.canonical(path) 140 | if path == "data.raw" and component.invoke(address, "isReady") then 141 | return component.invoke(address, "getSize") 142 | end 143 | return 0 144 | end 145 | proxyObj.isReadOnly = function() 146 | return false 147 | end 148 | proxyObj.setLabel = function(newlabel) 149 | component.invoke(address, "setLabel", newlabel) 150 | return newlabel 151 | end 152 | proxyObj.makeDirectory = function(path) 153 | checkArg(1,path,"string") 154 | return false 155 | end 156 | proxyObj.exists = function(path) 157 | checkArg(1,path,"string") 158 | path = fs.canonical(path) 159 | if (path == "data.raw" and component.invoke(address, "isReady")) or path == "" then 160 | return true 161 | end 162 | return false 163 | end 164 | proxyObj.spaceUsed = function() 165 | return component.invoke(address, "getSize") 166 | end 167 | proxyObj.write = function(fd,data) 168 | checkArg(1,fd,"number") 169 | checkArg(2,data,"string") 170 | if filedescript[fd] == nil or filedescript[fd].mode ~= "w" then 171 | return nil, "bad file descriptor" 172 | end 173 | 174 | component.invoke(address, "seek", -math.huge) 175 | component.invoke(address, "seek", filedescript[fd].seek) 176 | 177 | component.invoke(address, "write", data) 178 | filedescript[fd].seek = filedescript[fd].seek + #data 179 | return true 180 | end 181 | vcomp.register(proxyObj.address, proxyObj.type, proxyObj) 182 | return proxyObj 183 | end 184 | return tapefs -------------------------------------------------------------------------------- /filesystems/tapefsd.lua: -------------------------------------------------------------------------------- 1 | local tapefs = require("tapefs") 2 | local fs = require("filesystem") 3 | local component = require("component") 4 | local tapes = 0 5 | for k,v in component.list("tape_drive") do 6 | if v == "tape_drive" then 7 | tapes = tapes + 1 8 | local tapeProxy = tapefs.proxy(k) 9 | local mntpath 10 | while true do 11 | mntpath = "/mnt/tape" .. (tapes > 1 and tapes or "") 12 | if fs.exists(mntpath) then 13 | tapes = tapes + 1 14 | else 15 | break 16 | end 17 | end 18 | print("Mounting " .. k .. " on " .. mntpath) 19 | fs.mount(tapeProxy,mntpath) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /immibis-compress/compress.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | local shell = require("shell") 3 | local ipack = require("ipack") 4 | local inFN, outFN 5 | 6 | local args = {...} 7 | if #args ~= 2 then 8 | print("Usage: compress ") 9 | return 10 | end 11 | 12 | inFN = shell.resolve(args[1]) 13 | outFN = shell.resolve(args[2]) 14 | 15 | if not fs.exists(inFN) then 16 | error("No such file", 0) 17 | end 18 | 19 | local f = io.open(inFN, "rb") 20 | local data = f:read("*a") 21 | f:close() 22 | 23 | local out = ipack.compress(data, 8, 0) 24 | 25 | local f = io.open(outFN, "wb") 26 | f:write(out) 27 | f:close() 28 | 29 | print("uncompressed size: ", data:len()) 30 | print("compressed size: ", out:len()) 31 | print("compression ratio: ", math.floor(out:len() / data:len() * 1000)/10 .. "%") -------------------------------------------------------------------------------- /immibis-compress/decompress.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | local shell = require("shell") 3 | local ipack = require("ipack") 4 | local inFN, outFN 5 | 6 | local args = {...} 7 | if #args ~= 2 then 8 | print("Usage: decompress ") 9 | return 10 | end 11 | 12 | inFN = shell.resolve(args[1]) 13 | outFN = shell.resolve(args[2]) 14 | 15 | if not fs.exists(inFN) then 16 | error("No such file", 0) 17 | end 18 | 19 | local f = io.open(inFN, "rb") 20 | local data = f:read("*a") 21 | f:close() 22 | 23 | local out = ipack.decompress(data, 8, 0) 24 | 25 | local f = io.open(outFN, "wb") 26 | f:write(out) 27 | f:close() 28 | 29 | print("compressed size: ", data:len()) 30 | print("uncompressed size: ", out:len()) 31 | print("compression ratio: ", math.floor(data:len() / out:len() * 1000)/10 .. "%") -------------------------------------------------------------------------------- /immibis-compress/index.txt: -------------------------------------------------------------------------------- 1 | Immibis's compression programs ported to OpenComputers. 2 | 3 | compress.lua 4 | Compress a file using the ipack compression routine. 5 | Requires ipack.lua 6 | 7 | decompress.lua 8 | Decompress a file using the ipack decompression routine. 9 | Requires ipack.lua 10 | 11 | ipack.lua 12 | Immibis's compression routines ported to OpenComputers. 13 | Fixed a bug with the ending line not consistently existing. 14 | Install in /lib 15 | -------------------------------------------------------------------------------- /immibis-compress/ipack.lua: -------------------------------------------------------------------------------- 1 | -- Compression and decompression routines thanks to immibis 2 | local ipack = {} 3 | 4 | function ipack.compress(data, bitsPerByte, firstCharacter) 5 | local function countTabs(l) 6 | for k = 1, #l do 7 | if l:sub(k, k) ~= "\t" then 8 | return k - 1 9 | end 10 | end 11 | return #l 12 | end 13 | 14 | local compressNonIdentGroups = false 15 | local huffmanEncode = true 16 | 17 | -- read input data 18 | local lines = {} 19 | for line in (data .. "\n"):gmatch("([^\n]*)\n") do 20 | table.insert(lines,line) 21 | end 22 | 23 | -- convert indentation 24 | local inText = "" 25 | local lastIndent = 0 26 | for lineNo, line in ipairs(lines) do 27 | local thisIndent = countTabs(line) 28 | local nextIndent = lines[lineNo+1] and countTabs(lines[lineNo+1]) or thisIndent 29 | local prevIndent = lines[lineNo-1] and countTabs(lines[lineNo-1]) or 0 30 | 31 | if thisIndent > nextIndent and thisIndent > prevIndent then 32 | thisIndent = math.min(nextIndent, prevIndent) 33 | end 34 | 35 | while lastIndent < thisIndent do 36 | inText = inText .. "&+" 37 | lastIndent = lastIndent + 1 38 | end 39 | while lastIndent > thisIndent do 40 | inText = inText .. "&-" 41 | lastIndent = lastIndent - 1 42 | end 43 | 44 | if line:sub(1,1) == "&" then 45 | line = "&" .. line 46 | end 47 | 48 | inText = inText .. line:sub(lastIndent+1) .. "\n" 49 | end 50 | inText = inText:sub(1,-2) 51 | 52 | -- parse into alternating strings of alphanumerics and non-alphanumerics 53 | local parsed = {} 54 | local idents = "abcdefghijklmnopqrstvuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" 55 | local lastIdent = nil 56 | 57 | local function isIdentString(s) 58 | return idents:find(s:sub(1,1), 1, true) ~= nil 59 | end 60 | 61 | local groupCounts = {} 62 | 63 | local function onFinishSegment(isIdent, segment) 64 | if isIdent or compressNonIdentGroups then 65 | groupCounts[segment] = (groupCounts[segment] or 0) + 1 66 | end 67 | end 68 | 69 | for k = 1, #inText do 70 | local ch = inText:sub(k, k) 71 | local isIdent = idents:find(ch, 1, true) ~= nil 72 | if isIdent ~= lastIdent then 73 | if #parsed > 0 then 74 | onFinishSegment(lastIdent, parsed[#parsed]) 75 | end 76 | parsed[#parsed+1] = "" 77 | end 78 | lastIdent = isIdent 79 | parsed[#parsed] = parsed[#parsed]..ch 80 | end 81 | if #parsed > 0 then 82 | onFinishSegment(isIdent, parsed[#parsed]) 83 | end 84 | 85 | local id_literal_escape = "_" 86 | local pc_literal_escape = "$" 87 | 88 | local nextCompressed 89 | do 90 | local validchars_id = idents:gsub(id_literal_escape,"") 91 | local validchars_pc = "" 92 | 93 | for n=32,126 do 94 | local ch = string.char(n) 95 | if not idents:find(ch,1,true) and ch ~= pc_literal_escape then 96 | validchars_pc = validchars_pc .. ch 97 | end 98 | end 99 | 100 | local function encode(n, isIdent) 101 | local s = "" 102 | local validchars = isIdent and validchars_id or validchars_pc 103 | while n > 0 do 104 | local digit = (n % #validchars) + 1 105 | s = s .. validchars:sub(digit, digit) 106 | n = math.floor(n / #validchars) 107 | end 108 | return s 109 | end 110 | 111 | local next = {[true]=0,[false]=0} 112 | function nextCompressed(isIdent) 113 | next[isIdent] = next[isIdent] + 1 114 | return encode(next[isIdent], isIdent) 115 | end 116 | end 117 | 118 | local groupsSorted = {} 119 | local groups = {} 120 | for k, v in pairs(groupCounts) do 121 | if (#k > 1 and v > 1) or k:find(isIdentString(k) and id_literal_escape or pc_literal_escape) then 122 | local t = {k, v} 123 | groups[k] = t 124 | table.insert(groupsSorted, t) 125 | end 126 | end 127 | 128 | local avgCompressedLength = 2 129 | 130 | local function estSavings(a) 131 | local str = a[1] 132 | local count = a[2] 133 | local compressedLength = a[3] and #a[3] or avgCompressedLength 134 | 135 | -- estimates the number of chars saved by compressing this group 136 | 137 | -- it costs #str+1 chars to encode the group literally, or about compressedLength chars to compress it 138 | -- so by compressing it, each time the group occurs we save (#str + 1 - compressedLength) chars 139 | local saved = (#str + 1 - compressedLength) * count 140 | 141 | -- but we also use about #str + 1 chars in the name table if we compress it. 142 | saved = saved - (#str + 1) 143 | 144 | return saved 145 | end 146 | 147 | table.sort(groupsSorted, function(a, b) 148 | return estSavings(a) > estSavings(b) 149 | end) 150 | 151 | for _, v in ipairs(groupsSorted) do 152 | v[3] = nextCompressed(isIdentString(v[1])) 153 | end 154 | 155 | local out = #groupsSorted .. "^" 156 | for _, v in ipairs(groupsSorted) do 157 | local encoded = v[1]:gsub("&", "&a"):gsub("%^","&b") 158 | out = out .. encoded .. "^" 159 | end 160 | for _, v in pairs(parsed) do 161 | if groups[v] then 162 | out = out .. groups[v][3] 163 | elseif isIdentString(v) then 164 | out = out .. id_literal_escape .. v 165 | elseif compressNonIdentGroups then 166 | out = out .. pc_literal_escape .. v 167 | else 168 | out = out .. v 169 | end 170 | end 171 | 172 | if huffmanEncode then 173 | -- generate a huffman tree - first we need to count the number of times each symbol occurs 174 | local symbolCounts = {} 175 | local numSymbols = 0 176 | for k = 1, #out do 177 | local sym = out:sub(k,k) 178 | if not symbolCounts[sym] then 179 | numSymbols = numSymbols + 1 180 | symbolCounts[sym] = 1 181 | else 182 | symbolCounts[sym] = symbolCounts[sym] + 1 183 | end 184 | end 185 | 186 | -- convert them to tree nodes and sort them by count, ascending order 187 | -- a tree node is either {symbol, count} or {{subtree_0, subtree_1}, count} 188 | local treeFragments = {} 189 | for sym, count in pairs(symbolCounts) do 190 | treeFragments[#treeFragments + 1] = {sym, count} 191 | end 192 | table.sort(treeFragments, function(a, b) 193 | return a[2] < b[2] 194 | end) 195 | 196 | while #treeFragments > 1 do 197 | -- take the two lowest-count fragments and combine them 198 | local a = table.remove(treeFragments, 1) 199 | local b = table.remove(treeFragments, 1) 200 | 201 | local newCount = a[2] + b[2] 202 | local new = {{a, b}, newCount} 203 | 204 | -- insert the new fragment in the right place 205 | if #treeFragments == 0 or newCount > treeFragments[#treeFragments][2] then 206 | table.insert(treeFragments, new) 207 | else 208 | local ok = false 209 | for k=1,#treeFragments do 210 | if treeFragments[k][2] >= newCount then 211 | table.insert(treeFragments, k, new) 212 | ok = true 213 | break 214 | end 215 | end 216 | assert(ok, "internal error: couldn't find place for tree fragment") 217 | end 218 | end 219 | 220 | local symbolCodes = {} 221 | 222 | local function shallowCopyTable(t) 223 | local rv = {} 224 | for k,v in pairs(t) do 225 | rv[k] = v 226 | end 227 | return rv 228 | end 229 | 230 | -- now we have a huffman tree (codes -> symbols) but we need a map of symbols -> codes, so do that 231 | local function iterate(root, path) 232 | if type(root[1]) == "table" then 233 | local t = shallowCopyTable(path) 234 | t[#t+1] = false 235 | iterate(root[1][1], t) 236 | path[#path+1] = true 237 | iterate(root[1][2], path) 238 | else 239 | symbolCodes[root[1]] = path 240 | end 241 | end 242 | iterate(treeFragments[1], {}) 243 | 244 | local rv = {} 245 | 246 | local symbolBitWidth = 8 247 | 248 | local function writeTree(tree) 249 | if type(tree[1]) == "table" then 250 | rv[#rv+1] = false 251 | writeTree(tree[1][1]) 252 | writeTree(tree[1][2]) 253 | else 254 | rv[#rv+1] = true 255 | local symbol = tree[1]:byte() 256 | for k = 0, symbolBitWidth - 1 do 257 | 258 | local testBit = 2 ^ k 259 | 260 | local bit = (symbol % (2 * testBit)) >= testBit 261 | rv[#rv+1] = bit 262 | end 263 | end 264 | end 265 | 266 | writeTree(treeFragments[1]) 267 | 268 | for k = 1, #out do 269 | local symbol = out:sub(k,k) 270 | for _, bit in ipairs(symbolCodes[symbol] or error("internal error: symbol "..symbol.." has no code")) do 271 | rv[#rv+1] = bit 272 | end 273 | end 274 | 275 | -- convert the array of bits (rv) back to characters 276 | 277 | local s = "" 278 | 279 | -- pad to an integral number of bytes 280 | local padbit = not rv[#rv] 281 | repeat 282 | rv[#rv+1] = padbit 283 | until (#rv % bitsPerByte) == 0 284 | 285 | for k = 1, #rv, bitsPerByte do 286 | local byte = firstCharacter 287 | for i = 0, bitsPerByte-1 do 288 | if rv[k+i] then 289 | byte = byte + 2 ^ i 290 | end 291 | end 292 | s = s .. string.char(byte) 293 | end 294 | 295 | out = s 296 | end 297 | 298 | return out 299 | end 300 | 301 | function ipack.decompress(inText, bitsPerByte, firstCharacter) 302 | local inPos = 1 303 | 304 | local huffmanCompression = true 305 | 306 | if huffmanCompression then 307 | -- convert characters to bits 308 | local inBits = {} 309 | for k = 1, #inText do 310 | local byte = inText:sub(k, k):byte() - firstCharacter 311 | for i = 0, bitsPerByte - 1 do 312 | local testBit = 2 ^ i 313 | inBits[#inBits + 1] = (byte % (2 * testBit)) >= testBit 314 | end 315 | end 316 | 317 | -- remove padding 318 | local padbit = inBits[#inBits] 319 | while inBits[#inBits] == padbit do 320 | inBits[#inBits] = nil 321 | end 322 | 323 | local pos = 1 324 | local function readBit() 325 | if pos > #inBits then error("end of stream", 2) end 326 | pos = pos + 1 327 | return inBits[pos - 1] 328 | end 329 | 330 | -- read huffman tree 331 | local function readTree() 332 | if readBit() then 333 | local byte = 0 334 | for i = 0, 7 do 335 | if readBit() then 336 | byte = byte + 2 ^ i 337 | end 338 | end 339 | return string.char(byte) 340 | else 341 | local subtree_0 = readTree() 342 | local subtree_1 = readTree() 343 | return {[false]=subtree_0, [true]=subtree_1} 344 | end 345 | end 346 | local tree = readTree() 347 | 348 | inText = "" 349 | 350 | local treePos = tree 351 | while pos <= #inBits do 352 | local bit = readBit() 353 | treePos = treePos[bit] 354 | if type(treePos) ~= "table" then 355 | inText = inText .. treePos 356 | treePos = tree 357 | end 358 | end 359 | if treePos ~= tree then 360 | error("unexpected end of stream") 361 | end 362 | end 363 | 364 | local function readTo(delim) 365 | local start = inPos 366 | local nextCaret = inText:find(delim, inPos, true) 367 | if not nextCaret then 368 | inPos = #inText + 1 369 | return inText:sub(start) 370 | end 371 | inPos = nextCaret + 1 372 | return inText:sub(start, nextCaret - 1) 373 | end 374 | 375 | -- returns iterator 376 | local function splitString(str, delim) 377 | local pos = 1 378 | return function() 379 | if pos > #str then return end 380 | local start = pos 381 | local nextDelim = str:find(delim, pos, true) 382 | if not nextDelim then 383 | pos = #str + 1 384 | return str:sub(start) 385 | end 386 | pos = nextDelim + 1 387 | return str:sub(start, nextDelim - 1) 388 | end 389 | end 390 | 391 | local nameTable = {} 392 | 393 | local idents = "abcdefghijklmnopqrstvuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" 394 | local nextCompressed 395 | do 396 | local validchars = idents:gsub("_","") 397 | 398 | local function encode(n) 399 | local s = "" 400 | while n > 0 do 401 | local digit = (n % #validchars) + 1 402 | s = s .. validchars:sub(digit, digit) 403 | n = math.floor(n / #validchars) 404 | end 405 | return s 406 | end 407 | 408 | local next = 0 409 | function nextCompressed() 410 | next = next + 1 411 | return encode(next) 412 | end 413 | end 414 | 415 | for k = 1, tonumber(readTo("^")) do 416 | local key = nextCompressed() 417 | local value = readTo("^") 418 | nameTable[key] = value 419 | end 420 | 421 | local out = "" 422 | 423 | local function onFinishSegment(isIdent, segment) 424 | if isIdent then 425 | if segment:sub(1, 1) == "_" then 426 | out = out .. segment:sub(2) 427 | else 428 | out = out .. tostring(nameTable[segment]) 429 | end 430 | else 431 | out = out .. segment 432 | end 433 | end 434 | 435 | local parsed = {} 436 | local idents = "abcdefghijklmnopqrstvuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" 437 | local lastIdent = nil 438 | 439 | for k = inPos, #inText do 440 | local ch = inText:sub(k, k) 441 | local isIdent = idents:find(ch, 1, true) ~= nil 442 | if isIdent ~= lastIdent then 443 | if #parsed > 0 then 444 | onFinishSegment(lastIdent, parsed[#parsed]) 445 | end 446 | parsed[#parsed+1] = "" 447 | end 448 | lastIdent = isIdent 449 | parsed[#parsed] = parsed[#parsed]..ch 450 | end 451 | if #parsed > 0 then 452 | onFinishSegment(isIdent, parsed[#parsed]) 453 | end 454 | 455 | -- convert indentation back 456 | local out2 = "" 457 | local lastIndent = "" 458 | for line in (out .. "\n"):gmatch("([^\n]*)\n") do 459 | while line:sub(1,2) == "&+" do 460 | lastIndent = lastIndent .. "\t" 461 | line = line:sub(3) 462 | end 463 | while line:sub(1,2) == "&-" do 464 | lastIndent = lastIndent:sub(1, #lastIndent - 1) 465 | line = line:sub(3) 466 | end 467 | if line:sub(1,2) == "&&" then 468 | line = line:sub(2) 469 | end 470 | 471 | out2 = out2 .. lastIndent .. line .. "\n" 472 | end 473 | out2 = out2:sub(1,-2) 474 | 475 | return out2 476 | end 477 | 478 | ------------------------------------------------------------------------------- 479 | 480 | return ipack -------------------------------------------------------------------------------- /index.txt: -------------------------------------------------------------------------------- 1 | Miscellaneous utilities for OpenComputers by Gamax92. 2 | 3 | enhanced/ 4 | Modified versions of standard programs for improved or enhanced features. 5 | 6 | filesystems/ 7 | Various filesystem implementations for OpenComputers. 8 | 9 | immibis-compress/ 10 | Immibis's compression programs ported to OpenComputers. 11 | 12 | OCNetFS/ 13 | Networked (internet) filesystem for OpenComputers. 14 | 15 | tapeutils/ 16 | A collection of programs useful for manipulating tapes. 17 | 18 | utilset/ 19 | Common utilties found on many linux distributions. 20 | 21 | vcomponent/ 22 | Virtual Components system for OpenComputers. 23 | 24 | wocchat/ 25 | WocChat IRC chat client for OpenComputers. 26 | 27 | chip8.lua 28 | CHIP8 emulator for OpenComputers. Requires OCLights2. 29 | 30 | debug.lua 31 | Program debugger for OpenComputers. 32 | 33 | ocwe.lua 34 | Basic implementation of WorldEdit in OpenComputers. 35 | Debug card necessary, tablet recommended. 36 | 37 | unicode-more.lua 38 | Extra functions for the unicode api. 39 | 40 | unrequire.lua 41 | Library unloader for OpenComputers. 42 | -------------------------------------------------------------------------------- /nano/nano.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | nano clone based off GNU nano for OpenComputers 3 | by Gamax92 4 | --]] 5 | local component = require("component") 6 | local computer = require("computer") 7 | local kbd = require("keyboard") 8 | local keys = kbd.keys 9 | local fs = require("filesystem") 10 | local shell = require("shell") 11 | local event = require("event") 12 | local term = require("term") 13 | local unicode = require("unicode") 14 | 15 | local nanoVERSION = "1.0.0" 16 | 17 | local function printf(...) print(string.format(...)) end 18 | local function eprintf(...) io.stderr:write(string.format(...) .. "\n") end 19 | local event_pull = term.pull or event.pull 20 | -- Fix keys.none 21 | keys["none"] = 0 22 | keys[0] = "none" 23 | 24 | -- TODO: Replace with more gnuopt style parser 25 | local args, opts = shell.parse(...) 26 | 27 | -- TODO: Once above has been done, check for invalid arguments 28 | -- Default options 29 | local options = { 30 | fgcolor = 0xFFFFFF, 31 | bgcolor = 0x000000, 32 | tabsize = 8, 33 | } 34 | local flags={"autoindent","backup","backwards","brighttext","casesensitive","const","cut","morespace","mouse","multibuffer","noconvert","nohelp","nonewlines","nowrap","quiet","quickblank","patterns","smarthome","smooth","softwrap","tabstospaces","tempfile","undo","view"} 35 | for i = 1,#flags do 36 | options[flags[i]] = false 37 | end 38 | -- TODO: HACK, REMOVE WHEN HELP AREA IS IMPLEMENTED 39 | options.nohelp = true 40 | flags = nil 41 | -- Default bindings 42 | -- TODO: Bindings for things other than main 43 | local bind = { 44 | main = { 45 | {"help","^G","F1"}, 46 | {"exit","^X","F2"}, 47 | {"writeout","^O","F3"}, 48 | {"insert","^R","F5","insert"}, 49 | {"whereis","^W","F6"}, 50 | {"replace","^\\","M-R","F14"}, 51 | {"cut","^K","F9"}, 52 | {"uncut","^U","F10"}, 53 | {"justify","^J","F4"}, 54 | {"speller","^T","F12"}, 55 | {"curpos","^C","F11"}, 56 | {"gotoline","^_","M-G","F13"}, 57 | {"prevpage","^Y","F7","pageUp"}, 58 | {"nextpage","^V","F8","pageDown"}, 59 | {"firstline","M-\\","M-|"}, 60 | {"lastline","M-/","M-?"}, 61 | {"searchagain","M-W","F16"}, 62 | {"findbracket","M-]"}, 63 | {"mark","^^","M-A","F15"}, 64 | {"copytext","M-^","M-6"}, 65 | {"indent","M-}"}, 66 | {"unindent","M-{"}, 67 | {"undo","M-U"}, 68 | {"redo","M-E"}, 69 | {"left","^B","left"}, 70 | {"right","^F","right"}, 71 | {"prevword","M-Space"}, 72 | {"nextword","^Space"}, 73 | {"home","^A","home"}, 74 | {"end","^E","end"}, 75 | {"prevline","^P","up"}, 76 | {"nextline","^N","down"}, 77 | {"beginpara","M-(","M-9"}, 78 | {"endpara","M-)","M-0"}, 79 | {"scrollup","M--","M-_"}, 80 | {"scrolldown","M-+","M-="}, 81 | {"prevbuf","M-<","M-,"}, 82 | {"nextbuf","M->","M-."}, 83 | {"verbatim","M-V"}, 84 | {"tab","^I"}, 85 | {"enter","^M","enter"}, 86 | {"delete","^D","delete"}, 87 | {"backspace","^H","back"}, 88 | {"cutrestoffile","M-T"}, 89 | {"fulljustify","M-J"}, 90 | {"wordcount","M-D"}, 91 | {"refresh","^L"}, 92 | {"suspend","^Z"}, 93 | {"nohelp","M-X"}, 94 | {"constupdate","M-C"}, 95 | {"morespace","M-O"}, 96 | {"smoothscroll","M-S"}, 97 | {"softwrap","M-$"}, 98 | {"whitespacedisplay","M-P"}, 99 | {"nosyntax","M-Y"}, 100 | {"smarthome","M-H"}, 101 | {"autoindent","M-I"}, 102 | {"cuttoend","M-K"}, 103 | {"nowrap","M-L"}, 104 | {"tabstospaces","M-Q"}, 105 | {"backupfile","M-B"}, 106 | {"multibuffer","M-F"}, 107 | {"mouse","M-M"}, 108 | {"noconvert","M-N"}, 109 | {"suspendenable","M-Z"}, 110 | } 111 | } 112 | local vfn = {} 113 | for k,v in pairs(bind) do 114 | for i = 1,#v do 115 | local b = v[i] 116 | for j = 2,#b do 117 | v[unicode.upper(b[j])]=b[1] 118 | end 119 | vfn[b[1]]=true 120 | v[i] = nil 121 | end 122 | end 123 | 124 | local function parseRC(filename) 125 | local problem = false 126 | local i = 1 127 | local function bad(msg, ...) 128 | problem = true 129 | printf("Error in %s on line %d: " .. msg, filename, i, ...) 130 | end 131 | local file, err = io.open(filename,"rb") 132 | if not file then 133 | problem = true; printf("Error reading %s: %s", filename, err) 134 | else 135 | for line in file:lines() do 136 | local cmd = {} 137 | for part in line:gmatch("%S+") do 138 | cmd[#cmd+1] = part 139 | end 140 | if cmd[1] == "set" or cmd[1] == "unset" then 141 | if #cmd < 2 then 142 | bad("Missing flag") 143 | elseif options[cmd[2]] ~= nil then 144 | if type(options[cmd[2]]) == "boolean" then 145 | options[cmd[2]] = cmd[1] == "set" 146 | elseif cmd[1] == "unset" then 147 | bad("Cannot unset option \"%s\"", cmd[2]) 148 | elseif type(options[cmd[2]]) == "number" then 149 | if tonumber(cmd[3]) == nil then 150 | bad("Parameter \"%s\" is invalid", cmd[3]) 151 | else 152 | options[cmd[2]] = tonumber(cmd[3]) 153 | end 154 | end 155 | else 156 | bad("Unknown flag \"%s\"", cmd[2]) 157 | end 158 | elseif cmd[1] == "bind" or cmd[1] == "unbind" then 159 | if cmd[2] == nil then 160 | bad("Missing key name") 161 | else 162 | local first = unicode.upper(cmd[2]:sub(1,1)) 163 | local last = cmd[1] == "bind" and cmd[4] or cmd[3] 164 | if first ~= "^" and first ~= "M" and first ~= "F" then 165 | bad("Key name must begin with \"^\", \"M\", or \"F\"") 166 | elseif cmd[1] == "bind" and cmd[3] == nil then 167 | bad("Must specify a function to bind the key to") 168 | elseif last == nil then 169 | bad("Must specify a menu (or \"all\") in which to bind/unbind the key") 170 | elseif cmd[1] == "bind" and not vfn[cmd[3]] then 171 | bad("Cannot map name \"%s\" to a function", cmd[3]) 172 | elseif bind[last] == nil then 173 | bad("Cannot map name \"%s\" to a menu", last) 174 | else 175 | if cmd[1] == "bind" then 176 | bind[last][cmd[2]] = cmd[3] 177 | elseif cmd[1] == "unbind" then 178 | bind[last][cmd[2]] = nil 179 | end 180 | end 181 | end 182 | else 183 | bad("Command \"%s\" not understood", cmd[1]) 184 | end 185 | i = i + 1 186 | end 187 | end 188 | if problem then 189 | print("Press Enter to continue starting nano.") 190 | while true do 191 | local name,_,_,code = event_pull() 192 | if name == "key_down" and code == keys.enter then break end 193 | end 194 | end 195 | end 196 | if fs.exists("/etc/nanorc") then 197 | parseRC("/etc/nanorc") 198 | end 199 | -- TODO: Parse arguments 200 | 201 | local buffers, buffer = {} 202 | local gpu = component.gpu 203 | gpu.setForeground(options.fgcolor) 204 | gpu.setBackground(options.bgcolor) 205 | local gpuW, gpuH = gpu.getResolution() 206 | gpu.fill(1,1,gpuW,gpuH," ") 207 | term.setCursorBlink(true) 208 | 209 | -- TODO: more modes 210 | local mode = "main" 211 | -- Calculate positions for screen elements 212 | local linesY = (options.morespace and 2 or 3) 213 | local linesH = gpuH - linesY 214 | local statusbarY = gpuH 215 | if not options.nohelp then 216 | linesH = linesH - 2 217 | statusbarY = statusbarY - 2 218 | end 219 | -- TODO: Help lines 220 | 221 | 222 | local utf8char = "[%z\1-\127\194-\244][\128-\191]*" 223 | local function formatLine(text,map) 224 | local newstr, pos = {}, 0 225 | local cmap 226 | if map then 227 | cmap = {} 228 | end 229 | for char in text:gmatch(utf8char) do 230 | if map then 231 | cmap[#cmap+1] = pos+1 232 | end 233 | if char == "\t" then 234 | local amt = options.tabsize - pos%options.tabsize 235 | for i = 1,amt do 236 | newstr[#newstr+1] = " " 237 | end 238 | pos = pos + amt 239 | else 240 | newstr[#newstr+1] = char 241 | pos = pos + unicode.charWidth(char) 242 | end 243 | end 244 | if map then 245 | cmap[#cmap+1] = pos+1 246 | end 247 | return table.concat(newstr), cmap 248 | end 249 | 250 | local function setTitleBar(buffer) 251 | local title = " New Buffer " 252 | if buffer.filename ~= nil then 253 | title = " File: " .. buffer.filename:match(".*/(.+)") .. " " 254 | end 255 | gpu.setForeground(options.bgcolor) 256 | gpu.setBackground(options.fgcolor) 257 | gpu.fill(1,1,gpuW,1," ") 258 | local startX = (gpuW - unicode.wlen(title))/2 259 | gpu.set(3,1,"Lua nano " .. nanoVERSION) 260 | if buffer.modified then 261 | local str = "Modified" 262 | gpu.set(gpuW-unicode.wlen(str)-2,1,str) 263 | end 264 | gpu.set(startX,1,title) 265 | gpu.setForeground(options.fgcolor) 266 | gpu.setBackground(options.bgcolor) 267 | end 268 | 269 | local function setModified(buffer,mod) 270 | if mod == nil then mod = true end 271 | if buffer.modified ~= mod then 272 | buffer.modified = mod 273 | setTitleBar(buffer) 274 | end 275 | end 276 | 277 | local timeout = math.huge 278 | local function setStatusBar(text) 279 | timeout = 0 280 | text = "[ " .. text .. " ]" 281 | gpu.fill(1,statusbarY,gpuW,1," ") 282 | gpu.setForeground(options.bgcolor) 283 | gpu.setBackground(options.fgcolor) 284 | local startX = (gpuW - unicode.wlen(text))/2 285 | gpu.set(startX,statusbarY,text) 286 | gpu.setForeground(options.fgcolor) 287 | gpu.setBackground(options.bgcolor) 288 | end 289 | 290 | local function createNewBuffer() 291 | return { 292 | filename=nil, 293 | modified=false, 294 | lines = { 295 | }, 296 | x = 1, 297 | tx = 1, 298 | y = 1, 299 | startLine = 1, 300 | } 301 | end 302 | 303 | local function createBuffer(filename) 304 | local buffer = createNewBuffer() 305 | if fs.exists(filename) then 306 | local file, err = io.open(filename,"rb") 307 | if file then 308 | buffer.filename = filename 309 | for line in file:lines() do 310 | buffer.lines[#buffer.lines+1] = line 311 | end 312 | file:close() 313 | setStatusBar("Read " .. #buffer.lines .. " line" .. (#buffer.lines ~= 1 and "s" or "")) 314 | else 315 | setStatusBar("Couldn't open " .. filename .. ": " .. err) 316 | end 317 | else 318 | buffer.filename = filename 319 | end 320 | if #buffer.lines == 0 then 321 | buffer.lines[1] = "" 322 | end 323 | buffers[#buffers+1] = buffer 324 | end 325 | 326 | local function drawLine(line,y) 327 | local fline = formatLine(line) 328 | if unicode.wlen(fline) < gpuW then 329 | fline = fline .. string.rep(" ",gpuW - unicode.wlen(fline)) 330 | elseif unicode.wlen(fline) > gpuW then 331 | fline = unicode.wtrunc(fline,gpuW) 332 | fline = fline .. string.rep(" ",gpuW - unicode.wlen(fline) - 1) .. "$" 333 | end 334 | gpu.set(1,y,fline) 335 | end 336 | 337 | local function updateActiveLine(simple) 338 | local line = buffer.lines[buffer.y] 339 | term.setCursorBlink(false) 340 | if not simple then 341 | local fline,map = formatLine(line,true) 342 | -- TODO: This is not nano style line clamping, plus it messes up when tabs have things behind them 343 | local xwant = map[math.min(buffer.x,#map)] 344 | local gwant = math.floor(gpuW*3/4) 345 | if unicode.wlen(fline) >= gpuW and xwant >= gwant then 346 | for i = 2,#map do 347 | if xwant-map[i]+1 < gwant then 348 | line = unicode.sub(line,i) 349 | xwant = xwant-map[i]+1 350 | break 351 | end 352 | end 353 | end 354 | term.setCursor(xwant,buffer.y-buffer.startLine+linesY) 355 | end 356 | drawLine(line,buffer.y-buffer.startLine+linesY) 357 | for i=1,2 do term.setCursorBlink(true)end 358 | end 359 | 360 | local function updateTX() 361 | local _,map = formatLine(buffer.lines[buffer.y],true) 362 | buffer.tx = map[math.min(buffer.x,#map)] 363 | end 364 | 365 | local function scrollBuffer() 366 | local amt 367 | local startLine = buffer.startLine 368 | if buffer.y-startLine < 0 then 369 | amt = buffer.y-startLine 370 | elseif buffer.y-startLine > (linesH-1) then 371 | amt = buffer.y-startLine-(linesH-1) 372 | end 373 | if not amt then return end 374 | term.setCursorBlink(false) 375 | startLine = startLine + amt 376 | buffer.startLine = startLine 377 | if amt > 0 then 378 | gpu.copy(1,linesY+amt,gpuW,linesH-amt,0,-amt) 379 | for i = startLine+linesH-1-amt, math.min(startLine+linesH-1,#buffer.lines) do 380 | drawLine(buffer.lines[i],i-startLine+linesY) 381 | end 382 | else 383 | gpu.copy(1,linesY,gpuW,linesH+amt,0,-amt) 384 | for i = startLine, math.min(startLine-amt-1,#buffer.lines) do 385 | drawLine(buffer.lines[i],i-startLine+linesY) 386 | end 387 | end 388 | term.setCursorBlink(true) 389 | end 390 | 391 | local function redraw() 392 | setTitleBar(buffer) 393 | -- TODO: Only clear the area lacking lines 394 | gpu.fill(1,linesY,gpuW,linesH," ") 395 | local startLine, amt = buffer.startLine 396 | if buffer.y-startLine < 0 then 397 | amt = buffer.y-startLine 398 | elseif buffer.y-startLine > (linesH-1) then 399 | amt = buffer.y-startLine-(linesH-1) 400 | end 401 | if amt then 402 | startLine = startLine + amt 403 | buffer.startLine = startLine 404 | end 405 | for i = startLine, math.min(startLine+linesH-1,#buffer.lines) do 406 | drawLine(buffer.lines[i],i-startLine+linesY) 407 | end 408 | updateActiveLine() 409 | end 410 | 411 | local function switchBuffers(index) 412 | buffer = buffers[index] 413 | if buffers.cur ~= nil then 414 | local name = "New Buffer" 415 | if buffer.filename ~= nil then 416 | name = buffer.filename:match(".*/(.+)") 417 | end 418 | setStatusBar("Switched to " .. name) 419 | end 420 | buffers.cur = index 421 | redraw() 422 | end 423 | 424 | local function statusPrompt(text) 425 | gpu.fill(1,statusbarY,gpuW,1," ") 426 | gpu.set(1,statusbarY,text) 427 | term.setCursor(unicode.wlen(text)+1,statusbarY) 428 | end 429 | 430 | local function resetColor() 431 | gpu.setForeground(options.fgcolor) 432 | gpu.setBackground(options.bgcolor) 433 | end 434 | 435 | local running = true 436 | local function clul() return unicode.len(buffer.lines[buffer.y]) end 437 | local binding = {} 438 | function binding.exit() 439 | -- TODO: Get a MYESNO mode 440 | if buffer.modified then 441 | gpu.setForeground(options.bgcolor) 442 | gpu.setBackground(options.fgcolor) 443 | statusPrompt("Save modified buffer (ANSWERING \"No\" WILL DESTROY CHANGES) ? ") 444 | while true do 445 | local e,_,c = event_pull() 446 | if e == "key_down" then 447 | c = unicode.lower(unicode.char(c)) 448 | local ctrl = kbd.isControlDown() 449 | if not ctrl and c == "y" then 450 | -- TODO: Get a MWRITEFILE mode 451 | -- TODO: Needs more options 452 | local oldname = "" 453 | if buffer.filename ~= nil then 454 | oldname = buffer.filename:match(".*/(.+)") 455 | end 456 | local filename = oldname 457 | ::tryagain:: 458 | statusPrompt("File Name to Write: ") 459 | -- TODO: HACK HACK HACK 460 | computer.pushSignal("key_down",computer.address(),0,keys.up) 461 | filename = term.read({filename},false) 462 | if filename then 463 | filename = filename:gsub("[\r\n]","") 464 | end 465 | if filename == "" or filename == nil then 466 | resetColor() 467 | setStatusBar("Cancelled") 468 | return 469 | end 470 | if filename ~= oldname and fs.exists(shell.resolve(filename)) then 471 | -- TODO: Again, MYESNO mode 472 | statusPrompt("File exists, OVERWRITE ? ") 473 | while true do 474 | local e,_,c = event_pull() 475 | c = unicode.lower(unicode.char(c)) 476 | if e == "key_down" then 477 | local ctrl = kbd.isControlDown() 478 | if not ctrl and c == "y" then 479 | break 480 | elseif (not ctrl and c == "n") or (ctrl and c == "\3") then 481 | goto tryagain 482 | end 483 | end 484 | end 485 | end 486 | local file, err = io.open(shell.resolve(filename),"wb") 487 | if not file then 488 | resetColor() 489 | setStatusBar("Error writing " .. filename .. ": " .. err) 490 | return 491 | end 492 | for i = 1,#buffer.lines do 493 | file:write(buffer.lines[i] .. "\n") 494 | end 495 | file:close() 496 | break 497 | elseif not ctrl and c == "n" then 498 | break 499 | elseif ctrl and c == "\3" then 500 | resetColor() 501 | setStatusBar("Cancelled") 502 | return 503 | end 504 | end 505 | end 506 | end 507 | gpu.setForeground(options.fgcolor) 508 | gpu.setBackground(options.bgcolor) 509 | table.remove(buffers,buffers.cur) 510 | buffers.cur = math.min(buffers.cur,#buffers) 511 | if buffers.cur < 1 then 512 | running = false 513 | else 514 | switchBuffers(buffers.cur) 515 | end 516 | end 517 | function binding.left() 518 | if buffer.x > 1 or buffer.y > 1 then 519 | buffer.x = buffer.x - 1 520 | if buffer.x < 1 then 521 | updateActiveLine(true) 522 | buffer.y = buffer.y - 1 523 | buffer.x = unicode.len(buffer.lines[buffer.y])+1 524 | scrollBuffer() 525 | end 526 | updateTX() 527 | updateActiveLine() 528 | end 529 | end 530 | function binding.right() 531 | if buffer.x < clul()+1 or buffer.y < #buffer.lines then 532 | buffer.x = buffer.x + 1 533 | if buffer.x > clul()+1 then 534 | updateActiveLine(true) 535 | buffer.y = buffer.y + 1 536 | buffer.x = 1 537 | scrollBuffer() 538 | end 539 | updateTX() 540 | updateActiveLine() 541 | end 542 | end 543 | function binding.prevline() 544 | if buffer.y > 1 then 545 | updateActiveLine(true) 546 | buffer.y = buffer.y - 1 547 | local _,map = formatLine(buffer.lines[buffer.y],true) 548 | for i = 1,#map do 549 | if map[i] > buffer.tx then break end 550 | buffer.x = i 551 | end 552 | scrollBuffer() 553 | updateActiveLine() 554 | end 555 | end 556 | function binding.nextline() 557 | if buffer.y < #buffer.lines then 558 | updateActiveLine(true) 559 | buffer.y = buffer.y + 1 560 | local _,map = formatLine(buffer.lines[buffer.y],true) 561 | for i = 1,#map do 562 | if map[i] > buffer.tx then break end 563 | buffer.x = i 564 | end 565 | scrollBuffer() 566 | updateActiveLine() 567 | end 568 | end 569 | function binding.prevpage() 570 | if buffer.y > 1 then 571 | local diff = math.max(buffer.y - linesH, 1) - buffer.y 572 | buffer.y = buffer.y + diff 573 | buffer.startLine = math.max(buffer.startLine + diff, 1) 574 | local _,map = formatLine(buffer.lines[buffer.y],true) 575 | for i = 1,#map do 576 | if map[i] > buffer.tx then break end 577 | buffer.x = i 578 | end 579 | redraw() 580 | end 581 | end 582 | function binding.nextpage() 583 | if buffer.y < #buffer.lines then 584 | local diff = math.min(buffer.y + linesH, #buffer.lines) - buffer.y 585 | buffer.y = buffer.y + diff 586 | buffer.startLine = math.min(buffer.startLine + diff, #buffer.lines-linesH+1) 587 | local _,map = formatLine(buffer.lines[buffer.y],true) 588 | for i = 1,#map do 589 | if map[i] > buffer.tx then break end 590 | buffer.x = i 591 | end 592 | redraw() 593 | end 594 | end 595 | function binding.home() 596 | if buffer.x > 1 then 597 | buffer.x = 1 598 | updateTX() 599 | updateActiveLine() 600 | end 601 | end 602 | binding["end"] = function() 603 | if buffer.x < clul()+1 then 604 | buffer.x = clul()+1 605 | updateTX() 606 | updateActiveLine() 607 | end 608 | end 609 | function binding.backspace() 610 | if buffer.x == 1 then 611 | if buffer.y > 1 then 612 | setModified(buffer) 613 | buffer.x = unicode.len(buffer.lines[buffer.y-1])+1 614 | buffer.lines[buffer.y-1] = buffer.lines[buffer.y-1] .. buffer.lines[buffer.y] 615 | table.remove(buffer.lines,buffer.y) 616 | buffer.y = buffer.y - 1 617 | -- TODO: Don't redraw everything 618 | redraw() 619 | end 620 | else 621 | setModified(buffer) 622 | local line = buffer.lines[buffer.y] 623 | buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-2) .. unicode.sub(line,buffer.x) 624 | buffer.x = buffer.x - 1 625 | updateActiveLine() 626 | end 627 | updateTX() 628 | end 629 | function binding.delete() 630 | if buffer.x >= clul()+1 then 631 | setModified(buffer) 632 | if buffer.y < #buffer.lines then 633 | buffer.lines[buffer.y] = buffer.lines[buffer.y] .. buffer.lines[buffer.y+1] 634 | table.remove(buffer.lines,buffer.y+1) 635 | -- TODO: Don't redraw everything 636 | redraw() 637 | end 638 | else 639 | setModified(buffer) 640 | local line = buffer.lines[buffer.y] 641 | buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-1) .. unicode.sub(line,buffer.x+1) 642 | updateActiveLine() 643 | end 644 | end 645 | function binding.enter() 646 | setModified(buffer) 647 | local line = buffer.lines[buffer.y] 648 | table.insert(buffer.lines,buffer.y+1,unicode.sub(line,buffer.x)) 649 | buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-1) 650 | buffer.x = 1 651 | buffer.tx = 1 652 | buffer.y = buffer.y + 1 653 | -- TODO: Don't redraw everything 654 | redraw() 655 | end 656 | function binding.prevbuf() 657 | buffers.cur = ((buffers.cur-2)%#buffers)+1 658 | switchBuffers(buffers.cur) 659 | end 660 | function binding.nextbuf() 661 | buffers.cur = (buffers.cur%#buffers)+1 662 | switchBuffers(buffers.cur) 663 | end 664 | function binding.curpos() 665 | -- TODO: Character count 666 | local la,lb = buffer.y,#buffer.lines 667 | local oa,ob = buffer.x,#buffer.lines[buffer.y]+1 668 | local cb = 0 669 | for i = 1,buffer.y-1 do 670 | cb = cb + #buffer.lines[i]+1 671 | end 672 | local ca = cb + buffer.x 673 | for i = buffer.y,#buffer.lines do 674 | cb = cb + #buffer.lines[i]+1 675 | end 676 | setStatusBar(string.format("line %d/%d (%d%%), col %d/%d (%d%%), char %d/%d (%d%%)",la,lb,la/lb*100+0.5, oa,ob,oa/ob*100+0.5, ca,cb,ca/cb*100+0.5)) 677 | end 678 | 679 | -- TODO: Line and column arguments 680 | -- Load files 681 | if #args > 0 then 682 | for i = 1,#args do 683 | createBuffer(shell.resolve(args[i])) 684 | end 685 | if #buffers > 1 then 686 | setStatusBar("Read " .. #buffers[1].lines .. " line" .. (#buffers[1].lines ~= 1 and "s" or "")) 687 | end 688 | else 689 | local buffer = createNewBuffer() 690 | buffer.lines[1] = "" 691 | buffers[#buffers+1] = buffer 692 | end 693 | 694 | -- Show the first file 695 | switchBuffers(1) 696 | 697 | while running do 698 | local e = { event_pull() } 699 | if e[1] == "key_down" then 700 | local char, code = e[3], e[4] 701 | local ctrl = kbd.isControlDown() 702 | local alt = kbd.isAltDown() 703 | local scp,sc = keys[code]:sub(1,1), keys[code]:sub(2) 704 | local schar 705 | if not kbd.isControl(char) and char ~= 32 then 706 | schar = unicode.upper(unicode.char(char)) 707 | else 708 | schar = unicode.upper(keys[code]) 709 | end 710 | if ctrl then 711 | -- TODO: This doesn't cover everything 712 | if unicode.len(keys[code]) == 1 then 713 | schar = unicode.upper(keys[code]) 714 | end 715 | end 716 | if ctrl then 717 | schar = "^" .. schar 718 | elseif alt then 719 | schar = "M-" .. schar 720 | end 721 | if (scp == "l" or scp == "r") and (sc == "control" or sc == "menu" or sc == "shift") then 722 | elseif bind[mode][schar] ~= nil then 723 | local cmd = bind[mode][schar] 724 | if binding[cmd] ~= nil then 725 | binding[cmd]() 726 | else 727 | setStatusBar("Binding \"" .. cmd .. "\" Unimplemented") 728 | end 729 | elseif ctrl or alt then 730 | setStatusBar("Unknown Command") 731 | elseif not kbd.isControl(char) or char == 9 then 732 | timeout = timeout + 1 733 | if timeout >= (options.quickblank and 1 or 25) then 734 | gpu.fill(1,statusbarY,gpuW,1," ") 735 | end 736 | setModified(buffer) 737 | local line = buffer.lines[buffer.y] 738 | buffer.lines[buffer.y] = unicode.sub(line,1,buffer.x-1) .. unicode.char(char) .. unicode.sub(line,buffer.x) 739 | buffer.x = buffer.x + 1 740 | updateTX() 741 | updateActiveLine() 742 | end 743 | end 744 | end 745 | 746 | term.clear() 747 | term.setCursorBlink(false) 748 | 749 | -- Unfix keys.none 750 | keys["none"] = nil 751 | keys[0] = nil 752 | -------------------------------------------------------------------------------- /ocwe.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | if not component.isAvailable("debug") then 3 | errprint("Debug card is required") 4 | return 5 | end 6 | 7 | local player, username 8 | io.stdout:write("Input player name: ") 9 | while true do 10 | player = io.read() 11 | local health,err = component.debug.getPlayer(player).getHealth() 12 | if not health or err == "player is offline" then 13 | print("No such player") 14 | io.stdout:write("Input player name: ") 15 | else 16 | print("Bound OCWE to " .. player) 17 | username = player 18 | player = component.debug.getPlayer(player) 19 | break 20 | end 21 | end 22 | 23 | local world = component.debug.getWorld() 24 | 25 | local info = {} 26 | 27 | local function decodeBlock(block) 28 | if block:match(".*:.+") then 29 | local id,damage = block:match("(.*):(.+)") 30 | if tonumber(id) ~= nil and tonumber(damage) ~= nil then 31 | return tonumber(id), tonumber(damage) 32 | end 33 | else 34 | if tonumber(block) ~= nil then 35 | return tonumber(block), 0 36 | end 37 | end 38 | end 39 | 40 | while true do 41 | io.stdout:write("> ") 42 | local line = io.read() 43 | if not line then break end 44 | local parse = {} 45 | for item in (line .. " "):gmatch("(.-) ") do 46 | if item ~= "" then 47 | parse[#parse + 1] = item 48 | end 49 | end 50 | if parse[1] == nil then 51 | elseif parse[1] == "pos1" then 52 | local pX,pY,pZ = player.getPosition() 53 | if not pX then 54 | print("Unexpected error: " .. pY) 55 | else 56 | pX,pY,pZ = math.floor(pX),math.floor(pY),math.floor(pZ) 57 | if info.pos1 == nil then 58 | info.pos1 = {} 59 | end 60 | info.pos1[1] = pX 61 | info.pos1[2] = pY 62 | info.pos1[3] = pZ 63 | local size 64 | if info.pos1 and info.pos2 then 65 | size = (math.abs(info.pos1[1]-info.pos2[1])+1) * (math.abs(info.pos1[2]-info.pos2[2])+1) * (math.abs(info.pos1[3]-info.pos2[3])+1) 66 | end 67 | print("Set pos1 to (" .. pX .. "," .. pY .. "," .. pZ .. ")" .. (size and (" (" .. size .. " blocks)") or "")) 68 | end 69 | elseif parse[1] == "pos2" then 70 | local pX,pY,pZ = player.getPosition() 71 | if not pX then 72 | print("Unexpected error: " .. pY) 73 | else 74 | pX,pY,pZ = math.floor(pX),math.floor(pY),math.floor(pZ) 75 | if info.pos2 == nil then 76 | info.pos2 = {} 77 | end 78 | info.pos2[1] = pX 79 | info.pos2[2] = pY 80 | info.pos2[3] = pZ 81 | local size 82 | if info.pos1 and info.pos2 then 83 | size = (math.abs(info.pos1[1]-info.pos2[1])+1) * (math.abs(info.pos1[2]-info.pos2[2])+1) * (math.abs(info.pos1[3]-info.pos2[3])+1) 84 | end 85 | print("Set pos2 to (" .. pX .. "," .. pY .. "," .. pZ .. ")" .. (size and (" (" .. size .. " blocks)") or "")) 86 | end 87 | elseif parse[1] == "set" then 88 | local id, damage = decodeBlock(parse[2]) 89 | if parse[2] == nil then 90 | print("Invalid argument, Expected: set block") 91 | elseif not id or not damage then 92 | print("Invalid block code") 93 | elseif not info.pos1 then 94 | print("Please set pos1") 95 | elseif not info.pos2 then 96 | print("Please set pos2") 97 | else 98 | local size = (math.abs(info.pos1[1]-info.pos2[1])+1) * (math.abs(info.pos1[2]-info.pos2[2])+1) * (math.abs(info.pos1[3]-info.pos2[3])+1) 99 | print("Setting " .. size .. " block" .. (size == 1 and "" or "s")) 100 | world.setBlocks(info.pos1[1],info.pos1[2],info.pos1[3],info.pos2[1],info.pos2[2],info.pos2[3],id,damage) 101 | end 102 | elseif parse[1] == "replace" then 103 | local fid, fdamage = decodeBlock(parse[2]) 104 | local tid, tdamage = decodeBlock(parse[3]) 105 | if parse[2] == nil or parse[3] == nil then 106 | print("Invalid argument, Expected: replace from-block to-block") 107 | elseif not fid or not fdamage then 108 | print("Invalid from-block code") 109 | elseif not tid or not tdamage then 110 | print("Invalid to-block code") 111 | elseif not info.pos1 then 112 | print("Please set pos1") 113 | elseif not info.pos2 then 114 | print("Please set pos2") 115 | else 116 | local blocks = 0 117 | 118 | local x1,x2 = math.min(info.pos1[1],info.pos2[1]),math.max(info.pos1[1],info.pos2[1]) 119 | local y1,y2 = math.min(info.pos1[2],info.pos2[2]),math.max(info.pos1[2],info.pos2[2]) 120 | local z1,z2 = math.min(info.pos1[3],info.pos2[3]),math.max(info.pos1[3],info.pos2[3]) 121 | 122 | for y = y1,y2 do 123 | for x = x1,x2 do 124 | for z = z1,z2 do 125 | local cid = world.getBlockId(x,y,z) 126 | local cdamage = world.getMetadata(x,y,z) 127 | if cid == fid and cdamage == fdamage then 128 | world.setBlock(x,y,z,tid,tdamage) 129 | blocks = blocks + 1 130 | end 131 | end 132 | end 133 | end 134 | print("Replaced " .. blocks .. " block" .. (blocks == 1 and "" or "s")) 135 | end 136 | elseif parse[1] == "distr" then 137 | if not info.pos1 then 138 | print("Please set pos1") 139 | elseif not info.pos2 then 140 | print("Please set pos2") 141 | else 142 | local size = (math.abs(info.pos1[1]-info.pos2[1])+1) * (math.abs(info.pos1[2]-info.pos2[2])+1) * (math.abs(info.pos1[3]-info.pos2[3])+1) 143 | 144 | local x1,x2 = math.min(info.pos1[1],info.pos2[1]),math.max(info.pos1[1],info.pos2[1]) 145 | local y1,y2 = math.min(info.pos1[2],info.pos2[2]),math.max(info.pos1[2],info.pos2[2]) 146 | local z1,z2 = math.min(info.pos1[3],info.pos2[3]),math.max(info.pos1[3],info.pos2[3]) 147 | 148 | local stats = {} 149 | 150 | for y = y1,y2 do 151 | for x = x1,x2 do 152 | for z = z1,z2 do 153 | local cid = world.getBlockId(x,y,z) 154 | local cdamage = world.getMetadata(x,y,z) 155 | local id = cid .. ":" .. cdamage 156 | if stats[id] == nil then 157 | stats[id] = 0 158 | end 159 | stats[id] = stats[id] + 1 160 | end 161 | end 162 | end 163 | local stats2 = {} 164 | for k,v in pairs(stats) do 165 | stats2[#stats2 + 1] = {k,v} 166 | end 167 | table.sort(stats2,function(a,b) return a[2] < b[2] end) 168 | for i = 1,#stats2 do 169 | print(string.format("%-8d%s",stats2[i][2],stats2[i][1])) 170 | end 171 | end 172 | elseif parse[1] == "walls" then 173 | local id, damage = decodeBlock(parse[2]) 174 | if parse[2] == nil then 175 | print("Invalid argument, Expected: walls block") 176 | elseif not id or not damage then 177 | print("Invalid block code") 178 | elseif not info.pos1 then 179 | print("Please set pos1") 180 | elseif not info.pos2 then 181 | print("Please set pos2") 182 | else 183 | local size = "NaN" -- >_> why is this confusing. 184 | print("Setting " .. size .. " block" .. (size == 1 and "" or "s")) 185 | world.setBlocks(info.pos1[1],info.pos1[2],info.pos1[3],info.pos1[1],info.pos2[2],info.pos2[3],id,damage) 186 | world.setBlocks(info.pos2[1],info.pos1[2],info.pos1[3],info.pos2[1],info.pos2[2],info.pos2[3],id,damage) 187 | world.setBlocks(info.pos1[1],info.pos1[2],info.pos1[3],info.pos2[1],info.pos2[2],info.pos1[3],id,damage) 188 | world.setBlocks(info.pos1[1],info.pos1[2],info.pos2[3],info.pos2[1],info.pos2[2],info.pos2[3],id,damage) 189 | end 190 | elseif parse[1] == "tp" then 191 | if parse[2] == nil then 192 | print("Invalid argument, Expected: tp player") 193 | else 194 | local top = component.debug.getPlayer(parse[2]) 195 | local health,err = top.getHealth() 196 | if not health or err == "player is offline" then 197 | print("No such player") 198 | else 199 | print("Teleporting " .. username .. " to " .. parse[2]) 200 | tX,tY,tZ = top.getPosition() 201 | player.setPosition(tX,tY,tZ) 202 | end 203 | end 204 | elseif parse[1] == "kill" then 205 | if parse[2] == nil then 206 | print("Invalid argument, Expected: kill player") 207 | else 208 | local top = component.debug.getPlayer(parse[2]) 209 | local health,err = top.getHealth() 210 | if not health or err == "player is offline" then 211 | print("No such player") 212 | else 213 | print("Killing " .. parse[2]) 214 | top.setHealth(-1) 215 | end 216 | end 217 | elseif parse[1] == "quit" then 218 | print("Goodbye!") 219 | return 220 | else 221 | print("Unknown command: " .. parse[1]) 222 | end 223 | end 224 | -------------------------------------------------------------------------------- /ooci/ctif2ooci.lua: -------------------------------------------------------------------------------- 1 | local args = {...} 2 | if #args ~= 2 then 3 | print("Usage: ctif2ooci filename outname") 4 | return 5 | end 6 | 7 | local file, err = io.open(args[1], "rb") 8 | if not file then 9 | print(err) 10 | return 11 | end 12 | local function check(a, b, c) 13 | if a ~= b then 14 | file:close() 15 | error(c,0) 16 | os.exit(1) 17 | end 18 | return a 19 | end 20 | local function r8() 21 | return file:read(1):byte() 22 | end 23 | local function r16() 24 | return r8(file)+(r8(file)*256) 25 | end 26 | 27 | check(file:read(4), "CTIF", "Invalid magic bytes") 28 | check(r8(), 1, "Unknown header version") 29 | check(r8(), 0, "Unknown platform variant") 30 | check(r16(), 1, "Unknown platform ID") 31 | local width, height = r16(), r16() 32 | check(r8(), 2, "Invalid character width") 33 | check(r8(), 4, "Invalid character height") 34 | local bpp = r8() 35 | check(r8(), 3, "Invalid palette entry size") 36 | check(r16(), 16, "Invalid palette entry count") 37 | local pal={} 38 | for i=0,15 do 39 | pal[i] = {file:read(3):byte(1, -1)} 40 | end 41 | local img={} 42 | for y=1,height do 43 | img[y]={} 44 | for x=1,width do 45 | img[y][x]={file:read(3):byte(1, -1)} 46 | end 47 | end 48 | file:close() 49 | 50 | local newfile, err = io.open(args[2],"wb") 51 | if not newfile then 52 | print(err) 53 | return 54 | end 55 | local function w8(...) 56 | return newfile:write(string.char(...)) 57 | end 58 | 59 | -- Gather palette usage stats 60 | local prestats = {} 61 | for y = 1,height do 62 | for x = 1,width do 63 | local key = string.char(img[y][x][1], img[y][x][2]) 64 | if prestats[key] == nil then 65 | prestats[key] = 0 66 | end 67 | prestats[key] = prestats[key] + 1 68 | end 69 | end 70 | local stats = {} 71 | for k,usage in pairs(prestats) do 72 | local ti,bi = k:byte(1,-1) 73 | stats[#stats+1] = {ti,bi,usage} 74 | end 75 | table.sort(stats,function(a,b) return a[3] > b[3] end) 76 | 77 | -- Borrowed from ctif-oc.lua 78 | local q = {} 79 | for i=0,255 do 80 | local dat = (i & 0x01) << 7 81 | dat = dat | (i & 0x02) >> 1 << 6 82 | dat = dat | (i & 0x04) >> 2 << 5 83 | dat = dat | (i & 0x08) >> 3 << 2 84 | dat = dat | (i & 0x10) >> 4 << 4 85 | dat = dat | (i & 0x20) >> 5 << 1 86 | dat = dat | (i & 0x40) >> 6 << 3 87 | dat = dat | (i & 0x80) >> 7 88 | q[i] = dat 89 | end 90 | 91 | -- Write header 92 | newfile:write("OOCI") 93 | w8(1) -- Version 94 | w8(width,0) -- Width 95 | w8(height,0) -- Height 96 | -- Write palette colors 97 | w8(16) -- Number of Palette Entries 98 | for i = 0,15 do 99 | w8(table.unpack(pal[i],1,3)) 100 | end 101 | 102 | local callsbg, callsfg, callsd = 0, 0, 0 103 | local lastti, lastbi 104 | 105 | io.stdout:write("Testing drawing methods ... ") 106 | io.stdout:flush() 107 | 108 | -- Horizontal, Vertical, and Dynamic check 109 | local horiz = {} 110 | local vertz = {} 111 | local dynam = {} 112 | os.sleep(0) 113 | for i = 1,#stats do 114 | local ti = stats[i][1] 115 | local bi = stats[i][2] 116 | local calls = 0 117 | for y = 1,height do 118 | local blockcount = 0 119 | for x = 1,width do 120 | if img[y][x][1] == ti and img[y][x][2] == bi then 121 | blockcount = blockcount + 1 122 | else 123 | if blockcount > 0 then 124 | calls = calls + 1 125 | end 126 | blockcount = 0 127 | end 128 | end 129 | if blockcount > 0 then 130 | calls = calls + 1 131 | end 132 | end 133 | horiz[i] = calls 134 | end 135 | os.sleep(0) 136 | for i = 1,#stats do 137 | local ti = stats[i][1] 138 | local bi = stats[i][2] 139 | local calls = 0 140 | for x = 1,width do 141 | local blockcount = 0 142 | for y = 1,height do 143 | if img[y][x][1] == ti and img[y][x][2] == bi then 144 | blockcount = blockcount + 1 145 | else 146 | if blockcount > 0 then 147 | calls = calls + 1 148 | end 149 | blockcount = 0 150 | end 151 | end 152 | if blockcount > 0 then 153 | calls = calls + 1 154 | end 155 | end 156 | vertz[i] = calls 157 | end 158 | os.sleep(0) 159 | for i = 1,#stats do 160 | local ti = stats[i][1] 161 | local bi = stats[i][2] 162 | local calls = 0 163 | local donttouch = {} 164 | for y = 1,height do 165 | donttouch[y] = {} 166 | end 167 | for y = 1,height do 168 | for x = 1,width do 169 | if img[y][x][1] == ti and img[y][x][2] == bi and donttouch[y][x] ~= true then 170 | local xlength = 0 171 | local ylength = 0 172 | for nx = x,width do 173 | if img[y][nx][1] ~= ti or img[y][nx][2] ~= bi then break end 174 | xlength = xlength + 1 175 | end 176 | for yx = y,height do 177 | if img[yx][x][1] ~= ti or img[yx][x][2] ~= bi then break end 178 | ylength = ylength + 1 179 | end 180 | if xlength > ylength then 181 | calls = calls + 1 182 | for brk = x,x+xlength-1 do 183 | donttouch[y][brk] = true 184 | end 185 | else 186 | calls = calls + 1 187 | for brk = y,y+ylength-1 do 188 | donttouch[brk][x] = true 189 | end 190 | end 191 | end 192 | end 193 | end 194 | dynam[i] = calls 195 | end 196 | os.sleep(0) 197 | print("Done!") 198 | io.stdout:write("Writing Image ... ") 199 | -- Go through stack 200 | local function horizemit(braille,startx,y) 201 | if #braille > 0 then 202 | local same = true 203 | for i = 2,#braille do 204 | if braille[i] ~= braille[1] then 205 | same = false 206 | break 207 | end 208 | end 209 | if #braille>1 then 210 | if same then 211 | w8(0x03,startx,y,#braille,braille[1]) 212 | else 213 | w8(0x05,startx,y,#braille,table.unpack(braille)) 214 | end 215 | else 216 | w8(0x02,startx,y,braille[1]) 217 | end 218 | callsd = callsd + 1 219 | end 220 | end 221 | local function vertzemit(braille,x,starty) 222 | if #braille > 0 then 223 | local same = true 224 | for i = 2,#braille do 225 | if braille[i] ~= braille[1] then 226 | same = false 227 | break 228 | end 229 | end 230 | if #braille>1 then 231 | if same then 232 | w8(0x04,x,starty,#braille,braille[1]) 233 | else 234 | w8(0x06,x,starty,#braille,table.unpack(braille)) 235 | end 236 | else 237 | w8(0x02,x,starty,braille[1]) 238 | end 239 | callsd = callsd + 1 240 | end 241 | end 242 | for i = 1,#stats do 243 | local ti = stats[i][1] 244 | local bi = stats[i][2] 245 | if lastti ~= ti then 246 | w8(0x00, ti) 247 | callsbg = callsbg + 1 248 | lastti = ti 249 | end 250 | if lastbi ~= bi and bi ~= ti then 251 | w8(0x01, bi) 252 | callsfg = callsfg + 1 253 | lastbi = bi 254 | end 255 | if horiz[i] < vertz[i] and horiz[i] < dynam[i] then 256 | for y = 1,height do 257 | local braille = {} 258 | local startx = 1 259 | for x = 1,width do 260 | if img[y][x][1] == ti and img[y][x][2] == bi then 261 | if #braille == 0 then startx = x end 262 | braille[#braille+1] = q[img[y][x][3]] 263 | else 264 | horizemit(braille,startx,y) 265 | braille = {} 266 | end 267 | end 268 | horizemit(braille,startx,y) 269 | end 270 | elseif vertz[i] < horiz[i] and vertz[i] < dynam[i] then 271 | for x = 1,width do 272 | local braille = {} 273 | local starty = 1 274 | for y = 1,height do 275 | if img[y][x][1] == ti and img[y][x][2] == bi then 276 | if #braille == 0 then starty = y end 277 | braille[#braille+1] = q[img[y][x][3]] 278 | else 279 | vertzemit(braille,x,starty) 280 | braille = {} 281 | end 282 | end 283 | vertzemit(braille,x,starty) 284 | end 285 | else 286 | local donttouch = {} 287 | for y = 1,height do 288 | donttouch[y] = {} 289 | end 290 | for y = 1,height do 291 | for x = 1,width do 292 | if img[y][x][1] == ti and img[y][x][2] == bi and donttouch[y][x] ~= true then 293 | local xlength = 0 294 | local ylength = 0 295 | for nx = x,width do 296 | if img[y][nx][1] ~= ti or img[y][nx][2] ~= bi then break end 297 | xlength = xlength + 1 298 | end 299 | for yx = y,height do 300 | if img[yx][x][1] ~= ti or img[yx][x][2] ~= bi then break end 301 | ylength = ylength + 1 302 | end 303 | if xlength == 1 and ylength == 1 then 304 | w8(0x02,x,y,q[img[y][x][3]]) 305 | donttouch[y][x] = true 306 | else 307 | local braille = {} 308 | local extra 309 | if xlength > ylength then 310 | extra = 0x03 311 | for brk = x,x+xlength-1 do 312 | donttouch[y][brk] = true 313 | braille[#braille+1] = q[img[y][brk][3]] 314 | end 315 | else 316 | extra = 0x04 317 | for brk = y,y+ylength-1 do 318 | donttouch[brk][x] = true 319 | braille[#braille+1] = q[img[brk][x][3]] 320 | end 321 | end 322 | local same = true 323 | for i = 2,#braille do 324 | if braille[i] ~= braille[1] then 325 | same = false 326 | break 327 | end 328 | end 329 | if same and #braille > 2 then 330 | w8(extra,x,y,#braille,braille[1]) 331 | else 332 | w8(extra+2,x,y,#braille,table.unpack(braille)) 333 | end 334 | end 335 | callsd = callsd + 1 336 | end 337 | end 338 | end 339 | end 340 | end 341 | newfile:close() 342 | print("Done!") 343 | print(callsbg,callsfg,callsd,callsbg+callsfg+callsd,((callsbg/128)+(callsfg/128)+(callsd/256))/20 .. "s") 344 | -------------------------------------------------------------------------------- /ooci/index.txt: -------------------------------------------------------------------------------- 1 | Optimized OpenComputers Image utilties 2 | 3 | ctif2ooci 4 | Compile CTIF formatted images to OOCI images 5 | 6 | ooci2ctif 7 | Decompile OOCI images back into CTIF images 8 | 9 | ooci-view 10 | View Compiled OOCI images 11 | 12 | ooci.txt 13 | CTIF and OOCI documentation 14 | -------------------------------------------------------------------------------- /ooci/ooci-view.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local event = require("event") 3 | local unicode = require("unicode") 4 | local shell = require("shell") 5 | local gpu = component.gpu 6 | 7 | local args, options = shell.parse(...) 8 | if #args ~= 1 then 9 | print([[Usage: ooci-oc filename 10 | 11 | Options: 12 | --nores Disable resolution switching 13 | --noswap Disable resolution restoration 14 | --nopause Don't pause when finished]]) 15 | return 16 | end 17 | 18 | local oerror = error 19 | function error(str) 20 | oerror("Error: " .. str,0) 21 | os.exit(1) 22 | end 23 | 24 | local file, err = io.open(args[1], "rb") 25 | if not file then 26 | error(err) 27 | end 28 | function r8() 29 | local char = file:read(1) 30 | return char and char:byte() 31 | end 32 | local function r16() 33 | return r8(file) | (r8(file) << 8) 34 | end 35 | 36 | if file:read(4) ~= "OOCI" then 37 | error("Invalid header!") 38 | end 39 | 40 | local hdrVersion = r8() 41 | if hdrVersion > 1 then 42 | error("Unknown header version: " .. hdrVersion) 43 | end 44 | 45 | local width, height = r16(), r16() 46 | 47 | local paletteCount = r8() 48 | if paletteCount > 16 then 49 | error("Unsupported palette entry count: " .. paletteCount) 50 | end 51 | 52 | local pal = {} 53 | for i=0,paletteCount-1 do 54 | local b, g, r = file:read(3):byte(1,-1) 55 | pal[i] = r << 16 | g << 8 | b 56 | local _=(gpu.getPaletteColor(i)~=pal[i] and gpu.setPaletteColor(i, pal[i])) 57 | end 58 | for i=0,239 do 59 | local r = math.floor((math.floor(i / 40.0) % 6) * 255 / 5.0) 60 | local g = math.floor((math.floor(i / 5.0) % 8) * 255 / 7.0) 61 | local b = math.floor((i % 5) * 255 / 4.0) 62 | pal[i+16] = r << 16 | g << 8 | b 63 | end 64 | 65 | local q={} 66 | for i=0,255 do 67 | q[i]=unicode.char(0x2800+i) 68 | end 69 | local function byte2char(a) return q[a:byte()] end 70 | 71 | local oldw,oldh 72 | if not options.nores then 73 | oldw,oldh = gpu.getResolution() 74 | gpu.setResolution(width,height) 75 | end 76 | 77 | while true do 78 | local inst = r8() 79 | if not inst then 80 | break 81 | elseif inst == 0x00 then 82 | local col = r8() 83 | if col < 16 then 84 | gpu.setBackground(col,true) 85 | else 86 | gpu.setBackground(pal[col]) 87 | end 88 | elseif inst == 0x01 then 89 | local col = r8() 90 | if col < 16 then 91 | gpu.setForeground(col,true) 92 | else 93 | gpu.setForeground(pal[col]) 94 | end 95 | else 96 | local x = r8() 97 | local y = r8() 98 | if inst == 0x02 then 99 | gpu.set(x,y,q[r8()]) 100 | elseif inst == 0x03 then 101 | local len = r8() 102 | gpu.set(x,y,q[r8()]:rep(len)) 103 | elseif inst == 0x04 then 104 | local len = r8() 105 | gpu.set(x,y,q[r8()]:rep(len),true) 106 | elseif inst == 0x05 then 107 | gpu.set(x,y,(file:read(r8()):gsub(".", byte2char))) 108 | elseif inst == 0x06 then 109 | gpu.set(x,y,(file:read(r8()):gsub(".", byte2char)),true) 110 | else 111 | error("Unknown Instruction: " .. inst) 112 | end 113 | end 114 | end 115 | if not options.nopause then 116 | while true do 117 | local name,addr,char,key,player = event.pull("key_down") 118 | if key == 0x10 then 119 | break 120 | end 121 | end 122 | end 123 | gpu.setBackground(0, false) 124 | gpu.setForeground(16777215, false) 125 | if not options.nores and not options.noswap then 126 | gpu.setResolution(oldw, oldh) 127 | gpu.fill(1, 1, oldw, oldh, " ") 128 | end 129 | -------------------------------------------------------------------------------- /ooci/ooci.txt: -------------------------------------------------------------------------------- 1 | Size | Description 2 | --------+-------------------------- 3 | 4 bytes | "CTIF" 4 | 1 byte | Header Version (1) 5 | 1 byte | Platform Variant (0) 6 | 2 bytes | Platform ID (1 = OC, 2 = CC) 7 | 2 bytes | Image Width (Characters) 8 | 2 bytes | Image Height (Characters) 9 | 1 byte | Character Width (Pixels) 10 | 1 byte | Character Height (Pixels) 11 | 1 byte | Bits per Character [BPC] 12 | 1 byte | Bytes per Palette entry 13 | 2 bytes | Number of Palette entries 14 | --------+-------------------------- 15 | N/A | Palette entries 16 | --------+-------------------------- 17 | N/A | Image Data 18 | 19 | Image Data: 20 | If BPC == 4 21 | 1 byte | Background and Foreground Color (BG = 0b11110000, FG = 0b00001111) 22 | Else If BPC == 8 23 | 1 byte | Background Color 24 | 1 byte | Foreground Color 25 | 1 byte | Character bits (Platform ID dependant) 26 | 27 | ================================================================================ 28 | 29 | Size | Description 30 | --------+-------------------------- 31 | 4 bytes | "OOCI" 32 | 1 byte | Header Version (1) 33 | 2 bytes | Image Width (Characters) 34 | 2 bytes | Image Height (Characters) 35 | 1 byte | Number of Palette entries 36 | --------+-------------------------- 37 | N/A | Palette entries 38 | --------+-------------------------- 39 | N/A | Instructions 40 | 41 | Note: Image Width and Height are only a guideline 42 | 43 | Instructions: 44 | 0x00: Set Background 45 | 1 byte | 0x00 46 | 1 byte | Background Color (0-15 = Palette, 16-255 = RGB685) 47 | 48 | 0x01: Set Foreground 49 | 1 byte | 0x01 50 | 1 byte | Foreground Color (0-15 = Palette, 16-255 = RGB685) 51 | 52 | 0x02: Set Character 53 | 1 byte | 0x02 54 | 1 byte | X position 55 | 1 byte | Y position 56 | 1 byte | Character (0-255 = 0x2800-0x28FF) 57 | 58 | 0x03: Set Character Horizontal 59 | 1 byte | 0x03 60 | 1 byte | X position 61 | 1 byte | Y position 62 | 1 byte | Length 63 | 1 byte | Character (0-255 = 0x2800-0x28FF) 64 | 65 | 0x04: Set Character Vertical 66 | 1 byte | 0x04 67 | 1 byte | X position 68 | 1 byte | Y position 69 | 1 byte | Length 70 | 1 byte | Character (0-255 = 0x2800-0x28FF) 71 | 72 | 0x05: Set Characters Horizontal 73 | 1 byte | 0x05 74 | 1 byte | X position 75 | 1 byte | Y position 76 | 1 byte | Length 77 | Length | Characters (0-255 = 0x2800-0x28FF) 78 | 79 | 0x06: Set Characters Vertical 80 | 1 byte | 0x06 81 | 1 byte | X position 82 | 1 byte | Y position 83 | 1 byte | Length 84 | Length | Characters (0-255 = 0x2800-0x28FF) 85 | -------------------------------------------------------------------------------- /ooci/ooci2ctif.lua: -------------------------------------------------------------------------------- 1 | local args = {...} 2 | if #args ~= 2 then 3 | print("Usage: ooci2ctif filename outname") 4 | return 5 | end 6 | 7 | local file, err = io.open(args[1], "rb") 8 | if not file then 9 | print(err) 10 | return 11 | end 12 | local function check(a, b, c) 13 | if a ~= b then 14 | file:close() 15 | error(c,0) 16 | os.exit(1) 17 | end 18 | return a 19 | end 20 | local function r8() 21 | local char = file:read(1) 22 | return char and char:byte() 23 | end 24 | local function r16() 25 | return r8(file)+(r8(file)*256) 26 | end 27 | 28 | print("Warning: The CTIF files this Decompiler produces are not bit identical, but visually identical.") 29 | check(file:read(4), "OOCI", "Invalid magic bytes") 30 | check(r8(), 1, "Unknown header version") 31 | local width, height = r16(), r16() 32 | local palCount = r8() 33 | check(palCount <= 16, true, "Unsupported palette entry count: " .. palCount) 34 | local pal={} 35 | for i=0,palCount-1 do 36 | pal[i] = {file:read(3):byte(1, -1)} 37 | end 38 | local img={} 39 | for y=1,height do 40 | img[y]={} 41 | end 42 | 43 | -- Borrowed from ctif-oc.lua 44 | local q = {} 45 | for i=0,255 do 46 | local dat = (i & 0x01) << 7 47 | dat = dat | (i & 0x02) >> 1 << 6 48 | dat = dat | (i & 0x04) >> 2 << 5 49 | dat = dat | (i & 0x08) >> 3 << 2 50 | dat = dat | (i & 0x10) >> 4 << 4 51 | dat = dat | (i & 0x20) >> 5 << 1 52 | dat = dat | (i & 0x40) >> 6 << 3 53 | dat = dat | (i & 0x80) >> 7 54 | q[dat] = i 55 | end 56 | 57 | io.stdout:write("Decompiling Image ... ") 58 | local curb, curf 59 | while true do 60 | local inst = r8() 61 | if not inst then 62 | break 63 | elseif inst == 0x00 then 64 | curb = r8() 65 | elseif inst == 0x01 then 66 | curf = r8() 67 | else 68 | local x = r8() 69 | local y = r8() 70 | if inst == 0x02 then 71 | img[y][x]={curb,curf,q[r8()]} 72 | elseif inst == 0x03 then 73 | local len, char = r8(), q[r8()] 74 | for i=x,x+len-1 do 75 | img[y][i]={curb,curf,char} 76 | end 77 | elseif inst == 0x04 then 78 | local len, char = r8(), q[r8()] 79 | for i=y,y+len-1 do 80 | img[i][x]={curb,curf,char} 81 | end 82 | elseif inst == 0x05 then 83 | local len = r8() 84 | for i=x,x+len-1 do 85 | img[y][i]={curb,curf,q[r8()]} 86 | end 87 | elseif inst == 0x06 then 88 | local len = r8() 89 | for i=y,y+len-1 do 90 | img[i][x]={curb,curf,q[r8()]} 91 | end 92 | else 93 | error("Unknown Instruction: " .. inst) 94 | end 95 | end 96 | end 97 | file:close() 98 | print("Done!") 99 | io.stdout:write("Writing Image ... ") 100 | 101 | local newfile, err = io.open(args[2],"wb") 102 | if not newfile then 103 | print(err) 104 | return 105 | end 106 | local function w8(...) 107 | return newfile:write(string.char(...)) 108 | end 109 | 110 | -- Write header 111 | newfile:write("CTIF") 112 | w8(1) -- Version 113 | w8(0) -- Platform Variant 114 | w8(1,0) -- Platform ID 115 | w8(width,0) -- Width 116 | w8(height,0) -- Height 117 | w8(2) -- Char Width 118 | w8(4) -- Char Height 119 | w8(8) -- BPC 120 | w8(3) -- BPPE 121 | -- Write palette colors 122 | w8(16,0) -- Number of Palette Entries 123 | for i = 0,15 do 124 | w8(table.unpack(pal[i],1,3)) 125 | end 126 | --Write image data 127 | for y=1,height do 128 | for x=1,width do 129 | w8(table.unpack(img[y][x],1,3)) 130 | end 131 | end 132 | newfile:close() 133 | print("Done!") 134 | -------------------------------------------------------------------------------- /programs.cfg: -------------------------------------------------------------------------------- 1 | { 2 | ["immibis-compress"] = { 3 | files = { 4 | ["master/immibis-compress/compress.lua"] = "/bin", 5 | ["master/immibis-compress/decompress.lua"] = "/bin", 6 | }, 7 | dependencies = { 8 | ["ipack"] = "/", 9 | }, 10 | name = "ipack (de)compressor", 11 | description = "Compressor and Decompressor for the ipack library", 12 | authors = "gamax92, immibis", 13 | repo = "tree/master/immibis-compress", 14 | }, 15 | ["tapeutils"] = { 16 | files = { 17 | ["master/tapeutils/dumptape.lua"] = "/bin", 18 | ["master/tapeutils/formattape.lua"] = "/bin", 19 | ["master/tapeutils/loadtape.lua"] = "/bin", 20 | ["master/tapeutils/setspeed.lua"] = "/bin", 21 | }, 22 | dependencies = { 23 | }, 24 | name = "tapeutils", 25 | description = "Utilities for using Computronics Tape Drives", 26 | authors = "gamax92", 27 | note = "Requires Computronics mod to be installed.", 28 | repo = "tree/master/tapeutils", 29 | }, 30 | ["ipack"] = { 31 | files = { 32 | ["master/immibis-compress/ipack.lua"] = "/lib", 33 | }, 34 | dependencies = { 35 | }, 36 | name = "ipack compression library", 37 | description = "ipack compression library based on immibis's compression routine", 38 | authors = "gamax92, immibis", 39 | repo = "blob/master/immibis-compress/ipack.lua", 40 | }, 41 | ["libformat"] = { 42 | files = { 43 | ["master/enhanced/format.lua"] = "/lib", 44 | }, 45 | dependencies = { 46 | }, 47 | name = "table formatting library", 48 | description = "table formatting with justification and width options", 49 | authors = "gamax92", 50 | repo = "blob/master/enhanced/format.lua", 51 | }, 52 | ["fstools"] = { 53 | files = { 54 | ["master/filesystems/fslib.lua"] = "/lib", 55 | ["master/filesystems/mountfs.lua"] = "/bin", 56 | }, 57 | dependencies = { 58 | }, 59 | name = "filesystem tools", 60 | description = "Utilities and libraries for raw filesystems", 61 | authors = "gamax92", 62 | repo = "blob/master/filesystems/fslib.lua", 63 | }, 64 | ["tapefs"] = { 65 | files = { 66 | ["master/filesystems/tapefs.lua"] = "/lib", 67 | ["master/filesystems/tapefsd.lua"] = "/bin", 68 | }, 69 | dependencies = { 70 | ["vcomponent"] = "/", 71 | }, 72 | name = "tapefs", 73 | description = "tapefs library and utilities", 74 | authors = "gamax92", 75 | note = "Requires Computronics mod to be installed.", 76 | repo = "blob/master/filesystems/tapefs.lua", 77 | }, 78 | ["msdosfs"] = { 79 | files = { 80 | ["master/filesystems/msdos.lua"] = "/lib", 81 | }, 82 | dependencies = { 83 | ["fstools"] = "/", 84 | ["vcomponent"] = "/", 85 | }, 86 | name = "msdos filesystem", 87 | description = "Provides msdos filesystem support", 88 | authors = "gamax92", 89 | repo = "blob/master/filesystems/msdos.lua", 90 | }, 91 | ["socfs"] = { 92 | files = { 93 | ["master/filesystems/socfs.lua"] = "/lib", 94 | ["master/filesystems/mksocfs.lua"] = "/bin", 95 | }, 96 | dependencies = { 97 | ["fstools"] = "/", 98 | ["vcomponent"] = "/", 99 | }, 100 | name = "socfs filesystem", 101 | description = "Provides SOCFS filesystem support", 102 | authors = "gamax92", 103 | repo = "blob/master/filesystems/socfs.lua", 104 | }, 105 | ["vcomponent"] = { 106 | files = { 107 | ["master/vcomponent/vcomponent.lua"] = "/lib", 108 | ["master/vcomponent/vcmanage.lua"] = "/bin", 109 | }, 110 | dependencies = { 111 | }, 112 | name = "vcomponent", 113 | description = "Virtual Components for OC", 114 | authors = "gamax92", 115 | repo = "tree/master/vcomponent" 116 | }, 117 | ["chip8emu"] = { 118 | files = { 119 | ["master/chip8.lua"] = "/bin", 120 | }, 121 | dependencies = { 122 | }, 123 | name = "chip8 emulator", 124 | description = "CHIP8 Emulator for OC", 125 | authors = "gamax92", 126 | repo = "blob/master/chip8.lua", 127 | }, 128 | ["debug"] = { 129 | files = { 130 | ["master/debug.lua"] = "/bin", 131 | }, 132 | dependencies = { 133 | }, 134 | name = "program debugger", 135 | description = "Give full stacktraces for programs", 136 | authors = "gamax92", 137 | repo = "blob/master/debug.lua", 138 | }, 139 | ["ocnetfs"] = { 140 | files = { 141 | ["master/OCNetFS/ocnetfs.lua"] = "/bin", 142 | }, 143 | dependencies = { 144 | ["vcomponent"] = "/", 145 | }, 146 | name = "networked filesystem", 147 | description = "Connect a OCNetFS server as a local filesystem", 148 | authors = "gamax92", 149 | repo = "blob/master/OCNetFS/ocnetfs.lua", 150 | }, 151 | ["tar"] = { 152 | files = {}, 153 | dependencies = { 154 | ["mpm.tar"] = "/", 155 | }, 156 | name = "tar archiver for OpenComputers", 157 | description = "Manipulate and utilize tar archives", 158 | authors = "mpmxyz", 159 | }, 160 | ["unrequire"] = { 161 | files = { 162 | ["master/unrequire.lua"] = "/bin", 163 | }, 164 | dependencies = { 165 | }, 166 | name = "library unloader for OpenComputers", 167 | description = "Unload libraries from the package cache", 168 | authors = "gamax92", 169 | repo = "blob/master/unrequire.lua", 170 | }, 171 | ["wocchat"] = { 172 | files = { 173 | ["master/wocchat/wocchat.lua"] = "/bin", 174 | }, 175 | dependencies = { 176 | }, 177 | name = "wocchat irc client", 178 | description = "WocChat IRC client for OpenComputers", 179 | authors = "gamax92", 180 | repo = "blob/master/wocchat/wocchat.lua", 181 | }, 182 | ["nano"] = { 183 | files = { 184 | ["master/nano/nano.lua"] = "/bin", 185 | }, 186 | dependencies = { 187 | }, 188 | name = "Lua nano", 189 | description = "nano clone for OpenComputers", 190 | authors = "gamax92", 191 | repo = "blob/master/nano/nano.lua", 192 | }, 193 | ["ooci-tools"] = { 194 | files = { 195 | ["master/ooci/ctif2ooci.lua"] = "/bin", 196 | ["master/ooci/ooci2ctif.lua"] = "/bin", 197 | ["master/ooci/ooci-view.lua"] = "/bin", 198 | }, 199 | dependencies = { 200 | }, 201 | name = "ooci-tools", 202 | description = "OOCI Image Format utilities", 203 | authors = "gamax92", 204 | repo = "tree/master/ooci" 205 | }, 206 | ["dfpwm"] = { 207 | files = { 208 | ["master/dfpwm/lib/dfpwm.lua"] = "/lib", 209 | ["master/dfpwm/dfpwm.lua"] = "/bin", 210 | }, 211 | dependencies = { 212 | }, 213 | name = "dfpwm codec", 214 | description = "Lua implementation of the DFPWM codec", 215 | authors = "gamax92, GreaseMonkey (for Java implementation)", 216 | repo = "tree/master/dfpwm" 217 | }, 218 | ["soundcard"] = { 219 | files = { 220 | ["master/soundcard.lua"] = "/lib", 221 | }, 222 | name = "soundcard wrapper", 223 | description = "automatic processing of soundcard queues and delays", 224 | authors = "gamax92", 225 | repo = "blob/master/soundcard.lua", 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /soundcard.lua: -------------------------------------------------------------------------------- 1 | local component=require("component") 2 | 3 | local soundcard={} 4 | 5 | function soundcard.wrap(proxy, maxms) 6 | if maxms == nil then maxms = math.huge end 7 | if type(proxy) == "string" then 8 | proxy=component.proxy(component.get(proxy)) 9 | end 10 | 11 | local sound={ 12 | process=proxy.process, 13 | clear=proxy.clear, 14 | setTotalVolume=proxy.setTotalVolume 15 | } 16 | for k, v in pairs(proxy) do 17 | if sound[k] then 18 | -- don't replace it 19 | elseif type(v) == "function" or (type(v) == "table" and getmetatable(v) ~= nil) then 20 | sound[k]=function(...) 21 | local ok=v(...) 22 | if not ok then 23 | os.sleep(0) 24 | while not proxy.process() do os.sleep(0) end 25 | assert(v(...)) 26 | end 27 | return true 28 | end 29 | else 30 | sound[k]=proxy[k] 31 | end 32 | end 33 | 34 | local chcount=proxy.channel_count 35 | 36 | local time=0 37 | function sound.delay(ms) 38 | if ms < 1 then return end 39 | if time+ms >= maxms then 40 | proxy.delay(maxms-time) 41 | while not proxy.process() do os.sleep(0) end 42 | time=time+ms-maxms 43 | while time >= maxms do 44 | time=time-maxms 45 | proxy.delay(maxms) 46 | while not proxy.process() do os.sleep(0) end 47 | end 48 | if time > 0 then 49 | proxy.delay(time) 50 | end 51 | time=0 52 | else 53 | proxy.delay(ms) 54 | time=time+ms 55 | end 56 | end 57 | 58 | -- Reset Sound Card to known state. 59 | sound.clear() 60 | sound.setTotalVolume(1) 61 | for i = 1, chcount do 62 | sound.resetEnvelope(i) 63 | sound.resetAM(i) 64 | sound.resetFM(i) 65 | sound.setVolume(i, 1) 66 | sound.close(i) 67 | sound.setWave(i, sound.modes.square) 68 | end 69 | 70 | while not proxy.process() do os.sleep(0) end 71 | 72 | return sound 73 | end 74 | 75 | return soundcard 76 | -------------------------------------------------------------------------------- /tapeutils/dumptape.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | local component = require("component") 3 | local term = require("term") 4 | local shell = require("shell") 5 | 6 | local arg, options = shell.parse(...) 7 | if #arg < 1 then 8 | print("Usage: dumptape filename") 9 | print("Options:") 10 | print(" --address=addr use tapedrive at address") 11 | return 12 | end 13 | arg[1] = shell.resolve(arg[1]) 14 | if fs.exists(arg[1]) then 15 | error("File exists", 2) 16 | end 17 | local td 18 | if options.address then 19 | if type(options.address) ~= "string" or options.address == "" then 20 | error("Invalid address", 2) 21 | end 22 | local fulladdr = component.get(options.address) 23 | if fulladdr == nil then 24 | error("No component at address", 2) 25 | elseif component.type(fulladdr) ~= "tape_drive" then 26 | error("Component specified is a " .. component.type(fulladdr), 2) 27 | end 28 | td = component.proxy(fulladdr) 29 | else 30 | td = component.tape_drive 31 | end 32 | if not td.isReady() then 33 | error("No tape present",2) 34 | end 35 | local tapesize = td.getSize() 36 | local file = fs.open(arg[1],"wb") 37 | if file == nil then 38 | error("Could not open file",2) 39 | end 40 | local counter = 0 41 | if td.getState() ~= "STOPPED" then 42 | print("Stopping tape ...") 43 | td.stop() 44 | end 45 | print("Rewinding tape ...") 46 | td.seek(-math.huge) 47 | while true do 48 | local data = td.read(8192) 49 | if data == nil then break end 50 | counter = counter + #data 51 | local x,y = term.getCursor() 52 | term.setCursor(1, y) 53 | term.write("Read " .. counter .. "/" .. tapesize .. " (" .. math.ceil(counter/tapesize*100) .. "%) bytes") 54 | local stat = file:write(data) 55 | if stat ~= true then 56 | file:close() 57 | print("\nRewinding tape ...") 58 | td.seek(-math.huge) 59 | error("Failed to write to file",2) 60 | end 61 | if counter >= tapesize then break end 62 | end 63 | file:close() 64 | print("\nRewinding tape ...") 65 | td.seek(-math.huge) 66 | -------------------------------------------------------------------------------- /tapeutils/formattape.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local term = require("term") 3 | local shell = require("shell") 4 | local arg, options = shell.parse(...) 5 | 6 | if #arg > 0 then 7 | print("Usage: formattape") 8 | print("Options:") 9 | print(" --address=addr use tapedrive at address") 10 | return 11 | end 12 | local td 13 | if options.address then 14 | if type(options.address) ~= "string" or options.address == "" then 15 | error("Invalid address", 2) 16 | end 17 | local fulladdr = component.get(options.address) 18 | if fulladdr == nil then 19 | error("No component at address", 2) 20 | elseif component.type(fulladdr) ~= "tape_drive" then 21 | error("Component specified is a " .. component.type(fulladdr), 2) 22 | end 23 | td = component.proxy(fulladdr) 24 | else 25 | td = component.tape_drive 26 | end 27 | if not td.isReady() then 28 | error("No tape present",2) 29 | end 30 | local tapeSize = td.getSize() 31 | local counter = 0 32 | if td.getState() ~= "STOPPED" then 33 | print("Stopping tape ...") 34 | td.stop() 35 | end 36 | print("Rewinding tape ...") 37 | td.seek(-math.huge) 38 | while true do 39 | local x,y = term.getCursor() 40 | term.setCursor(1, y) 41 | term.write("Written " .. counter .. "/" .. tapeSize .. " (" .. math.ceil(counter/tapeSize*100) .. "%) bytes") 42 | local written = td.write(string.rep(string.char(0), 8192)) 43 | counter = counter + 8192 44 | if counter >= tapeSize then break end 45 | end 46 | print("\nRewinding tape ...") 47 | td.seek(-math.huge) 48 | -------------------------------------------------------------------------------- /tapeutils/index.txt: -------------------------------------------------------------------------------- 1 | A collection of programs useful for manipulating tapes. 2 | 3 | dumptape.lua 4 | Dumps the contents of a tape into a file. 5 | 6 | formattape.lua 7 | Clears an entire tape back to 0x00. 8 | 9 | loadtape.lua 10 | Loads a block of data into a tape drive, also sets playback speed. 11 | 12 | setspeed.lua 13 | Set the playback speed of a tape drive. 14 | -------------------------------------------------------------------------------- /tapeutils/loadtape.lua: -------------------------------------------------------------------------------- 1 | local fs = require("filesystem") 2 | local component = require("component") 3 | local term = require("term") 4 | local shell = require("shell") 5 | 6 | local arg, options = shell.parse(...) 7 | if #arg < 1 then 8 | print("Usage: loadtape filename") 9 | print("Options:") 10 | print(" --speed=n set playback speed") 11 | print(" --address=addr use tapedrive at address") 12 | return 13 | end 14 | arg[1] = shell.resolve(arg[1]) 15 | if not fs.exists(arg[1]) then 16 | error("No such file", 2) 17 | end 18 | if options.speed and (tonumber(options.speed) == nil or tonumber(options.speed) < 0.25 or tonumber(options.speed) > 2) then 19 | error("Invalid speed", 2) 20 | end 21 | local td 22 | if options.address then 23 | if type(options.address) ~= "string" or options.address == "" then 24 | error("Invalid address", 2) 25 | end 26 | local fulladdr = component.get(options.address) 27 | if fulladdr == nil then 28 | error("No component at address", 2) 29 | elseif component.type(fulladdr) ~= "tape_drive" then 30 | error("Component specified is a " .. component.type(fulladdr), 2) 31 | end 32 | td = component.proxy(fulladdr) 33 | else 34 | td = component.tape_drive 35 | end 36 | if not td.isReady() then 37 | error("No tape present", 2) 38 | end 39 | local filesize = fs.size(arg[1]) 40 | if td.getSize() < filesize then 41 | print("File is too large for tape, truncating") 42 | filesize = td.getSize() 43 | end 44 | local file = fs.open(arg[1],"rb") 45 | local counter = 0 46 | if td.getState() ~= "STOPPED" then 47 | print("Stopping tape ...") 48 | td.stop() 49 | end 50 | print("Rewinding tape ...") 51 | td.seek(-math.huge) 52 | while true do 53 | local data = file:read(math.min(filesize - counter, 8192)) 54 | if data == nil then break end 55 | counter = counter + #data 56 | local x,y = term.getCursor() 57 | term.setCursor(1, y) 58 | term.write("Loaded " .. counter .. "/" .. filesize .. " (" .. math.ceil(counter/filesize*100) .. "%) bytes") 59 | td.write(data) 60 | if counter >= filesize then break end 61 | end 62 | file:close() 63 | print("\nRewinding tape ...") 64 | td.seek(-math.huge) 65 | if options.speed then 66 | local speed = tonumber(options.speed) 67 | td.setSpeed(speed) 68 | print("Tape playback speed set to " .. speed .. ", " .. speed * 32768 .. "Hz") 69 | end 70 | -------------------------------------------------------------------------------- /tapeutils/setspeed.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local shell = require("shell") 3 | 4 | local arg, options = shell.parse(...) 5 | if #arg < 1 then 6 | print("Usage: setspeed speed") 7 | print("Options:") 8 | print(" --address=addr use tapedrive at address") 9 | return 10 | end 11 | if tonumber(arg[1]) == nil or tonumber(arg[1]) < 0.25 or tonumber(arg[1]) > 2 then 12 | error("Invalid speed", 2) 13 | end 14 | local speed = tonumber(arg[1]) 15 | local td 16 | if options.address then 17 | if type(options.address) ~= "string" or options.address == "" then 18 | error("Invalid address", 2) 19 | end 20 | local fulladdr = component.get(options.address) 21 | if fulladdr == nil then 22 | error("No component at address", 2) 23 | elseif component.type(fulladdr) ~= "tape_drive" then 24 | error("Component specified is a " .. component.type(fulladdr), 2) 25 | end 26 | td = component.proxy(fulladdr) 27 | else 28 | td = component.tape_drive 29 | end 30 | td.setSpeed(speed) 31 | print("Tape playback speed set to " .. speed .. ", " .. speed * 32768 .. "Hz") 32 | -------------------------------------------------------------------------------- /unicode-more.lua: -------------------------------------------------------------------------------- 1 | local unicode = require("unicode") 2 | 3 | local function uz(a) 4 | local b={a:byte(1,-1)} 5 | if #b==1 then return b[1] end 6 | local c=b[1]%(8*(2^(4-#b)))*(2^(6*#b-6)) 7 | for i=2,#b do 8 | c=c+(b[i]%64)*2^(6*(#b-i)) 9 | end 10 | return c 11 | end 12 | 13 | function unicode.find(str, pattern, init, plain) 14 | checkArg(1, str, "string") 15 | checkArg(2, pattern, "string") 16 | checkArg(3, init, "number", "nil") 17 | if init then 18 | if init < 0 then 19 | init = -#unicode.sub(str,init) 20 | elseif init > 0 then 21 | init = #unicode.sub(str,1,init-1)+1 22 | end 23 | end 24 | a, b = string.find(str, pattern, init, plain) 25 | if a then 26 | local ap,bp = str:sub(1,a-1), str:sub(a,b) 27 | a = unicode.len(ap)+1 28 | b = a + unicode.len(bp)-1 29 | return a,b 30 | else 31 | return a 32 | end 33 | end 34 | 35 | function unicode.byte(str, i, j) 36 | checkArg(1, str, "string") 37 | if i == nil then i = 1 end 38 | if j == nil then j = i end 39 | checkArg(2, i, "number") 40 | checkArg(3, j, "number") 41 | local part = unicode.sub(str,i,j) 42 | local results = {} 43 | for char in part:gmatch("[%z\1-\127\194-\244][\128-\191]*") do 44 | results[#results+1] = uz(char) 45 | end 46 | return table.unpack(results) 47 | end 48 | 49 | -------------------------------------------------------------------------------- /unrequire.lua: -------------------------------------------------------------------------------- 1 | local unload_shell = package.loaded["shell"] == nil 2 | local shell = require("shell") 3 | if unload_shell then 4 | package.loaded["shell"] = nil 5 | end 6 | 7 | local blacklist = { 8 | ["filesystem"] = true, 9 | ["shell"] = true, 10 | ["package"] = true, 11 | ["process"] = true, 12 | } 13 | 14 | local function errprint(msg) 15 | io.stderr:write(msg .. "\n") 16 | end 17 | 18 | local args, options = shell.parse(...) 19 | if #args == 0 then 20 | print 21 | [[Usage: unrequire list 22 | unrequire unload ... 23 | unrequire unload-all 24 | Options: 25 | -v --verbose List unloaded libraries 26 | --unsafe Unload blacklisted libraries]] 27 | return 28 | end 29 | if args[1] == "list" then 30 | local list = {} 31 | for k,v in pairs(package.loaded) do 32 | list[#list+1] = k 33 | end 34 | table.sort(list) 35 | print(table.concat(list,"\n")) 36 | elseif args[1] == "unload" then 37 | if #args < 2 then 38 | errprint("unrequire: missing name") 39 | end 40 | for i = 2,#args do 41 | if package.loaded[args[i]] == nil then 42 | errprint("unrequire: '" .. args[i] .. "' not loaded") 43 | elseif blacklist[args[i]] and not options.unsafe then 44 | errprint("unrequire: refusing to unload '" .. args[i] .. "'\nUse --unsafe to override this") 45 | else 46 | package.loaded[args[i]] = nil 47 | if options.v or options.verbose then 48 | print("unloaded '" .. args[i] .. "'") 49 | end 50 | end 51 | end 52 | elseif args[1] == "unload-all" then 53 | local list = {} 54 | for k,v in pairs(package.loaded) do 55 | if options.unsafe or not blacklist[k] then 56 | list[#list+1] = k 57 | end 58 | end 59 | for i = 1,#list do 60 | package.loaded[list[i]] = nil 61 | if options.v or options.verbose then 62 | print("unloaded '" .. list[i] .. "'") 63 | end 64 | end 65 | else 66 | errprint("Unknown command '" .. args[1] .. "'") 67 | end 68 | -------------------------------------------------------------------------------- /utilset/free.lua: -------------------------------------------------------------------------------- 1 | local computer = require("computer") 2 | local shell = require("shell") 3 | local format = require("format") 4 | 5 | local _, options = shell.parse(...) 6 | 7 | local function printUsage() 8 | io.write([[ 9 | Usage: free [options] 10 | 11 | Options: 12 | -b, --bytes show output in bytes 13 | -k, --kilo show output in kilobytes 14 | -m, --mega show output in megabytes 15 | -g, --giga show output in gigabytes 16 | --tera show output in terabytes 17 | -h, --human show human readable output 18 | --si use powers of 1000 not 1024 19 | -t, --total show total for all usage 20 | --help display this help text 21 | ]]) 22 | end 23 | 24 | local size = "k" 25 | local si = 1024 26 | local total = false 27 | 28 | for k,v in pairs(options) do 29 | if k == "b" or k == "bytes" or k == "k" or k == "kilo" or k == "m" or k == "mega" or k == "g" or k == "giga" or k == "tera" or k == "h" or k == "human" then 30 | size = k:sub(1,1) 31 | elseif k == "si" then 32 | si = 1000 33 | elseif k == "t" or k == "total" then 34 | total = true 35 | elseif k == "help" then 36 | printUsage() 37 | return 38 | else 39 | io.write("free: invalid option -- '" .. k .. "'\n") 40 | printUsage() 41 | return 42 | end 43 | end 44 | 45 | local function formatSize(value) 46 | if size == "b" then 47 | return value 48 | elseif size == "k" then 49 | return math.floor(value / si) 50 | elseif size == "m" then 51 | return math.floor(value / si / si) 52 | elseif size == "g" then 53 | return math.floor(value / si / si / si) 54 | elseif size == "t" then 55 | return math.floor(value / si / si / si / si) 56 | elseif size == "h" then 57 | local sizeLet = {"B","K","M","G","T"} 58 | local i = 1 59 | while i <= #sizeLet do 60 | if value < si then 61 | break 62 | end 63 | i = i + 1 64 | value = value / si 65 | end 66 | return math.floor(value * 10) / 10 .. sizeLet[i] 67 | end 68 | end 69 | 70 | local result = {{"","total","used","free"}} 71 | local compFree = computer.freeMemory() 72 | table.insert(result, {"Mem:",computer.totalMemory(),computer.totalMemory() - compFree,compFree}) 73 | if total then 74 | local totalEntry = {"Total",0,0,0} 75 | for j = 2, #result do 76 | for i = 2, 4 do 77 | totalEntry[i] = totalEntry[i] + result[j][i] 78 | end 79 | end 80 | table.insert(result, totalEntry) 81 | end 82 | for j = 2, #result do 83 | for i = 2, 4 do 84 | result[j][i] = formatSize(result[j][i]) 85 | end 86 | end 87 | format.tabulate(result,{0,1,1,1}) 88 | for _, entry in ipairs(result) do 89 | io.write(table.concat(entry, " "), "\n") 90 | end -------------------------------------------------------------------------------- /utilset/index.txt: -------------------------------------------------------------------------------- 1 | Common utilties found on many linux distributions. 2 | 3 | free.lua 4 | Reports computer memory usage. 5 | Requires format.lua from enhanced/ 6 | -------------------------------------------------------------------------------- /vcomponent/index.txt: -------------------------------------------------------------------------------- 1 | Virtual Components system for OpenComputers. 2 | 3 | vcomponent.lua 4 | Patches component api to support virtual components 5 | Install in /lib 6 | 7 | vcmanage.lua 8 | List and delete virtual components 9 | Requires vcomponent.lua 10 | 11 | vtest.lua 12 | Example for using vcomponent.lua 13 | Requires vcomponent.lua 14 | -------------------------------------------------------------------------------- /vcomponent/vcmanage.lua: -------------------------------------------------------------------------------- 1 | local args = { ... } 2 | if #args < 1 or #args > 2 or args[1] == "help" then 3 | print("Usage: vcmanage list [filter]") 4 | print(" vcmanage delete
") 5 | print(" vcmanage deleteall") 6 | return 7 | end 8 | 9 | local vcomp = require("vcomponent") 10 | local component = require("component") 11 | 12 | if args[1] == "list" then 13 | local vclist = vcomp.list() 14 | for k = 1,#vclist do 15 | if args[2] == nil or vclist[k][2]:find(args[2],nil,true) then 16 | print(vclist[k][2], vclist[k][1]) 17 | end 18 | end 19 | elseif args[1] == "delete" or args[1] == "remove" then 20 | if args[2] == nil then 21 | error("Must specify address for deletion", 0) 22 | end 23 | local vclist = vcomp.list() 24 | realaddr = vcomp.resolve(args[2]) 25 | if realaddr == nil then 26 | error("No such virtual component", 0) 27 | end 28 | for k = 1,#vclist do 29 | if vclist[k][1] == realaddr then 30 | local stat, problem = vcomp.unregister(vclist[k][1]) 31 | if stat ~= true then 32 | error("Unregister: " .. problem,0) 33 | end 34 | if realaddr ~= args[2] then 35 | print("Component removed at " .. realaddr) 36 | else 37 | print("Component removed") 38 | end 39 | return 40 | end 41 | end 42 | print("No component removed") 43 | elseif args[1] == "deleteall" or args[1] == "removeall" then 44 | local remv = 0 45 | local vclist = vcomp.list() 46 | for k = 1,#vclist do 47 | local stat, problem = vcomp.unregister(vclist[k][1]) 48 | remv = remv + 1 49 | end 50 | print("Removed " .. remv .. " component" .. (remv ~= 1 and "s" or "")) 51 | else 52 | error("Unknown command, " .. args[1], 0) 53 | end 54 | -------------------------------------------------------------------------------- /vcomponent/vcomponent.lua: -------------------------------------------------------------------------------- 1 | local component = require("component") 2 | local computer = require("computer") 3 | 4 | local proxylist = {} 5 | local proxyobjs = {} 6 | local typelist = {} 7 | local doclist = {} 8 | 9 | local oisAvailable = component.isAvailable 10 | function component.isAvailable(componentType) 11 | checkArg(1,componentType,"string") 12 | for k, v in pairs(typelist) do 13 | if v == componentType then 14 | return true 15 | end 16 | end 17 | return oisAvailable(componentType) 18 | end 19 | 20 | local oproxy = component.proxy 21 | function component.proxy(address) 22 | checkArg(1,address,"string") 23 | if proxyobjs[address] ~= nil then 24 | return proxyobjs[address] 25 | end 26 | return oproxy(address) 27 | end 28 | 29 | local olist = component.list 30 | function component.list(filter, exact) 31 | checkArg(1,filter,"string","nil") 32 | local result = {} 33 | local data = {} 34 | for k,v in olist(filter, exact) do 35 | data[#data + 1] = k 36 | data[#data + 1] = v 37 | result[k] = v 38 | end 39 | for k,v in pairs(typelist) do 40 | if filter == nil or (exact and v == filter) or (not exact and v:find(filter, nil, true)) then 41 | data[#data + 1] = k 42 | data[#data + 1] = v 43 | result[k] = v 44 | end 45 | end 46 | local place = 1 47 | return setmetatable(result, 48 | {__call=function() 49 | local addr,type = data[place], data[place + 1] 50 | place = place + 2 51 | return addr, type 52 | end} 53 | ) 54 | end 55 | 56 | local otype = component.type 57 | function component.type(address) 58 | checkArg(1,address,"string") 59 | if typelist[address] ~= nil then 60 | return typelist[address] 61 | end 62 | return otype(address) 63 | end 64 | 65 | local odoc = component.doc 66 | function component.doc(address, method) 67 | checkArg(1,address,"string") 68 | checkArg(2,method,"string") 69 | if proxylist[address] ~= nil then 70 | if proxylist[address][method] == nil then 71 | error("no such method",2) 72 | end 73 | if doclist[address] ~= nil then 74 | return doclist[address][method] 75 | end 76 | return nil 77 | end 78 | return odoc(address, method) 79 | end 80 | 81 | local oslot = component.slot 82 | function component.slot(address) 83 | checkArg(1,address,"string") 84 | if proxylist[address] ~= nil then 85 | return -1 -- vcomponents do not exist in a slot 86 | end 87 | return oslot(address) 88 | end 89 | 90 | local omethods = component.methods 91 | function component.methods(address) 92 | checkArg(1,address,"string") 93 | if proxylist[address] ~= nil then 94 | local methods = {} 95 | for k,v in pairs(proxylist[address]) do 96 | if type(v) == "function" then 97 | methods[k] = true -- All vcomponent methods are direct 98 | end 99 | end 100 | return methods 101 | end 102 | return omethods(address) 103 | end 104 | 105 | local oinvoke = component.invoke 106 | function component.invoke(address, method, ...) 107 | checkArg(1,address,"string") 108 | checkArg(2,method,"string") 109 | if proxylist[address] ~= nil then 110 | if proxylist[address][method] == nil then 111 | error("no such method",2) 112 | end 113 | return proxylist[address][method](...) 114 | end 115 | return oinvoke(address, method, ...) 116 | end 117 | 118 | local ofields = component.fields 119 | function component.fields(address) 120 | checkArg(1,address,"string") 121 | if proxylist[address] ~= nil then 122 | return {} -- What even is this? 123 | end 124 | return ofields(address) 125 | end 126 | 127 | local componentCallback = 128 | { 129 | __call = function(self, ...) return proxylist[self.address][self.name](...) end, 130 | __tostring = function(self) return (doclist[self.address] ~= nil and doclist[self.address][self.name] ~= nil) and doclist[self.address][self.name] or "function" end 131 | } 132 | 133 | local vcomponent = {} 134 | 135 | function vcomponent.register(address, ctype, proxy, doc) 136 | checkArg(1,address,"string") 137 | checkArg(2,ctype,"string") 138 | checkArg(3,proxy,"table") 139 | if proxylist[address] ~= nil then 140 | return nil, "component already at address" 141 | elseif component.type(address) ~= nil then 142 | return nil, "cannot register over real component" 143 | end 144 | proxy.address = address 145 | proxy.type = ctype 146 | local proxyobj = {} 147 | for k,v in pairs(proxy) do 148 | if type(v) == "function" then 149 | proxyobj[k] = setmetatable({name=k,address=address},componentCallback) 150 | else 151 | proxyobj[k] = v 152 | end 153 | end 154 | proxylist[address] = proxy 155 | proxyobjs[address] = proxyobj 156 | typelist[address] = ctype 157 | doclist[address] = doc 158 | computer.pushSignal("component_added",address,ctype) 159 | return true 160 | end 161 | 162 | function vcomponent.unregister(address) 163 | checkArg(1,address,"string") 164 | if proxylist[address] == nil then 165 | if component.type(address) ~= nil then 166 | return nil, "cannot unregister real component" 167 | else 168 | return nil, "no component at address" 169 | end 170 | end 171 | local thetype = typelist[address] 172 | proxylist[address] = nil 173 | proxyobjs[address] = nil 174 | typelist[address] = nil 175 | doclist[address] = nil 176 | computer.pushSignal("component_removed",address,thetype) 177 | return true 178 | end 179 | 180 | function vcomponent.list() 181 | local list = {} 182 | for k,v in pairs(proxylist) do 183 | list[#list + 1] = {k,typelist[k],v} 184 | end 185 | return list 186 | end 187 | 188 | function vcomponent.resolve(address, componentType) 189 | checkArg(1, address, "string") 190 | checkArg(2, componentType, "string", "nil") 191 | for k,v in pairs(typelist) do 192 | if componentType == nil or v == componentType then 193 | if k:sub(1, #address) == address then 194 | return k 195 | end 196 | end 197 | end 198 | return nil, "no such component" 199 | end 200 | 201 | local r = math.random 202 | function vcomponent.uuid() 203 | return string.format("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 204 | r(0,255),r(0,255),r(0,255),r(0,255), 205 | r(0,255),r(0,255), 206 | r(64,79),r(0,255), 207 | r(128,191),r(0,255), 208 | r(0,255),r(0,255),r(0,255),r(0,255),r(0,255),r(0,255)) 209 | end 210 | 211 | return vcomponent 212 | -------------------------------------------------------------------------------- /vcomponent/vtest.lua: -------------------------------------------------------------------------------- 1 | local vcomp = require("vcomponent") 2 | 3 | local proxy = { 4 | test = function(something) return type(something) end 5 | } 6 | local docs = { 7 | test = "function(value:something):string -- I do stuff." 8 | } 9 | vcomp.register("LALWZADDR","testcomp",proxy,docs) 10 | -------------------------------------------------------------------------------- /wocchat/index.txt: -------------------------------------------------------------------------------- 1 | WocChat IRC chat client for OpenComputers. 2 | 3 | wocchat.cfg 4 | Default configuration file for WocChat. 5 | 6 | wocchat.lua 7 | A graphical IRC client for OpenComputers. 8 | -------------------------------------------------------------------------------- /wocchat/wocchat.cfg: -------------------------------------------------------------------------------- 1 | [wocchat] 2 | mirc.colors.0=0xFFFFFF 3 | mirc.colors.1=0x000000 4 | mirc.colors.2=0x00007F 5 | mirc.colors.3=0x009300 6 | mirc.colors.4=0xFF0000 7 | mirc.colors.5=0x7F0000 8 | mirc.colors.6=0x9C009C 9 | mirc.colors.7=0xFC7F00 10 | mirc.colors.8=0xFFFF00 11 | mirc.colors.9=0x00FC00 12 | mirc.colors.10=0x009393 13 | mirc.colors.11=0x00FFFF 14 | mirc.colors.12=0x0000FC 15 | mirc.colors.13=0xFF00FF 16 | mirc.colors.14=0x7F7F7F 17 | mirc.colors.15=0xD2D2D2 18 | notifysound=true 19 | showcolors=true 20 | showmotd=false 21 | showraw=false 22 | version="v0.0.3" 23 | 24 | [default] 25 | quit="Proudly using WocChat!" 26 | 27 | [server] 28 | EsperNet.autojoin=true 29 | EsperNet.channels="#oc" 30 | EsperNet.name="EsperNet" 31 | EsperNet.server="irc.esper.net:6667" 32 | 33 | [theme] 34 | theme="solarized" 35 | 36 | [base.theme] 37 | 8=0xB58900 38 | 9=0xCB4B16 39 | 10=0xDC322F 40 | 11=0xD33682 41 | 12=0x6C71C4 42 | 13=0x268BD2 43 | 14=0x2AA198 44 | 15=0x859900 45 | actions.error.color=10 46 | actions.highlight.color=15 47 | actions.join.color=15 48 | actions.part.color=13 49 | actions.quit.color=8 50 | actions.title.color=11 51 | textbar.color=0 52 | textbar.text.color=7 53 | tree.active.color=3 54 | tree.color=2 55 | tree.dead.color=9 56 | tree.entry.color=7 57 | tree.entry.prefix.color=6 58 | tree.entry.prefix.last="└─" 59 | tree.entry.prefix.str="├─" 60 | tree.group.color=7 61 | tree.group.prefix.color=6 62 | tree.group.prefix.str="▼" 63 | tree.new.color=13 64 | window.color=1 65 | window.divider.color=2 66 | window.divider.str="┊" 67 | window.nick.color="6,8,9,10,11,12,13,14,15" 68 | window.text.color=7 69 | 70 | [gray.theme] 71 | 0=0x1F1F1F 72 | 1=0x3A3A3A 73 | 2=0x575757 74 | 3=0x757575 75 | 4=0x949494 76 | 5=0xB4B4B4 77 | 6=0xD5D5D5 78 | 7=0xF5F5F5 79 | parent="base" 80 | 81 | [solarized.theme] 82 | 0=0x002B36 83 | 1=0x073642 84 | 2=0x124C56 85 | 3=0x657B83 86 | 4=0x89999A 87 | 5=0xB0B8B1 88 | 6=0xE2CE9B 89 | 7=0xFDF6E3 90 | parent="base" 91 | 92 | --------------------------------------------------------------------------------