diff --git a/src/main/java/kamkeel/npcs/network/PacketUtil.java b/src/main/java/kamkeel/npcs/network/PacketUtil.java index 9235dc1f7..0db53b7fc 100644 --- a/src/main/java/kamkeel/npcs/network/PacketUtil.java +++ b/src/main/java/kamkeel/npcs/network/PacketUtil.java @@ -21,10 +21,37 @@ import noppes.npcs.items.ItemNpcTool; import java.io.IOException; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; public class PacketUtil { + // ==================== SCRIPT SESSION TOKEN SYSTEM ==================== + // Prevents saving script data before the client has received it from the server. + // Each GET generates a token sent to the client; each SAVE must echo it back. + + private static final Map scriptSessionTokens = new ConcurrentHashMap<>(); + + public static String createScriptSession(EntityPlayerMP player) { + String token = Long.toHexString(ThreadLocalRandom.current().nextLong()); + scriptSessionTokens.put(player.getUniqueID(), token); + return token; + } + + public static boolean verifyScriptSession(EntityPlayer player, String token) { + if (token == null || token.isEmpty()) + return false; + String expected = scriptSessionTokens.get(player.getUniqueID()); + return expected != null && expected.equals(token); + } + + public static void clearScriptSession(EntityPlayer player) { + scriptSessionTokens.remove(player.getUniqueID()); + } + public static boolean verifyItemPacket(String name, EnumItemPacketType type, EntityPlayer player) { if (player == null) return false; @@ -216,11 +243,14 @@ public class PacketUtil { } public static void getScripts(IScriptHandler data, EntityPlayerMP player) { + String token = createScriptSession(player); + NBTTagCompound compound = new NBTTagCompound(); compound.setBoolean("ScriptEnabled", data.getEnabled()); compound.setString("ScriptLanguage", data.getLanguage()); compound.setTag("Languages", ScriptController.Instance.nbtLanguages()); compound.setTag("ScriptConsole", NBTTags.NBTLongStringMap(data.getConsoleText())); + compound.setString("ScriptSessionToken", token); GuiDataPacket.sendGuiData(player, compound); List containers = data.getScripts(); for (int i = 0; i < containers.size(); i++) { @@ -237,9 +267,18 @@ public class PacketUtil { GuiDataPacket.sendGuiData(player, loadComplete); } - public static void saveScripts(IScriptHandler data, ByteBuf buffer) throws IOException { + public static boolean saveScripts(IScriptHandler data, ByteBuf buffer, EntityPlayer player) throws IOException { int tab = buffer.readInt(); int totalScripts = buffer.readInt(); + NBTTagCompound compound = ByteBufUtils.readNBT(buffer); + + // Verify script session token to prevent saving unloaded defaults + String token = compound.getString("ScriptSessionToken"); + if (!verifyScriptSession(player, token)) { + LogWriter.error(String.format("Rejected script save from %s: session not verified (data not loaded)", player.getCommandSenderName())); + return false; + } + if (totalScripts == 0) { data.getScripts().clear(); } @@ -250,12 +289,10 @@ public class PacketUtil { } else while (data.getScripts().size() < totalScripts) { data.getScripts().add(new ScriptContainer(data)); } - NBTTagCompound tabCompound = ByteBufUtils.readNBT(buffer); // Use factory method to create correct script unit type based on NBT - IScriptUnit script = IScriptUnit.createFromNBT(tabCompound, data); + IScriptUnit script = IScriptUnit.createFromNBT(compound, data); data.getScripts().set(tab, script); } else { - NBTTagCompound compound = ByteBufUtils.readNBT(buffer); data.setLanguage(compound.getString("ScriptLanguage")); if (!ScriptController.Instance.languages.containsKey(data.getLanguage())) { if (!ScriptController.Instance.languages.isEmpty()) { @@ -266,5 +303,6 @@ public class PacketUtil { } data.setEnabled(compound.getBoolean("ScriptEnabled")); } + return true; } } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java index ec3f6ff20..eceb04286 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/AbilityScriptPacket.java @@ -87,14 +87,20 @@ public final class AbilityScriptPacket extends AbstractPacket { String id = ByteBufUtils.readUTF8String(in); Ability ability = AbilityController.Instance.resolveAbility(id); - if (ability == null) + if (ability == null) { + // Ability not yet saved — send default empty script data so the GUI is usable + if (requestedAction == Action.GET) { + PacketUtil.getScripts((IScriptHandler) new AbilityScript(id), (EntityPlayerMP) player); + } return; + } AbilityScript data = ability.getOrCreateScriptHandler(); if (requestedAction == Action.GET) { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { - data.saveScript(in); + if (!data.saveScript(in, player)) + return; // Persist to disk AbilityController.Instance.saveCustomAbility(ability); if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/BlockScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/BlockScriptPacket.java index ce94c98f9..ac2303628 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/BlockScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/BlockScriptPacket.java @@ -7,6 +7,7 @@ import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; import kamkeel.npcs.network.enums.EnumRequestPacket; import kamkeel.npcs.network.packets.data.large.GuiDataPacket; import kamkeel.npcs.util.ByteBufUtils; @@ -15,6 +16,7 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.LogWriter; import noppes.npcs.blocks.tiles.TileScripted; import noppes.npcs.config.ConfigScript; import noppes.npcs.controllers.ScriptController; @@ -81,8 +83,10 @@ public final class BlockScriptPacket extends AbstractPacket { TileEntity tile = player.worldObj.getTileEntity(in.readInt(), in.readInt(), in.readInt()); if (!(tile instanceof TileScripted)) return; + String token = PacketUtil.createScriptSession((EntityPlayerMP) player); NBTTagCompound compound = ((TileScripted) tile).getNBT(new NBTTagCompound()); compound.setTag("Languages", ScriptController.Instance.nbtLanguages()); + compound.setString("ScriptSessionToken", token); GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); NBTTagCompound loadComplete = new NBTTagCompound(); @@ -95,8 +99,14 @@ public final class BlockScriptPacket extends AbstractPacket { TileEntity tile = player.worldObj.getTileEntity(in.readInt(), in.readInt(), in.readInt()); if (!(tile instanceof TileScripted)) return; + NBTTagCompound compound = ByteBufUtils.readNBT(in); + String token = compound.getString("ScriptSessionToken"); + if (!PacketUtil.verifyScriptSession(player, token)) { + LogWriter.error(String.format("Rejected block script save from %s: session not verified", player.getCommandSenderName())); + return; + } TileScripted script = (TileScripted) tile; - script.setNBT(ByteBufUtils.readNBT(in)); + script.setNBT(compound); script.lastInited = -1; } } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java index 45640bbfb..a8c455207 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/ChainedAbilityScriptPacket.java @@ -87,14 +87,20 @@ public final class ChainedAbilityScriptPacket extends AbstractPacket { String id = ByteBufUtils.readUTF8String(in); ChainedAbility chain = AbilityController.Instance.resolveChainedAbility(id); - if (chain == null) + if (chain == null) { + // Chain not yet saved — send default empty script data so the GUI is usable + if (requestedAction == Action.GET) { + PacketUtil.getScripts((IScriptHandler) new ChainedAbilityScript(id), (EntityPlayerMP) player); + } return; + } ChainedAbilityScript data = chain.getOrCreateScriptHandler(); if (requestedAction == Action.GET) { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { - data.saveScript(in); + if (!data.saveScript(in, player)) + return; // Persist to disk AbilityController.Instance.saveChainedAbility(chain); if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/EffectScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/EffectScriptPacket.java index 8171f085d..705e191be 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/EffectScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/EffectScriptPacket.java @@ -90,7 +90,8 @@ public final class EffectScriptPacket extends AbstractPacket { if (requestedAction == Action.GET) { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { - data.saveScript(in); + if (!data.saveScript(in, player)) + return; if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { LogWriter.script(String.format("[%s] (Player) %s SAVED EFFECT %s [%s]", "EFFECT SCRIPTS", player.getCommandSenderName(), effect.getName())); } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/EventScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/EventScriptPacket.java index 69f660a2c..2d63c5bbd 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/EventScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/EventScriptPacket.java @@ -89,7 +89,8 @@ public final class EventScriptPacket extends AbstractPacket { if (requestedAction == Action.GET) { PacketUtil.getScripts(data, (EntityPlayerMP) player); } else { - PacketUtil.saveScripts(data, in); + if (!PacketUtil.saveScripts(data, in, player)) + return; npc.updateAI = true; npc.script.hasInited = false; if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/ForgeScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/ForgeScriptPacket.java index 86584a766..e99abb702 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/ForgeScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/ForgeScriptPacket.java @@ -84,7 +84,8 @@ public final class ForgeScriptPacket extends AbstractPacket { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { int tab = in.getInt(in.readerIndex()); - PacketUtil.saveScripts(data, in); + if (!PacketUtil.saveScripts(data, in, player)) + return; ScriptController.Instance.lastForgeUpdate = System.currentTimeMillis(); if (tab == -1) ScriptController.Instance.saveForgeScriptsSync(); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/GlobalNPCScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/GlobalNPCScriptPacket.java index ae9e44a3d..9f1237638 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/GlobalNPCScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/GlobalNPCScriptPacket.java @@ -84,7 +84,8 @@ public final class GlobalNPCScriptPacket extends AbstractPacket { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { int tab = in.getInt(in.readerIndex()); - PacketUtil.saveScripts(data, in); + if (!PacketUtil.saveScripts(data, in, player)) + return; ScriptController.Instance.lastGlobalNpcUpdate = System.currentTimeMillis(); if (tab == -1) ScriptController.Instance.saveGlobalScriptsSync(); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/NPCScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/NPCScriptPacket.java index 7d5e0d3ed..35633cb07 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/NPCScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/NPCScriptPacket.java @@ -81,11 +81,19 @@ public final class NPCScriptPacket extends AbstractPacket { Action requestedAction = Action.values()[in.readInt()]; if (requestedAction == Action.GET) { + String token = PacketUtil.createScriptSession((EntityPlayerMP) player); NBTTagCompound compound = npc.script.writeToNBT(new NBTTagCompound()); compound.setTag("Languages", ScriptController.Instance.nbtLanguages()); + compound.setString("ScriptSessionToken", token); GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); } else { - npc.script.readFromNBT(ByteBufUtils.readNBT(in)); + NBTTagCompound compound = ByteBufUtils.readNBT(in); + String token = compound.getString("ScriptSessionToken"); + if (!PacketUtil.verifyScriptSession(player, token)) { + LogWriter.error(String.format("Rejected NPC script save from %s: session not verified", player.getCommandSenderName())); + return; + } + npc.script.readFromNBT(compound); npc.updateAI = true; npc.script.hasInited = false; if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/PlayerScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/PlayerScriptPacket.java index 242e7d092..30d999815 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/PlayerScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/PlayerScriptPacket.java @@ -84,7 +84,8 @@ public final class PlayerScriptPacket extends AbstractPacket { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { int tab = in.getInt(in.readerIndex()); - PacketUtil.saveScripts(data, in); + if (!PacketUtil.saveScripts(data, in, player)) + return; ScriptController.Instance.lastPlayerUpdate = System.currentTimeMillis(); if (tab == -1) ScriptController.Instance.savePlayerScriptsSync(); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/RecipeScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/RecipeScriptPacket.java index 3627631eb..36ab65201 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/RecipeScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/RecipeScriptPacket.java @@ -102,7 +102,8 @@ public final class RecipeScriptPacket extends AbstractPacket { if (action == Action.GET) { PacketUtil.getScripts(script, player); } else { - script.saveScript(in); + if (!script.saveScript(in, player)) + return; if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { // TODO: Log Recipe Script Saving } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/item/ItemScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/item/ItemScriptPacket.java index 7d21242bd..a1ae83610 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/item/ItemScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/item/ItemScriptPacket.java @@ -7,6 +7,7 @@ import kamkeel.npcs.network.AbstractPacket; import kamkeel.npcs.network.PacketChannel; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; import kamkeel.npcs.network.enums.EnumRequestPacket; import kamkeel.npcs.network.packets.data.large.GuiDataPacket; import kamkeel.npcs.util.ByteBufUtils; @@ -14,6 +15,7 @@ import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.LogWriter; import noppes.npcs.config.ConfigScript; import noppes.npcs.controllers.ScriptController; import noppes.npcs.scripted.NpcAPI; @@ -72,8 +74,10 @@ public final class ItemScriptPacket extends AbstractPacket { if (requestedAction == Action.GET) { ScriptCustomItem iw = (ScriptCustomItem) NpcAPI.Instance().getIItemStack(player.getHeldItem()); iw.loadScriptData(); + String token = PacketUtil.createScriptSession((EntityPlayerMP) player); NBTTagCompound compound = iw.getMCNbt(); compound.setTag("Languages", ScriptController.Instance.nbtLanguages()); + compound.setString("ScriptSessionToken", token); GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); NBTTagCompound loadComplete = new NBTTagCompound(); @@ -85,6 +89,11 @@ public final class ItemScriptPacket extends AbstractPacket { } NBTTagCompound compound = ByteBufUtils.readNBT(in); + String token = compound.getString("ScriptSessionToken"); + if (!PacketUtil.verifyScriptSession(player, token)) { + LogWriter.error(String.format("Rejected item script save from %s: session not verified", player.getCommandSenderName())); + return; + } ScriptCustomItem wrapper = (ScriptCustomItem) NpcAPI.Instance().getIItemStack(player.getHeldItem()); wrapper.setMCNbt(compound); wrapper.saveScriptData(); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/script/item/LinkedItemScriptPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/script/item/LinkedItemScriptPacket.java index faa14f4d1..a711aace2 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/script/item/LinkedItemScriptPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/script/item/LinkedItemScriptPacket.java @@ -90,7 +90,8 @@ public final class LinkedItemScriptPacket extends AbstractPacket { if (requestedAction == Action.GET) { PacketUtil.getScripts((IScriptHandler) data, (EntityPlayerMP) player); } else { - data.saveScript(in); + if (!data.saveScript(in, player)) + return; if (ConfigDebug.PlayerLogging && FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) { LogWriter.script(String.format("[%s] (Player) %s SAVED LINKED ITEM [%s]", "LINKED ITEM SCRIPTS", player.getCommandSenderName(), linkedItem.getName())); } diff --git a/src/main/java/noppes/npcs/blocks/tiles/TileScripted.java b/src/main/java/noppes/npcs/blocks/tiles/TileScripted.java index 64dbb767b..22e0d4f2f 100644 --- a/src/main/java/noppes/npcs/blocks/tiles/TileScripted.java +++ b/src/main/java/noppes/npcs/blocks/tiles/TileScripted.java @@ -188,8 +188,11 @@ public class TileScripted extends TileEntity implements IScriptBlockHandler, ISc } @Override - public void sync() { - sendSavePacket(-1, getScripts().size(), getNBT(new NBTTagCompound())); + public void sync(String sessionToken) { + NBTTagCompound compound = getNBT(new NBTTagCompound()); + if (sessionToken != null && !sessionToken.isEmpty()) + compound.setString("ScriptSessionToken", sessionToken); + sendSavePacket(-1, getScripts().size(), compound); } @Override diff --git a/src/main/java/noppes/npcs/client/gui/GuiScript.java b/src/main/java/noppes/npcs/client/gui/GuiScript.java index 7420d4387..caa8c74da 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiScript.java +++ b/src/main/java/noppes/npcs/client/gui/GuiScript.java @@ -343,6 +343,8 @@ public class GuiScript extends GuiScriptInterface { @Override public void setGuiData(NBTTagCompound compound) { script.readFromNBT(compound); + if (compound.hasKey("ScriptSessionToken")) + scriptSessionToken = compound.getString("ScriptSessionToken"); loadLanguagesData(compound); loaded = true; } @@ -351,7 +353,10 @@ public class GuiScript extends GuiScriptInterface { public void save() { if (loaded) { setScript(); - NPCScriptPacket.Save(script.writeToNBT(new NBTTagCompound())); + NBTTagCompound compound = script.writeToNBT(new NBTTagCompound()); + if (scriptSessionToken != null) + compound.setString("ScriptSessionToken", scriptSessionToken); + NPCScriptPacket.Save(compound); } } diff --git a/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java b/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java index 3d91446c9..012a6cd5f 100644 --- a/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java +++ b/src/main/java/noppes/npcs/client/gui/script/GuiScriptInterface.java @@ -49,8 +49,9 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb public Map> languages = new HashMap(); protected int scriptLimit = 1; public List hookList = new ArrayList(); - protected boolean loaded = false; + protected volatile boolean loaded = false; protected boolean serverDataReceived = false; + protected String scriptSessionToken = null; protected Map textAreas = new HashMap<>(); protected GuiScreen parent; @@ -755,6 +756,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb return; } if (result.kind == IScriptHandlerPacket.GuiDataKind.METADATA) { + if (compound.hasKey("ScriptSessionToken")) + scriptSessionToken = compound.getString("ScriptSessionToken"); loadLanguagesData(compound); return; } @@ -765,6 +768,8 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb return; } + if (compound.hasKey("ScriptSessionToken")) + scriptSessionToken = compound.getString("ScriptSessionToken"); loadLanguagesData(compound); } @@ -814,9 +819,7 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb this.setScript(); if (handler instanceof IScriptHandlerPacket) - ((IScriptHandlerPacket) handler).sync(); - - + ((IScriptHandlerPacket) handler).sync(scriptSessionToken); } public void textUpdate(String text) { diff --git a/src/main/java/noppes/npcs/controllers/data/IScriptHandlerPacket.java b/src/main/java/noppes/npcs/controllers/data/IScriptHandlerPacket.java index 88ff5fb94..0ff4fcb7e 100644 --- a/src/main/java/noppes/npcs/controllers/data/IScriptHandlerPacket.java +++ b/src/main/java/noppes/npcs/controllers/data/IScriptHandlerPacket.java @@ -71,16 +71,29 @@ public interface IScriptHandlerPacket extends IScriptHandler { * GuiScriptInterface is responsible for updating the active {@link IScriptUnit} with current editor text first. */ default void sync() { + sync(null); + } + + /** + * Emit save packets with a session token for server-side verification. + * The token prevents saving unloaded default data due to race conditions. + */ + default void sync(String sessionToken) { List containers = getScripts(); for (int i = 0; i < containers.size(); i++) { IScriptUnit container = containers.get(i); - sendSavePacket(i, containers.size(), container.writeToNBT(new NBTTagCompound())); + NBTTagCompound nbt = container.writeToNBT(new NBTTagCompound()); + if (sessionToken != null && !sessionToken.isEmpty()) + nbt.setString("ScriptSessionToken", sessionToken); + sendSavePacket(i, containers.size(), nbt); } NBTTagCompound scriptData = new NBTTagCompound(); scriptData.setString("ScriptLanguage", getLanguage()); scriptData.setBoolean("ScriptEnabled", getEnabled()); scriptData.setTag("ScriptConsole", NBTTags.NBTLongStringMap(getConsoleText())); + if (sessionToken != null && !sessionToken.isEmpty()) + scriptData.setString("ScriptSessionToken", sessionToken); sendSavePacket(-1, containers.size(), scriptData); } } diff --git a/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java b/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java index 854e77941..9844a02a3 100644 --- a/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java +++ b/src/main/java/noppes/npcs/controllers/data/SingleScriptHandler.java @@ -2,7 +2,9 @@ package noppes.npcs.controllers.data; import cpw.mods.fml.common.eventhandler.Event; import io.netty.buffer.ByteBuf; +import kamkeel.npcs.network.PacketUtil; import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.common.util.Constants; import noppes.npcs.CustomNpcs; @@ -117,9 +119,16 @@ public abstract class SingleScriptHandler extends ScriptHandler { * - tab == 0: Script content NBT follows * - tab != 0 (typically -1): Script metadata (language, enabled) follows */ - public void saveScript(ByteBuf buffer) throws IOException { + public boolean saveScript(ByteBuf buffer, EntityPlayer player) throws IOException { int tab = buffer.readInt(); int totalScripts = buffer.readInt(); + NBTTagCompound compound = ByteBufUtils.readNBT(buffer); + + // Verify session token + String token = compound.getString("ScriptSessionToken"); + if (!PacketUtil.verifyScriptSession(player, token)) { + return false; + } if (totalScripts == 0) { this.container = null; @@ -127,15 +136,14 @@ public abstract class SingleScriptHandler extends ScriptHandler { if (tab == 0) { // Script content - NBTTagCompound tabCompound = ByteBufUtils.readNBT(buffer); - this.container = IScriptUnit.createFromNBT(tabCompound, this); + this.container = IScriptUnit.createFromNBT(compound, this); } else { // Metadata (language, enabled) - NBTTagCompound compound = ByteBufUtils.readNBT(buffer); this.setLanguage(compound.getString("ScriptLanguage")); normalizeLanguage(); this.setEnabled(compound.getBoolean("ScriptEnabled")); } + return true; } /** diff --git a/src/main/java/noppes/npcs/scripted/item/ScriptCustomItem.java b/src/main/java/noppes/npcs/scripted/item/ScriptCustomItem.java index e3641570e..b89a241dd 100644 --- a/src/main/java/noppes/npcs/scripted/item/ScriptCustomItem.java +++ b/src/main/java/noppes/npcs/scripted/item/ScriptCustomItem.java @@ -99,8 +99,11 @@ public class ScriptCustomItem extends ScriptCustomizableItem implements IItemCus } @Override - public void sync() { - sendSavePacket(-1, getScripts().size(), getMCNbt()); + public void sync(String sessionToken) { + NBTTagCompound compound = getMCNbt(); + if (sessionToken != null && !sessionToken.isEmpty()) + compound.setString("ScriptSessionToken", sessionToken); + sendSavePacket(-1, getScripts().size(), compound); } public int getType() {