24 Commits

Author SHA1 Message Date
8322f256c5 Merge branch 'master' into 'master'
Prevent the symptoms of Issue #33

See merge request VanessaE/pipeworks!31
2020-12-18 21:44:11 +00:00
ba7eb19317 Prevent the symptoms of Issue #33
Try to prevent the crash from issue #33 by
replaving nil velocity or acceleration with (0,0,0).
This does not fix the underlying cause of them being nil,
but should prevent the crash.
2020-12-18 19:28:07 +01:00
065c953eba use the right gear item in node breaker 2020-10-28 12:30:48 -04:00
9a63d17e4a Merge branch 'm_disable_print_message' into 'master'
Log the "Pipeworks loaded!" message to infostream instead of printing it

See merge request VanessaE/pipeworks!30
2020-10-17 18:55:24 +00:00
d814357ddf Merge branch 'm_table_extends_clean' into 'master'
Make pipeworks.table_extend easier to read

See merge request VanessaE/pipeworks!29
2020-10-17 18:55:01 +00:00
c2fe5fe956 Make pipeworks.table_extend easier to read
In my opinion this clarifies that tbl2 is attached at the end of tbl
2020-10-17 20:50:20 +02:00
9dbaa5f4f6 Log the "Pipeworks loaded!" message to infostream instead of printing it 2020-10-17 20:46:44 +02:00
d93396600f Merge branch 'protected-access-to-wielders' into 'master'
Allow protected access to wielder inventories.

Closes #40

See merge request VanessaE/pipeworks!27
2020-09-24 15:33:17 +00:00
c966a8a57d Allow players with the protection_bypass privilege or access to
the protection to access wielder node inventories.

Fixes #40.
2020-09-24 05:12:32 -06:00
61b061f669 Merge branch 'avoid-protection-check-on-chest-close' into 'master'
Avoid protection check on chest close.

Closes #23

See merge request VanessaE/pipeworks!26
2020-09-11 09:45:04 +00:00
fe91d5eb46 Avoid protection check on chest close.
Fixes #23.
2020-09-10 23:12:11 -06:00
eb1064ca6d Merge branch 'patch-1' into 'master'
Update pipeworks.zh_CN.tr

See merge request VanessaE/pipeworks!25
2020-09-05 07:14:04 +00:00
f54e25ec52 Update pipeworks.zh_CN.tr 2020-09-05 06:25:58 +00:00
dedb0dd54e Merge branch 'tptube-api' into 'master'
Expose teleport tube database API

See merge request VanessaE/pipeworks!24
2020-08-10 17:54:28 +00:00
SX
477a024034 Expose teleport tube database API 2020-08-09 23:09:31 +03:00
34cb0e7682 Merge branch 'master' into 'master'
Add "get_recipe" digiline command for querying the current autocrafter recipe.

See merge request VanessaE/pipeworks!23
2020-07-30 00:40:41 +00:00
505fc7cc49 Add "get_recipe" digiline command for querying the current autocrafter recipe.
Useful for having the player teach the luacontroller how to craft various items.
2020-07-23 01:49:37 +02:00
cb58a646cf Merge branch 'undefined' into 'master'
Add Chinese Translation

See merge request VanessaE/pipeworks!22
2020-07-19 03:55:39 +00:00
e3135c53f0 Add Chinese Translation 2020-07-19 02:22:12 +00:00
366d57f4da Merge branch 'fix_connect_sides' into 'master'
Consider connect_sides for item transport

See merge request VanessaE/pipeworks!21
2020-06-30 10:39:52 +00:00
c79e68a80c Consider connect_sides for item transport
Previously connect_sides was only used to choose the correct visual
model, but not during item transport. This allowed items to exit tubes
in directions without a visual connection and enter objects from sides
that should not be connectable according to connect_sides.
For example an item could enter a chest from the front, if a tube passed
there.

This change saves the connect_sides in the meta table of the object
whenever the visual representation is updated. When nothing is cached
yet, it uses the old behavior. That way it does not break existing
builds.
2020-06-30 10:11:22 +00:00
9338c109a6 Merge branch 'rebased_luacontroller' into 'master'
Rebase lua_tube onto upstream luacontroller

See merge request VanessaE/pipeworks!20
2020-06-30 09:41:01 +00:00
63bee98948 Fix typo in luatube update_real_port_states helper 2020-06-30 09:17:46 +00:00
ee6c9991b9 Rebase lua_tube onto upstream luacontroller
Adds:
 - various bug fixes
 - error label on the formspec
 - lightweight interrupts
2020-06-30 09:15:50 +00:00
11 changed files with 441 additions and 93 deletions

View File

