forbid access to string metatable for sandbox; minor restructuring of libraries (mostly sorting by name); saving managed environments attached to an adapter. this is not very reliable, sadly, since there are way too many ways neighbors can change outside our control (chunks only partially loaded, types removed across games due to other mod changes, mods that move blocks such as RIM, ...); making some effort to re-attach peripherals using their previous address; removed connection code on chunk load in network manager, since this is also handled by the check in the tile entities' update function

This commit is contained in:
Florian Nücke 2013-11-04 20:53:31 +01:00
parent fcdb75e4bc
commit ab785eb3ac
16 changed files with 220 additions and 168 deletions

View File

@ -57,7 +57,10 @@ sandbox = {
dofile = nil, -- in lib/base.lua
error = error,
_G = nil, -- see below
getmetatable = getmetatable,
getmetatable = function(t)
if type(t) == "string" then return nil end
return getmetatable(t)
end,
ipairs = ipairs,
load = function(ld, source, mode, env)
assert((mode or "t") == "t", "unsupported mode")
@ -143,8 +146,8 @@ sandbox = {
uchar = string.uchar,
trim = function(s) -- from http://lua-users.org/wiki/StringTrim
local from = s:match("^%s*()")
return from > #s and "" or s:match(".*%S", from)
local from = string.match(s, "^%s*()")
return from > #s and "" or string.match(s, ".*%S", from)
end
},

View File

@ -22,9 +22,9 @@ for path, proxy in pairs(mounts) do
local used, total = proxy.spaceUsed(), proxy.spaceTotal()
local available, percent
if total == "unlimited" then
used = "N/A"
used = used or "N/A"
available = "unlimited"
percent = 0
percent = "0%"
else
available = total - used
percent = used / total

View File

@ -1,4 +1,4 @@
local listeners, timers = {}, {}
local event, listeners, timers = {}, {}, {}
local function matches(signal, name, filter)
if name and not (type(signal[1]) == "string" and signal[1]:match(name))
@ -58,7 +58,14 @@ end
-------------------------------------------------------------------------------
event = {}
function event.cancel(timerId)
checkArg(1, timerId, "number")
if timers[timerId] then
timers[timerId] = nil
return true
end
return false
end
--[[ Error handler for ALL event callbacks. If this throws an error or is not,
set the computer will immediately shut down. ]]
@ -103,36 +110,6 @@ function event.listen(name, callback)
return true
end
-------------------------------------------------------------------------------
function event.cancel(timerId)
checkArg(1, timerId, "number")
if timers[timerId] then
timers[timerId] = nil
return true
end
return false
end
function event.timer(interval, callback, times)
checkArg(1, interval, "number")
checkArg(2, callback, "function")
checkArg(3, times, "number", "nil")
local id
repeat
id = math.floor(math.random(1, 0x7FFFFFFF))
until not timers[id]
timers[id] = {
interval = interval,
after = os.uptime() + interval,
callback = callback,
times = times or 1
}
return id
end
-------------------------------------------------------------------------------
function event.pull(...)
local args = table.pack(...)
local seconds, name, filter
@ -182,3 +159,24 @@ function event.shouldInterrupt()
keyboard.isAltDown() and
keyboard.isKeyDown(keyboard.keys.c)
end
function event.timer(interval, callback, times)
checkArg(1, interval, "number")
checkArg(2, callback, "function")
checkArg(3, times, "number", "nil")
local id
repeat
id = math.floor(math.random(1, 0x7FFFFFFF))
until not timers[id]
timers[id] = {
interval = interval,
after = os.uptime() + interval,
callback = callback,
times = times or 1
}
return id
end
-------------------------------------------------------------------------------
_G.event = event

View File

@ -1,3 +1,4 @@
local filesystem, fileStream = {}, {}
local isAutorunEnabled = true
local mtab = {children={}}
@ -58,8 +59,6 @@ end
-------------------------------------------------------------------------------
filesystem = {}
function filesystem.autorun(enabled)
if enabled ~= nil then
checkArg(1, enabled, "boolean")
@ -204,8 +203,6 @@ function filesystem.umount(fsOrPath)
return result
end
-------------------------------------------------------------------------------
function filesystem.exists(path)
local node, rest = findNode(path)
if not rest then -- virtual directory
@ -269,8 +266,6 @@ function filesystem.list(path)
end
end
-------------------------------------------------------------------------------
function filesystem.makeDirectory(path)
local node, rest = findNode(path)
if node.fs and rest then
@ -336,10 +331,6 @@ function filesystem.copy(fromPath, toPath)
return true
end
-------------------------------------------------------------------------------
local fileStream = {}
function fileStream:close()
self.fs.close(self.handle)
self.handle = nil
@ -366,8 +357,6 @@ function fileStream:write(str)
return self.fs.write(self.handle, str)
end
-------------------------------------------------------------------------------
function filesystem.open(path, mode)
checkArg(1, path, "string")
mode = tostring(mode or "r")
@ -407,24 +396,20 @@ end
-------------------------------------------------------------------------------
fs = filesystem
-------------------------------------------------------------------------------
local function onComponentAdded(_, address, componentType)
if componentType == "filesystem" then
local proxy = component.proxy(address)
if proxy then
local name = address:sub(1, 3)
while fs.exists(fs.concat("/mnt", name)) and
while filesystem.exists(filesystem.concat("/mnt", name)) and
name:len() < address:len() -- just to be on the safe side
do
name = address:sub(1, name:len() + 1)
end
name = fs.concat("/mnt", name)
fs.mount(proxy, name)
name = filesystem.concat("/mnt", name)
filesystem.mount(proxy, name)
if isAutorunEnabled then
shell.execute(fs.concat(name, "autorun"), proxy)
shell.execute(filesystem.concat(name, "autorun"), proxy)
end
end
end
@ -432,10 +417,13 @@ end
local function onComponentRemoved(_, address, componentType)
if componentType == "filesystem" then
fs.umount(address)
filesystem.umount(address)
end
end
_G.filesystem = filesystem
_G.fs = filesystem
return function()
event.listen("component_added", onComponentAdded)
event.listen("component_removed", onComponentRemoved)

View File

@ -1,4 +1,19 @@
local file = {}
local io, file = {}, {}
function file.new(mode, stream, nogc)
local result = {
mode = mode or "r",
stream = stream,
buffer = "",
bufferSize = math.max(128, math.min(8 * 1024, os.freeMemory() / 8)),
bufferMode = "full"
}
local metatable = {
__index = file,
__metatable = "file"
}
return setmetatable(result, metatable)
end
function file:close()
if self.mode ~= "r" and self.mode ~= "rb" then
@ -252,23 +267,6 @@ end
-------------------------------------------------------------------------------
function file.new(mode, stream, nogc)
local result = {
mode = mode or "r",
stream = stream,
buffer = "",
bufferSize = math.max(128, math.min(8 * 1024, os.freeMemory() / 8)),
bufferMode = "full"
}
local metatable = {
__index = file,
__metatable = "file"
}
return setmetatable(result, metatable)
end
-------------------------------------------------------------------------------
local stdinStream = {handle="stdin"}
local stdoutStream = {handle="stdout"}
local stdinHistory = {}
@ -302,8 +300,6 @@ stdoutStream.seek = badFileDescriptor
-------------------------------------------------------------------------------
io = {}
io.stdin = file.new("r", stdinStream, true)
io.stdout = file.new("w", stdoutStream, true)
io.stderr = io.stdout
@ -420,3 +416,7 @@ end
function io.write(...)
return io.output():write(...)
end
-------------------------------------------------------------------------------
_G.io = io

View File

@ -1,9 +1,4 @@
local pressedChars = {}
local pressedCodes = {}
-------------------------------------------------------------------------------
keyboard = {}
local keyboard, pressedChars, pressedCodes = {}, {}, {}
keyboard.keys = {
["1"] = 0x02,
@ -140,10 +135,20 @@ for k, v in pairs(keyboard.keys) do
keyboard.keys[v] = k
end
-------------------------------------------------------------------------------
function keyboard.isAltDown()
return (pressedCodes[keyboard.keys.lmenu] or pressedCodes[keyboard.keys.rmenu]) ~= nil
end
function keyboard.isControl(char)
return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F))
end
function keyboard.isControlDown()
return (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil
end
function keyboard.isKeyDown(charOrCode)
checkArg(1, charOrCode, "string", "number")
if type(charOrCode) == "string" then
@ -153,14 +158,6 @@ function keyboard.isKeyDown(charOrCode)
end
end
function keyboard.isControlDown()
return (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil
end
function keyboard.isAltDown()
return (pressedCodes[keyboard.keys.lmenu] or pressedCodes[keyboard.keys.rmenu]) ~= nil
end
function keyboard.isShiftDown()
return (pressedCodes[keyboard.keys.lshift] or pressedCodes[keyboard.keys.rshift]) ~= nil
end
@ -188,6 +185,8 @@ local function onComponentUnavailable(_, componentType)
end
end
_G.keyboard = keyboard
return function()
event.listen("key_down", onKeyDown)
event.listen("key_up", onKeyUp)

View File

@ -1,25 +0,0 @@
local function stringToSide(side)
if type(side) == "string" and rs.sides[side] then
return rs.sides[side]
end
return side
end
-------------------------------------------------------------------------------
redstone = {}
rs = redstone
rs.sides = {
[0] = "bottom",
[1] = "top",
[2] = "back",
[3] = "front",
[4] = "right",
[5] = "left"
}
for k, v in pairs(rs.sides) do
rs.sides[v] = k
end
rs.sides.up = rs.sides.top
rs.sides.down = rs.sides.bottom

View File

@ -1,3 +1,4 @@
local shell = {}
local cwd = "/"
local path = {"/bin/", "/usr/bin/", "/home/bin/"}
local aliases = {dir="ls", move="mv", rename="mv", copy="cp", del="rm",
@ -48,8 +49,6 @@ end
-------------------------------------------------------------------------------
shell = {}
function shell.alias(alias, ...)
checkArg(1, alias, "string")
local result = aliases[alias]
@ -170,3 +169,7 @@ function shell.which(program)
return nil, "program not found"
end
end
-------------------------------------------------------------------------------
_G.shell = shell

View File

@ -0,0 +1,17 @@
local sides = {
[0] = "bottom",
[1] = "top",
[2] = "back",
[3] = "front",
[4] = "right",
[5] = "left"
}
for k, v in pairs(sides) do
sides[v] = k
end
sides.up = sides.top
sides.down = sides.bottom
-------------------------------------------------------------------------------
_G.sides = sides

View File

@ -1,3 +1,4 @@
local term = {}
local gpuAvailable, screenAvailable = false, false
local cursorX, cursorY = 1, 1
local cursorBlink = nil
@ -17,12 +18,6 @@ end
-------------------------------------------------------------------------------
term = {}
function term.isAvailable()
return gpuAvailable and screenAvailable
end
function term.clear()
if term.isAvailable() then
local w, h = gpu().getResolution()
@ -75,7 +70,9 @@ function term.cursorBlink(enabled)
return cursorBlink ~= nil
end
-------------------------------------------------------------------------------
function term.isAvailable()
return gpuAvailable and screenAvailable
end
function term.read(history)
checkArg(1, history, "table", "nil")
@ -379,6 +376,8 @@ local function onComponentUnavailable(_, componentType)
end
end
_G.term = term
return function()
event.listen("component_available", onComponentAvailable)
event.listen("component_unavailable", onComponentUnavailable)

View File

@ -21,14 +21,14 @@ class Proxy {
Blocks.init()
Items.init()
api.Driver.add(driver.CommandBlock)
api.Driver.add(driver.FileSystem)
api.Driver.add(driver.GraphicsCard)
api.Driver.add(driver.Memory)
api.Driver.add(driver.NetworkCard)
api.Driver.add(driver.RedstoneCard)
api.Driver.add(driver.CommandBlock)
api.Driver.add(driver.PowerSupply)
api.Driver.add(driver.Peripheral)
api.Driver.add(driver.PowerSupply)
api.Driver.add(driver.RedstoneCard)
MinecraftForge.EVENT_BUS.register(Computer)
MinecraftForge.EVENT_BUS.register(network.Network)

View File

@ -4,22 +4,19 @@ import dan200.computer.api.{ILuaContext, IComputerAccess, IPeripheral}
import li.cil.oc.api
import li.cil.oc.api.Network
import li.cil.oc.api.network._
import li.cil.oc.server
import li.cil.oc.server.driver
import net.minecraft.nbt.{NBTTagString, NBTTagList, NBTTagCompound}
import net.minecraft.nbt.{NBTTagList, NBTTagCompound}
import net.minecraftforge.common.ForgeDirection
import scala.Some
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
// TODO persist managed environments of attached blocks somehow...
class Adapter extends Rotatable with Environment with IPeripheral {
val node = api.Network.newNode(this, Visibility.Network).create()
private val blocks = Array.fill[Option[(ManagedEnvironment, api.driver.Block)]](6)(None)
private val blocksAddresses = Array.fill[String](6)(java.util.UUID.randomUUID.toString)
private val blocksData = Array.fill[Option[BlockData]](6)(None)
// ----------------------------------------------------------------------- //
@ -39,27 +36,33 @@ class Adapter extends Rotatable with Environment with IPeripheral {
val (x, y, z) = (xCoord + d.offsetX, yCoord + d.offsetY, zCoord + d.offsetZ)
driver.Registry.driverFor(worldObj, x, y, z) match {
case Some(newDriver) => blocks(d.ordinal()) match {
case Some((environment, driver)) =>
case Some((oldEnvironment, driver)) =>
if (newDriver != driver) {
// This is... odd. Maybe moved by some other mod?
node.disconnect(environment.node)
val newEnvironment = newDriver.createEnvironment(worldObj, x, y, z)
newEnvironment.node.asInstanceOf[server.network.Node].address = blocksAddresses(d.ordinal())
node.connect(newEnvironment.node)
blocks(d.ordinal()) = Some((newEnvironment, newDriver))
node.disconnect(oldEnvironment.node)
val environment = newDriver.createEnvironment(worldObj, x, y, z)
blocks(d.ordinal()) = Some((environment, newDriver))
blocksData(d.ordinal()) = Some(new BlockData(environment.getClass.getName, new NBTTagCompound()))
node.connect(environment.node)
} // else: the more things change, the more they stay the same.
case _ =>
// A challenger appears.
val environment = newDriver.createEnvironment(worldObj, x, y, z)
environment.node.asInstanceOf[server.network.Node].address = blocksAddresses(d.ordinal())
node.connect(environment.node)
blocks(d.ordinal()) = Some((environment, newDriver))
blocksData(d.ordinal()) match {
case Some(data) if data.name == environment.getClass.getName =>
environment.load(data.data)
case _ =>
}
blocksData(d.ordinal()) = Some(new BlockData(environment.getClass.getName, new NBTTagCompound()))
node.connect(environment.node)
}
case _ => blocks(d.ordinal()) match {
case Some((environment, driver)) =>
// We had something there, but it's gone now...
blocks(d.ordinal()) = None
node.disconnect(environment.node)
environment.save(blocksData(d.ordinal()).get.data)
blocks(d.ordinal()) = None
case _ => // Nothing before, nothing now.
}
}
@ -108,13 +111,16 @@ class Adapter extends Rotatable with Environment with IPeripheral {
super.readFromNBT(nbt)
node.load(nbt)
val addressesNbt = nbt.getTagList("oc.adapter.addresses")
(0 until (addressesNbt.tagCount min blocksAddresses.length)).
map(addressesNbt.tagAt).
map(_.asInstanceOf[NBTTagString].data).
val blocksNbt = nbt.getTagList("oc.adapter.blocks")
(0 until (blocksNbt.tagCount min blocksData.length)).
map(blocksNbt.tagAt).
map(_.asInstanceOf[NBTTagCompound]).
zipWithIndex.
foreach {
case (a, i) => blocksAddresses(i) = a
case (blockNbt, i) =>
if (blockNbt.hasKey("name") && blockNbt.hasKey("data")) {
blocksData(i) = Some(new BlockData(blockNbt.getString("name"), blockNbt.getCompoundTag("data")))
}
}
}
@ -122,11 +128,22 @@ class Adapter extends Rotatable with Environment with IPeripheral {
super.writeToNBT(nbt)
node.save(nbt)
val addressesNbt = new NBTTagList()
for (i <- 0 until blocksAddresses.length) {
addressesNbt.appendTag(new NBTTagString(null, blocksAddresses(i)))
val blocksNbt = new NBTTagList()
for (i <- 0 until blocks.length) {
val blockNbt = new NBTTagCompound()
blocksData(i) match {
case Some(data) =>
blocks(i) match {
case Some((environment, _)) => environment.save(data.data)
case _ =>
}
blockNbt.setString("name", data.name)
blockNbt.setCompoundTag("data", data.data)
case _ =>
}
blocksNbt.appendTag(blockNbt)
}
nbt.setTag("oc.adapter.addresses", addressesNbt)
nbt.setTag("oc.adapter.blocks", blocksNbt)
}
// ----------------------------------------------------------------------- //
@ -183,4 +200,9 @@ class Adapter extends Rotatable with Environment with IPeripheral {
throw new IllegalArgumentException("bad argument #%d (number in [1, 65535] expected)".format(index + 1))
port
}
// ----------------------------------------------------------------------- //
private class BlockData(val name: String, val data: NBTTagCompound)
}

View File

@ -187,12 +187,29 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
for (component <- addedComponents) {
if (component.canBeSeenFrom(owner.node)) {
components += component.address -> component.name
signal("component_added", component.address, component.name)
// Skip the signal if we're not initialized yet, since we'd generate a
// duplicate in the startup script otherwise.
if (kernelMemory > 0)
signal("component_added", component.address, component.name)
}
}
addedComponents.clear()
}
private def verifyComponents() {
val invalid = mutable.Set.empty[String]
for ((address, name) <- components) {
if (owner.node.network.node(address) == null) {
OpenComputers.log.warning("A component of type " + name + " disappeared!")
signal("component_removed", address, name)
invalid += address
}
}
for (address <- invalid) {
components -= address
}
}
def update() {
// Add components that were added since the last update to the actual list
// of components if we can see them. We use this delayed approach to avoid
@ -205,8 +222,11 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
processAddedComponents()
// Are we waiting for the world to settle?
if (lastUpdate == 0 && System.currentTimeMillis() < sleepUntil)
return
if (lastUpdate == 0)
if (System.currentTimeMillis() < sleepUntil)
return
else
verifyComponents()
// Update last time run to let our executor thread know it doesn't have to
// pause.
@ -1021,7 +1041,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
0
}
else {
System.nanoTime() - cpuStart
System.nanoTime() - cpuStart
}
cpuTime += runtime

View File

@ -3,6 +3,8 @@ package li.cil.oc.server.component
import dan200.computer.api.{IMount, IWritableMount, IComputerAccess, IPeripheral}
import li.cil.oc.api
import li.cil.oc.api.network._
import li.cil.oc.server.network.{Node => MutableNode}
import net.minecraft.nbt.{NBTTagString, NBTTagCompound}
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
@ -13,6 +15,9 @@ class Peripheral(peripheral: IPeripheral) extends ManagedComponent with ICompute
private val mounts = mutable.Map.empty[String, ManagedEnvironment]
// Used to restore mounts to their previous addresses after save/load.
private val mountAddresses = mutable.Map.empty[String, String]
// ----------------------------------------------------------------------- //
@LuaCallback(value = "getType", asynchronous = true)
@ -53,6 +58,31 @@ class Peripheral(peripheral: IPeripheral) extends ManagedComponent with ICompute
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
super.load(nbt)
if (nbt.hasKey("oc.peripheral.addresses")) {
val addressesNbt = nbt.getCompoundTag("oc.peripheral.addresses")
for (tag <- addressesNbt.getTags) tag match {
case addressNbt: NBTTagString =>
mountAddresses += addressNbt.getName -> addressNbt.data
case _ =>
}
}
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
val addressesNbt = new NBTTagCompound()
for ((location, env) <- mounts) {
addressesNbt.setString(location, env.node.address)
}
nbt.setCompoundTag("oc.peripheral.addresses", addressesNbt)
}
// ----------------------------------------------------------------------- //
def getID = -1
def getAttachmentName = node.address
@ -71,6 +101,10 @@ class Peripheral(peripheral: IPeripheral) extends ManagedComponent with ICompute
if (!mounts.contains(desiredLocation)) {
val env = api.FileSystem.asManagedEnvironment(fs, desiredLocation)
env.node.asInstanceOf[Component].setVisibility(Visibility.Network)
if (mountAddresses.contains(desiredLocation)) {
env.node.asInstanceOf[MutableNode].address = mountAddresses(desiredLocation)
mountAddresses -= desiredLocation
}
node.connect(env.node)
mounts += desiredLocation -> env
desiredLocation
@ -79,7 +113,9 @@ class Peripheral(peripheral: IPeripheral) extends ManagedComponent with ICompute
def unmount(location: String) {
mounts.remove(location) match {
case Some(env) => env.node.remove()
case Some(env) =>
mountAddresses -= location
env.node.remove()
case _ =>
}
}

View File

@ -84,13 +84,13 @@ trait Component extends api.network.Component with Persistable {
override def load(nbt: NBTTagCompound) {
super.load(nbt)
if (nbt.hasKey("visibility"))
visibility_ = Visibility.values()(nbt.getInteger("visibility"))
if (nbt.hasKey("oc.component.visibility"))
visibility_ = Visibility.values()(nbt.getInteger("oc.component.visibility"))
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
nbt.setInteger("visibility", visibility_.ordinal())
nbt.setInteger("oc.component.visibility", visibility_.ordinal())
}
}

View File

@ -332,10 +332,6 @@ object Network extends api.detail.NetworkAPI {
def onChunkUnload(e: ChunkEvent.Unload) =
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity]))
@ForgeSubscribe
def onChunkLoad(e: ChunkEvent.Load) =
onLoad(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity]))
private def onUnload(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
// TODO add a more efficient batch remove operation? something along the lines of if #remove > #nodes*factor remove all, re-add remaining?
tileEntities.
@ -344,10 +340,6 @@ object Network extends api.detail.NetworkAPI {
foreach(t => t.node.remove())
}
private def onLoad(w: World, tileEntities: Iterable[TileEntity]) = if (!w.isRemote) {
tileEntities.foreach(t => joinOrCreateNetwork(w, t.xCoord, t.yCoord, t.zCoord))
}
// ----------------------------------------------------------------------- //
private class Vertex(val data: MutableNode) {