From cace3052496ffee7862bcdb97d2ab8538ea35e5d Mon Sep 17 00:00:00 2001 From: Maxim Breitman <89523915+breitwan@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:02:48 +0300 Subject: [PATCH] Use LambdaMetafactory instead of classic reflection whenever possible (#160) --- .../injection/login/LoginListener.java | 34 +++++------ .../injection/login/LoginTasksQueue.java | 38 ++++++------ .../tablist/RewritingVelocityTabList.java | 11 ++-- .../elytrium/limboapi/utils/LambdaUtil.java | 58 +++++++++++++++++++ 4 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java index 0e6fe350..4a3e396b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java @@ -40,6 +40,7 @@ import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; +import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.VelocityServer; @@ -67,6 +68,7 @@ import java.net.InetSocketAddress; import java.util.Objects; import java.util.UUID; +import java.util.function.BiConsumer; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.api.event.LoginLimboRegisterEvent; @@ -78,20 +80,19 @@ import net.elytrium.limboapi.injection.tablist.RewritingKeyedVelocityTabList; import net.elytrium.limboapi.injection.tablist.RewritingVelocityTabList; import net.elytrium.limboapi.injection.tablist.RewritingVelocityTabListLegacy; +import net.elytrium.limboapi.utils.LambdaUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import sun.misc.Unsafe; public class LoginListener { private static final ClosedMinecraftConnection CLOSED_MINECRAFT_CONNECTION; - private static final Unsafe UNSAFE; private static final MethodHandle DELEGATE_FIELD; - private static final Field MC_CONNECTION_FIELD; + private static final BiConsumer MC_CONNECTION_SETTER; private static final MethodHandle CONNECTED_PLAYER_CONSTRUCTOR; private static final MethodHandle SPAWNED_FIELD; - private static final Field TABLIST_FIELD; + private static final BiConsumer TAB_LIST_SETTER; private final LimboAPI plugin; private final VelocityServer server; @@ -134,7 +135,7 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { } Object handler = connection.getActiveSessionHandler(); - MC_CONNECTION_FIELD.set(handler, CLOSED_MINECRAFT_CONNECTION); + MC_CONNECTION_SETTER.accept(handler, CLOSED_MINECRAFT_CONNECTION); LoginConfirmHandler loginHandler = null; if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { @@ -169,13 +170,12 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { playerKey ); - long fieldOffset = UNSAFE.objectFieldOffset(TABLIST_FIELD); if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - UNSAFE.putObject(player, fieldOffset, new RewritingVelocityTabList(player)); + TAB_LIST_SETTER.accept(player, new RewritingVelocityTabList(player)); } else if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - UNSAFE.putObject(player, fieldOffset, new RewritingKeyedVelocityTabList(player, this.server)); + TAB_LIST_SETTER.accept(player, new RewritingKeyedVelocityTabList(player, this.server)); } else { - UNSAFE.putObject(player, fieldOffset, new RewritingVelocityTabListLegacy(player, this.server)); + TAB_LIST_SETTER.accept(player, new RewritingVelocityTabListLegacy(player, this.server)); } if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { @@ -303,19 +303,17 @@ public void hookPlaySession(ServerConnectedEvent event) { DELEGATE_FIELD = MethodHandles.privateLookupIn(LoginInboundConnection.class, MethodHandles.lookup()) .findGetter(LoginInboundConnection.class, "delegate", InitialInboundConnection.class); - MC_CONNECTION_FIELD = AuthSessionHandler.class.getDeclaredField("mcConnection"); - MC_CONNECTION_FIELD.setAccessible(true); + Field mcConnectionField = AuthSessionHandler.class.getDeclaredField("mcConnection"); + mcConnectionField.setAccessible(true); + MC_CONNECTION_SETTER = LambdaUtil.setterOf(mcConnectionField); SPAWNED_FIELD = MethodHandles.privateLookupIn(ClientPlaySessionHandler.class, MethodHandles.lookup()) .findSetter(ClientPlaySessionHandler.class, "spawned", boolean.class); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - UNSAFE = (Unsafe) unsafeField.get(null); - - TABLIST_FIELD = ConnectedPlayer.class.getDeclaredField("tabList"); - TABLIST_FIELD.setAccessible(true); - } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException e) { + Field tabListField = ConnectedPlayer.class.getDeclaredField("tabList"); + tabListField.setAccessible(true); + TAB_LIST_SETTER = LambdaUtil.setterOf(tabListField); + } catch (Throwable e) { throw new ReflectionException(e); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java index 5423e0f3..bc027dbd 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java @@ -71,25 +71,25 @@ import java.util.Queue; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler; import net.elytrium.limboapi.server.LimboSessionHandlerImpl; +import net.elytrium.limboapi.utils.LambdaUtil; import net.kyori.adventure.text.Component; import org.slf4j.Logger; public class LoginTasksQueue { private static final MethodHandle PROFILE_FIELD; - private static final Field DEFAULT_PERMISSIONS_FIELD; + private static final PermissionProvider DEFAULT_PERMISSIONS; private static final MethodHandle SET_PERMISSION_FUNCTION_METHOD; private static final MethodHandle INITIAL_CONNECT_SESSION_HANDLER_CONSTRUCTOR; - private static final Field MC_CONNECTION_FIELD; + private static final BiConsumer MC_CONNECTION_SETTER; private static final MethodHandle CONNECT_TO_INITIAL_SERVER_METHOD; - private static final Field LOGIN_STATE_FIELD; - private static final Field CONNECTED_PLAYER_FIELD; private static final MethodHandle SET_CLIENT_BRAND; - private static final Field BRAND_CHANNEL; + private static final BiConsumer BRAND_CHANNEL_SETTER; private final LimboAPI plugin; private final Object handler; @@ -162,7 +162,7 @@ private void finish() { // From Velocity. eventManager - .fire(new PermissionsSetupEvent(this.player, (PermissionProvider) DEFAULT_PERMISSIONS_FIELD.get(null))) + .fire(new PermissionsSetupEvent(this.player, DEFAULT_PERMISSIONS)) .thenAcceptAsync(event -> { if (!connection.isClosed()) { // Wait for permissions to load, then set the players' permission function. @@ -268,7 +268,7 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon try { this.server.getEventManager().fireAndForget(new PlayerClientBrandEvent(this.player, sessionHandler.getBrand())); SET_CLIENT_BRAND.invokeExact(this.player, sessionHandler.getBrand()); - BRAND_CHANNEL.set(configHandler, "minecraft:brand"); + BRAND_CHANNEL_SETTER.accept(configHandler, "minecraft:brand"); } catch (Throwable e) { throw new ReflectionException(e); } @@ -280,7 +280,7 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon this.server.getEventManager().fire(new PostLoginEvent(this.player)).thenAccept(postLoginEvent -> { try { - MC_CONNECTION_FIELD.set(this.handler, connection); + MC_CONNECTION_SETTER.accept(this.handler, connection); CONNECT_TO_INITIAL_SERVER_METHOD.invoke((AuthSessionHandler) this.handler, this.player); } catch (Throwable e) { throw new ReflectionException(e); @@ -293,8 +293,9 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon PROFILE_FIELD = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) .findSetter(ConnectedPlayer.class, "profile", GameProfile.class); - DEFAULT_PERMISSIONS_FIELD = ConnectedPlayer.class.getDeclaredField("DEFAULT_PERMISSIONS"); - DEFAULT_PERMISSIONS_FIELD.setAccessible(true); + Field defaultPermissionsField = ConnectedPlayer.class.getDeclaredField("DEFAULT_PERMISSIONS"); + defaultPermissionsField.setAccessible(true); + DEFAULT_PERMISSIONS = (PermissionProvider) defaultPermissionsField.get(null); SET_PERMISSION_FUNCTION_METHOD = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) .findVirtual(ConnectedPlayer.class, "setPermissionFunction", MethodType.methodType(void.class, PermissionFunction.class)); @@ -306,20 +307,17 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon CONNECT_TO_INITIAL_SERVER_METHOD = MethodHandles.privateLookupIn(AuthSessionHandler.class, MethodHandles.lookup()) .findVirtual(AuthSessionHandler.class, "connectToInitialServer", MethodType.methodType(CompletableFuture.class, ConnectedPlayer.class)); - LOGIN_STATE_FIELD = AuthSessionHandler.class.getDeclaredField("loginState"); - LOGIN_STATE_FIELD.setAccessible(true); - CONNECTED_PLAYER_FIELD = AuthSessionHandler.class.getDeclaredField("connectedPlayer"); - CONNECTED_PLAYER_FIELD.setAccessible(true); - - MC_CONNECTION_FIELD = AuthSessionHandler.class.getDeclaredField("mcConnection"); - MC_CONNECTION_FIELD.setAccessible(true); + Field mcConnectionField = AuthSessionHandler.class.getDeclaredField("mcConnection"); + mcConnectionField.setAccessible(true); + MC_CONNECTION_SETTER = LambdaUtil.setterOf(mcConnectionField); SET_CLIENT_BRAND = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) .findVirtual(ConnectedPlayer.class, "setClientBrand", MethodType.methodType(void.class, String.class)); - BRAND_CHANNEL = ClientConfigSessionHandler.class.getDeclaredField("brandChannel"); - BRAND_CHANNEL.setAccessible(true); - } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException e) { + Field brandChannelField = ClientConfigSessionHandler.class.getDeclaredField("brandChannel"); + brandChannelField.setAccessible(true); + BRAND_CHANNEL_SETTER = LambdaUtil.setterOf(brandChannelField); + } catch (Throwable e) { throw new ReflectionException(e); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java index a4974e9f..c7d304c1 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java @@ -26,15 +26,18 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.function.Function; +import net.elytrium.limboapi.utils.LambdaUtil; public class RewritingVelocityTabList extends VelocityTabList implements RewritingTabList { - private static final Field ENTRIES; + private static final Function> ENTRIES_GETTER; static { try { - ENTRIES = VelocityTabList.class.getDeclaredField("entries"); - ENTRIES.setAccessible(true); + Field field = VelocityTabList.class.getDeclaredField("entries"); + field.setAccessible(true); + ENTRIES_GETTER = LambdaUtil.getterOf(field); } catch (Throwable throwable) { throw new ExceptionInInitializerError(throwable); } @@ -50,7 +53,7 @@ public RewritingVelocityTabList(ConnectedPlayer player) { try { this.player = player; this.connection = player.getConnection(); - this.entries = (Map) ENTRIES.get(this); + this.entries = ENTRIES_GETTER.apply(this); } catch (Throwable e) { throw new IllegalStateException(e); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java b/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java new file mode 100644 index 00000000..366ee443 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.utils; + +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public final class LambdaUtil { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + public static Function getterOf(Field field) throws Throwable { + MethodHandle handle = LOOKUP.unreflectGetter(field); + MethodType type = handle.type(); + //noinspection unchecked + return (Function) LambdaMetafactory.metafactory( + LOOKUP, + "apply", + MethodType.methodType(Function.class, MethodHandle.class), + type.generic(), + MethodHandles.exactInvoker(type), + type + ).getTarget().invokeExact(handle); + } + + public static BiConsumer setterOf(Field f) throws Throwable { + MethodHandle handle = LOOKUP.unreflectSetter(f); + MethodType type = handle.type(); + //noinspection unchecked + return (BiConsumer) LambdaMetafactory.metafactory( + LOOKUP, + "accept", + MethodType.methodType(BiConsumer.class, MethodHandle.class), + type.generic().changeReturnType(void.class), + MethodHandles.exactInvoker(type), + type + ).getTarget().invokeExact(handle); + } +}