new filesystem device feature, locked mode

A locked filesystem is readonly (in either managed or unmanaged modes)
It cannot be unlocked unless its mode is switched or it is recrafted -
both actions wipe the drive. The data is thus "locked" or protected
unless the drive is wiped. The player's displace name is also recorded
and shown in tooltips on the device to indicate who locked it. In this
manner, data authenticity can be trusted

closes #2138
This commit is contained in:
payonel 2018-11-05 23:58:58 -08:00
parent ed99a18944
commit 2611ecb265
12 changed files with 99 additions and 13 deletions

View File

@ -203,6 +203,8 @@ oc:gui.Chat.WarningRecipes=There were errors loading one or more recipes. Some i
oc:gui.Chat.WarningSimpleComponent=An addon (yours?) using the §aSimpleComponent§f interface did §esomething wrong§f. Component logic could not be injected. Please check your log file for more information. oc:gui.Chat.WarningSimpleComponent=An addon (yours?) using the §aSimpleComponent§f interface did §esomething wrong§f. Component logic could not be injected. Please check your log file for more information.
oc:gui.Drive.Managed=Managed oc:gui.Drive.Managed=Managed
oc:gui.Drive.Unmanaged=Unmanaged oc:gui.Drive.Unmanaged=Unmanaged
oc:gui.Drive.ReadOnlyLock=Lock
oc:gui.Drive.ReadOnlyLockWarning=§lRead Only§r lock. Cannot be removed unless the drive is wiped.
oc:gui.Drive.Warning=§lWarning§r: switching modes will result in a loss of all data currently stored on the disk! oc:gui.Drive.Warning=§lWarning§r: switching modes will result in a loss of all data currently stored on the disk!
oc:gui.Error.ComponentOverflow=Too many components connected to the computer. oc:gui.Error.ComponentOverflow=Too many components connected to the computer.
oc:gui.Error.InternalError=Internal error, please see the log file. This is probably a bug. oc:gui.Error.InternalError=Internal error, please see the log file. This is probably a bug.
@ -289,6 +291,7 @@ oc:tooltip.DiskDrive.CC=ComputerCraft floppies are §asupported§7.
oc:tooltip.DiskDrive=Allows reading and writing floppies. Can be installed in robots to allow inserting floppies later on. oc:tooltip.DiskDrive=Allows reading and writing floppies. Can be installed in robots to allow inserting floppies later on.
oc:tooltip.DiskDriveMountable=Provides the same functionality as a normal disk drive, but must be installed in a rack. oc:tooltip.DiskDriveMountable=Provides the same functionality as a normal disk drive, but must be installed in a rack.
oc:tooltip.DiskUsage=Disk usage: %s/%s Byte oc:tooltip.DiskUsage=Disk usage: %s/%s Byte
oc:tooltip.DiskLocked=Locked by: %s
oc:tooltip.DiskModeManaged=Mode: Managed oc:tooltip.DiskModeManaged=Mode: Managed
oc:tooltip.DiskModeUnmanaged=Mode: Unmanaged oc:tooltip.DiskModeUnmanaged=Mode: Unmanaged
oc:tooltip.Drone=Drones are light-weight, fast reconnaissance units with limited cargo space. oc:tooltip.Drone=Drones are light-weight, fast reconnaissance units with limited cargo space.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

After

Width:  |  Height:  |  Size: 764 B

View File

