From 4f8e73f9155ef142cedefffbf867643c20d0b9f9 Mon Sep 17 00:00:00 2001 From: Ruling-0 <47913168+Ruling-0@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:51:20 -0500 Subject: [PATCH] Run versioned transformers on player data read and write --- .../api/PlayerDataTransformContext.java | 47 ++++++++++++++++ .../postea/mixins/PosteaLoadingPlugin.java | 1 + .../postea/mixins/early/MixinEntity.java | 34 ++++++++++++ .../utility/PlayerDataFixerUtility.java | 55 +++++++++++++++++++ src/main/resources/mixins.postea.early.json | 1 + 5 files changed, 138 insertions(+) create mode 100644 src/main/java/com/gtnewhorizons/postea/mixins/early/MixinEntity.java create mode 100644 src/main/java/com/gtnewhorizons/postea/utility/PlayerDataFixerUtility.java diff --git a/src/main/java/com/gtnewhorizons/postea/api/PlayerDataTransformContext.java b/src/main/java/com/gtnewhorizons/postea/api/PlayerDataTransformContext.java index 61e19f4..e24eb88 100644 --- a/src/main/java/com/gtnewhorizons/postea/api/PlayerDataTransformContext.java +++ b/src/main/java/com/gtnewhorizons/postea/api/PlayerDataTransformContext.java @@ -1,8 +1,55 @@ package com.gtnewhorizons.postea.api; +import java.util.function.Consumer; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; + +import com.gtnewhorizons.postea.utility.NbtItemStacks; import com.gtnewhorizons.postea.utility.VersionStamps; +/** + * A player's saved data as handed to {@link IVersionedTransformer#transformPlayer}: the root tag about to be + * loaded into the player, which holds the inventory, ender chest, {@code ForgeData}, and every extended-properties + * compound. The player object is constructed but not yet loaded from the tag. + */ public final class PlayerDataTransformContext { public static final int UNSTAMPED = VersionStamps.UNSTAMPED; + + private final EntityPlayerMP player; + private final NBTTagCompound playerTag; + private final int storedVersion; + private final int currentVersion; + + public PlayerDataTransformContext(EntityPlayerMP player, NBTTagCompound playerTag, int storedVersion, + int currentVersion) { + this.player = player; + this.playerTag = playerTag; + this.storedVersion = storedVersion; + this.currentVersion = currentVersion; + } + + public EntityPlayerMP player() { + return player; + } + + public NBTTagCompound playerTag() { + return playerTag; + } + + public int storedVersion() { + return storedVersion; + } + + public int currentVersion() { + return currentVersion; + } + + /** + * Visits every serialized item stack anywhere in the player's data; see {@link NbtItemStacks#forEach}. + */ + public void forEachItemStackTag(Consumer visitor) { + NbtItemStacks.forEach(playerTag, visitor); + } } diff --git a/src/main/java/com/gtnewhorizons/postea/mixins/PosteaLoadingPlugin.java b/src/main/java/com/gtnewhorizons/postea/mixins/PosteaLoadingPlugin.java index 03c4428..d98f066 100644 --- a/src/main/java/com/gtnewhorizons/postea/mixins/PosteaLoadingPlugin.java +++ b/src/main/java/com/gtnewhorizons/postea/mixins/PosteaLoadingPlugin.java @@ -45,6 +45,7 @@ public final class PosteaLoadingPlugin implements IEarlyMixinLoader, IFMLLoading final List mixins = new ArrayList<>(); mixins.add("MixinItemStack"); mixins.add("MixinChunk"); + mixins.add("MixinEntity"); mixins.add("MixinAnvilChunkLoader"); mixins.add("MixinNBTTagList"); return mixins; diff --git a/src/main/java/com/gtnewhorizons/postea/mixins/early/MixinEntity.java b/src/main/java/com/gtnewhorizons/postea/mixins/early/MixinEntity.java new file mode 100644 index 0000000..e0a59a0 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/postea/mixins/early/MixinEntity.java @@ -0,0 +1,34 @@ +package com.gtnewhorizons.postea.mixins.early; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.gtnewhorizons.postea.utility.PlayerDataFixerUtility; + +@Mixin(Entity.class) +public abstract class MixinEntity { + + @Unique + private NBTTagCompound postea$versionStamps; + + @Inject(method = "readFromNBT", at = @At("HEAD"), require = 1) + private void postea$transformPlayerData(NBTTagCompound tag, CallbackInfo ci) { + if ((Object) this instanceof EntityPlayerMP player) { + postea$versionStamps = PlayerDataFixerUtility.onPlayerRead(player, tag); + } + } + + @Inject(method = "writeToNBT", at = @At("RETURN"), require = 1) + private void postea$stampPlayerData(NBTTagCompound tag, CallbackInfo ci) { + if ((Object) this instanceof EntityPlayerMP) { + PlayerDataFixerUtility.onPlayerWrite(tag, postea$versionStamps); + } + } +} diff --git a/src/main/java/com/gtnewhorizons/postea/utility/PlayerDataFixerUtility.java b/src/main/java/com/gtnewhorizons/postea/utility/PlayerDataFixerUtility.java new file mode 100644 index 0000000..0f0c090 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/postea/utility/PlayerDataFixerUtility.java @@ -0,0 +1,55 @@ +package com.gtnewhorizons.postea.utility; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.ReportedException; + +import com.gtnewhorizons.postea.Postea; +import com.gtnewhorizons.postea.api.IVersionedTransformer; +import com.gtnewhorizons.postea.api.PlayerDataTransformContext; +import com.gtnewhorizons.postea.api.VersionedReplacementManager; + +/** + * Runs versioned transformers over a server player's NBT as it is read, and stamps it as it is written. Hooked on + * {@code Entity.readFromNBT}/{@code writeToNBT} so both the {@code playerdata/.dat} files and the + * single-player host's copy inside {@code level.dat} are covered. + */ +public final class PlayerDataFixerUtility { + + private PlayerDataFixerUtility() {} + + /** + * Transforms {@code tag} in place and returns a copy of the stamps it carried (null when unstamped). + */ + public static NBTTagCompound onPlayerRead(EntityPlayerMP player, NBTTagCompound tag) { + NBTTagCompound stamps = VersionStamps.read(tag); + for (IVersionedTransformer transformer : VersionedReplacementManager.transformers()) { + int current = transformer.currentVersion(); + int stored = VersionStamps.stored(stamps, transformer.key()); + if (stored == current) continue; + Postea.LOG.debug( + "{}: player {} from version {} to {}", + transformer.key(), + player.getCommandSenderName(), + stored, + current); + try { + transformer.transformPlayer(new PlayerDataTransformContext(player, tag, stored, current)); + } catch (Throwable t) { + throw new ReportedException( + ChunkFixerUtility.describe( + t, + transformer, + stored, + current, + "Player", + player.getCommandSenderName() + " (" + player.getUniqueID() + ")")); + } + } + return stamps == null ? null : (NBTTagCompound) stamps.copy(); + } + + public static void onPlayerWrite(NBTTagCompound tag, NBTTagCompound carried) { + VersionStamps.write(tag, carried, VersionedReplacementManager.transformers()); + } +} diff --git a/src/main/resources/mixins.postea.early.json b/src/main/resources/mixins.postea.early.json index 55ec0ee..1ca68c3 100644 --- a/src/main/resources/mixins.postea.early.json +++ b/src/main/resources/mixins.postea.early.json @@ -8,6 +8,7 @@ "mixins": [ "MixinAnvilChunkLoader", "MixinChunk", + "MixinEntity", "MixinItemStack", "MixinNBTTagList" ],