diff --git a/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java index a33cae2e..2556ba27 100644 --- a/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java +++ b/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -4,6 +4,7 @@ import com.comphenix.protocol.injector.netty.NettyByteBufAdapter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtType; @@ -93,13 +94,24 @@ public class StreamSerializer { */ public void serializeCompound(DataOutputStream output, NbtCompound compound) { if (WRITE_NBT_METHOD == null) { - WRITE_NBT_METHOD = Accessors.getMethodAccessor(FuzzyReflection - .fromClass(MinecraftReflection.getPacketDataSerializerClass(), true) - .getMethodByParameters("writeNbtCompound", MinecraftReflection.getNBTCompoundClass())); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true); + if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { + FuzzyMethodContract writeNbtContract = FuzzyMethodContract.newBuilder() + .returnTypeExact(MinecraftReflection.getPacketDataSerializerClass()) + .parameterExactArray(MinecraftReflection.getNBTBaseClass()) + .build(); + WRITE_NBT_METHOD = Accessors.getMethodAccessor(fuzzy.getMethod(writeNbtContract)); + } else { + WRITE_NBT_METHOD = Accessors.getMethodAccessor(fuzzy.getMethodByParameters("writeNbtCompound", MinecraftReflection.getNBTCompoundClass())); + } } ByteBuf buf = NettyByteBufAdapter.packetWriter(output); - buf.writeByte(NbtType.TAG_COMPOUND.getRawID()); + + // 1.20.2+ will write the id automatically + if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { + buf.writeByte(NbtType.TAG_COMPOUND.getRawID()); + } // Get the NMS version of the compound Object handle = compound != null ? NbtFactory.fromBase(compound).getHandle() : null; diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java index 71dfe566..bec4371e 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java @@ -4,7 +4,9 @@ import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; @@ -22,13 +24,10 @@ public class NbtBinarySerializer { public static final NbtBinarySerializer DEFAULT = new NbtBinarySerializer(); private static final Class NBT_BASE_CLASS = MinecraftReflection.getNBTBaseClass(); - // Used to read and write NBT - private static MethodAccessor methodWrite; - /** - * Method selected for loading NBT compounds. + * Method selected for loading/writing NBT compounds. */ - private static LoadMethod loadMethod; + private static CodecMethod codecMethod; private static MethodAccessor getNbtLoadMethod(Class... parameters) { Method method = getUtilityClass().getMethodByReturnTypeAndParameters("load", NBT_BASE_CLASS, parameters); @@ -39,6 +38,18 @@ public class NbtBinarySerializer { return FuzzyReflection.fromClass(MinecraftReflection.getNbtCompressedStreamToolsClass(), true); } + private static CodecMethod getCodecMethod() { + if (codecMethod == null) { + // Save the selected method + if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) { + codecMethod = new LoadMethodConfigPhaseUpdate(); + } else { + codecMethod = new LoadMethodSkinUpdate(); + } + } + return codecMethod; + } + /** * Write the content of a wrapped NBT tag to a stream. * @@ -47,14 +58,7 @@ public class NbtBinarySerializer { * @param destination - the destination stream. */ public void serialize(NbtBase value, DataOutput destination) { - if (methodWrite == null) { - Class base = MinecraftReflection.getNBTBaseClass(); - Method writeNBT = getUtilityClass().getMethodByParameters("writeNBT", base, DataOutput.class); - - methodWrite = Accessors.getMethodAccessor(writeNBT); - } - - methodWrite.invoke(null, NbtFactory.fromBase(value).getHandle(), destination); + getCodecMethod().writeNbt(NbtFactory.fromBase(value).getHandle(), destination); } /** @@ -65,13 +69,8 @@ public class NbtBinarySerializer { * @return An NBT tag. */ public NbtWrapper deserialize(DataInput source) { - if (loadMethod == null) { - // Save the selected method - loadMethod = new LoadMethodSkinUpdate(); - } - try { - return NbtFactory.fromNMS(loadMethod.loadNbt(source), null); + return NbtFactory.fromNMS(getCodecMethod().loadNbt(source), null); } catch (Exception e) { throw new FieldAccessException("Unable to read NBT from " + source, e); } @@ -100,7 +99,7 @@ public class NbtBinarySerializer { return (NbtList) (NbtBase) this.deserialize(source); } - private interface LoadMethod { + private interface CodecMethod { /** * Load an NBT compound from a given stream. @@ -109,20 +108,73 @@ public class NbtBinarySerializer { * @return The loaded NBT compound. */ Object loadNbt(DataInput input); + + /** + * Write an NBT compound to the given stream. + * + * @param nbt the nbt to write. + * @param target the target to write the compound to. + */ + void writeNbt(Object nbt, DataOutput target); } /** * Load an NBT compound from the NBTCompressedStreamTools static method since 1.7. */ - private static class LoadMethodSkinUpdate implements LoadMethod { + private static class LoadMethodSkinUpdate implements CodecMethod { private final Class readLimitClass = MinecraftReflection.getNBTReadLimiterClass(); private final Object readLimiter = FuzzyReflection.fromClass(this.readLimitClass).getSingleton(); - private final MethodAccessor accessor = getNbtLoadMethod(DataInput.class, int.class, this.readLimitClass); + private final MethodAccessor readNbt = getNbtLoadMethod(DataInput.class, int.class, this.readLimitClass); + private final MethodAccessor writeNBT = Accessors.getMethodAccessor(getUtilityClass().getMethodByParameters("writeNBT", MinecraftReflection.getNBTBaseClass(), DataOutput.class)); @Override public Object loadNbt(DataInput input) { - return this.accessor.invoke(null, input, 0, this.readLimiter); + return this.readNbt.invoke(null, input, 0, this.readLimiter); + } + + @Override + public void writeNbt(Object nbt, DataOutput target) { + this.writeNBT.invoke(null, nbt, target); + } + } + + /** + * Load an NBT compound from the NBTCompressedStreamTools static method since 1.20.2. + */ + private static class LoadMethodConfigPhaseUpdate implements CodecMethod { + + private final Class readLimitClass = MinecraftReflection.getNBTReadLimiterClass(); + private final Object readLimiter = FuzzyReflection.fromClass(this.readLimitClass).getSingleton(); + + private final MethodAccessor readNbt; + private final MethodAccessor writeNbt; + + public LoadMethodConfigPhaseUpdate() { + // there are now two methods with the same signature: readAnyTag/readUnnamedTag & writeAnyTag/writeUnnamedTag + // we can only find the correct method here by using the method name... thanks Mojang + Method readNbtMethod = getUtilityClass().getMethod(FuzzyMethodContract.newBuilder() + .nameExact("b") + .returnTypeExact(MinecraftReflection.getNBTBaseClass()) + .parameterExactArray(DataInput.class, this.readLimitClass) + .build()); + this.readNbt = Accessors.getMethodAccessor(readNbtMethod); + + Method writeNbtMethod = getUtilityClass().getMethod(FuzzyMethodContract.newBuilder() + .nameExact("a") + .parameterExactArray(MinecraftReflection.getNBTBaseClass(), DataOutput.class) + .build()); + this.writeNbt = Accessors.getMethodAccessor(writeNbtMethod); + } + + @Override + public Object loadNbt(DataInput input) { + return this.readNbt.invoke(null, input, this.readLimiter); + } + + @Override + public void writeNbt(Object nbt, DataOutput target) { + this.writeNbt.invoke(null, nbt, target); } } }