Skip to content
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

Use LambdaMetafactory instead of classic reflection whenever possible #160

Merged
merged 2 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
UserNugget marked this conversation as resolved.
Show resolved Hide resolved

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);
}
}
Loading