Script Token Handling

This commit is contained in:
Kam committed 2026-03-03 01:58:45 -05:00
1 parent 6489f75b7d
commit 7df1cfbfb9
19 files changed
+150 -31

No files matched your search

@@ -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<UUID, String> 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<IScriptUnit> 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;
}
}
@@ -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) {
@@ -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;
}
}
@@ -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) {
@@ -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()));
}
@@ -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) {
@@ -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();
@@ -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();
@@ -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) {
@@ -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();
@@ -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
}
@@ -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();
@@ -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()));
}
@@ -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
@@ -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);
}
}
@@ -49,8 +49,9 @@ public class GuiScriptInterface extends GuiNPCInterface implements GuiYesNoCallb
public Map<String, List<String>> languages = new HashMap();
protected int scriptLimit = 1;
public List<String> hookList = new ArrayList<String>();
protected boolean loaded = false;
protected volatile boolean loaded = false;
protected boolean serverDataReceived = false;
protected String scriptSessionToken = null;
protected Map<Integer, GuiScriptTextArea> 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) {
@@ -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<IScriptUnit> 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);
}
}
@@ -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;
}
/**
@@ -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() {