From 5226010df6f67a03a813f24e680702089c2131e2 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 20 Feb 2026 00:02:08 +0100 Subject: [PATCH 1/4] Added metric to determine the impact of a breaking local linking change --- .../org/geysermc/floodgate/util/Metrics.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/floodgate/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java index 73706193..f338bc74 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/Metrics.java +++ b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bstats.MetricsBase; @@ -41,6 +42,7 @@ import org.geysermc.event.Listener; import org.geysermc.event.subscribe.Subscribe; import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig.MetricsConfig; @@ -55,7 +57,8 @@ public final class Metrics { @Inject Metrics(FloodgateConfig config, PlatformUtils platformUtils, FloodgateApi api, - @Named("implementationName") String implementationName, FloodgateLogger logger) { + @Named("implementationName") String implementationName, + PlayerLink link, FloodgateLogger logger) { MetricsConfig metricsConfig = config.getMetrics(); @@ -102,6 +105,18 @@ public final class Metrics { new SimplePie("floodgate_version", () -> Constants.VERSION) ); + metricsBase.addCustomChart( + new SimplePie("local_linking_type", () -> { + if (!config.getPlayerLink().isEnableOwnLinking()) { + return "disabled"; + } + if (!Objects.equals(link.getName(), config.getPlayerLink().getType())) { + return "not found (" + config.getPlayerLink().getType() + ")"; + } + return link.getName(); + }) + ); + metricsBase.addCustomChart( new SimplePie("using-backend-server-linking", () -> { if (platformUtils.authType() == AuthType.PROXIED) { From a09f3ee03c98214d98a40dd6544c584e32523c8f Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 26 Feb 2026 19:47:57 +0100 Subject: [PATCH 2/4] Resolve race condition when injecting floodgate handler (#651) --- .../floodgate/inject/spigot/SpigotInjector.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java index 17812624..5e48af1d 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java +++ b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java @@ -110,16 +110,17 @@ public void injectClient(ChannelFuture future) { future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - super.channelRead(ctx, msg); - Channel channel = (Channel) msg; - channel.pipeline().addLast(new ChannelInitializer() { + channel.pipeline().addLast("floodgate-injector", new ChannelInboundHandlerAdapter() { @Override - protected void initChannel(Channel channel) { - injectAddonsCall(channel, false); - addInjectedClient(channel); + public void channelActive(ChannelHandlerContext childCtx) throws Exception { + injectAddonsCall(childCtx.channel(), false); + addInjectedClient(childCtx.channel()); + childCtx.pipeline().remove(this); + super.channelActive(childCtx); } }); + super.channelRead(ctx, msg); } }); } From f4b08d9452de09aa2a1cf799ab641ebc19c4dc2f Mon Sep 17 00:00:00 2001 From: R00tB33rMan <36140389+r00tb33rman@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:08:44 +0100 Subject: [PATCH 3/4] Added a region check for hide/show Player on Folia (#632) Only kept the actual reported fix --- .../util/SpigotVersionSpecificMethods.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java index 65fa1ac1..17a9d107 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.geysermc.floodgate.SpigotPlugin; @@ -123,7 +124,17 @@ public void hideAndShowPlayer(Player on, Player target) { // In Folia we don't have to schedule this as there is no concept of a single main thread. // Instead, we have to schedule the task per player. if (ClassNames.IS_FOLIA) { - on.getScheduler().execute(plugin, () -> hideAndShowPlayer0(on, target), null, 0); + on.getScheduler().execute(plugin, () -> { + // This is a defensive check. The source should always be on the same region, as this is requested + // from that player's context. The target however can be in a different region, which seems to trigger + // ChunkMap$TrackedEntity.updatePlayer in a different thread for a thread-unsafe HashSet. + // This should result in the skin not showing up for those specific players, but it's better than an + // exception. + if (!Bukkit.isOwnedByCurrentRegion(on) || !Bukkit.isOwnedByCurrentRegion(target)) { + return; + } + hideAndShowPlayer0(on, target); + }, null, 0); return; } hideAndShowPlayer0(on, target); From d73192f7706bba8060f6067b9fef58aeccef50bf Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 28 Feb 2026 18:03:53 +0100 Subject: [PATCH 4/4] Fix: Ensure form response handlers aren't called when forms are closed manually (#646) * Clear current forms when closeForm is called * Clear the form for a player, not all queued forms * Ensure closeForm handles null or empty playerToForm list (#1) * Account for concurrency * Moved Floodgate player check away from individual channels * Calculate form id per player instead of globally --------- Co-authored-by: Pierpaolo Coletta Co-authored-by: Tim203 --- .../floodgate/api/player/PropertyKey.java | 10 +++ .../floodgate/listener/BungeeListener.java | 7 ++ .../module/BungeePlatformModule.java | 3 +- .../BungeePluginMessageUtils.java | 16 ++-- .../floodgate/api/SimpleFloodgateApi.java | 18 +++- .../floodgate/player/FloodgatePlayerImpl.java | 24 ++++-- .../pluginmessage/PluginMessageChannel.java | 7 +- .../pluginmessage/channel/FormChannel.java | 85 +++++++++++++------ .../pluginmessage/channel/PacketChannel.java | 8 +- .../pluginmessage/channel/SkinChannel.java | 13 +-- .../channel/TransferChannel.java | 8 +- .../floodgate/listener/SpigotListener.java | 7 ++ .../module/SpigotPlatformModule.java | 4 +- .../SpigotPluginMessageRegistration.java | 18 +++- .../floodgate/listener/VelocityListener.java | 7 ++ .../module/VelocityPlatformModule.java | 5 +- .../VelocityPluginMessageUtils.java | 16 ++-- 17 files changed, 183 insertions(+), 73 deletions(-) diff --git a/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java b/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java index 62bebaf8..3e8159d5 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java +++ b/api/src/main/java/org/geysermc/floodgate/api/player/PropertyKey.java @@ -78,6 +78,16 @@ public Result isAddAllowed(Object obj) { return Result.NOT_EQUALS; } + @Override + public boolean equals(Object obj) { + return obj instanceof PropertyKey && key.equals(((PropertyKey) obj).key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + public enum Result { NOT_EQUALS, INVALID_TAGS, diff --git a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java index e5100da5..26eacbbd 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java +++ b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java @@ -47,6 +47,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.pluginmessage.channel.FormChannel; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinDataImpl; import org.geysermc.floodgate.util.LanguageManager; @@ -82,6 +83,7 @@ public final class BungeeListener implements Listener { private AttributeKey kickMessageAttribute; @Inject private MojangUtils mojangUtils; + @Inject private FormChannel formChannel; @EventHandler(priority = EventPriority.LOWEST) public void onPreLogin(PreLoginEvent event) { @@ -159,6 +161,11 @@ public void onPostLogin(PostLoginEvent event) { @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerDisconnect(PlayerDisconnectEvent event) { + FloodgatePlayer player = api.getPendingRemovePlayer(event.getPlayer().getUniqueId()); + if (player != null) { + formChannel.disconnect(player); + } + api.playerRemoved(event.getPlayer().getUniqueId()); } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java index ec256d69..6f9aec86 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java @@ -111,8 +111,9 @@ public ListenerRegistration listenerRegistration() { @Singleton public PluginMessageUtils pluginMessageUtils( PluginMessageManager manager, + FloodgateApi api, FloodgateLogger logger) { - return new BungeePluginMessageUtils(manager, logger); + return new BungeePluginMessageUtils(manager, api, logger); } @Provides diff --git a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java index 178657b0..3ca07457 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java +++ b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeePluginMessageUtils.java @@ -36,7 +36,9 @@ import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; +import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel.Identity; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel.Result; @@ -44,6 +46,7 @@ @RequiredArgsConstructor public final class BungeePluginMessageUtils extends PluginMessageUtils implements Listener { private final PluginMessageManager pluginMessageManager; + private final FloodgateApi api; private final FloodgateLogger logger; @EventHandler(priority = EventPriority.LOW) @@ -53,23 +56,26 @@ public void onPluginMessage(PluginMessageEvent event) { return; } - UUID sourceUuid = null; - String sourceUsername = null; + FloodgatePlayer fSource = null; Identity sourceIdentity = Identity.UNKNOWN; Connection source = event.getSender(); if (source instanceof ProxiedPlayer) { ProxiedPlayer player = (ProxiedPlayer) source; - sourceUuid = player.getUniqueId(); - sourceUsername = player.getName(); + fSource = api.getPlayer(player.getUniqueId()); sourceIdentity = Identity.PLAYER; + if (fSource == null) { + logKick(source, "Only Floodgate players can send floodgate messages!"); + return; + } + } else if (source instanceof ServerConnection) { sourceIdentity = Identity.SERVER; } Result result = channel.handleProxyCall( - event.getData(), sourceUuid, sourceUsername, sourceIdentity + event.getData(), fSource, sourceIdentity ); event.setCancelled(!result.isAllowed()); diff --git a/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java b/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java index baf05da8..d7324a98 100644 --- a/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java +++ b/core/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java @@ -117,7 +117,13 @@ public boolean isFloodgateId(UUID uuid) { @Override public boolean sendForm(UUID uuid, Form form) { - return pluginMessageManager.getChannel(FormChannel.class).sendForm(uuid, form); + FloodgatePlayer player = getPlayer(uuid); + // Before this check was added, we used to just send the form to the user no matter if they + // were a FloodgatePlayer or not. Keep this since the Floodgate API is deprecated anyway. + if (player == null) { + return true; + } + return pluginMessageManager.getChannel(FormChannel.class).sendForm(player, form); } @Override @@ -127,7 +133,13 @@ public boolean sendForm(UUID uuid, FormBuilder formBuilder) { @Override public boolean closeForm(UUID uuid) { - return pluginMessageManager.getChannel(FormChannel.class).closeForm(uuid); + FloodgatePlayer player = getPlayer(uuid); + // Before this check was added, we used to just send the form to the user no matter if they + // were a FloodgatePlayer or not. Keep this since the Floodgate API is deprecated anyway. + if (player == null) { + return true; + } + return pluginMessageManager.getChannel(FormChannel.class).closeForm(player); } @Override @@ -217,7 +229,7 @@ public void playerRemoved(UUID correctUuid) { } } - private FloodgatePlayer getPendingRemovePlayer(UUID correctUuid) { + public FloodgatePlayer getPendingRemovePlayer(UUID correctUuid) { for (FloodgatePlayer player : pendingRemove.asMap().values()) { if (player.getCorrectUniqueId().equals(correctUuid)) { return player; diff --git a/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java b/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java index 5945f66e..6e81c958 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java +++ b/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java @@ -25,9 +25,10 @@ package org.geysermc.floodgate.player; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -176,8 +177,8 @@ public T removeProperty(PropertyKey key) { @Override public T addProperty(PropertyKey key, Object value) { if (stringToPropertyKey == null) { - stringToPropertyKey = new HashMap<>(); - propertyKeyToValue = new HashMap<>(); + stringToPropertyKey = new ConcurrentHashMap<>(); + propertyKeyToValue = new ConcurrentHashMap<>(); stringToPropertyKey.put(key.getKey(), key); propertyKeyToValue.put(key, value); @@ -202,8 +203,8 @@ public T addProperty(String key, Object value) { PropertyKey propertyKey = new PropertyKey(key, true, true); if (stringToPropertyKey == null) { - stringToPropertyKey = new HashMap<>(); - propertyKeyToValue = new HashMap<>(); + stringToPropertyKey = new ConcurrentHashMap<>(); + propertyKeyToValue = new ConcurrentHashMap<>(); stringToPropertyKey.put(key, propertyKey); propertyKeyToValue.put(propertyKey, value); @@ -223,4 +224,17 @@ public T addProperty(String key, Object value) { return propertyKey; }); } + + public T getOrAddProperty(PropertyKey key, Supplier supplier) { + if (stringToPropertyKey == null) { + stringToPropertyKey = new ConcurrentHashMap<>(); + propertyKeyToValue = new ConcurrentHashMap<>(); + } + + // The hashcode & equals of PropertyKey is based on the hashcode of key. + // stringToPropertyKey is solely for the updatable & removable checks, which we still handle + // correctly by using ifAbsent for both. + stringToPropertyKey.putIfAbsent(key.getKey(), key); + return (T) propertyKeyToValue.computeIfAbsent(key, (unused) -> supplier.get()); + } } diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java index 067edd31..2f9ea427 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/PluginMessageChannel.java @@ -25,10 +25,10 @@ package org.geysermc.floodgate.pluginmessage; -import java.util.UUID; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.api.player.FloodgatePlayer; public interface PluginMessageChannel { @@ -36,12 +36,11 @@ public interface PluginMessageChannel { Result handleProxyCall( byte[] data, - UUID sourceUuid, - String sourceUsername, + FloodgatePlayer source, Identity sourceIdentity ); - Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername); + Result handleServerCall(byte[] data, FloodgatePlayer source); @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java index 187d0edc..d73b0fdb 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/FormChannel.java @@ -27,24 +27,25 @@ import com.google.common.base.Charsets; import com.google.inject.Inject; -import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps; -import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; -import java.util.UUID; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.impl.FormDefinition; import org.geysermc.cumulus.form.impl.FormDefinitions; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.api.player.PropertyKey; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; +import org.geysermc.floodgate.player.FloodgatePlayerImpl; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel; public class FormChannel implements PluginMessageChannel { + private static final PropertyKey PROPERTY_LAST_FORM_ID = new PropertyKey("floodgate:last_form_id", true, false); + private static final PropertyKey PROPERTY_ACTIVE_FORMS = new PropertyKey("floodgate:active_forms", true, true); + private final FormDefinitions formDefinitions = FormDefinitions.instance(); - private final Short2ObjectMap
storedForms = - Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>()); - private final AtomicInteger nextFormId = new AtomicInteger(0); @Inject private PluginMessageUtils pluginMessageUtils; @Inject private FloodgateConfig config; @@ -58,8 +59,7 @@ public String getIdentifier() { @Override public Result handleProxyCall( byte[] data, - UUID sourceUuid, - String sourceUsername, + FloodgatePlayer source, Identity sourceIdentity ) { if (sourceIdentity == Identity.SERVER) { @@ -79,35 +79,54 @@ public Result handleProxyCall( return Result.forward(); } - if (!callResponseConsumer(data)) { + if (!callResponseConsumer(source, data)) { logger.error("Couldn't find stored form with id {} for player {}", - formId, sourceUsername); + formId, source.getCorrectUsername()); } } return Result.handled(); } @Override - public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) { - callResponseConsumer(data); + public Result handleServerCall(byte[] data, FloodgatePlayer source) { + if (!callResponseConsumer(source, data)) { + logger.error("Couldn't find stored form for player {}", source.getCorrectUsername()); + } return Result.handled(); } - public boolean closeForm(UUID player) { - return pluginMessageUtils.sendMessage(player, getIdentifier(), new byte[0]); + public boolean closeForm(FloodgatePlayer player) { + closeForms0(player); + return pluginMessageUtils.sendMessage(player.getCorrectUniqueId(), getIdentifier(), new byte[0]); } - public boolean sendForm(UUID player, Form form) { - byte[] formData = createFormData(form); - return pluginMessageUtils.sendMessage(player, getIdentifier(), formData); + private void closeForms0(FloodgatePlayer player) { + Map forms = player.removeProperty(PROPERTY_ACTIVE_FORMS); + if (forms != null && !forms.isEmpty()) { + for (Form form : forms.values()) { + try { + formDefinitions.definitionFor(form).handleFormResponse(form, ""); + } catch (Exception e) { + logger.error("Error while closing form!", e); + } + } + } } - public byte[] createFormData(Form form) { - short formId = getNextFormId(); + public boolean sendForm(FloodgatePlayer player, Form form) { + byte[] formData = createFormData(player, form); + return pluginMessageUtils.sendMessage(player.getCorrectUniqueId(), getIdentifier(), formData); + } + + public byte[] createFormData(FloodgatePlayer player, Form form) { + short formId = getNextFormId(player); if (config.isProxy()) { - formId |= 0x8000; + formId |= (short) 0x8000; } - storedForms.put(formId, form); + + ((FloodgatePlayerImpl) player) + .getOrAddProperty(PROPERTY_ACTIVE_FORMS, ConcurrentHashMap::new) + .put(formId, form); FormDefinition definition = formDefinitions.definitionFor(form); @@ -124,8 +143,15 @@ public byte[] createFormData(Form form) { return data; } - protected boolean callResponseConsumer(byte[] data) { - Form storedForm = storedForms.remove(getFormId(data)); + protected boolean callResponseConsumer(FloodgatePlayer player, byte[] data) { + short formId = getFormId(data); + + Map forms = player.getProperty(PROPERTY_ACTIVE_FORMS); + if (forms == null) { + return false; + } + + Form storedForm = forms.remove(formId); if (storedForm != null) { String responseData = new String(data, 2, data.length - 2, Charsets.UTF_8); try { @@ -139,11 +165,18 @@ protected boolean callResponseConsumer(byte[] data) { return false; } - protected short getFormId(byte[] data) { + public void disconnect(FloodgatePlayer player) { + closeForms0(player); + } + + private short getFormId(byte[] data) { return (short) ((data[0] & 0xFF) << 8 | data[1] & 0xFF); } - protected short getNextFormId() { + private short getNextFormId(FloodgatePlayer player) { + AtomicInteger nextFormId = + ((FloodgatePlayerImpl) player).getOrAddProperty(PROPERTY_LAST_FORM_ID, AtomicInteger::new); + // signed bit is used to check if the form is from a proxy or a server return (short) nextFormId.getAndUpdate( (number) -> number == Short.MAX_VALUE ? 0 : number + 1); diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java index 2296f35e..9fc423e1 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/PacketChannel.java @@ -28,6 +28,7 @@ import com.google.inject.Inject; import java.util.UUID; import org.geysermc.floodgate.api.UnsafeFloodgateApi; +import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel; @@ -42,8 +43,7 @@ public String getIdentifier() { @Override public Result handleProxyCall( byte[] data, - UUID sourceUuid, - String sourceUsername, + FloodgatePlayer source, Identity sourceIdentity ) { if (sourceIdentity == Identity.SERVER) { @@ -52,14 +52,14 @@ public Result handleProxyCall( } if (sourceIdentity == Identity.PLAYER) { - return handleServerCall(data, sourceUuid, sourceUsername); + return handleServerCall(data, source); } return Result.handled(); } @Override - public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) { + public Result handleServerCall(byte[] data, FloodgatePlayer source) { return Result.kick("Cannot send packets from Geyser/Floodgate to Floodgate"); } diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java index 58b60ed9..0e13cdde 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/SkinChannel.java @@ -27,7 +27,6 @@ import com.google.inject.Inject; import java.nio.charset.StandardCharsets; -import java.util.UUID; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.PropertyKey; @@ -50,13 +49,12 @@ public String getIdentifier() { @Override public Result handleProxyCall( byte[] data, - UUID sourceUuid, - String sourceUsername, + FloodgatePlayer source, Identity sourceIdentity ) { // we can only get skins from Geyser (client) if (sourceIdentity == Identity.PLAYER) { - Result result = handleServerCall(data, sourceUuid, sourceUsername); + Result result = handleServerCall(data, source); // aka translate 'handled' into 'forward' when send-floodgate-data is enabled if (!result.isAllowed() && result.getReason() == null) { if (config.isProxy() && ((ProxyFloodgateConfig) config).isSendFloodgateData()) { @@ -74,12 +72,7 @@ public Result handleProxyCall( } @Override - public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) { - FloodgatePlayer floodgatePlayer = api.getPlayer(playerUuid); - if (floodgatePlayer == null) { - return Result.kick("Player sent skins data for a non-Floodgate player"); - } - + public Result handleServerCall(byte[] data, FloodgatePlayer floodgatePlayer) { String message = new String(data, StandardCharsets.UTF_8); String[] split = message.split("\0"); diff --git a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java index 7ca754ed..3501ac70 100644 --- a/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java +++ b/core/src/main/java/org/geysermc/floodgate/pluginmessage/channel/TransferChannel.java @@ -28,6 +28,7 @@ import com.google.inject.Inject; import java.nio.charset.StandardCharsets; import java.util.UUID; +import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel; @@ -42,8 +43,7 @@ public String getIdentifier() { @Override public Result handleProxyCall( byte[] data, - UUID sourceUuid, - String sourceUsername, + FloodgatePlayer source, Identity sourceIdentity ) { if (sourceIdentity == Identity.SERVER) { @@ -52,14 +52,14 @@ public Result handleProxyCall( } if (sourceIdentity == Identity.PLAYER) { - handleServerCall(data, sourceUuid, sourceUsername); + handleServerCall(data, source); } return Result.handled(); } @Override - public Result handleServerCall(byte[] data, UUID playerUuid, String playerUsername) { + public Result handleServerCall(byte[] data, FloodgatePlayer source) { return Result.kick("I'm sorry, I'm unable to transfer a server :("); } diff --git a/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java index 5ef02e92..de2ed40c 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java +++ b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java @@ -35,6 +35,7 @@ import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.pluginmessage.channel.FormChannel; import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.MojangUtils; @@ -46,6 +47,7 @@ public final class SpigotListener implements Listener { @Inject private MojangUtils mojangUtils; @Inject private SkinApplier skinApplier; + @Inject private FormChannel formChannel; @EventHandler(priority = EventPriority.MONITOR) public void onPlayerJoin(PlayerJoinEvent event) { @@ -78,6 +80,11 @@ public void onPlayerJoin(PlayerJoinEvent event) { @EventHandler(priority = EventPriority.MONITOR) public void onPlayerQuit(PlayerQuitEvent event) { + FloodgatePlayer player = api.getPendingRemovePlayer(event.getPlayer().getUniqueId()); + if (player != null) { + formChannel.disconnect(player); + } + api.playerRemoved(event.getPlayer().getUniqueId()); } } diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java index 1c03dc95..0499ce2e 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java @@ -135,8 +135,8 @@ public PluginMessageUtils pluginMessageUtils() { @Provides @Singleton - public PluginMessageRegistration pluginMessageRegister() { - return new SpigotPluginMessageRegistration(plugin); + public PluginMessageRegistration pluginMessageRegister(FloodgateApi api) { + return new SpigotPluginMessageRegistration(plugin, api); } @Provides diff --git a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java index a73b5092..c582ae01 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotPluginMessageRegistration.java @@ -28,10 +28,14 @@ import lombok.RequiredArgsConstructor; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.messaging.Messenger; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.pluginmessage.PluginMessageChannel.Result; @RequiredArgsConstructor public class SpigotPluginMessageRegistration implements PluginMessageRegistration { private final JavaPlugin plugin; + private final FloodgateApi api; @Override public void register(PluginMessageChannel channel) { @@ -40,8 +44,18 @@ public void register(PluginMessageChannel channel) { messenger.registerIncomingPluginChannel( plugin, channel.getIdentifier(), - (channel1, player, message) -> - channel.handleServerCall(message, player.getUniqueId(), player.getName())); + (channel1, player, message) -> { + FloodgatePlayer fPlayer = api.getPlayer(player.getUniqueId()); + if (fPlayer == null) { + player.kickPlayer("Only Floodgate players can send floodgate messages!"); + return; + } + + Result result = channel.handleServerCall(message, fPlayer); + if (!result.isAllowed() && result.getReason() != null) { + player.kickPlayer(result.getReason()); + } + }); messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier()); } diff --git a/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java index 6c67a31f..c3e01240 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java +++ b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java @@ -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.pluginmessage.channel.FormChannel; import org.geysermc.floodgate.skin.SkinDataImpl; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.LanguageManager; @@ -116,6 +117,7 @@ public final class VelocityListener { @Inject private MojangUtils mojangUtils; + @Inject private FormChannel formChannel; @Subscribe(order = PostOrder.EARLY) public void onPreLogin(PreLoginEvent event) { @@ -201,6 +203,11 @@ public void onLogin(LoginEvent event) { @Subscribe(order = PostOrder.LAST) public void onDisconnect(DisconnectEvent event) { + FloodgatePlayer player = api.getPendingRemovePlayer(event.getPlayer().getUniqueId()); + if (player != null) { + formChannel.disconnect(player); + } + api.playerRemoved(event.getPlayer().getUniqueId()); } } diff --git a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java index 91ced577..6fdcca3b 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java @@ -35,6 +35,7 @@ import com.velocitypowered.api.proxy.ProxyServer; import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.VelocityPlugin; +import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.velocity.VelocityInjector; @@ -102,8 +103,8 @@ public ListenerRegistration listenerRegistration( @Provides @Singleton - public PluginMessageUtils pluginMessageUtils(PluginMessageManager pluginMessageManager) { - return new VelocityPluginMessageUtils(pluginMessageManager); + public PluginMessageUtils pluginMessageUtils(PluginMessageManager pluginMessageManager, FloodgateApi api) { + return new VelocityPluginMessageUtils(pluginMessageManager, api); } @Provides diff --git a/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java b/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java index f6a8b75f..e477f470 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java +++ b/velocity/src/main/java/org/geysermc/floodgate/pluginmessage/VelocityPluginMessageUtils.java @@ -38,7 +38,9 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.Component; +import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel.Identity; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel.Result; @@ -46,6 +48,7 @@ @RequiredArgsConstructor public class VelocityPluginMessageUtils extends PluginMessageUtils { private final PluginMessageManager pluginMessageManager; + private final FloodgateApi api; private ProxyServer proxy; private FloodgateLogger logger; @@ -63,23 +66,26 @@ public void onPluginMessage(PluginMessageEvent event) { return; } - UUID sourceUuid = null; - String sourceUsername = null; + FloodgatePlayer fSource = null; Identity sourceIdentity = Identity.UNKNOWN; ChannelMessageSource source = event.getSource(); if (source instanceof Player) { Player player = (Player) source; - sourceUuid = player.getUniqueId(); - sourceUsername = player.getUsername(); + fSource = api.getPlayer(player.getUniqueId()); sourceIdentity = Identity.PLAYER; + if (fSource == null) { + logKick(source, "Only Floodgate players can send floodgate messages!"); + return; + } + } else if (source instanceof ServerConnection) { sourceIdentity = Identity.SERVER; } Result result = channel.handleProxyCall( - event.getData(), sourceUuid, sourceUsername, sourceIdentity + event.getData(), fSource, sourceIdentity ); event.setResult(result.isAllowed() ? ForwardResult.forward() : ForwardResult.handled());