-
-
Notifications
You must be signed in to change notification settings - Fork 257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix packet instantiation on 1.20.6 #2941
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be more happy with this, if the case of unsafe not being available could be handled more gracefully (this might result in certain packets not being constructable, but prevents failing completely for no reason). Plus, not a fan of having an Unsafe field in the class, can't we hide the direct unsafe field by providing some sort of function that does the thing?
Something like (probably CLASS_INSTANCE_ALLOCATOR assignment will not work like this, needs an extra field outside the try-block - quickly wrote this out of my head):
private static final Function<Class<?>, Object> CLASS_INSTANCE_ALLOCATOR;
static {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
Object unsafeInstance = Accessors.getFieldAccessor(theUnsafeField).get(null);
MethodAccessor allocateInstance = Accessors.getMethodAccessor(unsafeClass, "allocateInstance", Class.class);
CLASS_INSTANCE_ALLOCATOR = clazz -> allocateInstance.invoke(unsafeInstance, clazz);
} catch (Exception ignored) {
CLASS_INSTANCE_ALLOCATOR = null;
}
}
Also, the check if class allocation can be done can then be reduced to CLASS_INSTANCE_ALLOCATOR != null
. There are some more edge cases where allocateInstance is not working (for example when trying to allocate an instance of Class), but these should not happen in this case.
Also, as described in JEP 471 the allocateInstance method will remain in medium term, so no big worries when using it here.
One last point: imo the unsafe allocation should be the second-last fallback (before using DefaultInstances), as allocating classes via allocateInstance has some implications (such as fields not being initialized), which seem ok to make construction happen at all, but should be avoided when possible.
I agree with @derklaro. However, could we potentially address this by adding additional "fake" methods to ProtocolLib's Byte Buddy buffer? This approach might allow us to avoid using |
I have just checked out your 1.20.6_new branch (that also contains #2940) and tested it out. Whilst protocol lib itself works again (yay) this change does unfortunately break some functionality. Since the constructor of the packets is never called because of your change, the team parameters (in the ClientboundSetPlayerTeamPacket or PacketType.Play.Server.SCOREBOARD_TEAM) are never set, and It's impossible to set team parameters like I'm used to e.g.
I'm no protocollib expert, but I don't think you can even do the above anymore without shady reflection at the user side. Although using StructureCache outside the protocollib internals is probably not intended, I still tried it and failed because your change only works if the packet has a stream codec, which the team parameters don't have. Instantiating them by myself is also not possible, since I need a RegistryFriendlyByteBuf. The only thing that remains is implementing the unsafe mess in my code myself. Which sort of destroys the beauty (The not having to do shady reflection stuff part) of protocollib for me. |
Another thing I found out: I don't know how the JVM works internally with the unsafe allocation, but it seems like you get random values (at least for integers, haven't checked other primitives) which could result in unexpected packets ending up at the client if someone doesn't overwrite some fields Edit: I was stupid, its all 0 |
It seems strange to me. I checked the OpenJDK code and, as far as I can tell, the object should be initialized to all zeros. The
|
You're totally right. My fault for spreading misinformation, thanks for checking. I tested it right now on a clean project and it actually is 0 now. The reason I came to my conclusion (that the numbers were randomized) was that when I checked why the packet isn't working correctly anymore I logged the first integer to check what it's set to… I then forgot that the initial creation of the packet containers (in that specific part of my plugin) is abstracted away and actually modifies the first integer of the created packet containers by default... (it tries to set it to the entity ID. Scoreboard team of course doesn't have one, but most packets sent in that specific part of my code are entity packets, so it checks out to have that. I should probably fix that - but it gets overwritten anyway) Stuff happens when you don't work on a specific part of your project for over a year 😅 I'm sorry for the confusion I caused, and sorry for the time you wasted researching. It really is all set to 0 |
@@ -461,7 +461,10 @@ private static synchronized Register createRegisterV1_20_5() { | |||
} | |||
|
|||
Object serializer = idCodecEntrySerializerAccessor.invoke(entry); | |||
result.classToCodec.put(packetClass, new WrappedStreamCodec(serializer)); | |||
Field outerThisField = serializer.getClass().getDeclaredField("this$0"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use FuzzyReflection here in case some JVM implements this differently?
I discovered the reason why the "fake" ByteBuf method no longer works. Mojang now calls a static method whenever an NbtTag is read from any ByteBuf. Since static methods can't be overridden, we'll need to come up with a new solution. This is particularly problematic because TextComponents are serialized using NbtTags. Here the static method ( @Nullable
public static NBTBase readNbt(ByteBuf bytebuf, NBTReadLimiter nbtreadlimiter) {
try {
NBTBase nbtbase = NBTCompressedStreamTools.readAnyTag(new ByteBufInputStream(bytebuf), nbtreadlimiter);
return nbtbase.getId() == 0 ? null : nbtbase;
} catch (IOException ioexception) {
throw new EncoderException(ioexception);
}
} One possible solution is to always "return" the bytes for an empty (NbtCompound) TextCompound when the caller is the |
Yes that's what i found too - because of this I tried the allocation without a constructor call. But I agree that it is definitely not a good solution.
This might be the best option we have. |
i have no problem with this |
@@ -91,6 +101,19 @@ public static Object newPacket(Class<?> packetClass) { | |||
// shrug, fall back to default behaviour |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment to say fallback to unsafe allocator
?
I am using Unsafe, because the packets were not creatable from a null stream.
Other options would be pre-created serialized data that could be read to instantiate the packets or accessing the constructors via reflection and filling in all the parameters.
Probably not mergeable because of the code style but at least it works.