Skip to content

Commit

Permalink
feat: add support for 1.20.5 part2 (paper maybe?)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ingrim4 committed May 5, 2024
1 parent f9d3266 commit e52a956
Show file tree
Hide file tree
Showing 15 changed files with 323 additions and 76 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ shadowJar {

test {
useJUnitPlatform()
testLogging {
exceptionFormat = 'full'
}
}

processResources {
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/com/comphenix/protocol/events/PacketContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.StructureModifier;
Expand All @@ -56,6 +57,7 @@
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
Expand Down Expand Up @@ -345,6 +347,11 @@ public static Object deserializeFromBuffer(PacketType packetType, Object buffer)
}

Function<Object, Object> deserializer = PACKET_DESERIALIZER_METHODS.computeIfAbsent(packetType, type -> {
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(type.getPacketClass());
if (streamCodec != null) {
return streamCodec::decode;
}

if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
// best guess - a constructor which takes a buffer as the only argument
ConstructorAccessor bufferConstructor = Accessors.getConstructorAccessorOrNull(
Expand Down Expand Up @@ -392,7 +399,14 @@ public Object serializeToBuffer() {
}

Object targetBuffer = MinecraftReflection.createPacketDataSerializer(0);
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);

WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(type.getPacketClass());
if (streamCodec != null) {
streamCodec.encode(targetBuffer, handle);
} else {
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
}

return targetBuffer;
}

Expand Down
82 changes: 68 additions & 14 deletions src/main/java/com/comphenix/protocol/injector/StructureCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,28 @@
import java.security.PublicKey;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftRegistryAccess;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.ZeroBuffer;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
import com.google.common.base.Preconditions;

import io.netty.buffer.ByteBuf;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default;
Expand All @@ -57,30 +63,55 @@ public class StructureCache {
private static final Object TRICK_INIT_LOCK = new Object();
private static boolean TRICK_TRIED = false;

private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
private static Supplier<Object> TRICKED_DATA_SERIALIZER_BASE;
private static Supplier<Object> TRICKED_DATA_SERIALIZER_JSON;

public static Object newPacket(Class<?> packetClass) {
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, packetClassKey -> {
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(packetClassKey);

// use the new stream codec for versions above 1.20.5 if possible
if (streamCodec != null && tryInitTrickDataSerializer()) {
try {
// first try with the base accessor
Object serializer = TRICKED_DATA_SERIALIZER_BASE.get();
streamCodec.decode(serializer); // throwaway instance, for testing

// method is working
return () -> streamCodec.decode(serializer);
} catch (Exception exception) {
try {
// try with the json accessor
Object serializer = TRICKED_DATA_SERIALIZER_JSON.get();
streamCodec.decode(serializer); // throwaway instance, for testing

// method is working
return () -> streamCodec.decode(serializer);
} catch (Exception ignored) {
// shrug, fall back to default behaviour
}
}
}

// prefer construction via PacketDataSerializer constructor on 1.17 and above
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
clazz,
packetClassKey,
MinecraftReflection.getPacketDataSerializerClass());
if (serializerAccessor != null) {
// check if the method is possible
if (tryInitTrickDataSerializer()) {
try {
// first try with the base accessor
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
Object serializer = TRICKED_DATA_SERIALIZER_BASE.get();
serializerAccessor.invoke(serializer); // throwaway instance, for testing

// method is working
return () -> serializerAccessor.invoke(serializer);
} catch (Exception exception) {
try {
// try with the json accessor
Object serializer = TRICKED_DATA_SERIALIZER_JSON.invoke(new ZeroBuffer());
Object serializer = TRICKED_DATA_SERIALIZER_JSON.get();
serializerAccessor.invoke(serializer); // throwaway instance, for testing

// method is working
Expand All @@ -95,8 +126,8 @@ public static Object newPacket(Class<?> packetClass) {

// try via DefaultInstances as fallback
return () -> {
Object packetInstance = DefaultInstances.DEFAULT.create(clazz);
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + clazz);
Object packetInstance = DefaultInstances.DEFAULT.create(packetClassKey);
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + packetClassKey + " - " + tryInitTrickDataSerializer() + " - " + streamCodec);
return packetInstance;
};
});
Expand Down Expand Up @@ -156,16 +187,19 @@ public static StructureModifier<Object> getStructure(final PacketType packetType
*/
public static Object newNullDataSerializer() {
tryInitTrickDataSerializer();
return TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
return TRICKED_DATA_SERIALIZER_BASE.get();
}

static void initTrickDataSerializer() {
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();

// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
Object textCompound = WrappedChatComponent.fromText("").getHandle();
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();

// base builder which intercepts a few methods
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.createSubclass(registryByteBuf.orElse(MinecraftReflection.getPacketDataSerializerClass()))
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
.method(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())
.and(ElementMatchers.returns(ElementMatchers.isSubTypeOf(MinecraftReflection.getNBTBaseClass()))))
Expand All @@ -177,17 +211,37 @@ static void initTrickDataSerializer() {
Class<?> serializerBase = baseBuilder.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);

// extended builder which intercepts the read string method as well
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(serializerBase, true).getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
.build()));
TRICKED_DATA_SERIALIZER_BASE = () -> accessor.invoke(new ZeroBuffer(), MinecraftRegistryAccess.get());
} else {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
TRICKED_DATA_SERIALIZER_BASE = () -> accessor.invoke(new ZeroBuffer());
}

//xtended builder which intercepts the read string method as well
Class<?> withStringIntercept = baseBuilder
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
.intercept(FixedValue.value("{}"))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER_JSON = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);

if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(withStringIntercept).getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
.build()));
TRICKED_DATA_SERIALIZER_JSON = () -> accessor.invoke(new ZeroBuffer(), MinecraftRegistryAccess.get());
} else {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
TRICKED_DATA_SERIALIZER_JSON = () -> accessor.invoke(new ZeroBuffer());
}
}

/**
Expand Down
Loading

0 comments on commit e52a956

Please sign in to comment.