Skip to content

Commit

Permalink
Use LambdaMetafactory instead of classic reflection whenever possible (
Browse files Browse the repository at this point in the history
  • Loading branch information
breitwan authored Aug 8, 2024
1 parent 0afdb5d commit cace305
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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<Object, MinecraftConnection> 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<ConnectedPlayer, TabList> TAB_LIST_SETTER;

private final LimboAPI plugin;
private final VelocityServer server;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object, MinecraftConnection> 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<ClientConfigSessionHandler, String> BRAND_CHANNEL_SETTER;

private final LimboAPI plugin;
private final Object handler;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
Expand All @@ -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));
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<VelocityTabList, Map<UUID, VelocityTabListEntry>> 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);
}
Expand All @@ -50,7 +53,7 @@ public RewritingVelocityTabList(ConnectedPlayer player) {
try {
this.player = player;
this.connection = player.getConnection();
this.entries = (Map<UUID, VelocityTabListEntry>) ENTRIES.get(this);
this.entries = ENTRIES_GETTER.apply(this);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
Expand Down
58 changes: 58 additions & 0 deletions plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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 <T, R> Function<T, R> getterOf(Field field) throws Throwable {
MethodHandle handle = LOOKUP.unreflectGetter(field);
MethodType type = handle.type();
//noinspection unchecked
return (Function<T, R>) LambdaMetafactory.metafactory(
LOOKUP,
"apply",
MethodType.methodType(Function.class, MethodHandle.class),
type.generic(),
MethodHandles.exactInvoker(type),
type
).getTarget().invokeExact(handle);
}

public static <T, R> BiConsumer<T, R> setterOf(Field f) throws Throwable {
MethodHandle handle = LOOKUP.unreflectSetter(f);
MethodType type = handle.type();
//noinspection unchecked
return (BiConsumer<T, R>) LambdaMetafactory.metafactory(
LOOKUP,
"accept",
MethodType.methodType(BiConsumer.class, MethodHandle.class),
type.generic().changeReturnType(void.class),
MethodHandles.exactInvoker(type),
type
).getTarget().invokeExact(handle);
}
}

0 comments on commit cace305

Please sign in to comment.