init.lua 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. -- ______
  2. -- |
  3. -- |
  4. -- | __ ___ _ __ _ _
  5. -- | | | | | |\ | | |_| | | | | |_ |_|
  6. -- |___| |______ |__| | \| | | \ |__| |_ |_ |_ |\
  7. -- |
  8. -- |
  9. --
  10. -- Reference
  11. -- ports = get_real_port_states(pos): gets if inputs are powered from outside
  12. -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port
  13. -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states
  14. -- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos
  15. -- run_inner(pos, code, event): runs code on the controller at pos and event
  16. -- reset_formspec(pos, code, errmsg): installs new code and prints error messages, without resetting LCID
  17. -- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error message
  18. -- run(pos, event): a wrapper for run_inner which gets code & handles errors via reset_meta
  19. -- resetn(pos): performs a hardware reset, turns off all ports
  20. --
  21. -- The Sandbox
  22. -- The whole code of the controller runs in a sandbox,
  23. -- a very restricted environment.
  24. -- Actually the only way to damage the server is to
  25. -- use too much memory from the sandbox.
  26. -- You can add more functions to the environment
  27. -- (see where local env is defined)
  28. -- Something nice to play is is appending minetest.env to it.
  29. local BASENAME = "mesecons_luacontroller:luacontroller"
  30. local rules = {
  31. a = {x = -1, y = 0, z = 0, name="A"},
  32. b = {x = 0, y = 0, z = 1, name="B"},
  33. c = {x = 1, y = 0, z = 0, name="C"},
  34. d = {x = 0, y = 0, z = -1, name="D"},
  35. }
  36. ------------------
  37. -- Action stuff --
  38. ------------------
  39. -- These helpers are required to set the port states of the luacontroller
  40. local function update_real_port_states(pos, rule_name, new_state)
  41. local meta = minetest.get_meta(pos)
  42. if rule_name == nil then
  43. meta:set_int("real_portstates", 1)
  44. return
  45. end
  46. local n = meta:get_int("real_portstates") - 1
  47. local L = {}
  48. for i = 1, 4 do
  49. L[i] = n % 2
  50. n = math.floor(n / 2)
  51. end
  52. -- (0,-1) (-1,0) (1,0) (0,1)
  53. local pos_to_side = { 4, 1, nil, 3, 2 }
  54. if rule_name.x == nil then
  55. for _, rname in ipairs(rule_name) do
  56. local port = pos_to_side[rname.x + (2 * rname.z) + 3]
  57. L[port] = (newstate == "on") and 1 or 0
  58. end
  59. else
  60. local port = pos_to_side[rule_name.x + (2 * rule_name.z) + 3]
  61. L[port] = (new_state == "on") and 1 or 0
  62. end
  63. meta:set_int("real_portstates",
  64. 1 +
  65. 1 * L[1] +
  66. 2 * L[2] +
  67. 4 * L[3] +
  68. 8 * L[4])
  69. end
  70. local port_names = {"a", "b", "c", "d"}
  71. local function get_real_port_states(pos)
  72. -- Determine if ports are powered (by itself or from outside)
  73. local meta = minetest.get_meta(pos)
  74. local L = {}
  75. local n = meta:get_int("real_portstates") - 1
  76. for _, name in ipairs(port_names) do
  77. L[name] = ((n % 2) == 1)
  78. n = math.floor(n / 2)
  79. end
  80. return L
  81. end
  82. local function merge_port_states(ports, vports)
  83. return {
  84. a = ports.a or vports.a,
  85. b = ports.b or vports.b,
  86. c = ports.c or vports.c,
  87. d = ports.d or vports.d,
  88. }
  89. end
  90. local function generate_name(ports)
  91. local d = ports.d and 1 or 0
  92. local c = ports.c and 1 or 0
  93. local b = ports.b and 1 or 0
  94. local a = ports.a and 1 or 0
  95. return BASENAME..d..c..b..a
  96. end
  97. local function set_port(pos, rule, state)
  98. if state then
  99. mesecon.receptor_on(pos, {rule})
  100. else
  101. mesecon.receptor_off(pos, {rule})
  102. end
  103. end
  104. local function clean_port_states(ports)
  105. ports.a = ports.a and true or false
  106. ports.b = ports.b and true or false
  107. ports.c = ports.c and true or false
  108. ports.d = ports.d and true or false
  109. end
  110. local function set_port_states(pos, ports)
  111. local node = minetest.get_node(pos)
  112. local name = node.name
  113. clean_port_states(ports)
  114. local vports = minetest.registered_nodes[name].virtual_portstates
  115. local new_name = generate_name(ports)
  116. if name ~= new_name and vports then
  117. -- Problem:
  118. -- We need to place the new node first so that when turning
  119. -- off some port, it won't stay on because the rules indicate
  120. -- there is an onstate output port there.
  121. -- When turning the output off then, it will however cause feedback
  122. -- so that the luacontroller will receive an "off" event by turning
  123. -- its output off.
  124. -- Solution / Workaround:
  125. -- Remember which output was turned off and ignore next "off" event.
  126. local meta = minetest.get_meta(pos)
  127. local ign = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
  128. if ports.a and not vports.a and not mesecon.is_powered(pos, rules.a) then ign.A = true end
  129. if ports.b and not vports.b and not mesecon.is_powered(pos, rules.b) then ign.B = true end
  130. if ports.c and not vports.c and not mesecon.is_powered(pos, rules.c) then ign.C = true end
  131. if ports.d and not vports.d and not mesecon.is_powered(pos, rules.d) then ign.D = true end
  132. meta:set_string("ignore_offevents", minetest.serialize(ign))
  133. minetest.swap_node(pos, {name = new_name, param2 = node.param2})
  134. if ports.a ~= vports.a then set_port(pos, rules.a, ports.a) end
  135. if ports.b ~= vports.b then set_port(pos, rules.b, ports.b) end
  136. if ports.c ~= vports.c then set_port(pos, rules.c, ports.c) end
  137. if ports.d ~= vports.d then set_port(pos, rules.d, ports.d) end
  138. end
  139. end
  140. -----------------
  141. -- Overheating --
  142. -----------------
  143. local function burn_controller(pos)
  144. local node = minetest.get_node(pos)
  145. node.name = BASENAME.."_burnt"
  146. minetest.swap_node(pos, node)
  147. minetest.get_meta(pos):set_string("lc_memory", "");
  148. -- Wait for pending operations
  149. minetest.after(0.2, mesecon.receptor_off, pos, mesecon.rules.flat)
  150. end
  151. local function overheat(pos, meta)
  152. if mesecon.do_overheat(pos) then -- If too hot
  153. burn_controller(pos)
  154. return true
  155. end
  156. end
  157. ------------------------
  158. -- Ignored off events --
  159. ------------------------
  160. local function ignore_event(event, meta)
  161. if event.type ~= "off" then return false end
  162. local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
  163. if ignore_offevents[event.pin.name] then
  164. ignore_offevents[event.pin.name] = nil
  165. meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents))
  166. return true
  167. end
  168. end
  169. -------------------------
  170. -- Parsing and running --
  171. -------------------------
  172. local function safe_print(param)
  173. local string_meta = getmetatable("")
  174. local sandbox = string_meta.__index
  175. string_meta.__index = string -- Leave string sandbox temporarily
  176. print(dump(param))
  177. string_meta.__index = sandbox -- Restore string sandbox
  178. end
  179. local function safe_date()
  180. return(os.date("*t",os.time()))
  181. end
  182. -- string.rep(str, n) with a high value for n can be used to DoS
  183. -- the server. Therefore, limit max. length of generated string.
  184. local function safe_string_rep(str, n)
  185. if #str * n > mesecon.setting("luacontroller_string_rep_max", 64000) then
  186. debug.sethook() -- Clear hook
  187. error("string.rep: string length overflow", 2)
  188. end
  189. return string.rep(str, n)
  190. end
  191. -- string.find with a pattern can be used to DoS the server.
  192. -- Therefore, limit string.find to patternless matching.
  193. local function safe_string_find(...)
  194. if (select(4, ...)) ~= true then
  195. debug.sethook() -- Clear hook
  196. error("string.find: 'plain' (fourth parameter) must always be true in a Luacontroller")
  197. end
  198. return string.find(...)
  199. end
  200. local function remove_functions(x)
  201. local tp = type(x)
  202. if tp == "function" then
  203. return nil
  204. end
  205. -- Make sure to not serialize the same table multiple times, otherwise
  206. -- writing mem.test = mem in the Luacontroller will lead to infinite recursion
  207. local seen = {}
  208. local function rfuncs(x)
  209. if x == nil then return end
  210. if seen[x] then return end
  211. seen[x] = true
  212. if type(x) ~= "table" then return end
  213. for key, value in pairs(x) do
  214. if type(key) == "function" or type(value) == "function" then
  215. x[key] = nil
  216. else
  217. if type(key) == "table" then
  218. rfuncs(key)
  219. end
  220. if type(value) == "table" then
  221. rfuncs(value)
  222. end
  223. end
  224. end
  225. end
  226. rfuncs(x)
  227. return x
  228. end
  229. -- The setting affects API so is not intended to be changeable at runtime
  230. local get_interrupt
  231. if mesecon.setting("luacontroller_lightweight_interrupts", false) then
  232. -- use node timer
  233. get_interrupt = function(pos, itbl, send_warning)
  234. return (function(time, iid)
  235. if type(time) ~= "number" then error("Delay must be a number") end
  236. if iid ~= nil then send_warning("Interrupt IDs are disabled on this server") end
  237. table.insert(itbl, function() minetest.get_node_timer(pos):start(time) end)
  238. end)
  239. end
  240. else
  241. -- use global action queue
  242. -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
  243. get_interrupt = function(pos, itbl, send_warning)
  244. -- iid = interrupt id
  245. local function interrupt(time, iid)
  246. -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
  247. -- Hence the values get moved out. Should take less time than original, so totally compatible
  248. if type(time) ~= "number" then error("Delay must be a number") end
  249. table.insert(itbl, function ()
  250. -- Outside string metatable sandbox, can safely run this now
  251. local luac_id = minetest.get_meta(pos):get_int("luac_id")
  252. -- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data.
  253. -- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended.
  254. -- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place
  255. iid = remove_functions(iid)
  256. local msg_ser = minetest.serialize(iid)
  257. if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then
  258. mesecon.queue:add_action(pos, "lc_interrupt", {luac_id, iid}, time, iid, 1)
  259. else
  260. send_warning("An interrupt ID was too large!")
  261. end
  262. end)
  263. end
  264. return interrupt
  265. end
  266. end
  267. -- Given a message object passed to digiline_send, clean it up into a form
  268. -- which is safe to transmit over the network and compute its "cost" (a very
  269. -- rough estimate of its memory usage).
  270. --
  271. -- The cleaning comprises the following:
  272. -- 1. Functions (and userdata, though user scripts ought not to get hold of
  273. -- those in the first place) are removed, because they break the model of
  274. -- Digilines as a network that carries basic data, and they could exfiltrate
  275. -- references to mutable objects from one Luacontroller to another, allowing
  276. -- inappropriate high-bandwidth, no-wires communication.
  277. -- 2. Tables are duplicated because, being mutable, they could otherwise be
  278. -- modified after the send is complete in order to change what data arrives
  279. -- at the recipient, perhaps in violation of the previous cleaning rule or
  280. -- in violation of the message size limit.
  281. --
  282. -- The cost indication is only approximate; it’s not a perfect measurement of
  283. -- the number of bytes of memory used by the message object.
  284. --
  285. -- Parameters:
  286. -- msg -- the message to clean
  287. -- back_references -- for internal use only; do not provide
  288. --
  289. -- Returns:
  290. -- 1. The cleaned object.
  291. -- 2. The approximate cost of the object.
  292. local function clean_and_weigh_digiline_message(msg, back_references)
  293. local t = type(msg)
  294. if t == "string" then
  295. -- Strings are immutable so can be passed by reference, and cost their
  296. -- length plus the size of the Lua object header (24 bytes on a 64-bit
  297. -- platform) plus one byte for the NUL terminator.
  298. return msg, #msg + 25
  299. elseif t == "number" then
  300. -- Numbers are passed by value so need not be touched, and cost 8 bytes
  301. -- as all numbers in Lua are doubles.
  302. return msg, 8
  303. elseif t == "boolean" then
  304. -- Booleans are passed by value so need not be touched, and cost 1
  305. -- byte.
  306. return msg, 1
  307. elseif t == "table" then
  308. -- Tables are duplicated. Check if this table has been seen before
  309. -- (self-referential or shared table); if so, reuse the cleaned value
  310. -- of the previous occurrence, maintaining table topology and avoiding
  311. -- infinite recursion, and charge zero bytes for this as the object has
  312. -- already been counted.
  313. back_references = back_references or {}
  314. local bref = back_references[msg]
  315. if bref then
  316. return bref, 0
  317. end
  318. -- Construct a new table by cleaning all the keys and values and adding
  319. -- up their costs, plus 8 bytes as a rough estimate of table overhead.
  320. local cost = 8
  321. local ret = {}
  322. back_references[msg] = ret
  323. for k, v in pairs(msg) do
  324. local k_cost, v_cost
  325. k, k_cost = clean_and_weigh_digiline_message(k, back_references)
  326. v, v_cost = clean_and_weigh_digiline_message(v, back_references)
  327. if k ~= nil and v ~= nil then
  328. -- Only include an element if its key and value are of legal
  329. -- types.
  330. ret[k] = v
  331. end
  332. -- If we only counted the cost of a table element when we actually
  333. -- used it, we would be vulnerable to the following attack:
  334. -- 1. Construct a huge table (too large to pass the cost limit).
  335. -- 2. Insert it somewhere in a table, with a function as a key.
  336. -- 3. Insert it somewhere in another table, with a number as a key.
  337. -- 4. The first occurrence doesn’t pay the cost because functions
  338. -- are stripped and therefore the element is dropped.
  339. -- 5. The second occurrence doesn’t pay the cost because it’s in
  340. -- back_references.
  341. -- By counting the costs regardless of whether the objects will be
  342. -- included, we avoid this attack; it may overestimate the cost of
  343. -- some messages, but only those that won’t be delivered intact
  344. -- anyway because they contain illegal object types.
  345. cost = cost + k_cost + v_cost
  346. end
  347. return ret, cost
  348. else
  349. return nil, 0
  350. end
  351. end
  352. -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
  353. local function get_digiline_send(pos, itbl, send_warning)
  354. if not minetest.global_exists("digilines") then return end
  355. local chan_maxlen = mesecon.setting("luacontroller_digiline_channel_maxlen", 256)
  356. local maxlen = mesecon.setting("luacontroller_digiline_maxlen", 50000)
  357. return function(channel, msg)
  358. -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
  359. -- or via anything that could.
  360. -- Make sure channel is string, number or boolean
  361. if type(channel) == "string" then
  362. if #channel > chan_maxlen then
  363. send_warning("Channel string too long.")
  364. return false
  365. end
  366. elseif (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then
  367. send_warning("Channel must be string, number or boolean.")
  368. return false
  369. end
  370. local msg_cost
  371. msg, msg_cost = clean_and_weigh_digiline_message(msg)
  372. if msg == nil or msg_cost > maxlen then
  373. send_warning("Message was too complex, or contained invalid data.")
  374. return false
  375. end
  376. table.insert(itbl, function ()
  377. -- Runs outside of string metatable sandbox
  378. local luac_id = minetest.get_meta(pos):get_int("luac_id")
  379. mesecon.queue:add_action(pos, "lc_digiline_relay", {channel, luac_id, msg})
  380. end)
  381. return true
  382. end
  383. end
  384. local safe_globals = {
  385. -- Don't add pcall/xpcall unless willing to deal with the consequences (unless very careful, incredibly likely to allow killing server indirectly)
  386. "assert", "error", "ipairs", "next", "pairs", "select",
  387. "tonumber", "tostring", "type", "unpack", "_VERSION"
  388. }
  389. local function create_environment(pos, mem, event, itbl, send_warning)
  390. -- Gather variables for the environment
  391. local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
  392. local vports_copy = {}
  393. for k, v in pairs(vports) do vports_copy[k] = v end
  394. local rports = get_real_port_states(pos)
  395. -- Create new library tables on each call to prevent one Luacontroller
  396. -- from breaking a library and messing up other Luacontrollers.
  397. local env = {
  398. pin = merge_port_states(vports, rports),
  399. port = vports_copy,
  400. event = event,
  401. mem = mem,
  402. heat = mesecon.get_heat(pos),
  403. heat_max = mesecon.setting("overheat_max", 20),
  404. print = safe_print,
  405. interrupt = get_interrupt(pos, itbl, send_warning),
  406. digiline_send = get_digiline_send(pos, itbl, send_warning),
  407. string = {
  408. byte = string.byte,
  409. char = string.char,
  410. format = string.format,
  411. len = string.len,
  412. lower = string.lower,
  413. upper = string.upper,
  414. rep = safe_string_rep,
  415. reverse = string.reverse,
  416. sub = string.sub,
  417. find = safe_string_find,
  418. },
  419. math = {
  420. abs = math.abs,
  421. acos = math.acos,
  422. asin = math.asin,
  423. atan = math.atan,
  424. atan2 = math.atan2,
  425. ceil = math.ceil,
  426. cos = math.cos,
  427. cosh = math.cosh,
  428. deg = math.deg,
  429. exp = math.exp,
  430. floor = math.floor,
  431. fmod = math.fmod,
  432. frexp = math.frexp,
  433. huge = math.huge,
  434. ldexp = math.ldexp,
  435. log = math.log,
  436. log10 = math.log10,
  437. max = math.max,
  438. min = math.min,
  439. modf = math.modf,
  440. pi = math.pi,
  441. pow = math.pow,
  442. rad = math.rad,
  443. random = math.random,
  444. sin = math.sin,
  445. sinh = math.sinh,
  446. sqrt = math.sqrt,
  447. tan = math.tan,
  448. tanh = math.tanh,
  449. },
  450. table = {
  451. concat = table.concat,
  452. insert = table.insert,
  453. maxn = table.maxn,
  454. remove = table.remove,
  455. sort = table.sort,
  456. },
  457. os = {
  458. clock = os.clock,
  459. difftime = os.difftime,
  460. time = os.time,
  461. datetable = safe_date,
  462. },
  463. }
  464. env._G = env
  465. for _, name in pairs(safe_globals) do
  466. env[name] = _G[name]
  467. end
  468. return env
  469. end
  470. local function timeout()
  471. debug.sethook() -- Clear hook
  472. error("Code timed out!", 2)
  473. end
  474. local function create_sandbox(code, env)
  475. if code:byte(1) == 27 then
  476. return nil, "Binary code prohibited."
  477. end
  478. local f, msg = loadstring(code)
  479. if not f then return nil, msg end
  480. setfenv(f, env)
  481. -- Turn off JIT optimization for user code so that count
  482. -- events are generated when adding debug hooks
  483. if rawget(_G, "jit") then
  484. jit.off(f, true)
  485. end
  486. local maxevents = mesecon.setting("luacontroller_maxevents", 10000)
  487. return function(...)
  488. -- NOTE: This runs within string metatable sandbox, so the setting's been moved out for safety
  489. -- Use instruction counter to stop execution
  490. -- after luacontroller_maxevents
  491. debug.sethook(timeout, "", maxevents)
  492. local ok, ret = pcall(f, ...)
  493. debug.sethook() -- Clear hook
  494. if not ok then error(ret, 0) end
  495. return ret
  496. end
  497. end
  498. local function load_memory(meta)
  499. return minetest.deserialize(meta:get_string("lc_memory"), true) or {}
  500. end
  501. local function save_memory(pos, meta, mem)
  502. local memstring = minetest.serialize(remove_functions(mem))
  503. local memsize_max = mesecon.setting("luacontroller_memsize", 100000)
  504. if (#memstring <= memsize_max) then
  505. meta:set_string("lc_memory", memstring)
  506. meta:mark_as_private("lc_memory")
  507. else
  508. print("Error: Luacontroller memory overflow. "..memsize_max.." bytes available, "
  509. ..#memstring.." required. Controller overheats.")
  510. burn_controller(pos)
  511. end
  512. end
  513. -- Returns success (boolean), errmsg (string)
  514. -- run (as opposed to run_inner) is responsible for setting up meta according to this output
  515. local function run_inner(pos, code, event)
  516. local meta = minetest.get_meta(pos)
  517. -- Note: These return success, presumably to avoid changing LC ID.
  518. if overheat(pos) then return true, "" end
  519. if ignore_event(event, meta) then return true, "" end
  520. -- Load code & mem from meta
  521. local mem = load_memory(meta)
  522. local code = meta:get_string("code")
  523. -- 'Last warning' label.
  524. local warning = ""
  525. local function send_warning(str)
  526. warning = "Warning: " .. str
  527. end
  528. -- Create environment
  529. local itbl = {}
  530. local env = create_environment(pos, mem, event, itbl, send_warning)
  531. -- Create the sandbox and execute code
  532. local f, msg = create_sandbox(code, env)
  533. if not f then return false, msg end
  534. -- Start string true sandboxing
  535. local onetruestring = getmetatable("")
  536. -- If a string sandbox is already up yet inconsistent, something is very wrong
  537. assert(onetruestring.__index == string)
  538. onetruestring.__index = env.string
  539. local success, msg = pcall(f)
  540. onetruestring.__index = string
  541. -- End string true sandboxing
  542. if not success then return false, msg end
  543. if type(env.port) ~= "table" then
  544. return false, "Ports set are invalid."
  545. end
  546. -- Actually set the ports
  547. set_port_states(pos, env.port)
  548. -- Save memory. This may burn the luacontroller if a memory overflow occurs.
  549. save_memory(pos, meta, env.mem)
  550. -- Execute deferred tasks
  551. for _, v in ipairs(itbl) do
  552. local failure = v()
  553. if failure then
  554. return false, failure
  555. end
  556. end
  557. return true, warning
  558. end
  559. local function reset_formspec(meta, code, errmsg)
  560. meta:set_string("code", code)
  561. meta:mark_as_private("code")
  562. code = minetest.formspec_escape(code or "")
  563. errmsg = minetest.formspec_escape(tostring(errmsg or ""))
  564. meta:set_string("formspec", "size[12,10]"
  565. .."background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]"
  566. .."label[0.1,8.3;"..errmsg.."]"
  567. .."textarea[0.2,0.2;12.2,9.5;code;;"..code.."]"
  568. .."image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]"
  569. .."image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"
  570. )
  571. end
  572. local function reset_meta(pos, code, errmsg)
  573. local meta = minetest.get_meta(pos)
  574. reset_formspec(meta, code, errmsg)
  575. meta:set_int("luac_id", math.random(1, 65535))
  576. end
  577. -- Wraps run_inner with LC-reset-on-error
  578. local function run(pos, event)
  579. local meta = minetest.get_meta(pos)
  580. local code = meta:get_string("code")
  581. local ok, errmsg = run_inner(pos, code, event)
  582. if not ok then
  583. reset_meta(pos, code, errmsg)
  584. else
  585. reset_formspec(meta, code, errmsg)
  586. end
  587. return ok, errmsg
  588. end
  589. local function reset(pos)
  590. set_port_states(pos, {a=false, b=false, c=false, d=false})
  591. end
  592. local function node_timer(pos)
  593. if minetest.registered_nodes[minetest.get_node(pos).name].is_burnt then
  594. return false
  595. end
  596. run(pos, {type="interrupt"})
  597. return false
  598. end
  599. -----------------------
  600. -- A.Queue callbacks --
  601. -----------------------
  602. mesecon.queue:add_function("lc_interrupt", function (pos, luac_id, iid)
  603. -- There is no luacontroller anymore / it has been reprogrammed / replaced / burnt
  604. if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
  605. if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
  606. run(pos, {type="interrupt", iid = iid})
  607. end)
  608. mesecon.queue:add_function("lc_digiline_relay", function (pos, channel, luac_id, msg)
  609. if not digiline then return end
  610. -- This check is only really necessary because in case of server crash, old actions can be thrown into the future
  611. if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
  612. if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
  613. -- The actual work
  614. digiline:receptor_send(pos, digiline.rules.default, channel, msg)
  615. end)
  616. -----------------------
  617. -- Node Registration --
  618. -----------------------
  619. local output_rules = {}
  620. local input_rules = {}
  621. local node_box = {
  622. type = "fixed",
  623. fixed = {
  624. {-8/16, -8/16, -8/16, 8/16, -7/16, 8/16}, -- Bottom slab
  625. {-5/16, -7/16, -5/16, 5/16, -6/16, 5/16}, -- Circuit board
  626. {-3/16, -6/16, -3/16, 3/16, -5/16, 3/16}, -- IC
  627. }
  628. }
  629. local selection_box = {
  630. type = "fixed",
  631. fixed = { -8/16, -8/16, -8/16, 8/16, -5/16, 8/16 },
  632. }
  633. local digiline = {
  634. receptor = {},
  635. effector = {
  636. action = function(pos, node, channel, msg)
  637. msg = clean_and_weigh_digiline_message(msg)
  638. run(pos, {type = "digiline", channel = channel, msg = msg})
  639. end
  640. }
  641. }
  642. local function get_program(pos)
  643. local meta = minetest.get_meta(pos)
  644. return meta:get_string("code")
  645. end
  646. local function set_program(pos, code)
  647. reset(pos)
  648. reset_meta(pos, code)
  649. return run(pos, {type="program"})
  650. end
  651. local function on_receive_fields(pos, form_name, fields, sender)
  652. if not fields.program then
  653. return
  654. end
  655. local name = sender:get_player_name()
  656. if minetest.is_protected(pos, name) and not minetest.check_player_privs(name, {protection_bypass=true}) then
  657. minetest.record_protection_violation(pos, name)
  658. return
  659. end
  660. local ok, err = set_program(pos, fields.code)
  661. if not ok then
  662. -- it's not an error from the server perspective
  663. minetest.log("action", "Lua controller programming error: " .. tostring(err))
  664. end
  665. end
  666. for a = 0, 1 do -- 0 = off 1 = on
  667. for b = 0, 1 do
  668. for c = 0, 1 do
  669. for d = 0, 1 do
  670. local cid = tostring(d)..tostring(c)..tostring(b)..tostring(a)
  671. local node_name = BASENAME..cid
  672. local top = "jeija_luacontroller_top.png"
  673. if a == 1 then
  674. top = top.."^jeija_luacontroller_LED_A.png"
  675. end
  676. if b == 1 then
  677. top = top.."^jeija_luacontroller_LED_B.png"
  678. end
  679. if c == 1 then
  680. top = top.."^jeija_luacontroller_LED_C.png"
  681. end
  682. if d == 1 then
  683. top = top.."^jeija_luacontroller_LED_D.png"
  684. end
  685. local groups
  686. if a + b + c + d ~= 0 then
  687. groups = {dig_immediate=2, not_in_creative_inventory=1, overheat = 1}
  688. else
  689. groups = {dig_immediate=2, overheat = 1}
  690. end
  691. output_rules[cid] = {}
  692. input_rules[cid] = {}
  693. if a == 1 then table.insert(output_rules[cid], rules.a) end
  694. if b == 1 then table.insert(output_rules[cid], rules.b) end
  695. if c == 1 then table.insert(output_rules[cid], rules.c) end
  696. if d == 1 then table.insert(output_rules[cid], rules.d) end
  697. if a == 0 then table.insert( input_rules[cid], rules.a) end
  698. if b == 0 then table.insert( input_rules[cid], rules.b) end
  699. if c == 0 then table.insert( input_rules[cid], rules.c) end
  700. if d == 0 then table.insert( input_rules[cid], rules.d) end
  701. local mesecons = {
  702. effector = {
  703. rules = input_rules[cid],
  704. action_change = function (pos, _, rule_name, new_state)
  705. update_real_port_states(pos, rule_name, new_state)
  706. run(pos, {type=new_state, pin=rule_name})
  707. end,
  708. },
  709. receptor = {
  710. state = mesecon.state.on,
  711. rules = output_rules[cid]
  712. },
  713. luacontroller = {
  714. get_program = get_program,
  715. set_program = set_program,
  716. },
  717. }
  718. minetest.register_node(node_name, {
  719. description = "Luacontroller",
  720. drawtype = "nodebox",
  721. tiles = {
  722. top,
  723. "jeija_microcontroller_bottom.png",
  724. "jeija_microcontroller_sides.png",
  725. "jeija_microcontroller_sides.png",
  726. "jeija_microcontroller_sides.png",
  727. "jeija_microcontroller_sides.png"
  728. },
  729. inventory_image = top,
  730. paramtype = "light",
  731. is_ground_content = false,
  732. groups = groups,
  733. drop = BASENAME.."0000",
  734. sunlight_propagates = true,
  735. selection_box = selection_box,
  736. node_box = node_box,
  737. on_construct = reset_meta,
  738. on_receive_fields = on_receive_fields,
  739. sounds = default.node_sound_stone_defaults(),
  740. mesecons = mesecons,
  741. digiline = digiline,
  742. -- Virtual portstates are the ports that
  743. -- the node shows as powered up (light up).
  744. virtual_portstates = {
  745. a = a == 1,
  746. b = b == 1,
  747. c = c == 1,
  748. d = d == 1,
  749. },
  750. after_dig_node = function (pos, node)
  751. mesecon.do_cooldown(pos)
  752. mesecon.receptor_off(pos, output_rules)
  753. end,
  754. is_luacontroller = true,
  755. on_timer = node_timer,
  756. on_blast = mesecon.on_blastnode,
  757. })
  758. end
  759. end
  760. end
  761. end
  762. ------------------------------
  763. -- Overheated Luacontroller --
  764. ------------------------------
  765. minetest.register_node(BASENAME .. "_burnt", {
  766. drawtype = "nodebox",
  767. tiles = {
  768. "jeija_luacontroller_burnt_top.png",
  769. "jeija_microcontroller_bottom.png",
  770. "jeija_microcontroller_sides.png",
  771. "jeija_microcontroller_sides.png",
  772. "jeija_microcontroller_sides.png",
  773. "jeija_microcontroller_sides.png"
  774. },
  775. inventory_image = "jeija_luacontroller_burnt_top.png",
  776. is_burnt = true,
  777. paramtype = "light",
  778. is_ground_content = false,
  779. groups = {dig_immediate=2, not_in_creative_inventory=1},
  780. drop = BASENAME.."0000",
  781. sunlight_propagates = true,
  782. selection_box = selection_box,
  783. node_box = node_box,
  784. on_construct = reset_meta,
  785. on_receive_fields = on_receive_fields,
  786. sounds = default.node_sound_stone_defaults(),
  787. virtual_portstates = {a = false, b = false, c = false, d = false},
  788. mesecons = {
  789. effector = {
  790. rules = mesecon.rules.flat,
  791. action_change = function(pos, _, rule_name, new_state)
  792. update_real_port_states(pos, rule_name, new_state)
  793. end,
  794. },
  795. },
  796. on_blast = mesecon.on_blastnode,
  797. })
  798. ------------------------
  799. -- Craft Registration --
  800. ------------------------
  801. minetest.register_craft({
  802. output = BASENAME.."0000 2",
  803. recipe = {
  804. {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
  805. {'mesecons_materials:silicon', 'mesecons_materials:silicon', 'group:mesecon_conductor_craftable'},
  806. {'group:mesecon_conductor_craftable', 'group:mesecon_conductor_craftable', ''},
  807. }
  808. })