@ -389,6 +389,27 @@ minetest.register_node("pipeworks:autocrafter", {
end end
end end
after_recipe_change(pos,inv) after_recipe_change(pos,inv)
elseif msg == "get_recipe" then
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local recipe = {}
for y=0,2,1 do
local row = {}
for x=1,3,1 do
local slot = y*3+x
table.insert(row, inv:get_stack("recipe",slot):get_name())
end
table.insert(recipe, row)
end
local setchan = meta:get_string("channel")
local output = inv:get_stack("output", 1)
digiline:receptor_send(pos, digiline.rules.default, setchan, {
recipe = recipe,
result = {
name = output:get_name(),
count = output:get_count(),
}
})
elseif msg == "off" then elseif msg == "off" then
update_meta(meta, false) update_meta(meta, false)
minetest.get_node_timer(pos):stop() minetest.get_node_timer(pos):stop()

View File

@ -53,27 +53,28 @@ local function tube_autoroute(pos)
} }
-- xm = 1, xp = 2, ym = 3, yp = 4, zm = 5, zp = 6 -- xm = 1, xp = 2, ym = 3, yp = 4, zm = 5, zp = 6
local positions = {} local adjlist = {} -- this will be used in item_transport
local nodes = {}
for i, adj in ipairs(adjustments) do for i, adj in ipairs(adjustments) do
positions[i] = vector.add(pos, adj) local position = vector.add(pos, adj)
nodes[i] = minetest.get_node(positions[i]) local node = minetest.get_node(position)
end
for i, node in ipairs(nodes) do
local idef = minetest.registered_nodes[node.name] local idef = minetest.registered_nodes[node.name]
-- handle the tubes themselves -- handle the tubes themselves
if is_tube(node.name) then if is_tube(node.name) then
active[i] = 1 active[i] = 1
table.insert(adjlist, adj)
-- handle new style connectors -- handle new style connectors
elseif idef and idef.tube and idef.tube.connect_sides then elseif idef and idef.tube and idef.tube.connect_sides then
local dir = adjustments[i] if idef.tube.connect_sides[nodeside(node, vector.multiply(adj, -1))] then
if idef.tube.connect_sides[nodeside(node, vector.multiply(dir, -1))] then
active[i] = 1 active[i] = 1
table.insert(adjlist, adj)
end end
end end
end end
minetest.get_meta(pos):set_string("adjlist", minetest.serialize(adjlist))
-- all sides checked, now figure which tube to use. -- all sides checked, now figure which tube to use.
local nodedef = minetest.registered_nodes[nctr.name] local nodedef = minetest.registered_nodes[nctr.name]

View File

@ -86,10 +86,9 @@ function pipeworks.table_contains(tbl, element)
end end
function pipeworks.table_extend(tbl, tbl2) function pipeworks.table_extend(tbl, tbl2)
local index = #tbl + 1 local oldlength = #tbl
for _, elt in ipairs(tbl2) do for i = 1,#tbl2 do
tbl[index] = elt tbl[oldlength + i] = tbl2[i]
index = index + 1
end end
end end

View File

@ -77,10 +77,8 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
pipeworks.after_place(pos) pipeworks.after_place(pos)
end) end)
minetest.sound_play(sound, {gain = 0.3, pos = pos, max_hear_distance = 10}) minetest.sound_play(sound, {gain = 0.3, pos = pos, max_hear_distance = 10})
end elseif pipeworks.may_configure(pos, player) then
-- Pipeworks Switch -- Pipeworks Switch
if pipeworks.may_configure(pos, player) and not fields.quit then
fs_helpers.on_receive_fields(pos, fields) fs_helpers.on_receive_fields(pos, fields)
minetest.show_formspec(player:get_player_name(), "pipeworks:chest_formspec", get_chest_formspec(pos)) minetest.show_formspec(player:get_player_name(), "pipeworks:chest_formspec", get_chest_formspec(pos))
end end

View File

@ -71,8 +71,8 @@ function pipeworks.may_configure(pos, player)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local owner = meta:get_string("owner") local owner = meta:get_string("owner")
if owner ~= "" then -- wielders and filters if owner ~= "" and owner == name then -- wielders and filters
return owner == name return true
end end
return not minetest.is_protected(pos, name) return not minetest.is_protected(pos, name)
end end
@ -150,4 +150,4 @@ end
minetest.register_alias("pipeworks:pipe", "pipeworks:pipe_110000_empty") minetest.register_alias("pipeworks:pipe", "pipeworks:pipe_110000_empty")
print("Pipeworks loaded!") minetest.log("info", "Pipeworks loaded!")

View File

@ -25,7 +25,7 @@ end
-- both optional w/ sensible defaults and fallback to normal allow_* function -- both optional w/ sensible defaults and fallback to normal allow_* function
-- XXX: possibly change insert_object to insert_item -- XXX: possibly change insert_object to insert_item
local adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}} local default_adjlist={{x=0,y=0,z=1},{x=0,y=0,z=-1},{x=0,y=1,z=0},{x=0,y=-1,z=0},{x=1,y=0,z=0},{x=-1,y=0,z=0}}
function pipeworks.notvel(tbl, vel) function pipeworks.notvel(tbl, vel)
local tbl2={} local tbl2={}
@ -80,6 +80,9 @@ local function go_next_compat(pos, cnode, cmeta, cycledir, vel, stack, owner)
if minetest.registered_nodes[cnode.name] and minetest.registered_nodes[cnode.name].tube and minetest.registered_nodes[cnode.name].tube.can_go then if minetest.registered_nodes[cnode.name] and minetest.registered_nodes[cnode.name].tube and minetest.registered_nodes[cnode.name].tube.can_go then
can_go = minetest.registered_nodes[cnode.name].tube.can_go(pos, cnode, vel, stack) can_go = minetest.registered_nodes[cnode.name].tube.can_go(pos, cnode, vel, stack)
else else
local adjlist_string = minetest.get_meta(pos):get_string("adjlist")
local adjlist = minetest.deserialize(adjlist_string) or default_adjlist -- backward compat: if not found, use old behavior: all directions
can_go = pipeworks.notvel(adjlist, vel) can_go = pipeworks.notvel(adjlist, vel)
end end
-- can_go() is expected to return an array-like table of candidate offsets. -- can_go() is expected to return an array-like table of candidate offsets.