@ -116,6 +116,10 @@ object Localization {
def Unmanaged = localizeImmediately("gui.Drive.Unmanaged") def Unmanaged = localizeImmediately("gui.Drive.Unmanaged")
def Warning = localizeImmediately("gui.Drive.Warning") def Warning = localizeImmediately("gui.Drive.Warning")
def ReadOnlyLock = localizeImmediately("gui.Drive.ReadOnlyLock")
def LockWarning = localizeImmediately("gui.Drive.ReadOnlyLockWarning")
} }
object Raid { object Raid {
@ -159,6 +163,8 @@ object Localization {
def DiskMode(isUnmanaged: Boolean) = localizeImmediately(if (isUnmanaged) "tooltip.DiskModeUnmanaged" else "tooltip.DiskModeManaged") def DiskMode(isUnmanaged: Boolean) = localizeImmediately(if (isUnmanaged) "tooltip.DiskModeUnmanaged" else "tooltip.DiskModeManaged")
def DiskLock(lockInfo: String): String = if (lockInfo.isEmpty) "" else localizeImmediately("tooltip.DiskLocked", lockInfo)
def Materials = localizeImmediately("tooltip.Materials") def Materials = localizeImmediately("tooltip.Materials")
def Tier(tier: Int) = localizeImmediately("tooltip.Tier", tier.toString) def Tier(tier: Int) = localizeImmediately("tooltip.Tier", tier.toString)

View File

@ -34,6 +34,12 @@ object PacketSender {
pb.sendToServer() pb.sendToServer()
} }
def sendDriveLock(): Unit = {
val pb = new SimplePacketBuilder(PacketType.DriveLock)
pb.sendToServer()
}
def sendDronePower(e: Drone, power: Boolean) { def sendDronePower(e: Drone, power: Boolean) {
val pb = new SimplePacketBuilder(PacketType.DronePower) val pb = new SimplePacketBuilder(PacketType.DronePower)

View File

@ -10,19 +10,21 @@ import net.minecraft.entity.player.InventoryPlayer
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
class Drive(playerInventory: InventoryPlayer, val driveStack: () => ItemStack) extends GuiScreen with traits.Window { class Drive(playerInventory: InventoryPlayer, val driveStack: () => ItemStack) extends GuiScreen with traits.Window {
override val windowHeight = 85 override val windowHeight = 120
override def backgroundImage = Textures.guiDrive override def backgroundImage = Textures.guiDrive
protected var managedButton: ImageButton = _ protected var managedButton: ImageButton = _
protected var unmanagedButton: ImageButton = _ protected var unmanagedButton: ImageButton = _
protected var lockedButton: ImageButton = _
protected override def actionPerformed(button: GuiButton) { protected override def actionPerformed(button: GuiButton) {
if (button.id == 0) { if (button.id == 0) {
ClientPacketSender.sendDriveMode(false) ClientPacketSender.sendDriveMode(false)
} } else if (button.id == 1) {
if (button.id == 1) {
ClientPacketSender.sendDriveMode(true) ClientPacketSender.sendDriveMode(true)
} else if (button.id == 2) {
ClientPacketSender.sendDriveLock()
} }
} }
@ -30,18 +32,24 @@ class Drive(playerInventory: InventoryPlayer, val driveStack: () => ItemStack) e
super.initGui() super.initGui()
managedButton = new ImageButton(0, guiLeft + 11, guiTop + 11, 74, 18, Textures.guiButtonDriveMode, text = Localization.Drive.Managed, textColor = 0x608060, canToggle = true) managedButton = new ImageButton(0, guiLeft + 11, guiTop + 11, 74, 18, Textures.guiButtonDriveMode, text = Localization.Drive.Managed, textColor = 0x608060, canToggle = true)
unmanagedButton = new ImageButton(1, guiLeft + 91, guiTop + 11, 74, 18, Textures.guiButtonDriveMode, text = Localization.Drive.Unmanaged, textColor = 0x608060, canToggle = true) unmanagedButton = new ImageButton(1, guiLeft + 91, guiTop + 11, 74, 18, Textures.guiButtonDriveMode, text = Localization.Drive.Unmanaged, textColor = 0x608060, canToggle = true)
lockedButton = new ImageButton(2, guiLeft + 11, guiTop + windowHeight - 42, 44, 18, Textures.guiButtonDriveMode, text = Localization.Drive.ReadOnlyLock, textColor = 0x608060, canToggle = true)
add(buttonList, managedButton) add(buttonList, managedButton)
add(buttonList, unmanagedButton) add(buttonList, unmanagedButton)
add(buttonList, lockedButton)
} }
override def updateScreen(): Unit = { override def updateScreen(): Unit = {
unmanagedButton.toggled = new DriveData(driveStack()).isUnmanaged val data = new DriveData(driveStack())
unmanagedButton.toggled = data.isUnmanaged
managedButton.toggled = !unmanagedButton.toggled managedButton.toggled = !unmanagedButton.toggled
lockedButton.toggled = data.isLocked
lockedButton.enabled = !data.isLocked
super.updateScreen() super.updateScreen()
} }
override def drawScreen(mouseX: Int, mouseY: Int, dt: Float): Unit = { override def drawScreen(mouseX: Int, mouseY: Int, dt: Float): Unit = {
super.drawScreen(mouseX, mouseY, dt) super.drawScreen(mouseX, mouseY, dt)
fontRendererObj.drawSplitString(Localization.Drive.Warning, guiLeft + 7, guiTop + 37, xSize - 16, 0x404040) fontRendererObj.drawSplitString(Localization.Drive.Warning, guiLeft + 11, guiTop + 37, xSize - 20, 0x404040)
fontRendererObj.drawSplitString(Localization.Drive.LockWarning, guiLeft + 61, guiTop + windowHeight - 48, xSize - 68, 0x404040)
} }
} }

View File

@ -73,6 +73,7 @@ object PacketType extends Enumeration {
// Client -> Server // Client -> Server
ComputerPower, ComputerPower,
CopyToAnalyzer, CopyToAnalyzer,
DriveLock,
DriveMode, DriveMode,
DronePower, DronePower,
KeyDown, KeyDown,

View File

@ -11,12 +11,24 @@ class DriveData extends ItemData(null) {
} }
var isUnmanaged = false var isUnmanaged = false
var lockInfo: String = ""
def isLocked: Boolean = {
lockInfo != null && !lockInfo.isEmpty
}
private val UnmanagedKey = Settings.namespace + "unmanaged"
private val LockKey = Settings.namespace + "lock"
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
isUnmanaged = nbt.getBoolean(Settings.namespace + "unmanaged") isUnmanaged = nbt.getBoolean(UnmanagedKey)
lockInfo = if (nbt.hasKey(LockKey)) {
nbt.getString(LockKey)
} else ""
} }
override def save(nbt: NBTTagCompound) { override def save(nbt: NBTTagCompound) {
nbt.setBoolean(Settings.namespace + "unmanaged", isUnmanaged) nbt.setBoolean(UnmanagedKey, isUnmanaged)
nbt.setString(LockKey, lockInfo)
} }
} }

