diff --git a/safer_lua/depends.txt b/safer_lua/depends.txt new file mode 100644 index 0000000..e69de29 diff --git a/safer_lua/description.txt b/safer_lua/description.txt new file mode 100644 index 0000000..6049760 --- /dev/null +++ b/safer_lua/description.txt @@ -0,0 +1,4 @@ +SaferLUA [safer_lua], a subset of the language LUA for safe and secure LUA sandboxes + + + diff --git a/safer_lua/environ.lua b/safer_lua/environ.lua new file mode 100644 index 0000000..38cb5b7 --- /dev/null +++ b/safer_lua/environ.lua @@ -0,0 +1,97 @@ +safer_lua.MaxCodeSize = 1000 -- size in length of byte code +safer_lua.MaxTableSize = 1000 -- number of table entries considering string lenghts + + +local BASE_ENV = { + Store = safer_lua.Store, + math = { + floor = math.floor + }, + ticks = 0, +} + +local function map(dest, source) + for k,v in pairs(source) do + dest[k] = v + end + return dest +end + +function safer_lua.config(max_code_size, max_table_size) + safer_lua.MaxCodeSize = max_code_size + safer_lua.MaxTableSize = max_table_size +end + +local function compile(pos, text, label, err_clbk) + if safer_lua:check(text, label, err_clbk) == 0 then + text = text:gsub("%$", "S:") + local code, err = loadstring(text) + if not code then + err = err:gsub("%[string .+%]:", label) + err_clbk(pos, err) + else + return code + end + end +end + +function safer_lua.init(pos, init, loop, environ, err_clbk) + if #init > safer_lua.MaxCodeSize then + err_clbk(pos, "init() Code size limit exceeded") + return + end + if #loop > safer_lua.MaxCodeSize then + err_clbk(pos, "loop() Code size limit exceeded") + return + end + local code = compile(pos, init, "init() ", err_clbk) + if code then + local env = BASE_ENV + env.S = {} + env.S._G = _G + env.S = map(env.S, environ) + setfenv(code, env) + local res, err = pcall(code) + if not res then + err = err:gsub("%[string .+%]:", "init() ") + err_clbk(pos, err) + else + env = getfenv(code) + code = compile(pos, loop, "loop() ", err_clbk) + if code then + setfenv(code, env) + return code + end + end + end +end + +function safer_lua.run_loop(pos, elapsed, code, err_clbk) + local env = getfenv(code) + env.event = false + env.ticks = env.ticks + 1 + env.elapsed = elapsed + setfenv(code, env) + local res, err = pcall(code) + if not res then + err = err:gsub("%[string .+%]:", "loop() ") + err_clbk(pos, err) + return false + end + return true +end + +function safer_lua.run_event(pos, code, err_clbk) + local env = getfenv(code) + env.event = true + setfenv(code, env) + local res, err = pcall(code) + if not res then + err = err:gsub("%[string .+%]:", "loop() ") + err_clbk(pos, err) + return false + end + return true +end + + diff --git a/safer_lua/init.lua b/safer_lua/init.lua new file mode 100644 index 0000000..2bce958 --- /dev/null +++ b/safer_lua/init.lua @@ -0,0 +1,17 @@ +--[[ + + SaferLUA + ======== + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + +]]-- + +safer_lua = {} + +dofile(minetest.get_modpath("safer_lua") .. "/store.lua") +dofile(minetest.get_modpath("safer_lua") .. "/scanner.lua") +dofile(minetest.get_modpath("safer_lua") .. "/environ.lua") \ No newline at end of file diff --git a/safer_lua/mod.conf b/safer_lua/mod.conf new file mode 100644 index 0000000..d319b89 --- /dev/null +++ b/safer_lua/mod.conf @@ -0,0 +1 @@ +name=safer_lua diff --git a/safer_lua/readme.md b/safer_lua/readme.md new file mode 100644 index 0000000..9571f43 --- /dev/null +++ b/safer_lua/readme.md @@ -0,0 +1,11 @@ +SaferLUA [safer_lua] +==================== + +A subset of the language LUA for safe and secure LUA sandboxes with: + - limited code length + - limited execution time + - limited memory use + - limited posibilities to call functions + + + diff --git a/safer_lua/scanner.lua b/safer_lua/scanner.lua new file mode 100644 index 0000000..0e48e5a --- /dev/null +++ b/safer_lua/scanner.lua @@ -0,0 +1,94 @@ +local function trim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +function safer_lua:word(ch, pttrn) + local word = "" + while ch:match(pttrn) do + word = word .. ch + self.pos = self.pos + 1 + ch = self.line:sub(self.pos, self.pos) + end + return word +end + +function safer_lua:string(pttrn) + self.pos = self.pos + 1 + local ch = self.line:sub(self.pos, self.pos) + while not ch:match(pttrn) and self.pos < #self.line do + self.pos = self.pos + 1 + ch = self.line:sub(self.pos, self.pos) + end + self.pos = self.pos + 1 + -- result is not needed +end + +function safer_lua:scanner(text) + local lToken = {} + for idx, line in ipairs(text:split("\n")) do + self.line = line + self.pos = 1 + self.line = trim(self.line) + self.line = self.line:split("--")[1] + if self.line then + -- devide line in tokens + table.insert(lToken, idx) -- line number + while true do + if self.pos > #self.line then break end + local ch = self.line:sub(self.pos, self.pos) + if ch:match("[%u%l_]") then -- identifier? + table.insert(lToken, self:word(ch, "[%w_]")) + elseif ch:match("[%d]") then -- number? + table.insert(lToken, self:word(ch, "[%d%xx]")) + elseif ch:match("'") then -- string? + self:string("'") + elseif ch:match('"') then -- string? + self:string('"') + elseif ch:match("[%s]") then -- Space? + self.pos = self.pos + 1 + elseif ch:match("[:{}]") then -- critical tokens? + table.insert(lToken,ch) + self.pos = self.pos + 1 + else + self.pos = self.pos + 1 + end + end + end + end + return lToken +end + +local InvalidKeywords = { + ["while"] = true, + ["repeat"] = true, + ["break"] = true, + ["until"] = true, + ["for"] = true, + ["do"] = true, + ["function"] = true, + ["_G"] = true, +} + +local InvalidChars = { + [":"] = true, + ["{"] = true, + ["}"] = true, +} + +function safer_lua:check(text, label, err_clbk) + local lToken = self:scanner(text) + local lineno = 0 + local errno = 0 + for _,token in ipairs(lToken) do + if type(token) == "number" then + lineno = token + elseif InvalidKeywords[token] then + err_clbk(label..lineno..": Invalid keyword '"..token.."'") + errno = errno + 1 + elseif InvalidChars[token] then + err_clbk(label..lineno..": Invalid character '"..token.."'") + errno = errno + 1 + end + end + return errno +end diff --git a/safer_lua/store.lua b/safer_lua/store.lua new file mode 100644 index 0000000..51b13cc --- /dev/null +++ b/safer_lua/store.lua @@ -0,0 +1,118 @@ +safer_lua.StoreHelp = [[ + Store - a secure shell over the LUA table type. + + For records: + tbl = Store() --> {} + tbl.set(key,value) --> {key=value} + tbl.get(key) --> value + + 'key' can be a number or string + 'value' can be number, string, boolean, or Store + Example: tbl.set("a","test") + + For lists: + tbl = Store(1,2,3,4) --> {1,2,3,4} + tbl.insert(pos, value) + tbl.remove(pos) + + 'pos' must be a number + + Methods: + tbl.set(key, value) --> add/set a value + tbl.get(key) --> read a value + tbl.size() --> return the table size + tbl.insert(pos, value) --> insert into list + tbl.remove(pos) --> return and remove from list + tbl.sort() -- sort list + tbl.dump() --> format as string (debugging) +]] + +function safer_lua.Store(...) + + local new_t = {__data__ = {}} + local mt = {} + + -- `all` will represent the number of both + local Count = 0 + + mt.__newindex = function(t, k, v) return end + + mt.count = function(v) + if type(v) == "number" then + return 1 + elseif type(v) == "boolean" then + return 1 + elseif v == nil then + return 0 + elseif type(v) == "string" then + return #v + elseif type(v) == "table" then + return v.size() + else + return nil + end + end + + for idx = 1,select('#',...) do + local v = select(idx,...) + local cnt = mt.count(v) + if cnt then + Count = Count + cnt + if Count < safer_lua.MaxTableSize then + rawset(new_t.__data__,idx, v) + end + end + end + + new_t.set = function(k,v) + if type(k) == "string" or type(k) == "number" then + Count = Count - mt.count(rawget(new_t.__data__, k)) + local cnt = mt.count(v) + if cnt then + Count = Count + cnt + if Count < safer_lua.MaxTableSize then + rawset(new_t.__data__,k,v) + end + end + end + end + + new_t.get = function(k) + return rawget(new_t.__data__, k) + end + + new_t.size = function(t) + return Count + end + + new_t.insert = function(v, i) + local cnt = mt.count(v) + if cnt then + Count = Count + cnt + if i == nil then i = #new_t.__data__ + 1 end + if Count < safer_lua.MaxTableSize then + table.insert(new_t.__data__,i,v) + end + end + end + + new_t.remove = function(i) + local v = table.remove(new_t.__data__,i) + local cnt = mt.count(v) + Count = Count - cnt + return v + end + + new_t.sort = function() + table.sort(new_t.__data__) + end + + new_t.dump = function(size) + size = size or 200 + local s = dump(new_t.__data__) + if #s > size then s = s:sub(1, size).."..." end + return s + end + +return setmetatable(new_t, mt) +end diff --git a/safer_lua/test_environ.lua b/safer_lua/test_environ.lua new file mode 100644 index 0000000..6daacb7 --- /dev/null +++ b/safer_lua/test_environ.lua @@ -0,0 +1,49 @@ +core = {} + +function core.global_exists(name) + return false +end + +dofile('/home/joachim/minetest/builtin/common/vector.lua') +dofile('/home/joachim/minetest/builtin/common/misc_helpers.lua') + +safer_lua = {} +dofile('/home/joachim/minetest/mods/techpack/safer_lua/store.lua') +dofile('/home/joachim/minetest/mods/techpack/safer_lua/scanner.lua') +dofile('/home/joachim/minetest/mods/techpack/safer_lua/environ.lua') + +--local Cache = {} +--local key = minetest.pos_to_hash(pos) +--code = Cache[key] + +local function foo(self, val) + _G = self._G + print("Hallo", val) +end + +local function error(pos, s) + print("[Test] "..s) +end + +local init = "init = 5" +local loop = [[ + $foo("hallo") + S.foo("hallo") + --S._G.print("Fehler") + $foo(math.floor(5.5)) + $foo("Joe") + a = Store() + a.set("a", 123) + $foo(a.get("a")) + $foo(ticks) +]] + +local env = {foo = foo} + + +local code = safer_lua.init(0, init, loop, env, error) +if code then + print(safer_lua.run_loop(0, code, error)) + safer_lua.run_loop(0, code, error) + safer_lua.run_loop(0, code, error) +end \ No newline at end of file diff --git a/safer_lua/test_scanner.lua b/safer_lua/test_scanner.lua new file mode 100644 index 0000000..fca99ac --- /dev/null +++ b/safer_lua/test_scanner.lua @@ -0,0 +1,28 @@ +core = {} + +function core.global_exists(name) + return false +end + +dofile('/home/joachim/minetest/builtin/common/vector.lua') +dofile('/home/joachim/minetest/builtin/common/misc_helpers.lua') +dofile('/home/joachim/minetest/mods/techpack/safer_lua/scanner.lua') + +code = [[ +-- GOOD +a = 1 +a = a + 1 +print(a) +foo(a) + +-- BAD +_G.print(() +t = {} +for i = 1,1000 do +]] + +local function error(s) + print("[Robbi] "..s) +end + +safer_lua:check(code, "Code", error) diff --git a/safer_lua/test_store.lua b/safer_lua/test_store.lua new file mode 100644 index 0000000..f30de11 --- /dev/null +++ b/safer_lua/test_store.lua @@ -0,0 +1,57 @@ +core = {} + +function core.global_exists(name) + return false +end + +dofile('/home/joachim/minetest/builtin/common/vector.lua') +dofile('/home/joachim/minetest/builtin/common/misc_helpers.lua') + +safer_lua = {} +safer_lua.MaxTableSize = 1000 -- number of table entries considering string lenghts + +dofile('/home/joachim/minetest/mods/techpack/safer_lua/store.lua') + + +print("S1") +local s1 = safer_lua.Store() +assert(s1.size() == 0) + +s1.a = 3 +s1[1] = 4 +assert(s1.size() == 0) + +s1.set("b", "Hallo") +assert(s1.size() == 5) + +assert(s1.get("b") == "Hallo") +assert(s1.size() == 5) + +print("S2") +local s2 = safer_lua.Store() +assert(s2.size() == 0) +s2.set("b", "Joe") +assert(s2.size() == 3) + +assert(s2.b == nil) +assert(s2.get('b') == "Joe") +s2.c = "XXX!" +assert(s2.c == nil) + +s1.set("c", s2) +print(dump(s1.get("c"))) + +print("S3") +local s3 = safer_lua.Store(1,2,3,4) +assert(s3.size() == 4) +print(dump(s3)) + +s3.insert(0, 1) +s3.insert(5) +print(s3.dump()) +print(s2.dump()) + +s2.set("s2", s2) +print(s2.dump()) + +print(dump(s2)) diff --git a/sl_controller/commands.lua b/sl_controller/commands.lua new file mode 100644 index 0000000..dc29a5b --- /dev/null +++ b/sl_controller/commands.lua @@ -0,0 +1,143 @@ +--[[ + + sl_controller + ============= + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + commands.lua: + + Register all basic controller commands + +]]-- + +sl_controller.register_function("get_input", { + cmnd = function(self, num) + _G = self._G + return sl_controller.get_input(self.meta.number, num) + end, + help = " $get_input(num) --> 'on', 'off', or nil\n".. + " Read local input value from device with number 'num'.\n".. + ' example: inp = $get_input("1234")\n'.. + " The device has to be connected with the controller." +}) + +sl_controller.register_function("get_status", { + cmnd = function(self, num) + _G = self._G + return tubelib.send_request(num, "state", nil) + end, + help = " $get_status(num) --> 'stopped', 'running',\n".. + " 'standby', 'blocked' or 'fault'\n".. + " Read status from a remote device.\n".. + ' example: sts = $get_status("1234")' +}) + +sl_controller.register_function("time_as_str", { + cmnd = function(self) + _G = self._G + local t = minetest.get_timeofday() + local h = math.floor(t*24) % 24 + local m = math.floor(t*1440) % 60 + return string.format("%02d:%02d", h, m) + end, + help = " $time_as_str() --> e.g. '18:45'\n".. + " Read time of day as string (24h).\n".. + ' example: time = $time_as_str()' +}) + +sl_controller.register_function("time_as_num", { + cmnd = function(self, num) + _G = self._G + local t = minetest.get_timeofday() + local h = math.floor(t*24) % 24 + local m = math.floor(t*1440) % 60 + return h * 100 + m + end, + help = " $time_as_num() --> e.g.: 1845\n".. + " Read time of day as number (24h).\n".. + ' example: time = $time_as_num()' +}) + +sl_controller.register_function("playerdetector", { + cmnd = function(self, num) + _G = self._G + return tubelib.send_request(num, "name", nil) + end, + help = " $playerdetector(num) --> e.g. 'Joe'\n".. + " '' is returned if no layer is nearby.\n".. + ' example: name = $playerdetector("1234")' +}) + +sl_controller.register_action("send_cmnd", { + cmnd = function(self, num, text) + _G = self._G + tubelib.send_message(num, self.meta.owner, nil, text, nil) + end, + help = " $send_cmnd(num, text)\n".. + " Send a command to the device with number 'num'.\n".. + " For most devices: 'on', 'off'\n".. + " Signal Tower: 'green', 'amber', 'red'\n".. + ' example: $send_cmnd("1234", "on")' +}) + +sl_controller.register_action("display", { + cmnd = function(self, num, row, text1, text2, text3) + _G = self._G + local text = (text1 or "") .. (text2 or "") .. (text3 or "") + tubelib.send_message(num, self.meta.owner, nil, "row", {row = row, str = text}) + end, + help = " $display(num, row, text,...)\n".. + " Send a text line to the display with number 'num'.\n".. + " 'row' is a value from 1..9\n".. + " The function accepts up to 3 text parameters\n".. + ' example: $display("0123", 1, "Hello ", name, " !")' +}) + +sl_controller.register_action("clear_screen", { + cmnd = function(self, num) + _G = self._G + tubelib.send_message(num, self.meta.owner, nil, "clear", nil) + end, + help = " $clear_screen(num)\n".. + " Clear the screen of the display\n".. + " with number 'num'.\n".. + ' example: $clear_screen("1234")' +}) + +sl_controller.register_action("chat", { + cmnd = function(self, text) + _G = self._G + minetest.chat_send_player(self.meta.owner, "[SmartLine Controller] "..text) + end, + help = " $chat(text)\n".. + " Send yourself a chat message.\n".. + ' example: $chat("Hello")' +}) + +sl_controller.register_action("door", { + cmnd = function(self, pos, text) + _G = self._G + pos = minetest.string_to_pos("("..pos..")") + if pos then + local door = doors.get(pos) + if door then + local player = { + get_player_name = function() return self.meta.owner end, + } + if state == "open" then + door:open(player) + elseif state == "close" then + door:close(player) + end + end + end + end, + help = " %door(pos, text)\n".. + " Open/Close a door at position 'pos'\n".. + ' example: door("123,7,-1200", "close")\n'.. + " Hint: Use the Tubelib Programmer to\ndetermine the door position." +}) diff --git a/sl_controller/controller.lua b/sl_controller/controller.lua new file mode 100644 index 0000000..844be44 --- /dev/null +++ b/sl_controller/controller.lua @@ -0,0 +1,371 @@ +--[[ + + sl_controller + ============= + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + + controller.lua: + +]]-- + +local sHELP = [[Safer LUA Controller + +Safer LUA is a subset of LUA with the following restrictions: + - No loop keywords like: for, while, repeat,... + - No table constructions via: { ... } + - Limited set of available functions + - Store() as alternative to LUA tables + +]] + +local mail_exists = minetest.get_modpath("mail") and mail ~= nil + +sl_controller = {} + +local Cache = {} + +local tCommands = {} +local tFunctions = {" Overview", " Store"} +local tHelpTexts = {[" Overview"] = sHELP, [" Store"] = safer_lua.StoreHelp} +local sFunctionList = "" +local tFunctionIndex = {} + +minetest.after(2, function() + sFunctionList = table.concat(tFunctions, ",") + for idx,key in ipairs(tFunctions) do + tFunctionIndex[key] = idx + end +end) + +local function output(pos, text) + local meta = minetest.get_meta(pos) + text = meta:get_string("output") .. "\n" .. (text or "") + text = text:sub(-500,-1) + meta:set_string("output", text) +end + +-- +-- API functions for function/action registrations +-- +function sl_controller.register_function(key, attr) + tCommands[key] = attr.cmnd + table.insert(tFunctions, " $"..key) + tHelpTexts[" $"..key] = attr.help +end + +function sl_controller.register_action(key, attr) + tCommands[key] = attr.cmnd + table.insert(tFunctions, " $"..key) + tHelpTexts[" $"..key] = attr.help +end + +local function merge(dest, keys, values) + for idx,key in ipairs(keys) do + dest.env[key] = values[idx] + end + return dest +end + +sl_controller.register_action("print", { + cmnd = function(self, text1, text2, text3) + _G = self._G + local pos = self.meta.pos + local text = (text1 or "")..(text2 or "")..(text3 or "") + output(pos, text) + end, + help = "$print(text,...)\n".. + "Send a text line to the output window.\n".. + "The function accepts up to 3 text strings\n".. + 'e.g. $print("Hello ", name, " !")' +}) + + +local function formspec1(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local init = meta:get_string("init") + init = minetest.formspec_escape(init) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,loop,outp,notes,help;1;;true]".. + "textarea[0.3,0.2;10,8.3;init;function init();"..init.."]".. + "label[0,7.3;end]".. + "button_exit[4.4,7.5;1.8,1;cancel;Cancel]".. + "button[6.3,7.5;1.8,1;save;Save]".. + "button[8.2,7.5;1.8,1;"..cmnd.."]" +end + +local function formspec2(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local loop = meta:get_string("loop") + loop = minetest.formspec_escape(loop) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,loop,outp,notes,help;2;;true]".. + "textarea[0.3,0.2;10,8.3;loop;function loop(ticks, elapsed);"..loop.."]".. + "label[0,7.3;end]".. + "button_exit[4.4,7.5;1.8,1;cancel;Cancel]".. + "button[6.3,7.5;1.8,1;save;Save]".. + "button[8.2,7.5;1.8,1;"..cmnd.."]" +end + +local function formspec3(meta) + local running = meta:get_int("state") == tubelib.RUNNING + local cmnd = running and "stop;Stop" or "start;Start" + local output = meta:get_string("output") + output = minetest.formspec_escape(output) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,loop,outp,notes,help;3;;true]".. + "textarea[0.3,0.2;10,8.3;help;Output:;"..output.."]".. + "button[4.4,7.5;1.8,1;clear;Clear]".. + "button[6.3,7.5;1.8,1;update;Update]".. + "button[8.2,7.5;1.8,1;"..cmnd.."]" +end + +local function formspec4(meta) + local notes = meta:get_string("notes") + notes = minetest.formspec_escape(notes) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,loop,outp,notes,help;4;;true]".. + "textarea[0.3,0.2;10,8.3;notes;Notepad:;"..notes.."]".. + "button_exit[6.3,7.5;1.8,1;cancel;Cancel]".. + "button[8.2,7.5;1.8,1;save;Save]" +end + +local function formspec5(items, pos, text) + text = minetest.formspec_escape(text) + return "size[10,8]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "tabheader[0,0;tab;init,loop,outp,notes,help;5;;true]".. + "label[0,-0.2;Functions:]".. + "dropdown[0.3,0.2;10,8.3;functions;"..items..";"..pos.."]".. + "textarea[0.3,1.3;10,8;help;Help:;"..text.."]" +end + +local function error(pos, err) + output(pos, err) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_string("infotext", "Controller "..number..": error") + return false +end + +local function compile(pos, meta, number) + local init = meta:get_string("init") + local loop = meta:get_string("loop") + local owner = meta:get_string("owner") + local env = table.copy(tCommands) + env.meta = {pos=pos, owner=owner, number=number} + local code = safer_lua.init(pos, init, loop, env, error) + + if code then + Cache[number] = {code=code, inputs={}} + return true + end + return false +end + +local function on_timer(pos, elapsed) + local t = minetest.get_us_time() + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + if Cache[number] or compile(pos, meta, number) then + + local code = Cache[number].code + local res = safer_lua.run_loop(pos, elapsed, code, error) + + t = minetest.get_us_time() - t + print("time", t) + return res + end + return false +end + +local function start_controller(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + if compile(pos, meta, number) then + meta:set_int("state", tubelib.RUNNING) + minetest.get_node_timer(pos):start(1) + meta:set_string("infotext", "Controller "..number..": running") + end + meta:set_string("output", "") + meta:set_string("formspec", formspec3(meta)) +end + +local function stop_controller(pos) + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + meta:set_int("state", tubelib.STOPPED) + minetest.get_node_timer(pos):stop() + meta:set_string("infotext", "Controller "..number..": stopped") + meta:set_string("formspec", formspec2(meta)) +end + +local function on_receive_fields(pos, formname, fields, player) + if minetest.is_protected(pos, player:get_player_name()) then + return + end + local meta = minetest.get_meta(pos) + + --print(dump(fields)) + if fields.cancel == nil then + if fields.init then + meta:set_string("init", fields.init) + meta:set_string("formspec", formspec1(meta)) + elseif fields.loop then + meta:set_string("loop", fields.loop) + meta:set_string("formspec", formspec2(meta)) + elseif fields.notes then + meta:set_string("notes", fields.notes) + meta:set_string("formspec", formspec4(meta)) + end + end + + if fields.update then + meta:set_string("formspec", formspec3(meta)) + elseif fields.clear then + meta:set_string("output", "") + meta:set_string("formspec", formspec3(meta)) + elseif fields.tab == "1" then + meta:set_string("formspec", formspec1(meta)) + elseif fields.tab == "2" then + meta:set_string("formspec", formspec2(meta)) + elseif fields.tab == "3" then + meta:set_string("formspec", formspec3(meta)) + elseif fields.tab == "4" then + meta:set_string("formspec", formspec4(meta)) + elseif fields.tab == "5" then + meta:set_string("formspec", formspec5(sFunctionList, 1, sHELP)) + elseif fields.start == "Start" then + start_controller(pos) + elseif fields.stop == "Stop" then + stop_controller(pos) + elseif fields.functions then + local key = fields.functions + local text = tHelpTexts[key] or "" + local pos = tFunctionIndex[key] or 1 + meta:set_string("formspec", formspec5(sFunctionList, pos, text)) + end +end + +minetest.register_node("sl_controller:controller", { + description = "SaferLUA Controller", + inventory_image = "sl_controller_inventory.png", + wield_image = "sl_controller_inventory.png", + stack_max = 1, + tiles = { + -- up, down, right, left, back, front + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png", + "smartline.png^sl_controller.png", + }, + + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + { -6/32, -6/32, 14/32, 6/32, 6/32, 16/32}, + }, + }, + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local number = tubelib.add_node(pos, "sl_controller:controller") + meta:set_string("owner", placer:get_player_name()) + --print("after_place_node", number) + meta:set_string("number", number) + meta:set_int("state", tubelib.STOPPED) + meta:set_string("init", "-- called only once") + meta:set_string("loop", "-- called every second") + meta:set_string("notes", "For your notes / snippets") + meta:set_string("formspec", formspec1(meta)) + meta:set_string("infotext", "Controller "..number..": stopped") + end, + + on_receive_fields = on_receive_fields, + + on_dig = function(pos, node, puncher, pointed_thing) + if minetest.is_protected(pos, puncher:get_player_name()) then + return + end + + minetest.node_dig(pos, node, puncher, pointed_thing) + tubelib.remove_node(pos) + end, + + on_timer = on_timer, + + paramtype = "light", + sunlight_propagates = true, + paramtype2 = "facedir", + groups = {choppy=1, cracky=1, crumbly=1}, + is_ground_content = false, + sounds = default.node_sound_stone_defaults(), +}) + + +minetest.register_craft({ + type = "shapeless", + output = "sl_controller:controller", + recipe = {"smartline:controller"} +}) + + +local function set_input(number, input, val) + if input then + if Cache[number] and Cache[number].inputs then + --print("set_input", input, val) + Cache[number].inputs[input] = val + end + end +end + +function sl_controller.get_input(number, input) + --print("get_input", number, input, dump(Cache[number].inputs)) + if input then + if Cache[number] and Cache[number].inputs then + return Cache[number].inputs[input] or "off" + end + end + return "off" +end + +tubelib.register_node("sl_controller:controller", {}, { + on_recv_message = function(pos, topic, payload) + ----------------------------------------------------- + local meta = minetest.get_meta(pos) + local number = meta:get_string("number") + --------------------------------------------- + if topic == "on" then + set_input(number, payload, topic) + elseif topic == "off" then + set_input(number, payload, topic) + elseif topic == "state" then + local state = meta:get_int("state") + return tubelib.statestring(state) + else + return "unsupported" + end + end, +}) diff --git a/sl_controller/depends.txt b/sl_controller/depends.txt new file mode 100644 index 0000000..f87d359 --- /dev/null +++ b/sl_controller/depends.txt @@ -0,0 +1,5 @@ +default +doors +tubelib +safer_lua + diff --git a/sl_controller/description.txt b/sl_controller/description.txt new file mode 100644 index 0000000..077e00a --- /dev/null +++ b/sl_controller/description.txt @@ -0,0 +1,2 @@ +tbd. + diff --git a/sl_controller/init.lua b/sl_controller/init.lua new file mode 100644 index 0000000..f7b08c8 --- /dev/null +++ b/sl_controller/init.lua @@ -0,0 +1,14 @@ +--[[ + + sl_controller + ============= + + Copyright (C) 2018 Joachim Stolberg + + LGPLv2.1+ + See LICENSE.txt for more information + +]]-- + +dofile(minetest.get_modpath("sl_controller") .. "/controller.lua") +dofile(minetest.get_modpath("sl_controller") .. "/commands.lua") \ No newline at end of file diff --git a/sl_controller/mod.conf b/sl_controller/mod.conf new file mode 100644 index 0000000..b2058b6 --- /dev/null +++ b/sl_controller/mod.conf @@ -0,0 +1 @@ +name=sl_controller diff --git a/sl_controller/textures/sl_controller.png b/sl_controller/textures/sl_controller.png new file mode 100644 index 0000000..67e484f Binary files /dev/null and b/sl_controller/textures/sl_controller.png differ diff --git a/sl_controller/textures/sl_controller_inventory.png b/sl_controller/textures/sl_controller_inventory.png new file mode 100644 index 0000000..4a5de55 Binary files /dev/null and b/sl_controller/textures/sl_controller_inventory.png differ