1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Introduced connectionByPlatformIdentifier, removed guava usage

This commit is contained in:
Tim203
2023-06-25 16:00:48 +02:00
parent f47ff5e008
commit 01497ba86c
31 changed files with 544 additions and 340 deletions

View File

@@ -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) {
}

View File

@@ -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(

View File

@@ -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;

View File

@@ -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<Connection> playerAttribute;
@Inject BungeeConnectionManager connectionManager;
@Inject ProxyFloodgateConfig config;
@Inject ProxyFloodgateApi api;
@Inject LanguageManager languageManager;
@Inject FloodgateLogger logger;
@Inject SkinApplier skinApplier;
@Inject
@Named("kickMessageAttribute")
private AttributeKey<String> kickMessageAttribute;
AttributeKey<String> 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());
}
}

View File

@@ -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<Connection> 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");
}
}

View File

@@ -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();

View File

@@ -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)

View File

@@ -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<String> kickMessageAttribute;
protected final PacketBlocker blocker;
protected final Queue<Object> packetQueue = Queues.newConcurrentLinkedQueue();
protected final Queue<Object> packetQueue = new ConcurrentLinkedQueue<>();
protected Object handshakePacket;
protected ChannelHandlerContext ctx;

View File

@@ -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<Object> packetQueue = Queues.newConcurrentLinkedQueue();
private final Queue<Object> packetQueue = new ConcurrentLinkedQueue<>();
private volatile boolean blockPackets;
private ChannelHandlerContext ctx;

View File

@@ -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<UUID, Connection> players = new ConcurrentHashMap<>();
private final Cache<UUID, Connection> pendingRemove =
CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.build();
@Inject ConnectionManager connectionManager;
@Inject BeanProvider<PluginMessageManager> pluginMessageManager;
@Inject FloodgateConfig config;
@Inject FloodgateLogger logger;
@@ -73,12 +64,12 @@ public class SimpleFloodgateApi implements GeyserApiBase {
@Override
public @NonNull List<? extends Connection> 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();
}

View File

@@ -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<Object, FloodgateSubscriber<?>>
public class EventBus extends EventBusImpl<Object, FloodgateSubscriber<?>>
implements FloodgateEventBus {
@Inject ApplicationContext context;
@Override
@SuppressWarnings("rawtypes")
public boolean fire(@NonNull Object event) {

View File

@@ -82,10 +82,10 @@ public abstract class CommandUtil {
protected abstract Collection<?> getOnlinePlayers();
public @NonNull Collection<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
public @NonNull List<String> getOnlineUsernames(@NonNull PlayerType limitTo) {
Collection<?> players = getOnlinePlayers();
Collection<String> usernames = new ArrayList<>();
List<String> usernames = new ArrayList<>();
switch (limitTo) {
case ALL_PLAYERS:
for (Object player : players) {

View File

@@ -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<Connection> connections = Collections.synchronizedSet(new HashSet<>());
private final Set<Connection> pendingConnections = Collections.synchronizedSet(new HashSet<>());
private final Map<String, Connection> xuidToConnection =
Collections.synchronizedMap(new HashMap<>());
private final Map<UUID, Connection> uuidToConnection =
Collections.synchronizedMap(new HashMap<>());
protected final Map<Object, Connection> 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<Connection> acceptedConnections() {
return uuidToConnection.values();
}
public int acceptedConnectionsCount() {
return uuidToConnection.size();
}
}

View File

@@ -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(),

View File

@@ -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<HandshakeResult> 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);

View File

@@ -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<UserAudience, Profi
String trimmedInput = input.trim();
if (trimmedInput.isEmpty()) {
return ImmutableList.copyOf(commandUtil.getOnlineUsernames(limitTo));
return Collections.unmodifiableList(commandUtil.getOnlineUsernames(limitTo));
}
String lowercaseInput = input.toLowerCase(Locale.ROOT);
ImmutableList.Builder<String> builder = ImmutableList.builder();
List<String> 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

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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" }

View File

@@ -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<String> kickMessageAttribute;
@Inject
@Named("playerAttribute")
AttributeKey<Connection> 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) {
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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> T checkNotNull(T toCheck, String objectName) {
return Preconditions.checkNotNull(toCheck, objectName + " cannot be null");
private static <T> T requireNonNull(T toCheck, String objectName) {
return Objects.requireNonNull(toCheck, objectName + " cannot be null");
}
}

View File

@@ -1,6 +1,5 @@
var log4jVersion = "2.11.2"
var gsonVersion = "2.8.8"
var guavaVersion = "25.1-jre"
dependencies {
api(projects.core)

View File

@@ -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) {
}

View File

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

View File

@@ -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;

View File

@@ -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<InboundConnection, Connection> 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<String> 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());
}
}

View File

@@ -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<Connection> 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);
}
}