Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@

package org.geysermc.floodgate.api;

import java.util.Collection;
import java.net.InetSocketAddress;import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.geysermc.cumulus.form.Form;
import org.checkerframework.checker.nullness.qual.Nullable;import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.link.PlayerLink;
Expand Down Expand Up @@ -59,6 +59,26 @@ static FloodgateApi getInstance() {
*/
int getPlayerCount();

/**
* Get a pending FloodgatePlayer by their connection address.
* This is useful in PreLoginEvent where UUID is not yet available.
*
* @param address the InetSocketAddress of the connection
* @return FloodgatePlayer if this is a pending Bedrock connection, null otherwise
*/
@Nullable
FloodgatePlayer getPendingPlayer(InetSocketAddress address);

/**
* Get a pending FloodgatePlayer by their raw username (without prefix).
* This is useful in PreLoginEvent where the username hasn't been modified yet.
*
* @param rawUsername the raw username without Floodgate prefix
* @return FloodgatePlayer if this is a pending Bedrock connection, null otherwise
*/
@Nullable
FloodgatePlayer getPendingPlayerByUsername(String rawUsername);

/**
* Method to determine if the given <b>online</b> player is a bedrock player
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.LoginEvent;
Expand All @@ -47,6 +49,7 @@
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.player.PendingPlayerManager;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.LanguageManager;
Expand Down Expand Up @@ -82,6 +85,7 @@ public final class BungeeListener implements Listener {
private AttributeKey<String> kickMessageAttribute;

@Inject private MojangUtils mojangUtils;
@Inject private PendingPlayerManager pendingPlayerManager;

@EventHandler(priority = EventPriority.LOWEST)
public void onPreLogin(PreLoginEvent event) {
Expand Down Expand Up @@ -130,6 +134,13 @@ public void onLogin(LoginEvent event) {

@EventHandler(priority = EventPriority.LOWEST)
public void onPostLogin(PostLoginEvent event) {
SocketAddress socketAddress = event.getPlayer().getSocketAddress();

if (socketAddress instanceof InetSocketAddress) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
pendingPlayerManager.remove(inetSocketAddress);
}

FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId());
if (player == null) {
return;
Expand Down Expand Up @@ -160,5 +171,12 @@ public void onPostLogin(PostLoginEvent event) {
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
api.playerRemoved(event.getPlayer().getUniqueId());

SocketAddress socketAddress = event.getPlayer().getSocketAddress();

if (socketAddress instanceof InetSocketAddress) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
pendingPlayerManager.remove(inetSocketAddress);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.unsafe.Unsafe;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.player.PendingPlayerManager;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.pluginmessage.channel.FormChannel;
import org.geysermc.floodgate.pluginmessage.channel.TransferChannel;
Expand All @@ -61,6 +64,7 @@ public class SimpleFloodgateApi implements FloodgateApi {
@Inject private FloodgateConfig config;
@Inject private HttpClient httpClient;
@Inject private FloodgateLogger logger;
@Inject private PendingPlayerManager pendingPlayerManager;

@Override
public String getPlayerPrefix() {
Expand Down Expand Up @@ -225,4 +229,16 @@ private FloodgatePlayer getPendingRemovePlayer(UUID correctUuid) {
}
return null;
}

@Override
@Nullable
public FloodgatePlayer getPendingPlayer(InetSocketAddress address) {
return pendingPlayerManager.get(address);
}

@Override
@Nullable
public FloodgatePlayer getPendingPlayerByUsername(String rawUsername) {
return pendingPlayerManager.getByUsername(rawUsername);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import org.geysermc.floodgate.link.PlayerLinkHolder;
import org.geysermc.floodgate.packet.PacketHandlersImpl;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.player.PendingPlayerManager;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.skin.SkinUploadManager;
import org.geysermc.floodgate.util.Constants;
Expand Down Expand Up @@ -175,10 +176,11 @@ public FloodgateHandshakeHandler handshakeHandler(
SkinUploadManager skinUploadManager,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger,
LanguageManager languageManager) {
LanguageManager languageManager,
PendingPlayerManager pendingPlayerManager) {

return new FloodgateHandshakeHandler(handshakeHandlers, api, cipher, config,
skinUploadManager, playerAttribute, logger, languageManager);
skinUploadManager, playerAttribute, logger, languageManager, pendingPlayerManager);
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public final class FloodgateHandshakeHandler {
private final AttributeKey<FloodgatePlayer> playerAttribute;
private final FloodgateLogger logger;
private final LanguageManager languageManager;
private final PendingPlayerManager pendingPlayerManager;

public FloodgateHandshakeHandler(
HandshakeHandlersImpl handshakeHandlers,
Expand All @@ -76,7 +77,8 @@ public FloodgateHandshakeHandler(
SkinUploadManager skinUploadManager,
AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger,
LanguageManager languageManager) {
LanguageManager languageManager,
PendingPlayerManager pendingPlayerManager) {

this.handshakeHandlers = handshakeHandlers;
this.api = api;
Expand All @@ -86,6 +88,7 @@ public FloodgateHandshakeHandler(
this.playerAttribute = playerAttribute;
this.logger = logger;
this.languageManager = languageManager;
this.pendingPlayerManager = pendingPlayerManager;
}

/**
Expand Down Expand Up @@ -241,6 +244,8 @@ private HandshakeResult handlePart2(
InetSocketAddress socketAddress = new InetSocketAddress(handshakeData.getIp(), port);
player.addProperty(PropertyKey.SOCKET_ADDRESS, socketAddress);

pendingPlayerManager.add(socketAddress, player);

return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player);
} catch (Exception exception) {
exception.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2019-2025 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.player;

import jakarta.inject.Singleton;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.geysermc.floodgate.api.player.FloodgatePlayer;

@Singleton
public class PendingPlayerManager {

private final Map<InetSocketAddress, FloodgatePlayer> pendingByAddress = new ConcurrentHashMap<>();
private final Map<String, FloodgatePlayer> pendingByUsername = new ConcurrentHashMap<>();

/**
* Register a pending player after successful handshake decryption.
* Called from platform-specific handshake handlers.
*
* @param address the connection address
* @param player the FloodgatePlayer instance
*/
public void add(InetSocketAddress address, FloodgatePlayer player) {
pendingByAddress.put(address, player);
pendingByUsername.put(player.getUsername().toLowerCase(), player);
}

/**
* Remove a pending player after login completion or disconnect.
*
* @param address the connection address
*/
public void remove(InetSocketAddress address) {
FloodgatePlayer player = pendingByAddress.remove(address);
if (player != null) {
pendingByUsername.remove(player.getUsername().toLowerCase());
}
}

/**
* Remove a pending player by username.
*
* @param rawUsername the raw username without prefix
*/
public void removeByUsername(String rawUsername) {
if (rawUsername == null) return;
FloodgatePlayer player = pendingByUsername.remove(rawUsername.toLowerCase());
if (player != null) {
pendingByAddress.entrySet().removeIf(entry ->
entry.getValue().getUsername().equalsIgnoreCase(rawUsername));
}
}

/**
* Get a pending player by connection address.
*
* @param address the connection address
* @return the FloodgatePlayer or null if not found
*/
public FloodgatePlayer get(InetSocketAddress address) {
return pendingByAddress.get(address);
}

/**
* Get a pending player by raw username (without prefix).
*
* @param rawUsername the raw username
* @return the FloodgatePlayer or null if not found
*/
public FloodgatePlayer getByUsername(String rawUsername) {
if (rawUsername == null) return null;
return pendingByUsername.get(rawUsername.toLowerCase());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@
package org.geysermc.floodgate.listener;

import com.google.inject.Inject;
import java.net.InetSocketAddress;
import java.util.UUID;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.player.PendingPlayerManager;
import org.geysermc.floodgate.util.LanguageManager;

public final class SpigotListener implements Listener {
@Inject private SimpleFloodgateApi api;
@Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger;
@Inject private PendingPlayerManager pendingPlayerManager;

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
Expand All @@ -60,8 +64,15 @@ public void onPlayerJoin(PlayerJoinEvent event) {
}
}

@EventHandler
public void onPlayerLogin(PlayerLoginEvent event) {
InetSocketAddress address = event.getPlayer().getAddress();
pendingPlayerManager.remove(address);
}

@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
api.playerRemoved(event.getPlayer().getUniqueId());
pendingPlayerManager.remove(event.getPlayer().getAddress());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.PendingPlayerManager;
import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.LanguageManager;
Expand Down Expand Up @@ -117,6 +118,9 @@ public final class VelocityListener {
@Inject
private MojangUtils mojangUtils;

@Inject
private PendingPlayerManager pendingPlayerManager;

@Subscribe(order = PostOrder.EARLY)
public void onPreLogin(PreLoginEvent event) {
FloodgatePlayer player = null;
Expand Down Expand Up @@ -197,10 +201,13 @@ public void onLogin(LoginEvent event) {
languageManager.loadLocale(player.getLanguageCode());
}
}

pendingPlayerManager.remove(event.getPlayer().getRemoteAddress());
}

@Subscribe(order = PostOrder.LAST)
public void onDisconnect(DisconnectEvent event) {
api.playerRemoved(event.getPlayer().getUniqueId());
pendingPlayerManager.remove(event.getPlayer().getRemoteAddress());
}
}