View File

@ -6,6 +6,7 @@ import li.cil.oc.Localization
import li.cil.oc.OpenComputers import li.cil.oc.OpenComputers
import li.cil.oc.Settings import li.cil.oc.Settings
import li.cil.oc.common.GuiType import li.cil.oc.common.GuiType
import li.cil.oc.common.item.data.DriveData
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.world.World import net.minecraft.world.World
@ -31,7 +32,9 @@ trait FileSystemLike extends Delegate {
} }
} }
} }
tooltip.add(Localization.Tooltip.DiskMode(nbt.getBoolean(Settings.namespace + "unmanaged"))) val data = new DriveData(stack)
tooltip.add(Localization.Tooltip.DiskMode(data.isUnmanaged))
tooltip.add(Localization.Tooltip.DiskLock(data.lockInfo))
} }
super.tooltipLines(stack, player, tooltip, advanced) super.tooltipLines(stack, player, tooltip, advanced)
} }

View File

@ -70,10 +70,13 @@ object DriverFileSystem extends Item {
val sound = Settings.resourceDomain + ":" + (if (isFloppy) "floppy_access" else "hdd_access") val sound = Settings.resourceDomain + ":" + (if (isFloppy) "floppy_access" else "hdd_access")
val drive = new DriveData(stack) val drive = new DriveData(stack)
val environment = if (drive.isUnmanaged) { val environment = if (drive.isUnmanaged) {
new Drive(capacity max 0, platterCount, label, Option(host), Option(sound), speed) new Drive(capacity max 0, platterCount, label, Option(host), Option(sound), speed, drive.isLocked)
} }
else { else {
val fs = oc.api.FileSystem.fromSaveDirectory(address, capacity max 0, Settings.get.bufferChanges) var fs = oc.api.FileSystem.fromSaveDirectory(address, capacity max 0, Settings.get.bufferChanges)
if (drive.isLocked) {
fs = oc.api.FileSystem.asReadOnly(fs)
}
oc.api.FileSystem.asManagedEnvironment(fs, label, host, sound, speed) oc.api.FileSystem.asManagedEnvironment(fs, label, host, sound, speed)
} }
if (environment != null && environment.node != null) { if (environment != null && environment.node != null) {

View File

@ -35,6 +35,7 @@ object PacketHandler extends CommonPacketHandler {
p.packetType match { p.packetType match {
case PacketType.ComputerPower => onComputerPower(p) case PacketType.ComputerPower => onComputerPower(p)
case PacketType.CopyToAnalyzer => onCopyToAnalyzer(p) case PacketType.CopyToAnalyzer => onCopyToAnalyzer(p)
case PacketType.DriveLock => onDriveLock(p)
case PacketType.DriveMode => onDriveMode(p) case PacketType.DriveMode => onDriveMode(p)
case PacketType.DronePower => onDronePower(p) case PacketType.DronePower => onDronePower(p)
case PacketType.KeyDown => onKeyDown(p) case PacketType.KeyDown => onKeyDown(p)
@ -86,13 +87,34 @@ object PacketHandler extends CommonPacketHandler {
} }
} }
def onDriveMode(p: PacketParser) = p.player match { def onDriveLock(p: PacketParser) = p.player match {
case player: EntityPlayerMP => case player: EntityPlayerMP =>
Delegator.subItem(player.getHeldItem) match { Delegator.subItem(player.getHeldItem) match {
case Some(drive: FileSystemLike) => case Some(drive: FileSystemLike) =>
val data = new DriveData(player.getHeldItem) val data = new DriveData(player.getHeldItem)
data.isUnmanaged = p.readBoolean() data.lockInfo = player.getDisplayName match {
case name: String if name != null && !name.isEmpty => name
case _ => "notch" // meaning: "unknown"
}
data.save(player.getHeldItem) data.save(player.getHeldItem)
case _ => // Invalid packet
}
case _ => // Invalid Packet
}
def onDriveMode(p: PacketParser) = p.player match {
case player: EntityPlayerMP =>
val heldItem = player.getHeldItem
Delegator.subItem(heldItem) match {
case Some(drive: FileSystemLike) =>
val data = new DriveData(heldItem)
val newIsUnmanaged = p.readBoolean()
if (data.isUnmanaged != newIsUnmanaged) {
fs.FileSystem.removeAddress(heldItem)
data.lockInfo = ""
}
data.isUnmanaged = newIsUnmanaged
data.save(heldItem)
case _ => // Invalid packet. case _ => // Invalid packet.
} }
case _ => // Invalid packet. case _ => // Invalid packet.

View File

@ -28,7 +28,7 @@ import net.minecraftforge.common.DimensionManager
import scala.collection.convert.WrapAsJava._ import scala.collection.convert.WrapAsJava._
class Drive(val capacity: Int, val platterCount: Int, val label: Label, host: Option[EnvironmentHost], val sound: Option[String], val speed: Int) extends prefab.ManagedEnvironment with DeviceInfo { class Drive(val capacity: Int, val platterCount: Int, val label: Label, host: Option[EnvironmentHost], val sound: Option[String], val speed: Int, val isLocked: Boolean) extends prefab.ManagedEnvironment with DeviceInfo {
override val node = Network.newNode(this, Visibility.Network). override val node = Network.newNode(this, Visibility.Network).
withComponent("drive", Visibility.Neighbors). withComponent("drive", Visibility.Neighbors).
withConnector(). withConnector().
@ -74,6 +74,7 @@ class Drive(val capacity: Int, val platterCount: Int, val label: Label, host: Op
@Callback(doc = """function(value:string):string -- Sets the label of the drive. Returns the new value, which may be truncated.""") @Callback(doc = """function(value:string):string -- Sets the label of the drive. Returns the new value, which may be truncated.""")
def setLabel(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def setLabel(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
if (isLocked) throw new Exception("drive is read only")
if (label == null) throw new Exception("drive does not support labeling") if (label == null) throw new Exception("drive does not support labeling")
if (args.checkAny(0) == null) label.setLabel(null) if (args.checkAny(0) == null) label.setLabel(null)
else label.setLabel(args.checkString(0)) else label.setLabel(args.checkString(0))
@ -101,6 +102,7 @@ class Drive(val capacity: Int, val platterCount: Int, val label: Label, host: Op
@Callback(direct = true, doc = """function(sector:number, value:string) -- Write the specified contents to the specified sector.""") @Callback(direct = true, doc = """function(sector:number, value:string) -- Write the specified contents to the specified sector.""")
def writeSector(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def writeSector(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
if (isLocked) throw new Exception("drive is read only")
context.consumeCallBudget(writeSectorCosts(speed)) context.consumeCallBudget(writeSectorCosts(speed))
val sectorData = args.checkByteArray(1) val sectorData = args.checkByteArray(1)
val sector = moveToSector(context, checkSector(args, 0)) val sector = moveToSector(context, checkSector(args, 0))
@ -120,6 +122,7 @@ class Drive(val capacity: Int, val platterCount: Int, val label: Label, host: Op
@Callback(direct = true, doc = """function(offset:number, value:number) -- Write a single byte to the specified offset.""") @Callback(direct = true, doc = """function(offset:number, value:number) -- Write a single byte to the specified offset.""")
def writeByte(context: Context, args: Arguments): Array[AnyRef] = this.synchronized { def writeByte(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
if (isLocked) throw new Exception("drive is read only")
context.consumeCallBudget(writeByteCosts(speed)) context.consumeCallBudget(writeByteCosts(speed))
val offset = args.checkInteger(0) - 1 val offset = args.checkInteger(0) - 1
val value = args.checkInteger(1).toByte val value = args.checkInteger(1).toByte

View File

@ -11,6 +11,8 @@ import li.cil.oc.Settings
import li.cil.oc.api import li.cil.oc.api
import li.cil.oc.api.fs.Label import li.cil.oc.api.fs.Label
import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.network.EnvironmentHost
import li.cil.oc.common.item.Delegator
import li.cil.oc.common.item.traits.FileSystemLike
import li.cil.oc.integration.Mods import li.cil.oc.integration.Mods
import li.cil.oc.integration.computercraft.DriverComputerCraftMedia import li.cil.oc.integration.computercraft.DriverComputerCraftMedia
import li.cil.oc.server.component import li.cil.oc.server.component
@ -112,6 +114,23 @@ object FileSystem extends api.detail.FileSystemAPI {
else null else null
} }
def removeAddress(fsStack: ItemStack): Boolean = {
Delegator.subItem(fsStack) match {
case Some(drive: FileSystemLike) => {
val data = li.cil.oc.integration.opencomputers.Item.dataTag(fsStack)
if (data.hasKey("node")) {
val nodeData = data.getCompoundTag("node")
if (nodeData.hasKey("address")) {
nodeData.removeTag("address")
return true
}
}
}
case _ =>
}
false
}
def fromMemory(capacity: Long): api.fs.FileSystem = new RamFileSystem(capacity) def fromMemory(capacity: Long): api.fs.FileSystem = new RamFileSystem(capacity)
def fromComputerCraft(mount: AnyRef): api.fs.FileSystem = def fromComputerCraft(mount: AnyRef): api.fs.FileSystem =