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 62bebaf82..3e8159d50 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 e5100da50..26eacbbd2 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 ec256d690..6f9aec865 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 178657b04..3ca074577 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 baf05da80..d7324a989 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 5945f66ec..6e81c9584 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 067edd31b..2f9ea427c 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 187d0edc3..d73b0fdbf 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 2296f35ea..9fc423e12 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 58b60ed93..0e13cdde1 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 7ca754edd..3501ac707 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/core/src/main/java/org/geysermc/floodgate/util/Metrics.java b/core/src/main/java/org/geysermc/floodgate/util/Metrics.java index 737061932..f338bc74c 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) { 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 178126247..5e48af1d1 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); } }); } 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 5ef02e923..de2ed40cd 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 1c03dc956..0499ce2ef 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 a73b50921..c582ae013 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/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotVersionSpecificMethods.java index 65fa1ac16..17a9d1074 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); 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 6c67a31fc..c3e012404 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 91ced577e..6fdcca3b7 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 f6a8b75f8..e477f470c 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());