diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java index bae6cad8..fcd71b67 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeDataAddon.java @@ -32,7 +32,6 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.PacketBlocker; import org.geysermc.floodgate.core.api.ProxyFloodgateApi; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; @@ -43,7 +42,6 @@ public class BungeeDataAddon implements InjectorAddon { @Inject private FloodgateHandshakeHandler handshakeHandler; @Inject private ProxyFloodgateConfig config; @Inject private ProxyFloodgateApi api; - @Inject private FloodgateLogger logger; @Inject @Named("packetHandler") @@ -86,14 +84,6 @@ public class BungeeDataAddon implements InjectorAddon { ); } - @Override - public void onChannelClosed(Channel channel) { - Connection player = channel.attr(playerAttribute).get(); - if (player != null && api.setPendingRemove(player)) { - logger.translatedInfo("floodgate.ingame.disconnect_name", player.javaUsername()); - } - } - @Override public void onRemoveInject(Channel channel) { } diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java index b482570e..ecb0f3b8 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeProxyDataHandler.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.bungee.addon.data; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import io.netty.channel.Channel; import io.netty.util.AttributeKey; @@ -50,11 +50,11 @@ public class BungeeProxyDataHandler extends CommonDataHandler { static { HANDLER = ReflectionUtils.getField(HandlerBoss.class, "handler"); - checkNotNull(HANDLER, "handler field cannot be null"); + requireNonNull(HANDLER, "handler field cannot be null"); CHANNEL_WRAPPER = ReflectionUtils.getFieldOfType(InitialHandler.class, ChannelWrapper.class); - checkNotNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); + requireNonNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); } public BungeeProxyDataHandler( diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeServerDataHandler.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeServerDataHandler.java index d89ab514..6aed8709 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeServerDataHandler.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/addon/data/BungeeServerDataHandler.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.bungee.addon.data; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; @@ -54,14 +54,14 @@ public class BungeeServerDataHandler extends ChannelOutboundHandlerAdapter { static { HANDLER = ReflectionUtils.getField(HandlerBoss.class, "handler"); - checkNotNull(HANDLER, "handler field cannot be null"); + requireNonNull(HANDLER, "handler field cannot be null"); USER_CONNECTION = ReflectionUtils.getField(ServerConnector.class, "user"); - checkNotNull(USER_CONNECTION, "user field cannot be null"); + requireNonNull(USER_CONNECTION, "user field cannot be null"); CHANNEL_WRAPPER = ReflectionUtils.getFieldOfType(UserConnection.class, ChannelWrapper.class); - checkNotNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); + requireNonNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); } private final ProxyFloodgateApi api; diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java index 8a1200ec..dba341b3 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/listener/BungeeListener.java @@ -25,15 +25,13 @@ package org.geysermc.floodgate.bungee.listener; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; -import io.netty.channel.Channel; import io.netty.util.AttributeKey; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; import java.lang.reflect.Field; -import java.util.UUID; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; @@ -43,9 +41,9 @@ import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; -import net.md_5.bungee.netty.ChannelWrapper; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.bungee.player.BungeeConnectionManager; import org.geysermc.floodgate.core.api.ProxyFloodgateApi; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.listener.McListener; @@ -57,31 +55,23 @@ import org.geysermc.floodgate.core.util.ReflectionUtils; @Singleton @SuppressWarnings("ConstantConditions") public final class BungeeListener implements Listener, McListener { - private static final Field CHANNEL_WRAPPER; private static final Field PLAYER_NAME; static { - CHANNEL_WRAPPER = - ReflectionUtils.getFieldOfType(InitialHandler.class, ChannelWrapper.class); - checkNotNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); - PLAYER_NAME = ReflectionUtils.getField(InitialHandler.class, "name"); - checkNotNull(PLAYER_NAME, "Initial name field cannot be null"); + requireNonNull(PLAYER_NAME, "Initial name field cannot be null"); } - @Inject private ProxyFloodgateConfig config; - @Inject private ProxyFloodgateApi api; - @Inject private LanguageManager languageManager; - @Inject private FloodgateLogger logger; - @Inject private SkinApplier skinApplier; - - @Inject - @Named("playerAttribute") - private AttributeKey playerAttribute; + @Inject BungeeConnectionManager connectionManager; + @Inject ProxyFloodgateConfig config; + @Inject ProxyFloodgateApi api; + @Inject LanguageManager languageManager; + @Inject FloodgateLogger logger; + @Inject SkinApplier skinApplier; @Inject @Named("kickMessageAttribute") - private AttributeKey kickMessageAttribute; + AttributeKey kickMessageAttribute; @EventHandler(priority = EventPriority.LOWEST) public void onPreLogin(PreLoginEvent event) { @@ -90,10 +80,8 @@ public final class BungeeListener implements Listener, McListener { return; } - PendingConnection connection = event.getConnection(); - - ChannelWrapper wrapper = ReflectionUtils.getCastedValue(connection, CHANNEL_WRAPPER); - Channel channel = wrapper.getHandle(); + PendingConnection pendingConnection = event.getConnection(); + var channel = connectionManager.channelFor(pendingConnection); // check if the player has to be kicked String kickReason = channel.attr(kickMessageAttribute).get(); @@ -103,44 +91,41 @@ public final class BungeeListener implements Listener, McListener { return; } - Connection player = channel.attr(playerAttribute).get(); - if (player != null) { - connection.setOnlineMode(false); - connection.setUniqueId(player.javaUuid()); - ReflectionUtils.setValue(connection, PLAYER_NAME, player.javaUsername()); + Connection connection = connectionManager.connectionByPlatformIdentifier(channel); + if (connection == null) { + return; } + pendingConnection.setOnlineMode(false); + pendingConnection.setUniqueId(connection.javaUuid()); + ReflectionUtils.setValue(pendingConnection, PLAYER_NAME, connection.javaUsername()); } @EventHandler public void onLogin(LoginEvent event) { // if there was another player with the same uuid / name online, // he has been disconnected by now - UUID uniqueId = event.getConnection().getUniqueId(); - Connection player = api.connectionByUuid(uniqueId); - if (player != null) { - //todo we should probably move this log message earlier in the process, so that we know - // that Floodgate has done its job - logger.translatedInfo( - "floodgate.ingame.login_name", - player.javaUsername(), uniqueId - ); - languageManager.loadLocale(player.languageCode()); + Connection connection = api.connectionByPlatformIdentifier(event.getConnection()); + if (connection == null) { + return; } + + languageManager.loadLocale(connection.languageCode()); + connectionManager.addAcceptedConnection(connection); } @EventHandler(priority = EventPriority.LOWEST) public void onPostLogin(PostLoginEvent event) { // To fix the February 2 2022 Mojang authentication changes if (!config.sendFloodgateData()) { - Connection player = api.connectionByUuid(event.getPlayer().getUniqueId()); - if (player != null && !player.isLinked()) { - skinApplier.applySkin(player, new SkinDataImpl("", "")); + Connection connection = api.connectionByPlatformIdentifier(event.getPlayer()); + if (connection != null && !connection.isLinked()) { + skinApplier.applySkin(connection, new SkinDataImpl("", "")); } } } @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerDisconnect(PlayerDisconnectEvent event) { - api.playerRemoved(event.getPlayer().getUniqueId()); + connectionManager.removeConnection(event.getPlayer()); } } diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/player/BungeeConnectionManager.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/player/BungeeConnectionManager.java new file mode 100644 index 00000000..0217c99b --- /dev/null +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/player/BungeeConnectionManager.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.bungee.player; + +import static java.util.Objects.requireNonNull; + +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import java.lang.reflect.Field; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.connection.InitialHandler; +import net.md_5.bungee.netty.ChannelWrapper; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.core.player.ConnectionManager; +import org.geysermc.floodgate.core.util.ReflectionUtils; + +@Singleton +public final class BungeeConnectionManager extends ConnectionManager { + private static final Field CHANNEL_WRAPPER; + + @Inject + @Named("playerAttribute") + AttributeKey playerAttribute; + + @Override + protected @Nullable Object platformIdentifierOrConnectionFor(Object input) { + if (input instanceof ProxiedPlayer player) { + return connectionByPlatformIdentifier(player.getPendingConnection()); + } + if (input instanceof PendingConnection pendingConnection) { + return channelFor(pendingConnection); + } + if (input instanceof Channel channel) { + return channel.attr(playerAttribute).get(); + } + return null; + } + + public Channel channelFor(PendingConnection connection) { + ChannelWrapper wrapper = ReflectionUtils.getCastedValue(connection, CHANNEL_WRAPPER); + return wrapper.getHandle(); + } + + static { + CHANNEL_WRAPPER = + ReflectionUtils.getFieldOfType(InitialHandler.class, ChannelWrapper.class); + requireNonNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); + } +} diff --git a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java index 2ccd6043..b734d8f4 100644 --- a/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java +++ b/bungee/base/src/main/java/org/geysermc/floodgate/bungee/pluginmessage/BungeeSkinApplier.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.bungee.pluginmessage; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static org.geysermc.floodgate.core.util.ReflectionUtils.getFieldOfType; import jakarta.inject.Inject; @@ -55,7 +55,7 @@ public final class BungeeSkinApplier implements SkinApplier { static { LOGIN_RESULT_FIELD = getFieldOfType(InitialHandler.class, LoginResult.class); - checkNotNull(LOGIN_RESULT_FIELD, "LoginResult field cannot be null"); + requireNonNull(LOGIN_RESULT_FIELD, "LoginResult field cannot be null"); } private final ProxyServer server = ProxyServer.getInstance(); diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 86f47bef..7693f2e7 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { api(libs.micronaut.context) api(libs.micronaut.http.client) api(libs.micronaut.validation) + annotationProcessor(libs.micronaut.validation.processor) api(libs.micronaut.serde.jsonp) compileOnlyApi(libs.jsonb.annotations) diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonDataHandler.java b/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonDataHandler.java index 3a6b6e68..664c412c 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonDataHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/data/CommonDataHandler.java @@ -25,13 +25,13 @@ package org.geysermc.floodgate.core.addon.data; -import com.google.common.collect.Queues; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.AttributeKey; import java.net.InetSocketAddress; import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.api.handshake.HandshakeData; import org.geysermc.floodgate.core.config.FloodgateConfig; @@ -48,7 +48,7 @@ public abstract class CommonDataHandler extends ChannelInboundHandlerAdapter { protected final AttributeKey kickMessageAttribute; protected final PacketBlocker blocker; - protected final Queue packetQueue = Queues.newConcurrentLinkedQueue(); + protected final Queue packetQueue = new ConcurrentLinkedQueue<>(); protected Object handshakePacket; protected ChannelHandlerContext ctx; diff --git a/core/src/main/java/org/geysermc/floodgate/core/addon/data/PacketBlocker.java b/core/src/main/java/org/geysermc/floodgate/core/addon/data/PacketBlocker.java index 229efbca..1d6e27b9 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/addon/data/PacketBlocker.java +++ b/core/src/main/java/org/geysermc/floodgate/core/addon/data/PacketBlocker.java @@ -25,10 +25,10 @@ package org.geysermc.floodgate.core.addon.data; -import com.google.common.collect.Queues; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; /** * In Floodgate the PacketBlocker is used to temporarily prevent packets from being decoded. A @@ -44,7 +44,7 @@ import java.util.Queue; * caused the server to switch to the login state. */ public class PacketBlocker extends ChannelInboundHandlerAdapter { - private final Queue packetQueue = Queues.newConcurrentLinkedQueue(); + private final Queue packetQueue = new ConcurrentLinkedQueue<>(); private volatile boolean blockPackets; private ChannelHandlerContext ctx; diff --git a/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java b/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java index 0095c8d0..ef7d97dd 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java +++ b/core/src/main/java/org/geysermc/floodgate/core/api/SimpleFloodgateApi.java @@ -25,17 +25,12 @@ package org.geysermc.floodgate.core.api; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableList; import io.micronaut.context.BeanProvider; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.api.GeyserApiBase; @@ -47,6 +42,7 @@ import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.http.xbox.XboxClient; +import org.geysermc.floodgate.core.player.ConnectionManager; import org.geysermc.floodgate.core.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.core.pluginmessage.channel.FormChannel; import org.geysermc.floodgate.core.pluginmessage.channel.TransferChannel; @@ -55,12 +51,7 @@ import org.geysermc.floodgate.core.scope.ServerOnly; @ServerOnly @Singleton public class SimpleFloodgateApi implements GeyserApiBase { - private final Map players = new ConcurrentHashMap<>(); - private final Cache pendingRemove = - CacheBuilder.newBuilder() - .expireAfterWrite(20, TimeUnit.SECONDS) - .build(); - + @Inject ConnectionManager connectionManager; @Inject BeanProvider pluginMessageManager; @Inject FloodgateConfig config; @Inject FloodgateLogger logger; @@ -73,12 +64,12 @@ public class SimpleFloodgateApi implements GeyserApiBase { @Override public @NonNull List onlineConnections() { - return ImmutableList.copyOf(players.values()); + return new ArrayList<>(connectionManager.acceptedConnections()); } @Override public int onlineConnectionsCount() { - return players.size(); + return connectionManager.acceptedConnectionsCount(); } @Override @@ -88,31 +79,16 @@ public class SimpleFloodgateApi implements GeyserApiBase { @Override public @Nullable Connection connectionByUuid(@NonNull UUID uuid) { - Connection selfPlayer = players.get(uuid); - if (selfPlayer != null) { - return selfPlayer; - } + return connectionManager.connectionByUuid(uuid); + } - // bedrock players are always stored by their xuid, - // so we return the instance if we know that the given uuid is a Floodgate uuid - if (isFloodgateId(uuid)) { - return pendingRemove.getIfPresent(uuid); - } - - // make it possible to find player by Java id (linked players) - // TODO still needed? - for (Connection player : players.values()) { - if (player.javaUuid().equals(uuid)) { - return player; - } - } - // and don't forget the pending remove linked players - return getPendingRemovePlayer(uuid); + public @Nullable Connection connectionByPlatformIdentifier(@NonNull Object platformIdentifier) { + return connectionManager.connectionByPlatformIdentifier(platformIdentifier); } @Override - public @Nullable Connection connectionByXuid(@NonNull String s) { - return null; + public @Nullable Connection connectionByXuid(@NonNull String xuid) { + return connectionManager.connectionByXuid(xuid); } public boolean isFloodgateId(UUID uuid) { @@ -157,43 +133,6 @@ public class SimpleFloodgateApi implements GeyserApiBase { } */ - public Connection addPlayer(Connection player) { - // Bedrock players are always stored by their xuid - return players.put(player.javaUuid(), player); - } - - /** - * This method is invoked when the player is no longer on the server, but the related platform- - * dependant event hasn't fired yet - * @param player - */ - public boolean setPendingRemove(Connection player) { - pendingRemove.put(player.javaUuid(), player); - return players.remove(player.javaUuid(), player); - } - - public void playerRemoved(UUID correctUuid) { - // we can remove the player directly if it is a Floodgate UUID. - // since it's stored by their Floodgate UUID - if (isFloodgateId(correctUuid)) { - pendingRemove.invalidate(correctUuid); - return; - } - Connection linkedPlayer = getPendingRemovePlayer(correctUuid); - if (linkedPlayer != null) { - pendingRemove.invalidate(linkedPlayer.javaUuid()); - } - } - - private Connection getPendingRemovePlayer(UUID correctUuid) { - for (Connection player : pendingRemove.asMap().values()) { - if (player.javaUuid().equals(correctUuid)) { - return player; - } - } - return null; - } - public PlayerLink getPlayerLink() { // TODO return InstanceHolder.getPlayerLink(); } diff --git a/core/src/main/java/org/geysermc/floodgate/core/event/EventBus.java b/core/src/main/java/org/geysermc/floodgate/core/event/EventBus.java index a3d73aec..46b5b8c8 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/event/EventBus.java +++ b/core/src/main/java/org/geysermc/floodgate/core/event/EventBus.java @@ -30,6 +30,7 @@ import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.inject.qualifiers.Qualifiers; import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; +import jakarta.inject.Singleton; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.checkerframework.checker.nullness.qual.NonNull; @@ -40,15 +41,13 @@ import org.geysermc.event.subscribe.Subscribe; import org.geysermc.event.subscribe.Subscriber; import org.geysermc.floodgate.api.event.FloodgateEventBus; import org.geysermc.floodgate.api.event.FloodgateSubscriber; -import org.geysermc.floodgate.core.util.EagerSingleton; -@EagerSingleton +@Singleton @SuppressWarnings("unchecked") -public final class EventBus extends EventBusImpl> +public class EventBus extends EventBusImpl> implements FloodgateEventBus { @Inject ApplicationContext context; - @Override @SuppressWarnings("rawtypes") public boolean fire(@NonNull Object event) { diff --git a/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java b/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java index feb55849..35c903fc 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java +++ b/core/src/main/java/org/geysermc/floodgate/core/platform/command/CommandUtil.java @@ -82,10 +82,10 @@ public abstract class CommandUtil { protected abstract Collection getOnlinePlayers(); - public @NonNull Collection getOnlineUsernames(@NonNull PlayerType limitTo) { + public @NonNull List getOnlineUsernames(@NonNull PlayerType limitTo) { Collection players = getOnlinePlayers(); - Collection usernames = new ArrayList<>(); + List usernames = new ArrayList<>(); switch (limitTo) { case ALL_PLAYERS: for (Object player : players) { diff --git a/core/src/main/java/org/geysermc/floodgate/core/player/ConnectionManager.java b/core/src/main/java/org/geysermc/floodgate/core/player/ConnectionManager.java new file mode 100644 index 00000000..1a5fff83 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/core/player/ConnectionManager.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.core.player; + +import jakarta.inject.Inject; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.WeakHashMap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +public abstract class ConnectionManager { + private final Set connections = Collections.synchronizedSet(new HashSet<>()); + private final Set pendingConnections = Collections.synchronizedSet(new HashSet<>()); + + private final Map xuidToConnection = + Collections.synchronizedMap(new HashMap<>()); + private final Map uuidToConnection = + Collections.synchronizedMap(new HashMap<>()); + protected final Map platformIdentifierToConnection = + Collections.synchronizedMap(new WeakHashMap<>()); + + @Inject FloodgateLogger logger; + + public Connection connectionByUuid(UUID javaId) { + return uuidToConnection.get(javaId); + } + + public Connection connectionByXuid(String xuid) { + return xuidToConnection.get(xuid); + } + + public @Nullable Connection connectionByPlatformIdentifier(@NonNull Object platformIdentifier) { + Objects.requireNonNull(platformIdentifier); + var connection = platformIdentifierToConnection.get(platformIdentifier); + if (connection != null) { + return connection; + } + // try to fetch a connection or return a different platform identifier we can try + var identifierOrConnection = platformIdentifierOrConnectionFor(platformIdentifier); + if (identifierOrConnection == null) { + return null; + } + // if it returns a connection it found a way to fetch it from the given platformIdentifier + if (identifierOrConnection instanceof Connection foundConnection) { + platformIdentifierToConnection.put(platformIdentifier, foundConnection); + return foundConnection; + } + // if it returns a different platform identifier, + // call this method again with the new identifier and store the result if it returned any + connection = connectionByPlatformIdentifier(identifierOrConnection); + if (connection != null) { + platformIdentifierToConnection.put(identifierOrConnection, connection); + } + return connection; + } + + protected abstract @Nullable Object platformIdentifierOrConnectionFor(Object input); + + public void addConnection(Connection connection) { + connections.add(connection); + pendingConnections.add(connection); + + logger.translatedInfo( + "floodgate.ingame.login_name", + connection.javaUsername(), connection.javaUuid() + ); + } + + public boolean addAcceptedConnection(Connection connection) { + pendingConnections.remove(connection); + + xuidToConnection.put(connection.xuid(), connection); + var old = uuidToConnection.put(connection.javaUuid(), connection); + if (old == null) { + return false; + } + + logger.debug(String.format( + "Replaced Floodgate player playing as %s uuid %s with %s uuid %s", + old.javaUsername(), old.javaUuid(), + connection.javaUsername(), connection.javaUuid() + )); + return true; + } + + public Connection findPendingConnection(UUID javaId) { + for (Connection pendingConnection : pendingConnections) { + if (pendingConnection.javaUuid().equals(javaId)) { + return pendingConnection; + } + } + return null; + } + + public void removeConnection(Object platformIdentifier) { + var connection = connectionByPlatformIdentifier(platformIdentifier); + if (connection == null) { + return; + } + + connections.remove(connection); + pendingConnections.remove(connection); + uuidToConnection.remove(connection.javaUuid(), connection); + xuidToConnection.remove(connection.xuid(), connection); + logger.translatedInfo("floodgate.ingame.disconnect_name", connection.javaUsername()); + } + + public Collection acceptedConnections() { + return uuidToConnection.values(); + } + + public int acceptedConnectionsCount() { + return uuidToConnection.size(); + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateConnection.java b/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateConnection.java index c18783eb..d986bc5d 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateConnection.java +++ b/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateConnection.java @@ -69,7 +69,7 @@ public final class FloodgateConnection implements Connection { private final PropertyGlue propertyGlue = new PropertyGlue(); private LegacyPlayerWrapper legacyPlayer; - static FloodgateConnection from(BedrockData data, HandshakeData handshakeData, int port) { + static FloodgateConnection from(BedrockData data, HandshakeData handshakeData) { UUID javaUniqueId = Utils.getJavaUuid(data.getXuid()); BedrockPlatform deviceOs = BedrockPlatform.fromId(data.getDeviceOs()); @@ -78,7 +78,7 @@ public final class FloodgateConnection implements Connection { LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer(); - InetSocketAddress socketAddress = new InetSocketAddress(data.getIp(), port); + InetSocketAddress socketAddress = new InetSocketAddress(data.getIp(), 0); return new FloodgateConnection( data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(), diff --git a/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java b/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java index 10e35312..b0b65c31 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java +++ b/core/src/main/java/org/geysermc/floodgate/core/player/FloodgateHandshakeHandler.java @@ -29,7 +29,6 @@ import static org.geysermc.floodgate.core.player.FloodgateHandshakeHandler.Resul import static org.geysermc.floodgate.core.player.FloodgateHandshakeHandler.ResultType.NOT_FLOODGATE_DATA; import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH; -import com.google.common.base.Charsets; import io.netty.channel.Channel; import io.netty.util.AttributeKey; import it.unimi.dsi.fastutil.Pair; @@ -37,6 +36,7 @@ import it.unimi.dsi.fastutil.objects.ObjectObjectImmutablePair; import jakarta.inject.Inject; import jakarta.inject.Named; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import lombok.AccessLevel; @@ -61,6 +61,7 @@ import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.LinkedPlayer; public final class FloodgateHandshakeHandler { + @Inject ConnectionManager connectionManager; @Inject HandshakeHandlersImpl handshakeHandlers; @Inject SimpleFloodgateApi api; @Inject CommonPlayerLink link; @@ -106,9 +107,9 @@ public final class FloodgateHandshakeHandler { public CompletableFuture handle( @NonNull Channel channel, @NonNull String floodgateDataString, - @NonNull String hostname) { - - byte[] floodgateData = floodgateDataString.getBytes(Charsets.UTF_8); + @NonNull String hostname + ) { + byte[] floodgateData = floodgateDataString.getBytes(StandardCharsets.UTF_8); return CompletableFuture.supplyAsync(() -> { @@ -193,8 +194,8 @@ public final class FloodgateHandshakeHandler { Channel channel, String hostname, BedrockData bedrockData, - LinkedPlayer linkedPlayer) { - + LinkedPlayer linkedPlayer + ) { try { HandshakeData handshakeData = new HandshakeDataImpl( channel, true, bedrockData.clone(), config, @@ -216,15 +217,12 @@ public final class FloodgateHandshakeHandler { bedrockData.getVerifyCode()); } - int port = ((InetSocketAddress) channel.remoteAddress()).getPort(); + var connection = FloodgateConnection.from(bedrockData, handshakeData); - Connection player = FloodgateConnection.from(bedrockData, handshakeData, port); + connectionManager.addConnection(connection); + channel.attr(playerAttribute).set(connection); - api.addPlayer(player); - - channel.attr(playerAttribute).set(player); - - return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player); + return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, connection); } catch (Exception exception) { exception.printStackTrace(); return callHandlerAndReturnResult(ResultType.EXCEPTION, channel, null, hostname); @@ -235,8 +233,8 @@ public final class FloodgateHandshakeHandler { ResultType resultType, Channel channel, BedrockData bedrockData, - String hostname) { - + String hostname + ) { HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null, bedrockData, config, null, hostname); handshakeHandlers.callHandshakeHandlers(handshakeData); diff --git a/core/src/main/java/org/geysermc/floodgate/core/player/audience/ProfileAudienceArgument.java b/core/src/main/java/org/geysermc/floodgate/core/player/audience/ProfileAudienceArgument.java index c874e1b6..84e7e5a8 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/player/audience/ProfileAudienceArgument.java +++ b/core/src/main/java/org/geysermc/floodgate/core/player/audience/ProfileAudienceArgument.java @@ -29,7 +29,8 @@ import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.context.CommandContext; -import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Queue; @@ -170,19 +171,19 @@ public class ProfileAudienceArgument extends CommandArgument builder = ImmutableList.builder(); + List profileSuggestions = new ArrayList<>(); for (final String player : commandUtil.getOnlineUsernames(limitTo)) { if (player.toLowerCase(Locale.ROOT).startsWith(lowercaseInput)) { - builder.add(player); + profileSuggestions.add(player); } } - return builder.build(); + return Collections.unmodifiableList(profileSuggestions); } @Override diff --git a/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java b/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java index 86ca3a83..1c25f486 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/core/pluginmessage/channel/FormChannel.java @@ -25,11 +25,11 @@ package org.geysermc.floodgate.core.pluginmessage.channel; -import com.google.common.base.Charsets; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import jakarta.inject.Inject; +import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.geysermc.cumulus.form.Form; @@ -110,7 +110,7 @@ public class FormChannel implements PluginMessageChannel { byte[] jsonData = definition.codec() .jsonData(form) - .getBytes(Charsets.UTF_8); + .getBytes(StandardCharsets.UTF_8); byte[] data = new byte[jsonData.length + 3]; data[0] = (byte) definition.formType().ordinal(); @@ -123,7 +123,7 @@ public class FormChannel implements PluginMessageChannel { protected boolean callResponseConsumer(byte[] data) { Form storedForm = storedForms.remove(getFormId(data)); if (storedForm != null) { - String responseData = new String(data, 2, data.length - 2, Charsets.UTF_8); + String responseData = new String(data, 2, data.length - 2, StandardCharsets.UTF_8); try { formDefinitions.definitionFor(storedForm) .handleFormResponse(storedForm, responseData); diff --git a/core/src/main/java/org/geysermc/floodgate/core/skin/SkinUploadSocket.java b/core/src/main/java/org/geysermc/floodgate/core/skin/SkinUploadSocket.java index 184159d5..c8ac03c0 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/skin/SkinUploadSocket.java +++ b/core/src/main/java/org/geysermc/floodgate/core/skin/SkinUploadSocket.java @@ -38,7 +38,6 @@ import org.geysermc.api.GeyserApiBase; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.core.util.Utils; import org.geysermc.floodgate.core.util.WebsocketEventType; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -107,7 +106,7 @@ final class SkinUploadSocket extends WebSocketClient { break; case SKIN_UPLOADED: String xuid = message.get("xuid").getAsString(); - Connection player = api.connectionByUuid(Utils.getJavaUuid(xuid)); + Connection player = api.connectionByXuid(xuid); if (player != null) { if (!message.get("success").getAsBoolean()) { logger.info("Failed to upload skin for {} ({})", xuid, diff --git a/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java b/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java index 947ec975..d2802d75 100644 --- a/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java +++ b/core/src/main/java/org/geysermc/floodgate/core/util/LanguageManager.java @@ -25,15 +25,16 @@ package org.geysermc.floodgate.core.util; -import com.google.common.base.Joiner; import jakarta.inject.Inject; import jakarta.inject.Singleton; import java.net.URL; import java.text.MessageFormat; +import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; import lombok.Getter; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.config.FloodgateConfig; @@ -186,7 +187,8 @@ public final class LanguageManager { return true; } - private String formatNotFound(String key, Object... args) { - return key + " " + Joiner.on(", ").join(args); + private String formatNotFound(String key, Object... rawArgs) { + var args = Arrays.stream(rawArgs).map(Object::toString).collect(Collectors.joining(", ")); + return key + " " + args; } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d012a6a..f6903c66 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,10 +17,11 @@ java-websocket = "1.5.2" cloud = "1.5.0" snakeyaml = "2.0" bstats = "3.0.2" +# todo remove guava from events to be able to remove it here guava = "31.1-jre" # bungee -bungee = "dfd847f" +bungee = "master-SNAPSHOT" # spigot folia = "1.19.4-R0.1-SNAPSHOT" @@ -61,6 +62,7 @@ micronaut-inject = { module = "io.micronaut:micronaut-inject-java" } micronaut-context = { module = "io.micronaut:micronaut-context" } micronaut-http-client = { module = "io.micronaut:micronaut-http-client-jdk" } micronaut-validation = { module = "io.micronaut.validation:micronaut-validation" } +micronaut-validation-processor = { module = "io.micronaut.validation:micronaut-validation-processor" } micronaut-data-processor = { module = "io.micronaut.data:micronaut-data-processor" } micronaut-hibernate = { module = "io.micronaut.data:micronaut-data-hibernate-jpa" } micronaut-hikari = { module = "io.micronaut.sql:micronaut-jdbc-hikari" } diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java index c7f6682f..ff24f623 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/addon/data/SpigotDataAddon.java @@ -30,10 +30,7 @@ import io.netty.util.AttributeKey; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; -import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.inject.InjectorAddon; -import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.config.FloodgateConfig; import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler; @@ -41,8 +38,6 @@ import org.geysermc.floodgate.core.player.FloodgateHandshakeHandler; public final class SpigotDataAddon implements InjectorAddon { @Inject FloodgateHandshakeHandler handshakeHandler; @Inject FloodgateConfig config; - @Inject SimpleFloodgateApi api; - @Inject FloodgateLogger logger; @Inject @Named("packetHandler") @@ -52,10 +47,6 @@ public final class SpigotDataAddon implements InjectorAddon { @Named("kickMessageAttribute") AttributeKey kickMessageAttribute; - @Inject - @Named("playerAttribute") - AttributeKey playerAttribute; - @Override public void onInject(Channel channel, boolean toServer) { // we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks @@ -65,14 +56,6 @@ public final class SpigotDataAddon implements InjectorAddon { ); } - @Override - public void onChannelClosed(Channel channel) { - Connection player = channel.attr(playerAttribute).get(); - if (player != null && api.setPendingRemove(player)) { - logger.translatedInfo("floodgate.ingame.disconnect_name", player.javaUsername()); - } - } - @Override public void onRemoveInject(Channel channel) { } diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java index 53248f9d..43f0bfa2 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/listener/SpigotListener.java @@ -27,47 +27,39 @@ package org.geysermc.floodgate.spigot.listener; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import java.util.UUID; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.api.SimpleFloodgateApi; import org.geysermc.floodgate.core.listener.McListener; +import org.geysermc.floodgate.core.player.ConnectionManager; import org.geysermc.floodgate.core.util.LanguageManager; @Singleton public final class SpigotListener implements Listener, McListener { + @Inject ConnectionManager connectionManager; @Inject SimpleFloodgateApi api; @Inject LanguageManager languageManager; @Inject FloodgateLogger logger; - @EventHandler(priority = EventPriority.MONITOR) + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerLogin(PlayerLoginEvent event) { - UUID uniqueId = event.getPlayer().getUniqueId(); - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + // see SpigotConnectionManager#platformIdentifierOrConnectionFor for more info + + var connection = connectionManager.findPendingConnection(event.getPlayer().getUniqueId()); + if (connection == null) { return; } - // if there was another player with the same uuid online, - // he would've been disconnected by now - Connection player = api.connectionByUuid(uniqueId); - if (player != null) { - //todo we should probably move this log message earlier in the process, so that we know - // that Floodgate has done its job - logger.translatedInfo( - "floodgate.ingame.login_name", - player.javaUsername(), player.javaUuid() - ); - languageManager.loadLocale(player.languageCode()); - } + languageManager.loadLocale(connection.languageCode()); + connectionManager.addAcceptedConnection(connection); } @EventHandler(priority = EventPriority.MONITOR) public void onPlayerQuit(PlayerQuitEvent event) { - api.playerRemoved(event.getPlayer().getUniqueId()); + connectionManager.removeConnection(event.getPlayer()); } } diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/player/SpigotConnectionManager.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/player/SpigotConnectionManager.java new file mode 100644 index 00000000..69b49461 --- /dev/null +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/player/SpigotConnectionManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.spigot.player; + +import jakarta.inject.Singleton; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.floodgate.core.player.ConnectionManager; + +@Singleton +public class SpigotConnectionManager extends ConnectionManager { + @Override + protected @Nullable Object platformIdentifierOrConnectionFor(Object input) { + // in PlayerList#canPlayerLogin the old players with the same profile are disconnected ( + // PlayerKickEvent, see ServerGamePacketListener#disconnect & + // PlayerQuitEvent, see PlayerList#remove + // ) before the first event runs with the Player instance (PlayerLoginEvent). + // This means that we can always use the Player's uuid + if (input instanceof Player player) { + return connectionByUuid(player.getUniqueId()); + } + return null; + } +} diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/pluginmessage/SpigotSkinApplier.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/pluginmessage/SpigotSkinApplier.java index fb6db0bf..247e16f1 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/pluginmessage/SpigotSkinApplier.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/pluginmessage/SpigotSkinApplier.java @@ -36,7 +36,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.event.skin.SkinApplyEvent; import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; -import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.core.event.EventBus; import org.geysermc.floodgate.core.event.skin.SkinApplyEventImpl; import org.geysermc.floodgate.core.skin.SkinApplier; @@ -75,8 +74,6 @@ public final class SpigotSkinApplier implements SkinApplier { throw new IllegalStateException("The GameProfile cannot be null! " + player.getName()); } - // Need to be careful here - getProperties() returns an authlib PropertyMap, which extends - // MultiMap from Guava. Floodgate relocates Guava. PropertyMap properties = profile.getProperties(); SkinData currentSkin = currentSkin(properties); diff --git a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/ClassNames.java b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/ClassNames.java index 2fcf9fae..147a18d7 100644 --- a/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/ClassNames.java +++ b/spigot/base/src/main/java/org/geysermc/floodgate/spigot/util/ClassNames.java @@ -35,13 +35,13 @@ import static org.geysermc.floodgate.core.util.ReflectionUtils.getMethod; import static org.geysermc.floodgate.core.util.ReflectionUtils.getValue; import static org.geysermc.floodgate.core.util.ReflectionUtils.invoke; -import com.google.common.base.Preconditions; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelHandlerContext; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.SocketAddress; +import java.util.Objects; import java.util.function.BooleanSupplier; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -90,7 +90,7 @@ public class ClassNames { Class craftPlayerClass = ReflectionUtils.getClass( "org.bukkit.craftbukkit." + version + ".entity.CraftPlayer"); GET_PROFILE_METHOD = getMethod(craftPlayerClass, "getProfile"); - checkNotNull(GET_PROFILE_METHOD, "Get profile method"); + requireNonNull(GET_PROFILE_METHOD, "Get profile method"); String nmsPackage = SPIGOT_MAPPING_PREFIX + '.'; @@ -129,7 +129,7 @@ public class ClassNames { ); HANDSHAKE_HOST = getFieldOfType(HANDSHAKE_PACKET, String.class); - checkNotNull(HANDSHAKE_HOST, "Handshake host"); + requireNonNull(HANDSHAKE_HOST, "Handshake host"); LOGIN_START_PACKET = getClassOrFallback( "net.minecraft.network.protocol.login.PacketLoginInStart", @@ -142,10 +142,10 @@ public class ClassNames { ); LOGIN_PROFILE = getFieldOfType(LOGIN_LISTENER, GameProfile.class); - checkNotNull(LOGIN_PROFILE, "Profile from LoginListener"); + requireNonNull(LOGIN_PROFILE, "Profile from LoginListener"); LOGIN_DISCONNECT = getMethod(LOGIN_LISTENER, "disconnect", String.class); - checkNotNull(LOGIN_DISCONNECT, "LoginListener's disconnect method"); + requireNonNull(LOGIN_DISCONNECT, "LoginListener's disconnect method"); NETWORK_EXCEPTION_CAUGHT = getMethod( networkManager, @@ -155,14 +155,14 @@ public class ClassNames { // there are multiple no-arg void methods INIT_UUID = getMethod(LOGIN_LISTENER, "initUUID"); - checkNotNull(INIT_UUID, "initUUID from LoginListener"); + requireNonNull(INIT_UUID, "initUUID from LoginListener"); Class packetListenerClass = getClassOrFallback( "net.minecraft.network.PacketListener", nmsPackage + "PacketListener" ); PACKET_LISTENER = getFieldOfType(networkManager, packetListenerClass); - checkNotNull(PACKET_LISTENER, "Packet listener"); + requireNonNull(PACKET_LISTENER, "Packet listener"); LOGIN_HANDLER = getClassOrFallback( "net.minecraft.server.network.LoginListener$LoginHandler", @@ -171,10 +171,10 @@ public class ClassNames { LOGIN_HANDLER_CONSTRUCTOR = ReflectionUtils.getConstructor(LOGIN_HANDLER, true, LOGIN_LISTENER); - checkNotNull(LOGIN_HANDLER_CONSTRUCTOR, "LoginHandler constructor"); + requireNonNull(LOGIN_HANDLER_CONSTRUCTOR, "LoginHandler constructor"); FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents"); - checkNotNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler"); + requireNonNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler"); PAPER_DISABLE_USERNAME_VALIDATION = getField(LOGIN_LISTENER, @@ -187,23 +187,23 @@ public class ClassNames { // ProxyUtils Class spigotConfig = ReflectionUtils.getClass("org.spigotmc.SpigotConfig"); - checkNotNull(spigotConfig, "Spigot config"); + requireNonNull(spigotConfig, "Spigot config"); BUNGEE = getField(spigotConfig, "bungee"); - checkNotNull(BUNGEE, "Bungee field"); + requireNonNull(BUNGEE, "Bungee field"); Class paperConfigNew = getClassSilently( "io.papermc.paper.configuration.GlobalConfiguration"); if (paperConfigNew != null) { // 1.19 and later - Method paperConfigGet = checkNotNull(getMethod(paperConfigNew, "get"), + Method paperConfigGet = requireNonNull(getMethod(paperConfigNew, "get"), "GlobalConfiguration get"); - Field paperConfigProxies = checkNotNull(getField(paperConfigNew, "proxies"), + Field paperConfigProxies = requireNonNull(getField(paperConfigNew, "proxies"), "Proxies field"); - Field paperConfigVelocity = checkNotNull( + Field paperConfigVelocity = requireNonNull( getField(paperConfigProxies.getType(), "velocity"), "velocity field"); - Field paperVelocityEnabled = checkNotNull( + Field paperVelocityEnabled = requireNonNull( getField(paperConfigVelocity.getType(), "enabled"), "Velocity enabled field"); PAPER_VELOCITY_SUPPORT = () -> { @@ -232,7 +232,7 @@ public class ClassNames { ) != null; } - private static T checkNotNull(T toCheck, String objectName) { - return Preconditions.checkNotNull(toCheck, objectName + " cannot be null"); + private static T requireNonNull(T toCheck, String objectName) { + return Objects.requireNonNull(toCheck, objectName + " cannot be null"); } } diff --git a/velocity/base/build.gradle.kts b/velocity/base/build.gradle.kts index 887b8d83..af8f09bb 100644 --- a/velocity/base/build.gradle.kts +++ b/velocity/base/build.gradle.kts @@ -1,6 +1,5 @@ var log4jVersion = "2.11.2" var gsonVersion = "2.8.8" -var guavaVersion = "25.1-jre" dependencies { api(projects.core) diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java index ca2eabab..5a475501 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityDataAddon.java @@ -89,14 +89,6 @@ public final class VelocityDataAddon implements InjectorAddon { ); } - @Override - public void onChannelClosed(Channel channel) { - Connection player = channel.attr(playerAttribute).get(); - if (player != null && api.setPendingRemove(player)) { - logger.translatedInfo("floodgate.ingame.disconnect_name", player.javaUsername()); - } - } - @Override public void onRemoveInject(Channel channel) { } diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java index 72e4f4ed..e1286191 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityProxyDataHandler.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.velocity.addon.data; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static org.geysermc.floodgate.core.util.ReflectionUtils.getCastedValue; import static org.geysermc.floodgate.core.util.ReflectionUtils.getField; import static org.geysermc.floodgate.core.util.ReflectionUtils.getMethodByName; @@ -38,7 +38,6 @@ import io.netty.util.AttributeKey; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetSocketAddress; -import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.core.addon.data.CommonDataHandler; import org.geysermc.floodgate.core.addon.data.PacketBlocker; @@ -60,30 +59,30 @@ public final class VelocityProxyDataHandler extends CommonDataHandler { static { Class iic = getPrefixedClass("connection.client.InitialInboundConnection"); - checkNotNull(iic, "InitialInboundConnection class cannot be null"); + requireNonNull(iic, "InitialInboundConnection class cannot be null"); HANDSHAKE = getField(iic, "handshake"); - checkNotNull(HANDSHAKE, "Handshake field cannot be null"); + requireNonNull(HANDSHAKE, "Handshake field cannot be null"); HANDSHAKE_PACKET = getPrefixedClass("protocol.packet.Handshake"); - checkNotNull(HANDSHAKE_PACKET, "Handshake packet class cannot be null"); + requireNonNull(HANDSHAKE_PACKET, "Handshake packet class cannot be null"); HANDSHAKE_SERVER_ADDRESS = getField(HANDSHAKE_PACKET, "serverAddress"); - checkNotNull(HANDSHAKE_SERVER_ADDRESS, "Address in the Handshake packet cannot be null"); + requireNonNull(HANDSHAKE_SERVER_ADDRESS, "Address in the Handshake packet cannot be null"); Class minecraftConnection = getPrefixedClass("connection.MinecraftConnection"); REMOTE_ADDRESS = getField(minecraftConnection, "remoteAddress"); - checkNotNull(REMOTE_ADDRESS, "remoteAddress cannot be null"); + requireNonNull(REMOTE_ADDRESS, "remoteAddress cannot be null"); SERVER_LOGIN_PACKET = getPrefixedClass("protocol.packet.ServerLogin"); - checkNotNull(SERVER_LOGIN_PACKET, "ServerLogin packet class cannot be null"); + requireNonNull(SERVER_LOGIN_PACKET, "ServerLogin packet class cannot be null"); GET_SESSION_HANDLER = getMethodByName(minecraftConnection, "getSessionHandler", true); - checkNotNull(GET_SESSION_HANDLER, "getSessionHandler method cannot be null"); + requireNonNull(GET_SESSION_HANDLER, "getSessionHandler method cannot be null"); INITIAL_LOGIN_SESSION_HANDLER = getPrefixedClass("connection.client.InitialLoginSessionHandler"); - checkNotNull(INITIAL_LOGIN_SESSION_HANDLER, "InitialLoginSessionHandler cannot be null"); + requireNonNull(INITIAL_LOGIN_SESSION_HANDLER, "InitialLoginSessionHandler cannot be null"); // allowed to be null if it's an old Velocity version FORCE_KEY_AUTHENTICATION = getField(INITIAL_LOGIN_SESSION_HANDLER, "forceKeyAuthentication"); @@ -115,14 +114,9 @@ public final class VelocityProxyDataHandler extends CommonDataHandler { @Override protected boolean shouldRemoveHandler(HandshakeResult result) { if (result.getResultType() == ResultType.SUCCESS) { - Connection player = result.getFloodgatePlayer(); - logger.info("Floodgate player who is logged in as {} {} joined", - player.javaUsername(), player.javaUuid()); - // the way Velocity stores whether to force key authentication - boolean forceKeyAuthentication = Boolean.getBoolean("auth.forceSecureProfiles"); // we need the login packet to bypass the 'force key authentication' - return !forceKeyAuthentication; + return !Boolean.getBoolean("auth.forceSecureProfiles"); } return super.shouldRemoveHandler(result); } diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityServerDataHandler.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityServerDataHandler.java index 7e46af07..da633462 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityServerDataHandler.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/addon/data/VelocityServerDataHandler.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.velocity.addon.data; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static org.geysermc.floodgate.core.util.ReflectionUtils.castedInvoke; import static org.geysermc.floodgate.core.util.ReflectionUtils.getCastedValue; import static org.geysermc.floodgate.core.util.ReflectionUtils.getField; @@ -54,20 +54,20 @@ public final class VelocityServerDataHandler extends ChannelOutboundHandlerAdapt static { HANDSHAKE_PACKET = getPrefixedClass("protocol.packet.Handshake"); - checkNotNull(HANDSHAKE_PACKET, "Handshake packet class cannot be null"); + requireNonNull(HANDSHAKE_PACKET, "Handshake packet class cannot be null"); HANDSHAKE_ADDRESS = getField(HANDSHAKE_PACKET, "serverAddress"); - checkNotNull(HANDSHAKE_ADDRESS, "Address field of the Handshake packet cannot be null"); + requireNonNull(HANDSHAKE_ADDRESS, "Address field of the Handshake packet cannot be null"); Class minecraftConnection = getPrefixedClass("connection.MinecraftConnection"); GET_ASSOCIATION = getMethod(minecraftConnection, "getAssociation"); - checkNotNull(GET_ASSOCIATION, "getAssociation in MinecraftConnection cannot be null"); + requireNonNull(GET_ASSOCIATION, "getAssociation in MinecraftConnection cannot be null"); Class serverConnection = getPrefixedClass("connection.backend.VelocityServerConnection"); GET_PLAYER = getMethod(serverConnection, "getPlayer"); - checkNotNull(GET_PLAYER, "getPlayer in VelocityServerConnection cannot be null"); + requireNonNull(GET_PLAYER, "getPlayer in VelocityServerConnection cannot be null"); } private final ProxyFloodgateApi api; diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java index 2505feaf..3fac6871 100644 --- a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/listener/VelocityListener.java @@ -25,22 +25,12 @@ package org.geysermc.floodgate.velocity.listener; -import static org.geysermc.floodgate.core.util.ReflectionUtils.getCastedValue; -import static org.geysermc.floodgate.core.util.ReflectionUtils.getField; -import static org.geysermc.floodgate.core.util.ReflectionUtils.getFieldOfType; -import static org.geysermc.floodgate.core.util.ReflectionUtils.getPrefixedClass; -import static org.geysermc.floodgate.core.util.ReflectionUtils.getPrefixedClassSilently; -import static org.geysermc.floodgate.core.util.ReflectionUtils.getValue; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.LoginEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; -import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile.Property; import io.netty.channel.Channel; @@ -48,10 +38,7 @@ import io.netty.util.AttributeKey; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; -import java.lang.reflect.Field; import java.util.Collections; -import java.util.Objects; -import java.util.concurrent.TimeUnit; import net.kyori.adventure.text.Component; import org.geysermc.api.connection.Connection; import org.geysermc.floodgate.api.logger.FloodgateLogger; @@ -59,40 +46,11 @@ import org.geysermc.floodgate.core.api.ProxyFloodgateApi; import org.geysermc.floodgate.core.config.ProxyFloodgateConfig; import org.geysermc.floodgate.core.listener.McListener; import org.geysermc.floodgate.core.util.LanguageManager; +import org.geysermc.floodgate.velocity.player.VelocityConnectionManager; @Singleton public final class VelocityListener implements McListener { - private static final Field INITIAL_MINECRAFT_CONNECTION; - private static final Field INITIAL_CONNECTION_DELEGATE; - private static final Field CHANNEL; - - static { - Class initialConnection = getPrefixedClass("connection.client.InitialInboundConnection"); - Class minecraftConnection = getPrefixedClass("connection.MinecraftConnection"); - INITIAL_MINECRAFT_CONNECTION = getFieldOfType(initialConnection, minecraftConnection); - - // Since Velocity 3.1.0 - Class loginInboundConnection = - getPrefixedClassSilently("connection.client.LoginInboundConnection"); - if (loginInboundConnection != null) { - INITIAL_CONNECTION_DELEGATE = getField(loginInboundConnection, "delegate"); - Objects.requireNonNull( - INITIAL_CONNECTION_DELEGATE, - "initial inbound connection delegate cannot be null" - ); - } else { - INITIAL_CONNECTION_DELEGATE = null; - } - - CHANNEL = getFieldOfType(minecraftConnection, Channel.class); - } - - private final Cache playerCache = - CacheBuilder.newBuilder() - .maximumSize(500) - .expireAfterAccess(20, TimeUnit.SECONDS) - .build(); - + @Inject VelocityConnectionManager connectionManager; @Inject ProxyFloodgateConfig config; @Inject ProxyFloodgateApi api; @Inject LanguageManager languageManager; @@ -106,26 +64,17 @@ public final class VelocityListener implements McListener { @Named("kickMessageAttribute") AttributeKey kickMessageAttribute; - @Subscribe(order = PostOrder.EARLY) + @Subscribe(order = PostOrder.FIRST) public void onPreLogin(PreLoginEvent event) { Connection player = null; String kickMessage; try { - InboundConnection connection = event.getConnection(); - if (INITIAL_CONNECTION_DELEGATE != null) { - // Velocity 3.1.0 added LoginInboundConnection which is used in the login state, - // but that class doesn't have a Channel field. However, it does have - // InitialInboundConnection as a field - connection = getCastedValue(connection, INITIAL_CONNECTION_DELEGATE); - } - Object mcConnection = getValue(connection, INITIAL_MINECRAFT_CONNECTION); - Channel channel = getCastedValue(mcConnection, CHANNEL); - + Channel channel = connectionManager.channelFor(event.getConnection()); player = channel.attr(playerAttribute).get(); kickMessage = channel.attr(kickMessageAttribute).get(); } catch (Exception exception) { logger.error("Failed get the FloodgatePlayer from the player's channel", exception); - kickMessage = "Failed to get the FloodgatePlayer from the players's Channel"; + kickMessage = "Failed to get the FloodgatePlayer from the player's Channel"; } if (kickMessage != null) { @@ -137,41 +86,44 @@ public final class VelocityListener implements McListener { if (player != null) { event.setResult(PreLoginEvent.PreLoginComponentResult.forceOfflineMode()); - playerCache.put(event.getConnection(), player); } } @Subscribe(order = PostOrder.EARLY) public void onGameProfileRequest(GameProfileRequestEvent event) { - Connection player = playerCache.getIfPresent(event.getConnection()); - if (player != null) { - playerCache.invalidate(event.getConnection()); - - GameProfile profile = new GameProfile( - player.javaUuid(), - player.javaUsername(), - Collections.emptyList() - ); - // The texture properties addition is to fix the February 2 2022 Mojang authentication changes - if (!config.sendFloodgateData() && !player.isLinked()) { - profile = profile.addProperty(new Property("textures", "", "")); - } - event.setGameProfile(profile); + Connection connection = connectionManager.connectionByPlatformIdentifier(event.getConnection()); + if (connection == null) { + return; } + + GameProfile profile = new GameProfile( + connection.javaUuid(), + connection.javaUsername(), + Collections.emptyList() + ); + // The texture properties addition is to fix the February 2 2022 Mojang authentication changes + if (!config.sendFloodgateData() && !connection.isLinked()) { + profile = profile.addProperty(new Property("textures", "", "")); + } + event.setGameProfile(profile); } - @Subscribe(order = PostOrder.LAST) - public void onLogin(LoginEvent event) { - if (event.getResult().isAllowed()) { - Connection player = api.connectionByUuid(event.getPlayer().getUniqueId()); - if (player != null) { - languageManager.loadLocale(player.languageCode()); - } + @Subscribe(order = PostOrder.FIRST) + public void onLogin(PostLoginEvent event) { + Connection connection = api.connectionByPlatformIdentifier(event.getPlayer()); + if (connection == null) { + return; } + languageManager.loadLocale(connection.languageCode()); + + // Depending on whether online-mode-kick-existing-players is enabled there are a few events + // where there are two players online: PermissionSetupEvent and LoginEvent. + // Only after LoginEvent the duplicated player is kicked. This is the first event after that + connectionManager.addAcceptedConnection(connection); } @Subscribe(order = PostOrder.LAST) public void onDisconnect(DisconnectEvent event) { - api.playerRemoved(event.getPlayer().getUniqueId()); + connectionManager.removeConnection(event.getPlayer().getUniqueId()); } } diff --git a/velocity/base/src/main/java/org/geysermc/floodgate/velocity/player/VelocityConnectionManager.java b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/player/VelocityConnectionManager.java new file mode 100644 index 00000000..febce216 --- /dev/null +++ b/velocity/base/src/main/java/org/geysermc/floodgate/velocity/player/VelocityConnectionManager.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019-2023 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + */ + +package org.geysermc.floodgate.velocity.player; + +import static org.geysermc.floodgate.core.util.ReflectionUtils.getField; +import static org.geysermc.floodgate.core.util.ReflectionUtils.getFieldOfType; +import static org.geysermc.floodgate.core.util.ReflectionUtils.getMethod; +import static org.geysermc.floodgate.core.util.ReflectionUtils.getPrefixedClass; +import static org.geysermc.floodgate.core.util.ReflectionUtils.getValue; +import static org.geysermc.floodgate.core.util.ReflectionUtils.invoke; + +import com.velocitypowered.api.proxy.Player; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.core.player.ConnectionManager; + +@Singleton +public class VelocityConnectionManager extends ConnectionManager { + private static final Class LOGIN_INBOUND_CONNECTION; + private static final Field INITIAL_CONNECTION_DELEGATE; + private static final Class INITIAL_INBOUND_CONNECTION; + private static final Class MINECRAFT_CONNECTION; + private static final Method GET_CONNECTION; + private static final Field CHANNEL; + + @Inject + @Named("playerAttribute") + AttributeKey playerAttribute; + + @Override + protected @Nullable Object platformIdentifierOrConnectionFor(Object input) { + if (input instanceof Player) { + // ConnectedPlayer implements VelocityInboundConnection, + // just like InitialInboundConnection + return invoke(input, GET_CONNECTION); + } + + // LoginInboundConnection doesn't have a direct Channel reference, + // but it does have an InitialInboundConnection reference + if (LOGIN_INBOUND_CONNECTION.isInstance(input)) { + return getValue(input, INITIAL_CONNECTION_DELEGATE); + } + + // InitialInboundConnection -> MinecraftConnection -> Channel -> FloodgateConnection attribute + + if (INITIAL_INBOUND_CONNECTION.isInstance(input)) { + return invoke(input, GET_CONNECTION); + } + if (MINECRAFT_CONNECTION.isInstance(input)) { + return getValue(input, CHANNEL); + } + if (input instanceof Channel channel) { + return channel.attr(playerAttribute).get(); + } + return null; + } + + public Channel channelFor(Object input) { + var result = platformIdentifierOrConnectionFor(input); + if (result instanceof Channel channel) { + return channel; + } + return channelFor(result); + } + + static { + LOGIN_INBOUND_CONNECTION = getPrefixedClass("connection.client.LoginInboundConnection"); + INITIAL_CONNECTION_DELEGATE = getField(LOGIN_INBOUND_CONNECTION, "delegate"); + + INITIAL_INBOUND_CONNECTION = getPrefixedClass("connection.client.InitialInboundConnection"); + MINECRAFT_CONNECTION = getPrefixedClass("connection.MinecraftConnection"); + + Class velocityInboundConnection = getPrefixedClass("connection.util.VelocityInboundConnection"); + GET_CONNECTION = getMethod(velocityInboundConnection, "getConnection"); + + CHANNEL = getFieldOfType(MINECRAFT_CONNECTION, Channel.class); + } +}