110
locale/pipeworks.zh_CN.tr Normal file
View File

@ -0,0 +1,110 @@
# textdomain: pipeworks
# License: CC-by-SA 4.0
# Author: pevernow <3450354617@qq.com>
## digilines interfacing
Channel=频道
## init
Allow splitting incoming stacks from tubes=允许从管道中拆分传入堆栈
## autocrafter
Unknown item=通道
unconfigured Autocrafter: unknown recipe=未配置的自动工作台: 未知配方
unconfigured Autocrafter=未配置的自动工作台
'@1' Autocrafter (@2)=自动工作台 '@1' (@2)
Save=保存
paused '@1' Autocrafter=暂停的自动工作台
Autocrafter=自动工作台
## compat-furnaces
Allow splitting incoming material (not fuel) stacks from tubes=允许从管子中分离进来的材料(不是燃料)堆
## decorative tubes
Airtight steelblock embedded tube=密封嵌入式铁块管道
Airtight panel embedded tube=密封嵌入式片状管道
## devices
Pump/Intake Module=泵/进气模块
Valve=阀门
Decorative grating=Decorative grating
Spigot outlet=龙头
Airtight Pipe entry/exit=密闭管进/出
Flow Sensor=流量传感器
Flow sensor (on)=流量传感器(上)
empty=空的
@1% full=满的@1 %
Expansion Tank (@1)=扩展水箱 (@1)
Fluid Storage Tank (@1)=储液罐 (@1)
Fountainhead=源泉
Straight-only Pipe=直管
## filter-injector
(slot #@1 next)=(下一个插槽 : #@1)
@1 Filter-Injector=@1取物器
Sequence slots by Priority=优先顺序排列
Sequence slots Randomly=随机排列时隙
Sequence slots by Rotation=旋转顺序槽
Exact match - off=完全匹配-关闭
Exact match - on=完全匹配-开启
Prefer item types:=偏好物品类型 :
Itemwise=逐项
Stackwise=堆叠方式
Digiline=Digiline
## legacy
Auto-Tap=自动轴阀
## pipes
Pipe Segment=管道
Pipe Segment (legacy)=管道(旧版)
## routing tubes
Pneumatic tube segment=普通管道
Broken Tube=断管
High Priority Tube Segment=高优先级管段
Accelerating Pneumatic Tube Segment=加速管道
Crossing Pneumatic Tube Segment=定向管道
One way tube=单向管
## signal tubes
Detecting Pneumatic Tube Segment on=检测管道(运行中)
Detecting Pneumatic Tube Segment=检测管道
Digiline Detecting Pneumatic Tube Segment=Digiline检测管道
Digiline Detecting Tube=Digiline检测管
Conducting Pneumatic Tube Segment=传导管道
Conducting Pneumatic Tube Segment on=传导管道(运行中)
Digiline Conducting Pneumatic Tube Segment=Digiline传导式气动管道
Mesecon and Digiline Conducting Pneumatic Tube Segment=Mesecon和Digiline传导管道
Mesecon and Digiline Conducting Pneumatic Tube Segment on=Mesecon和Digiline传导管道运行中
## sorting tubes
Sorting Pneumatic Tube Segment=分类管道
Sorting pneumatic tube=分类管道
## teleport tube
Receive=接收
channels are public by default=频道默认为公开
use <player>:<channel> for fully private channels=将<player>:<channel>用于完全私人的频道
use <player>;<channel> for private receivers=使用<player>;<channel>作为私人接收器
Teleporting Pneumatic Tube Segment=传送管道
unconfigured Teleportation Tube=未配置的传送管道
Sorry, channel '@1' is reserved for exclusive use by @2=抱歉,频道‘@1保留供@2专用
Sorry, receiving from channel '@1' is reserved for @2=抱歉,从频道'@1'接收的内容已保留给'@2'
Teleportation Tube @1 on '@2'=传送管'@1'在'@2'上
## trashcan
Trash Can=垃圾箱
## tube registration
Pneumatic tube segment (legacy)=普通管道(旧式)
## vacuum tubes
Vacuuming Pneumatic Tube Segment=拾取管道
Adjustable Vacuuming Pneumatic Tube Segment=高级拾取管道
Adjustable Vacuuming Pneumatic Tube Segment (@1m)=高级拾取管道(@1m)
## wielder
Node Breaker=方块破坏器
Deployer=放置器
Dispenser=投掷器

View File

@ -13,8 +13,10 @@
-- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port -- newport = merge_port_states(state1, state2): just does result = state1 or state2 for every port
-- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states -- set_port(pos, rule, state): activates/deactivates the mesecons according to the port states
-- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos -- set_port_states(pos, ports): Applies new port states to a Luacontroller at pos
-- run(pos): runs the code in the controller at pos -- run_inner(pos, code, event): runs code on the controller at pos and event
-- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error messages -- reset_formspec(pos, code, errmsg): installs new code and prints error messages, without resetting LCID
-- reset_meta(pos, code, errmsg): performs a software-reset, installs new code and prints error message
-- run(pos, event): a wrapper for run_inner which gets code & handles errors via reset_meta
-- resetn(pos): performs a hardware reset, turns off all ports -- resetn(pos): performs a hardware reset, turns off all ports
-- --
-- The Sandbox -- The Sandbox
@ -77,7 +79,7 @@ local function update_real_port_states(pos, rule_name, new_state)
if rule_name.x == nil then if rule_name.x == nil then
for _, rname in ipairs(rule_name) do for _, rname in ipairs(rule_name) do
local port = pos_to_side[rname.x + (2 * rname.y) + (3 * rname.z) + 4] local port = pos_to_side[rname.x + (2 * rname.y) + (3 * rname.z) + 4]
L[port] = (newstate == "on") and 1 or 0 L[port] = (new_state == "on") and 1 or 0
end end
else else
local port = pos_to_side[rule_name.x + (2 * rule_name.y) + (3 * rule_name.z) + 4] local port = pos_to_side[rule_name.x + (2 * rule_name.y) + (3 * rule_name.z) + 4]
@ -168,7 +170,7 @@ local function set_port_states(pos, ports)
-- Solution / Workaround: -- Solution / Workaround:
-- Remember which output was turned off and ignore next "off" event. -- Remember which output was turned off and ignore next "off" event.
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
local ign = minetest.deserialize(meta:get_string("ignore_offevents")) or {} local ign = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
if ports.red and not vports.red and not mesecon.is_powered(pos, rules.red) then ign.red = true end if ports.red and not vports.red and not mesecon.is_powered(pos, rules.red) then ign.red = true end
if ports.blue and not vports.blue and not mesecon.is_powered(pos, rules.blue) then ign.blue = true end if ports.blue and not vports.blue and not mesecon.is_powered(pos, rules.blue) then ign.blue = true end
if ports.yellow and not vports.yellow and not mesecon.is_powered(pos, rules.yellow) then ign.yellow = true end if ports.yellow and not vports.yellow and not mesecon.is_powered(pos, rules.yellow) then ign.yellow = true end
@ -214,7 +216,7 @@ end
local function ignore_event(event, meta) local function ignore_event(event, meta)
if event.type ~= "off" then return false end if event.type ~= "off" then return false end
local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents")) or {} local ignore_offevents = minetest.deserialize(meta:get_string("ignore_offevents"), true) or {}
if ignore_offevents[event.pin.name] then if ignore_offevents[event.pin.name] then
ignore_offevents[event.pin.name] = nil ignore_offevents[event.pin.name] = nil
meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents)) meta:set_string("ignore_offevents", minetest.serialize(ignore_offevents))
@ -227,7 +229,11 @@ end
------------------------- -------------------------
local function safe_print(param) local function safe_print(param)
local string_meta = getmetatable("")
local sandbox = string_meta.__index
string_meta.__index = string -- Leave string sandbox temporarily
print(dump(param)) print(dump(param))
string_meta.__index = sandbox -- Restore string sandbox
end end
local function safe_date() local function safe_date()
@ -267,6 +273,7 @@ local function remove_functions(x)
local seen = {} local seen = {}
local function rfuncs(x) local function rfuncs(x)
if x == nil then return end
if seen[x] then return end if seen[x] then return end
seen[x] = true seen[x] = true
if type(x) ~= "table" then return end if type(x) ~= "table" then return end
@ -290,49 +297,174 @@ local function remove_functions(x)
return x return x
end end
local function get_interrupt(pos) -- The setting affects API so is not intended to be changeable at runtime
local get_interrupt
if mesecon.setting("luacontroller_lightweight_interrupts", false) then
-- use node timer
get_interrupt = function(pos, itbl, send_warning)
return (function(time, iid)
if type(time) ~= "number" then error("Delay must be a number") end
if iid ~= nil then send_warning("Interrupt IDs are disabled on this server") end
table.insert(itbl, function() minetest.get_node_timer(pos):start(time) end)
end)
end
else
-- use global action queue
-- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
get_interrupt = function(pos, itbl, send_warning)
-- iid = interrupt id -- iid = interrupt id
local function interrupt(time, iid) local function interrupt(time, iid)
if type(time) ~= "number" then return end -- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
-- Hence the values get moved out. Should take less time than original, so totally compatible
if type(time) ~= "number" then error("Delay must be a number") end
table.insert(itbl, function ()
-- Outside string metatable sandbox, can safely run this now
local luac_id = minetest.get_meta(pos):get_int("luac_id") local luac_id = minetest.get_meta(pos):get_int("luac_id")
-- Check if IID is dodgy, so you can't use interrupts to store an infinite amount of data.
-- Note that this is safe from alter-after-free because this code gets run after the sandbox has ended.
-- This runs outside of the timer and *shouldn't* harm perf. unless dodgy data is being sent in the first place
iid = remove_functions(iid)
local msg_ser = minetest.serialize(iid)
if #msg_ser <= mesecon.setting("luacontroller_interruptid_maxlen", 256) then
mesecon.queue:add_action(pos, "pipeworks:lc_tube_interrupt", {luac_id, iid}, time, iid, 1) mesecon.queue:add_action(pos, "pipeworks:lc_tube_interrupt", {luac_id, iid}, time, iid, 1)
else
send_warning("An interrupt ID was too large!")
end
end)
end end
return interrupt return interrupt
end end
end
-- Given a message object passed to digiline_send, clean it up into a form
-- which is safe to transmit over the network and compute its "cost" (a very
-- rough estimate of its memory usage).
--
-- The cleaning comprises the following:
-- 1. Functions (and userdata, though user scripts ought not to get hold of
-- those in the first place) are removed, because they break the model of
-- Digilines as a network that carries basic data, and they could exfiltrate
-- references to mutable objects from one Luacontroller to another, allowing
-- inappropriate high-bandwidth, no-wires communication.
-- 2. Tables are duplicated because, being mutable, they could otherwise be
-- modified after the send is complete in order to change what data arrives
-- at the recipient, perhaps in violation of the previous cleaning rule or
-- in violation of the message size limit.
--
-- The cost indication is only approximate; its not a perfect measurement of
-- the number of bytes of memory used by the message object.
--
-- Parameters:
-- msg -- the message to clean
-- back_references -- for internal use only; do not provide
--
-- Returns:
-- 1. The cleaned object.
-- 2. The approximate cost of the object.
local function clean_and_weigh_digiline_message(msg, back_references)
local t = type(msg)
if t == "string" then
-- Strings are immutable so can be passed by reference, and cost their
-- length plus the size of the Lua object header (24 bytes on a 64-bit
-- platform) plus one byte for the NUL terminator.
return msg, #msg + 25
elseif t == "number" then
-- Numbers are passed by value so need not be touched, and cost 8 bytes
-- as all numbers in Lua are doubles.
return msg, 8
elseif t == "boolean" then
-- Booleans are passed by value so need not be touched, and cost 1
-- byte.
return msg, 1
elseif t == "table" then
-- Tables are duplicated. Check if this table has been seen before
-- (self-referential or shared table); if so, reuse the cleaned value
-- of the previous occurrence, maintaining table topology and avoiding
-- infinite recursion, and charge zero bytes for this as the object has
-- already been counted.
back_references = back_references or {}
local bref = back_references[msg]
if bref then
return bref, 0
end
-- Construct a new table by cleaning all the keys and values and adding
-- up their costs, plus 8 bytes as a rough estimate of table overhead.
local cost = 8
local ret = {}
back_references[msg] = ret
for k, v in pairs(msg) do
local k_cost, v_cost
k, k_cost = clean_and_weigh_digiline_message(k, back_references)
v, v_cost = clean_and_weigh_digiline_message(v, back_references)
if k ~= nil and v ~= nil then
-- Only include an element if its key and value are of legal
-- types.
ret[k] = v
end
-- If we only counted the cost of a table element when we actually
-- used it, we would be vulnerable to the following attack:
-- 1. Construct a huge table (too large to pass the cost limit).
-- 2. Insert it somewhere in a table, with a function as a key.
-- 3. Insert it somewhere in another table, with a number as a key.
-- 4. The first occurrence doesnt pay the cost because functions
-- are stripped and therefore the element is dropped.
-- 5. The second occurrence doesnt pay the cost because its in
-- back_references.
-- By counting the costs regardless of whether the objects will be
-- included, we avoid this attack; it may overestimate the cost of
-- some messages, but only those that wont be delivered intact
-- anyway because they contain illegal object types.
cost = cost + k_cost + v_cost
end
return ret, cost
else
return nil, 0
end
end
local function get_digiline_send(pos) -- itbl: Flat table of functions to run after sandbox cleanup, used to prevent various security hazards
if not digiline then return end local function get_digiline_send(pos, itbl, send_warning)
if not minetest.global_exists("digilines") then return end
local chan_maxlen = mesecon.setting("luacontroller_digiline_channel_maxlen", 256)
local maxlen = mesecon.setting("luacontroller_digiline_maxlen", 50000)
return function(channel, msg) return function(channel, msg)
-- NOTE: This runs within string metatable sandbox, so don't *rely* on anything of the form (""):y
-- or via anything that could.
-- Make sure channel is string, number or boolean -- Make sure channel is string, number or boolean
if (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then if type(channel) == "string" then
if #channel > chan_maxlen then
send_warning("Channel string too long.")
return false
end
elseif (type(channel) ~= "string" and type(channel) ~= "number" and type(channel) ~= "boolean") then
send_warning("Channel must be string, number or boolean.")
return false return false
end end
-- It is technically possible to send functions over the wire since local msg_cost
-- the high performance impact of stripping those from the data has msg, msg_cost = clean_and_weigh_digiline_message(msg)
-- been decided to not be worth the added realism. if msg == nil or msg_cost > maxlen then
-- Make sure serialized version of the data is not insanely long to send_warning("Message was too complex, or contained invalid data.")
-- prevent DoS-like attacks
local msg_ser = minetest.serialize(msg)
if #msg_ser > mesecon.setting("luacontroller_digiline_maxlen", 50000) then
return false return false
end end
minetest.after(0, function() table.insert(itbl, function ()
digilines.receptor_send(pos, digiline_rules_luatube, channel, msg) -- Runs outside of string metatable sandbox
local luac_id = minetest.get_meta(pos):get_int("luac_id")
mesecon.queue:add_action(pos, "pipeworks:lt_digiline_relay", {channel, luac_id, msg})
end) end)
return true return true
end end
end end
local safe_globals = { local safe_globals = {
-- Don't add pcall/xpcall unless willing to deal with the consequences (unless very careful, incredibly likely to allow killing server indirectly)
"assert", "error", "ipairs", "next", "pairs", "select", "assert", "error", "ipairs", "next", "pairs", "select",
"tonumber", "tostring", "type", "unpack", "_VERSION" "tonumber", "tostring", "type", "unpack", "_VERSION"
} }
local function create_environment(pos, mem, event) local function create_environment(pos, mem, event, itbl, send_warning)
-- Make sure the tube hasn't broken. -- Make sure the tube hasn't broken.
local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates local vports = minetest.registered_nodes[minetest.get_node(pos).name].virtual_portstates
if not vports then return {} end if not vports then return {} end
@ -352,8 +484,8 @@ local function create_environment(pos, mem, event)
heat = mesecon.get_heat(pos), heat = mesecon.get_heat(pos),
heat_max = mesecon.setting("overheat_max", 20), heat_max = mesecon.setting("overheat_max", 20),
print = safe_print, print = safe_print,
interrupt = get_interrupt(pos), interrupt = get_interrupt(pos, itbl, send_warning),
digiline_send = get_digiline_send(pos), digiline_send = get_digiline_send(pos, itbl, send_warning),
string = { string = {
byte = string.byte, byte = string.byte,
char = string.char, char = string.char,
@ -441,10 +573,11 @@ local function create_sandbox(code, env)
jit.off(f, true) jit.off(f, true)
end end
local maxevents = mesecon.setting("luacontroller_maxevents", 10000)
return function(...) return function(...)
-- NOTE: This runs within string metatable sandbox, so the setting's been moved out for safety
-- Use instruction counter to stop execution -- Use instruction counter to stop execution
-- after luacontroller_maxevents -- after luacontroller_maxevents
local maxevents = mesecon.setting("luacontroller_maxevents", 10000)
debug.sethook(timeout, "", maxevents) debug.sethook(timeout, "", maxevents)
local ok, ret = pcall(f, ...) local ok, ret = pcall(f, ...)
debug.sethook() -- Clear hook debug.sethook() -- Clear hook
@ -455,7 +588,7 @@ end
local function load_memory(meta) local function load_memory(meta)
return minetest.deserialize(meta:get_string("lc_memory")) or {} return minetest.deserialize(meta:get_string("lc_memory"), true) or {}
end end
@ -465,6 +598,7 @@ local function save_memory(pos, meta, mem)
if (#memstring <= memsize_max) then if (#memstring <= memsize_max) then
meta:set_string("lc_memory", memstring) meta:set_string("lc_memory", memstring)
meta:mark_as_private("lc_memory")
else else
print("Error: lua_tube memory overflow. "..memsize_max.." bytes available, " print("Error: lua_tube memory overflow. "..memsize_max.." bytes available, "
..#memstring.." required. Controller overheats.") ..#memstring.." required. Controller overheats.")
@ -472,26 +606,42 @@ local function save_memory(pos, meta, mem)
end end
end end
-- Returns success (boolean), errmsg (string), retval(any, return value of the user supplied code)
local function run(pos, event) -- run (as opposed to run_inner) is responsible for setting up meta according to this output
local function run_inner(pos, code, event)
local meta = minetest.get_meta(pos) local meta = minetest.get_meta(pos)
if overheat(pos) then return end -- Note: These return success, presumably to avoid changing LC ID.
if ignore_event(event, meta) then return end if overheat(pos) then return true, "", nil end
if ignore_event(event, meta) then return true, "", nil end
-- Load code & mem from meta -- Load code & mem from meta
local mem = load_memory(meta) local mem = load_memory(meta)
local code = meta:get_string("code") local code = meta:get_string("code")
-- 'Last warning' label.
local warning = ""
local function send_warning(str)
warning = "Warning: " .. str
end
-- Create environment -- Create environment
local env = create_environment(pos, mem, event) local itbl = {}
local env = create_environment(pos, mem, event, itbl, send_warning)
-- Create the sandbox and execute code -- Create the sandbox and execute code
local f, msg = create_sandbox(code, env) local f, msg = create_sandbox(code, env)
if not f then return false, msg end if not f then return false, msg, nil end
local succ, msg = pcall(f) -- Start string true sandboxing
if not succ then return false, msg end local onetruestring = getmetatable("")
-- If a string sandbox is already up yet inconsistent, something is very wrong
assert(onetruestring.__index == string)
onetruestring.__index = env.string
local success, msg = pcall(f)
onetruestring.__index = string
-- End string true sandboxing
if not success then return false, msg, nil end
if type(env.port) ~= "table" then if type(env.port) ~= "table" then
return false, "Ports set are invalid." return false, "Ports set are invalid.", nil
end end
-- Actually set the ports -- Actually set the ports
@ -500,8 +650,65 @@ local function run(pos, event)
-- Save memory. This may burn the luacontroller if a memory overflow occurs. -- Save memory. This may burn the luacontroller if a memory overflow occurs.
save_memory(pos, meta, env.mem) save_memory(pos, meta, env.mem)
return succ, msg -- Execute deferred tasks
for _, v in ipairs(itbl) do
local failure = v()
if failure then
return false, failure, nil
end end
end
return true, warning, msg
end
local function reset_formspec(meta, code, errmsg)
meta:set_string("code", code)
meta:mark_as_private("code")
code = minetest.formspec_escape(code or "")
errmsg = minetest.formspec_escape(tostring(errmsg or ""))
meta:set_string("formspec", "size[12,10]"
.."background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]"
.."label[0.1,8.3;"..errmsg.."]"
.."textarea[0.2,0.2;12.2,9.5;code;;"..code.."]"
.."image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]"
.."image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"
)
end
local function reset_meta(pos, code, errmsg)
local meta = minetest.get_meta(pos)
reset_formspec(meta, code, errmsg)
meta:set_int("luac_id", math.random(1, 65535))
end
-- Wraps run_inner with LC-reset-on-error
local function run(pos, event)
local meta = minetest.get_meta(pos)
local code = meta:get_string("code")
local ok, errmsg, retval = run_inner(pos, code, event)
if not ok then
reset_meta(pos, code, errmsg)
else
reset_formspec(meta, code, errmsg)
end
return ok, errmsg, retval
end
local function reset(pos)
set_port_states(pos, {red = false, blue = false, yellow = false,
green = false, black = false, white = false})
end
local function node_timer(pos)
if minetest.registered_nodes[minetest.get_node(pos).name].is_burnt then
return false
end
run(pos, {type="interrupt"})
return false
end
-----------------------
-- A.Queue callbacks --
-----------------------
mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id, iid) mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id, iid)
-- There is no lua_tube anymore / it has been reprogrammed / replaced / burnt -- There is no lua_tube anymore / it has been reprogrammed / replaced / burnt
@ -510,25 +717,14 @@ mesecon.queue:add_function("pipeworks:lc_tube_interrupt", function (pos, luac_id
run(pos, {type="interrupt", iid = iid}) run(pos, {type="interrupt", iid = iid})
end) end)
local function reset_meta(pos, code, errmsg) mesecon.queue:add_function("pipeworks:lt_digiline_relay", function (pos, channel, luac_id, msg)
local meta = minetest.get_meta(pos) if not digiline then return end
meta:set_string("code", code) -- This check is only really necessary because in case of server crash, old actions can be thrown into the future
code = minetest.formspec_escape(code or "") if (minetest.get_meta(pos):get_int("luac_id") ~= luac_id) then return end
errmsg = minetest.formspec_escape(tostring(errmsg or "")) if (minetest.registered_nodes[minetest.get_node(pos).name].is_burnt) then return end
meta:set_string("formspec", "size[12,10]".. -- The actual work
"background[-0.2,-0.25;12.4,10.75;jeija_luac_background.png]".. digiline:receptor_send(pos, digiline_rules_luatube, channel, msg)
"textarea[0.2,0.2;12.2,9.5;code;;"..code.."]".. end)
"image_button[4.75,8.75;2.5,1;jeija_luac_runbutton.png;program;]"..
"image_button_exit[11.72,-0.25;0.425,0.4;jeija_close_window.png;exit;]"..
"label[0.1,9;"..errmsg.."]")
meta:set_int("luac_id", math.random(1, 65535))
end
local function reset(pos)
set_port_states(pos, {red = false, blue = false, yellow = false,
green = false, black = false, white = false})
end
----------------------- -----------------------
-- Node Registration -- -- Node Registration --
@ -558,6 +754,7 @@ local digiline = {
receptor = {}, receptor = {},
effector = { effector = {
action = function(pos, node, channel, msg) action = function(pos, node, channel, msg)
msg = clean_and_weigh_digiline_message(msg)
run(pos, {type = "digiline", channel = channel, msg = msg}) run(pos, {type = "digiline", channel = channel, msg = msg})
end end
}, },
@ -565,6 +762,18 @@ local digiline = {
rules = pipeworks.digilines_rules rules = pipeworks.digilines_rules
}, },
} }
local function get_program(pos)
local meta = minetest.get_meta(pos)
return meta:get_string("code")
end
local function set_program(pos, code)
reset(pos)
reset_meta(pos, code)
return run(pos, {type="program"})
end
local function on_receive_fields(pos, form_name, fields, sender) local function on_receive_fields(pos, form_name, fields, sender)
if not fields.program then if not fields.program then
return return
@ -574,12 +783,10 @@ local function on_receive_fields(pos, form_name, fields, sender)
minetest.record_protection_violation(pos, name) minetest.record_protection_violation(pos, name)
return return
end end
reset(pos) local ok, err = set_program(pos, fields.code)
reset_meta(pos, fields.code) if not ok then
local succ, err = run(pos, {type="program"}) -- it's not an error from the server perspective
if not succ then minetest.log("action", "Lua controller programming error: " .. tostring(err))
print(err)
reset_meta(pos, fields.code, err)
end end
end end
@ -715,7 +922,11 @@ for white = 0, 1 do
receptor = { receptor = {
state = mesecon.state.on, state = mesecon.state.on,
rules = output_rules[cid] rules = output_rules[cid]
} },
luacontroller = {
get_program = get_program,
set_program = set_program,
},
} }
minetest.register_node(node_name, { minetest.register_node(node_name, {
@ -723,6 +934,7 @@ for white = 0, 1 do
drawtype = "nodebox", drawtype = "nodebox",
tiles = tiles, tiles = tiles,
paramtype = "light", paramtype = "light",
is_ground_content = false,
groups = groups, groups = groups,
drop = BASENAME.."000000", drop = BASENAME.."000000",
sunlight_propagates = true, sunlight_propagates = true,
@ -749,6 +961,7 @@ for white = 0, 1 do
pipeworks.after_dig(pos, node) pipeworks.after_dig(pos, node)
end, end,
is_luacontroller = true, is_luacontroller = true,
on_timer = node_timer,
tubelike = 1, tubelike = 1,
tube = { tube = {
connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1}, connect_sides = {front = 1, back = 1, left = 1, right = 1, top = 1, bottom = 1},
@ -762,7 +975,7 @@ for white = 0, 1 do
break break
end end
end end
local succ, msg = run(pos, { local succ, _, msg = run(pos, {
type = "item", type = "item",
pin = src, pin = src,
itemstring = stack:to_string(), itemstring = stack:to_string(),
@ -785,14 +998,6 @@ for white = 0, 1 do
minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"}) minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
pipeworks.scan_for_tube_objects(pos) pipeworks.scan_for_tube_objects(pos)
end, end,
on_blast = function(pos, intensity)
if not intensity or intensity > 1 + 3^0.5 then
minetest.remove_node(pos)
return {string.format("%s_%s", name, dropname)}
end
minetest.swap_node(pos, {name = "pipeworks:broken_tube_1"})
pipeworks.scan_for_tube_objects(pos)
end,
}) })
end end
end end
@ -836,6 +1041,7 @@ minetest.register_node(BASENAME .. "_burnt", {
tiles = tiles_burnt, tiles = tiles_burnt,
is_burnt = true, is_burnt = true,
paramtype = "light", paramtype = "light",
is_ground_content = false,
groups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory=1}, groups = {snappy = 3, tube = 1, tubedevice = 1, not_in_creative_inventory=1},
drop = BASENAME.."000000", drop = BASENAME.."000000",
sunlight_propagates = true, sunlight_propagates = true,

View File

@ -340,6 +340,8 @@ local move_entities_globalstep_part2 = function(dtime)
entity._velocity = master_entity:get_velocity() entity._velocity = master_entity:get_velocity()
entity._acceleration = master_entity:get_acceleration() entity._acceleration = master_entity:get_acceleration()
else else
entity._velocity = entity._velocity or vector.new(0,0,0)
entity._acceleration = entity._acceleration or vector.new(0,0,0)
entity._pos = vector.add(vector.add( entity._pos = vector.add(vector.add(
entity._pos, entity._pos,
vector.multiply(entity._velocity, dtime)), vector.multiply(entity._velocity, dtime)),

View File

@ -261,3 +261,11 @@ if minetest.get_modpath("mesecons_mvps") ~= nil then
end end
end) end)
end end
-- Expose teleport tube database API for other mods
pipeworks.tptube = {
hash = hash,
save_tube_db = save_tube_db,
get_db = function() return tp_tube_db or read_tube_db() end,
tp_tube_db_version = tp_tube_db_version
}

View File

@ -378,7 +378,7 @@ if pipeworks.enable_node_breaker then
minetest.register_craft({ minetest.register_craft({
output = "pipeworks:nodebreaker_off", output = "pipeworks:nodebreaker_off",
recipe = { recipe = {
{ "pipeworks:gear", "pipeworks:gear", "pipeworks:gear" }, { "basic_materials:gear_steel", "basic_materials:gear_steel", "basic_materials:gear_steel" },
{ "default:stone", "mesecons:piston", "default:stone" }, { "default:stone", "mesecons:piston", "default:stone" },
{ "group:wood", "mesecons:mesecon", "group:wood" }, { "group:wood", "mesecons:mesecon", "group:wood" },
} }