From 913c85c154f8c75d198988732c1ab29ae5fb18d4 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 28 Dec 2022 02:11:26 +0100 Subject: [PATCH] Added a SkinApplyEvent that can cancel/edit the to be applied skin --- .../geysermc/floodgate/api/FloodgateApi.java | 5 + .../floodgate/api/InstanceHolder.java | 13 +- .../api/event/FloodgateEventBus.java | 31 +++++ .../api/event/FloodgateSubscriber.java | 31 +++++ .../api/event/skin/SkinApplyEvent.java | 74 +++++++++++ .../floodgate/listener/BungeeListener.java | 6 +- .../module/BungeePlatformModule.java | 7 +- .../pluginmessage/BungeeSkinApplier.java | 120 +++++++----------- .../geysermc/floodgate/FloodgatePlatform.java | 13 +- .../geysermc/floodgate/event/EventBus.java | 7 +- .../floodgate/event/EventSubscriber.java | 3 +- .../{ => lifecycle}/PostEnableEvent.java | 2 +- .../event/{ => lifecycle}/ShutdownEvent.java | 2 +- .../event/skin/SkinApplyEventImpl.java | 67 ++++++++++ .../floodgate/link/CommonPlayerLink.java | 2 +- .../floodgate/link/PlayerLinkHolder.java | 2 +- .../floodgate/module/CommonModule.java | 16 +-- .../geysermc/floodgate/news/NewsChecker.java | 2 +- .../floodgate/player/FloodgatePlayerImpl.java | 3 +- .../pluginmessage/channel/SkinChannel.java | 13 +- .../geysermc/floodgate/skin/SkinApplier.java | 13 +- .../skin/{SkinData.java => SkinDataImpl.java} | 35 +++-- .../floodgate/skin/SkinUploadManager.java | 13 +- .../floodgate/skin/SkinUploadSocket.java | 17 ++- .../floodgate/util/PostEnableMessages.java | 2 +- .../module/SpigotPlatformModule.java | 8 +- .../pluginmessage/SpigotSkinApplier.java | 91 ++++++------- .../module/VelocityPlatformModule.java | 7 +- .../floodgate/util/VelocitySkinApplier.java | 55 +++++--- 29 files changed, 425 insertions(+), 235 deletions(-) create mode 100644 api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java create mode 100644 api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java create mode 100644 api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java rename core/src/main/java/org/geysermc/floodgate/event/{ => lifecycle}/PostEnableEvent.java (96%) rename core/src/main/java/org/geysermc/floodgate/event/{ => lifecycle}/ShutdownEvent.java (96%) create mode 100644 core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java rename core/src/main/java/org/geysermc/floodgate/skin/{SkinData.java => SkinDataImpl.java} (66%) diff --git a/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java b/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java index 0d2b0efd..999935ab 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java +++ b/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java @@ -30,6 +30,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; 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; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.unsafe.Unsafe; @@ -148,6 +149,10 @@ public interface FloodgateApi { */ CompletableFuture getGamertagFor(long xuid); + default FloodgateEventBus getEventBus() { + return InstanceHolder.getEventBus(); + } + /** * Returns the instance that manages all the linking. */ diff --git a/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java b/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java index d0c6af24..159310ee 100644 --- a/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java +++ b/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java @@ -27,6 +27,7 @@ package org.geysermc.floodgate.api; import java.util.UUID; import lombok.Getter; +import org.geysermc.floodgate.api.event.FloodgateEventBus; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.link.PlayerLink; @@ -35,6 +36,7 @@ import org.geysermc.floodgate.api.packet.PacketHandlers; public final class InstanceHolder { @Getter private static FloodgateApi api; @Getter private static PlayerLink playerLink; + @Getter private static FloodgateEventBus eventBus; @Getter private static PlatformInjector injector; @Getter private static PacketHandlers packetHandlers; @@ -44,11 +46,12 @@ public final class InstanceHolder { public static boolean set( FloodgateApi floodgateApi, PlayerLink link, + FloodgateEventBus floodgateEventBus, PlatformInjector platformInjector, PacketHandlers packetHandlers, HandshakeHandlers handshakeHandlers, - UUID key) { - + UUID key + ) { if (storedKey != null) { if (!storedKey.equals(key)) { return false; @@ -59,14 +62,10 @@ public final class InstanceHolder { api = floodgateApi; playerLink = link; + eventBus = floodgateEventBus; injector = platformInjector; InstanceHolder.packetHandlers = packetHandlers; InstanceHolder.handshakeHandlers = handshakeHandlers; return true; } - - @SuppressWarnings("unchecked") - public static T castApi(Class cast) { - return (T) api; - } } diff --git a/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java new file mode 100644 index 00000000..935cde21 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateEventBus.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2022 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.api.event; + +import org.geysermc.event.bus.EventBus; + +public interface FloodgateEventBus extends EventBus> { +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java new file mode 100644 index 00000000..0d9ab473 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/event/FloodgateSubscriber.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2022 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.api.event; + +import org.geysermc.event.subscribe.Subscriber; + +public interface FloodgateSubscriber extends Subscriber { +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java b/api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java new file mode 100644 index 00000000..7a4b16c1 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/event/skin/SkinApplyEvent.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019-2022 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.api.event.skin; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.returnsreceiver.qual.This; +import org.geysermc.event.Cancellable; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +/** + * An event that's fired when Floodgate receives a player skin. The event will be cancelled by + * default when hasSkin is true, as Floodgate by default only applies skins when the player has no + * skin applied yet. + */ +public interface SkinApplyEvent extends Cancellable { + /** + * Returns the player that will receive the skin. + */ + @NonNull FloodgatePlayer player(); + + /** + * Returns the skin texture currently applied to the player. + */ + @Nullable SkinData currentSkin(); + + /** + * Returns the skin texture to be applied to the player. + */ + @NonNull SkinData newSkin(); + + /** + * Sets the skin texture to be applied to the player + * + * @param skinData the skin to apply + * @return this + */ + @This SkinApplyEvent newSkin(@NonNull SkinData skinData); + + interface SkinData { + /** + * Returns the value of the skin texture. + */ + @NonNull String value(); + + /** + * Returns the signature of the skin texture. + */ + @NonNull String signature(); + } +} 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 5aa717f8..9723cf58 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java +++ b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java @@ -48,7 +48,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.skin.SkinApplier; -import org.geysermc.floodgate.skin.SkinData; +import org.geysermc.floodgate.skin.SkinDataImpl; import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.ReflectionUtils; @@ -130,8 +130,8 @@ public final class BungeeListener implements Listener { // To fix the February 2 2022 Mojang authentication changes if (!config.isSendFloodgateData()) { FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId()); - if (player != null && !player.isLinked() && !skinApplier.hasSkin(player)) { - skinApplier.applySkin(player, new SkinData("", "")); + if (player != null && !player.isLinked()) { + skinApplier.applySkin(player, new SkinDataImpl("", "")); } } } 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 e66fc138..4943d88c 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java @@ -70,6 +70,7 @@ public final class BungeePlatformModule extends AbstractModule { bind(PlatformUtils.class).to(BungeePlatformUtils.class); bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger()); bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class); + bind(SkinApplier.class).to(BungeeSkinApplier.class); } @Provides @@ -121,12 +122,6 @@ public final class BungeePlatformModule extends AbstractModule { return new BungeePluginMessageRegistration(); } - @Provides - @Singleton - public SkinApplier skinApplier(FloodgateLogger logger) { - return new BungeeSkinApplier(logger); - } - /* DebugAddon / PlatformInjector */ diff --git a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java index 3e360615..ce6105ec 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java +++ b/bungee/src/main/java/org/geysermc/floodgate/pluginmessage/BungeeSkinApplier.java @@ -26,68 +26,55 @@ package org.geysermc.floodgate.pluginmessage; import static com.google.common.base.Preconditions.checkNotNull; -import static org.geysermc.floodgate.util.ReflectionUtils.getConstructor; import static org.geysermc.floodgate.util.ReflectionUtils.getFieldOfType; -import static org.geysermc.floodgate.util.ReflectionUtils.getMethodByName; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; +import com.google.inject.Inject; +import com.google.inject.Singleton; import java.lang.reflect.Field; -import java.lang.reflect.Method; -import lombok.RequiredArgsConstructor; +import java.util.ArrayList; +import java.util.List; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.protocol.Property; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.event.EventBus; +import org.geysermc.floodgate.event.skin.SkinApplyEventImpl; import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.skin.SkinData; +import org.geysermc.floodgate.skin.SkinDataImpl; import org.geysermc.floodgate.util.ReflectionUtils; -@RequiredArgsConstructor +@Singleton public final class BungeeSkinApplier implements SkinApplier { - private static final Constructor LOGIN_RESULT_CONSTRUCTOR; private static final Field LOGIN_RESULT_FIELD; - private static final Method SET_PROPERTIES_METHOD; - - private static final Class PROPERTY_CLASS; - private static final Constructor PROPERTY_CONSTRUCTOR; static { - PROPERTY_CLASS = ReflectionUtils.getClassOrFallbackPrefixed( - "protocol.Property", "connection.LoginResult$Property" - ); - - LOGIN_RESULT_CONSTRUCTOR = getConstructor( - LoginResult.class, true, - String.class, String.class, Array.newInstance(PROPERTY_CLASS, 0).getClass() - ); - LOGIN_RESULT_FIELD = getFieldOfType(InitialHandler.class, LoginResult.class); checkNotNull(LOGIN_RESULT_FIELD, "LoginResult field cannot be null"); - - SET_PROPERTIES_METHOD = getMethodByName(LoginResult.class, "setProperties", true); - - PROPERTY_CONSTRUCTOR = ReflectionUtils.getConstructor( - PROPERTY_CLASS, true, - String.class, String.class, String.class - ); - checkNotNull(PROPERTY_CONSTRUCTOR, "Property constructor cannot be null"); } - private final FloodgateLogger logger; + private final ProxyServer server = ProxyServer.getInstance(); + + @Inject private EventBus eventBus; + @Inject private FloodgateLogger logger; @Override - public void applySkin(FloodgatePlayer uuid, SkinData skinData) { - ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId()); + public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { + ProxiedPlayer player = server.getPlayer(floodgatePlayer.getCorrectUniqueId()); if (player == null) { return; } - InitialHandler handler = getHandler(player); - if (handler == null) { + InitialHandler handler; + try { + handler = (InitialHandler) player.getPendingConnection(); + } catch (Exception exception) { + logger.error("Incompatible Bungeecord fork detected", exception); return; } @@ -95,57 +82,46 @@ public final class BungeeSkinApplier implements SkinApplier { // expected to be null since LoginResult is the data from hasJoined, // which Floodgate players don't have if (loginResult == null) { - // id and name are unused and properties will be overridden - loginResult = (LoginResult) ReflectionUtils.newInstance( - LOGIN_RESULT_CONSTRUCTOR, null, null, null - ); + // id and name are unused + loginResult = new LoginResult(null, null, new Property[0]); ReflectionUtils.setValue(handler, LOGIN_RESULT_FIELD, loginResult); } - Object property = ReflectionUtils.newInstance( - PROPERTY_CONSTRUCTOR, - "textures", skinData.getValue(), skinData.getSignature() - ); + Property[] properties = loginResult.getProperties(); - Object propertyArray = Array.newInstance(PROPERTY_CLASS, 1); - Array.set(propertyArray, 0, property); + SkinData currentSkin = currentSkin(properties); - ReflectionUtils.invoke(loginResult, SET_PROPERTIES_METHOD, propertyArray); + SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData); + event.setCancelled(floodgatePlayer.isLinked()); + + eventBus.fire(event); + + if (event.isCancelled()) { + return; + } + + loginResult.setProperties(replaceSkin(properties, event.newSkin())); } - @Override - public boolean hasSkin(FloodgatePlayer fPlayer) { - ProxiedPlayer player = ProxyServer.getInstance().getPlayer(fPlayer.getCorrectUniqueId()); - if (player == null) { - return false; - } - - InitialHandler handler = getHandler(player); - if (handler == null) { - return false; - } - - LoginResult loginResult = handler.getLoginProfile(); - if (loginResult == null) { - return false; - } - - for (Property property : loginResult.getProperties()) { + private SkinData currentSkin(Property[] properties) { + for (Property property : properties) { if (property.getName().equals("textures")) { if (!property.getValue().isEmpty()) { - return true; + return new SkinDataImpl(property.getValue(), property.getSignature()); } } } - return false; + return null; } - private InitialHandler getHandler(ProxiedPlayer player) { - try { - return (InitialHandler) player.getPendingConnection(); - } catch (Exception exception) { - logger.error("Incompatible Bungeecord fork detected", exception); - return null; + private Property[] replaceSkin(Property[] properties, SkinData skinData) { + List list = new ArrayList<>(); + for (Property property : properties) { + if (!property.getName().equals("textures")) { + list.add(property); + } } + list.add(new Property("textures", skinData.value(), skinData.signature())); + return list.toArray(new Property[0]); } } diff --git a/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java b/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java index a02b52a1..70487093 100644 --- a/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java +++ b/core/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java @@ -31,14 +31,15 @@ import com.google.inject.Module; import java.util.UUID; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.InstanceHolder; +import org.geysermc.floodgate.api.event.FloodgateEventBus; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.event.EventBus; -import org.geysermc.floodgate.event.PostEnableEvent; -import org.geysermc.floodgate.event.ShutdownEvent; +import org.geysermc.floodgate.event.lifecycle.PostEnableEvent; +import org.geysermc.floodgate.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.module.PostInitializeModule; public class FloodgatePlatform { @@ -52,9 +53,13 @@ public class FloodgatePlatform { public void init( FloodgateApi api, PlayerLink link, + FloodgateEventBus eventBus, PacketHandlers packetHandlers, - HandshakeHandlers handshakeHandlers) { - InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY); + HandshakeHandlers handshakeHandlers + ) { + InstanceHolder.set( + api, link, eventBus, this.injector, packetHandlers, handshakeHandlers, KEY + ); } public void enable(Module... postInitializeModules) throws RuntimeException { diff --git a/core/src/main/java/org/geysermc/floodgate/event/EventBus.java b/core/src/main/java/org/geysermc/floodgate/event/EventBus.java index abef2db2..c8e6f9bc 100644 --- a/core/src/main/java/org/geysermc/floodgate/event/EventBus.java +++ b/core/src/main/java/org/geysermc/floodgate/event/EventBus.java @@ -25,6 +25,7 @@ package org.geysermc.floodgate.event; +import com.google.inject.Singleton; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.checkerframework.checker.nullness.qual.NonNull; @@ -32,9 +33,13 @@ import org.geysermc.event.PostOrder; import org.geysermc.event.bus.impl.EventBusImpl; 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; +@Singleton @SuppressWarnings("unchecked") -public final class EventBus extends EventBusImpl> { +public final class EventBus extends EventBusImpl> + implements FloodgateEventBus { @Override protected > B makeSubscription( @NonNull Class eventClass, diff --git a/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java b/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java index e37d2fa1..f01c209b 100644 --- a/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java +++ b/core/src/main/java/org/geysermc/floodgate/event/EventSubscriber.java @@ -30,8 +30,9 @@ import java.util.function.Consumer; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.event.PostOrder; import org.geysermc.event.subscribe.impl.SubscriberImpl; +import org.geysermc.floodgate.api.event.FloodgateSubscriber; -public final class EventSubscriber extends SubscriberImpl { +public final class EventSubscriber extends SubscriberImpl implements FloodgateSubscriber { EventSubscriber( @NonNull Class eventClass, @NonNull Consumer handler, diff --git a/core/src/main/java/org/geysermc/floodgate/event/PostEnableEvent.java b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java similarity index 96% rename from core/src/main/java/org/geysermc/floodgate/event/PostEnableEvent.java rename to core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java index 0ae55e26..f274ff16 100644 --- a/core/src/main/java/org/geysermc/floodgate/event/PostEnableEvent.java +++ b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/PostEnableEvent.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.event; +package org.geysermc.floodgate.event.lifecycle; public class PostEnableEvent { } diff --git a/core/src/main/java/org/geysermc/floodgate/event/ShutdownEvent.java b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/ShutdownEvent.java similarity index 96% rename from core/src/main/java/org/geysermc/floodgate/event/ShutdownEvent.java rename to core/src/main/java/org/geysermc/floodgate/event/lifecycle/ShutdownEvent.java index 345c9f30..30e782c5 100644 --- a/core/src/main/java/org/geysermc/floodgate/event/ShutdownEvent.java +++ b/core/src/main/java/org/geysermc/floodgate/event/lifecycle/ShutdownEvent.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Floodgate */ -package org.geysermc.floodgate.event; +package org.geysermc.floodgate.event.lifecycle; public class ShutdownEvent { } diff --git a/core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java b/core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java new file mode 100644 index 00000000..52148986 --- /dev/null +++ b/core/src/main/java/org/geysermc/floodgate/event/skin/SkinApplyEventImpl.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019-2022 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.event.skin; + +import java.util.Objects; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.event.util.AbstractCancellable; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +public class SkinApplyEventImpl extends AbstractCancellable implements SkinApplyEvent { + private final FloodgatePlayer player; + private final SkinData currentSkin; + private SkinData newSkin; + + public SkinApplyEventImpl( + @NonNull FloodgatePlayer player, + @Nullable SkinData currentSkin, + @NonNull SkinData newSkin + ) { + this.player = Objects.requireNonNull(player); + this.currentSkin = currentSkin; + this.newSkin = Objects.requireNonNull(newSkin); + } + + @Override + public @NonNull FloodgatePlayer player() { + return player; + } + + public @Nullable SkinData currentSkin() { + return currentSkin; + } + + public @NonNull SkinData newSkin() { + return newSkin; + } + + public SkinApplyEventImpl newSkin(@NonNull SkinData skinData) { + this.newSkin = Objects.requireNonNull(skinData); + return this; + } +} diff --git a/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java b/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java index f1bfb912..9324a097 100644 --- a/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java +++ b/core/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java @@ -43,7 +43,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.database.config.DatabaseConfig; import org.geysermc.floodgate.database.config.DatabaseConfigLoader; -import org.geysermc.floodgate.event.ShutdownEvent; +import org.geysermc.floodgate.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.util.InjectorHolder; @Listener diff --git a/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java b/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java index 618c9b27..a91ba679 100644 --- a/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java +++ b/core/src/main/java/org/geysermc/floodgate/link/PlayerLinkHolder.java @@ -51,7 +51,7 @@ 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.PlayerLinkConfig; -import org.geysermc.floodgate.event.ShutdownEvent; +import org.geysermc.floodgate.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.InjectorHolder; import org.geysermc.floodgate.util.Utils; diff --git a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java index c597922c..500cad26 100644 --- a/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java +++ b/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java @@ -43,6 +43,7 @@ import org.geysermc.event.PostOrder; import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.event.FloodgateEventBus; import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.link.PlayerLink; @@ -57,14 +58,13 @@ import org.geysermc.floodgate.crypto.Base64Topping; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.KeyProducer; import org.geysermc.floodgate.event.EventBus; -import org.geysermc.floodgate.event.ShutdownEvent; +import org.geysermc.floodgate.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.event.util.ListenerAnnotationMatcher; import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.link.PlayerLinkHolder; import org.geysermc.floodgate.packet.PacketHandlersImpl; import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.pluginmessage.PluginMessageManager; -import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinUploadManager; import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.HttpClient; @@ -77,6 +77,7 @@ public class CommonModule extends AbstractModule { @Override protected void configure() { bind(EventBus.class).toInstance(eventBus); + bind(FloodgateEventBus.class).to(EventBus.class); // register every class that has the Listener annotation bindListener(new ListenerAnnotationMatcher(), new TypeListener() { @Override @@ -164,17 +165,6 @@ public class CommonModule extends AbstractModule { return new PluginMessageManager(); } - @Provides - @Singleton - public SkinUploadManager skinUploadManager( - FloodgateApi api, - SkinApplier skinApplier, - FloodgateLogger logger) { - SkinUploadManager manager = new SkinUploadManager(api, skinApplier, logger); - eventBus.register(manager); - return manager; - } - @Provides @Singleton @Named("gitBranch") diff --git a/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java b/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java index cdbeed07..bff279d0 100644 --- a/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java +++ b/core/src/main/java/org/geysermc/floodgate/news/NewsChecker.java @@ -41,7 +41,7 @@ import org.geysermc.event.Listener; import org.geysermc.event.subscribe.Subscribe; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.command.util.Permission; -import org.geysermc.floodgate.event.ShutdownEvent; +import org.geysermc.floodgate.event.lifecycle.ShutdownEvent; import org.geysermc.floodgate.news.data.AnnouncementData; import org.geysermc.floodgate.news.data.BuildSpecificData; import org.geysermc.floodgate.news.data.CheckAfterData; 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 acfcab75..5945f66e 100644 --- a/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java +++ b/core/src/main/java/org/geysermc/floodgate/player/FloodgatePlayerImpl.java @@ -32,7 +32,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.api.FloodgateApi; -import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.handshake.HandshakeData; import org.geysermc.floodgate.api.player.FloodgatePlayer; @@ -72,7 +71,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer { private Map stringToPropertyKey; static FloodgatePlayerImpl from(BedrockData data, HandshakeData handshakeData) { - FloodgateApi api = InstanceHolder.getApi(); + FloodgateApi api = FloodgateApi.getInstance(); UUID javaUniqueId = Utils.getJavaUuid(data.getXuid()); 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 02f0161b..8e9e64b0 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 @@ -25,7 +25,6 @@ package org.geysermc.floodgate.pluginmessage.channel; -import com.google.gson.JsonObject; import com.google.inject.Inject; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -36,7 +35,7 @@ import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel; import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.skin.SkinData; +import org.geysermc.floodgate.skin.SkinDataImpl; public class SkinChannel implements PluginMessageChannel { @Inject private FloodgateApi api; @@ -89,18 +88,10 @@ public class SkinChannel implements PluginMessageChannel { return Result.kick("Got invalid skin data"); } - if (floodgatePlayer.isLinked() || skinApplier.hasSkin(floodgatePlayer)) { - return Result.handled(); - } - String value = split[0]; String signature = split[1]; - JsonObject result = new JsonObject(); - result.addProperty("value", value); - result.addProperty("signature", signature); - - SkinData skinData = new SkinData(value, signature); + SkinDataImpl skinData = new SkinDataImpl(value, signature); floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, skinData); skinApplier.applySkin(floodgatePlayer, skinData); diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java index 93dc3cb4..c8e7684c 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinApplier.java @@ -25,6 +25,8 @@ package org.geysermc.floodgate.skin; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; import org.geysermc.floodgate.api.player.FloodgatePlayer; public interface SkinApplier { @@ -34,14 +36,5 @@ public interface SkinApplier { * @param floodgatePlayer player to apply skin to * @param skinData data for skin to apply to player */ - void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData); - - /** - * Check if a {@link FloodgatePlayer player} currently - * has a skin applied. - * - * @param floodgatePlayer player to check skin of - * @return if player has a skin - */ - boolean hasSkin(FloodgatePlayer floodgatePlayer); + void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData); } diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinData.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinDataImpl.java similarity index 66% rename from core/src/main/java/org/geysermc/floodgate/skin/SkinData.java rename to core/src/main/java/org/geysermc/floodgate/skin/SkinDataImpl.java index 84162710..9f44af79 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinData.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinDataImpl.java @@ -26,22 +26,33 @@ package org.geysermc.floodgate.skin; import com.google.gson.JsonObject; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import java.util.Objects; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; -@Getter -@RequiredArgsConstructor -public class SkinData { +public class SkinDataImpl implements SkinData { private final String value; private final String signature; + public SkinDataImpl(@NonNull String value, @NonNull String signature) { + this.value = Objects.requireNonNull(value); + this.signature = Objects.requireNonNull(signature); + } + public static SkinData from(JsonObject data) { - if (data.has("signature") && !data.get("signature").isJsonNull()) { - return new SkinData( - data.get("value").getAsString(), - data.get("signature").getAsString() - ); - } - return new SkinData(data.get("value").getAsString(), null); + return new SkinDataImpl( + data.get("value").getAsString(), + data.get("signature").getAsString() + ); + } + + @Override + public @NonNull String value() { + return value; + } + + @Override + public @NonNull String signature() { + return signature; } } diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java index 1603723d..61db1ab6 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadManager.java @@ -25,25 +25,26 @@ package org.geysermc.floodgate.skin; +import com.google.inject.Inject; +import com.google.inject.Singleton; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.AllArgsConstructor; import org.geysermc.event.Listener; import org.geysermc.event.subscribe.Subscribe; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.logger.FloodgateLogger; -import org.geysermc.floodgate.event.ShutdownEvent; +import org.geysermc.floodgate.event.lifecycle.ShutdownEvent; @Listener -@AllArgsConstructor +@Singleton public final class SkinUploadManager { private final Int2ObjectMap connections = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); - private final FloodgateApi api; - private final SkinApplier applier; - private final FloodgateLogger logger; + @Inject private FloodgateApi api; + @Inject private SkinApplier applier; + @Inject private FloodgateLogger logger; public void addConnectionIfNeeded(int id, String verifyCode) { connections.computeIfAbsent(id, (ignored) -> { diff --git a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java index c933d7f0..5018f8fa 100644 --- a/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java +++ b/core/src/main/java/org/geysermc/floodgate/skin/SkinUploadSocket.java @@ -35,6 +35,7 @@ import java.net.URI; import javax.net.ssl.SSLException; import lombok.Getter; import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.PropertyKey; @@ -61,8 +62,8 @@ final class SkinUploadSocket extends WebSocketClient { SkinUploadManager uploadManager, FloodgateApi api, SkinApplier applier, - FloodgateLogger logger) { - + FloodgateLogger logger + ) { super(getWebsocketUri(id, verifyCode)); this.id = id; this.verifyCode = verifyCode; @@ -83,7 +84,7 @@ final class SkinUploadSocket extends WebSocketClient { } @Override - public void onOpen(ServerHandshake handshakedata) { + public void onOpen(ServerHandshake ignored) { setConnectionLostTimeout(11); } @@ -114,10 +115,14 @@ final class SkinUploadSocket extends WebSocketClient { player.getCorrectUsername()); return; } - if (!player.isLinked() && !applier.hasSkin(player)) { - SkinData skinData = SkinData.from(message.getAsJsonObject("data")); + + SkinData skinData = SkinDataImpl.from(message.getAsJsonObject("data")); + applier.applySkin(player, skinData); + + // legacy stuff, + // will be removed shortly after or during the Floodgate-Geyser integration + if (!player.isLinked()) { player.addProperty(PropertyKey.SKIN_UPLOADED, skinData); - applier.applySkin(player, skinData); } } break; diff --git a/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java b/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java index baadb195..70bcdc30 100644 --- a/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java +++ b/core/src/main/java/org/geysermc/floodgate/util/PostEnableMessages.java @@ -32,7 +32,7 @@ import org.geysermc.event.Listener; import org.geysermc.event.subscribe.Subscribe; import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.config.FloodgateConfig; -import org.geysermc.floodgate.event.PostEnableEvent; +import org.geysermc.floodgate.event.lifecycle.PostEnableEvent; @AutoBind @Listener 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 1654badb..cd7e03b1 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java @@ -61,9 +61,11 @@ public final class SpigotPlatformModule extends AbstractModule { @Override protected void configure() { + bind(SpigotPlugin.class).toInstance(plugin); bind(PlatformUtils.class).to(SpigotPlatformUtils.class); bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger()); bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class); + bind(SkinApplier.class).to(SpigotSkinApplier.class); } @Provides @@ -142,12 +144,6 @@ public final class SpigotPlatformModule extends AbstractModule { return new SpigotPluginMessageRegistration(plugin); } - @Provides - @Singleton - public SkinApplier skinApplier(SpigotVersionSpecificMethods versionSpecificMethods) { - return new SpigotSkinApplier(versionSpecificMethods, plugin); - } - @Provides @Singleton public SpigotVersionSpecificMethods versionSpecificMethods() { diff --git a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java index edc57279..fa599f40 100644 --- a/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java +++ b/spigot/src/main/java/org/geysermc/floodgate/pluginmessage/SpigotSkinApplier.java @@ -25,71 +25,48 @@ package org.geysermc.floodgate.pluginmessage; +import com.google.inject.Inject; +import com.google.inject.Singleton; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.floodgate.SpigotPlugin; +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.event.EventBus; +import org.geysermc.floodgate.event.skin.SkinApplyEventImpl; import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.skin.SkinData; +import org.geysermc.floodgate.skin.SkinDataImpl; import org.geysermc.floodgate.util.ClassNames; import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; +@Singleton public final class SpigotSkinApplier implements SkinApplier { - private final SpigotVersionSpecificMethods versionSpecificMethods; - private final SpigotPlugin plugin; - - public SpigotSkinApplier( - SpigotVersionSpecificMethods versionSpecificMethods, - SpigotPlugin plugin) { - this.versionSpecificMethods = versionSpecificMethods; - this.plugin = plugin; - } + @Inject private SpigotVersionSpecificMethods versionSpecificMethods; + @Inject private SpigotPlugin plugin; + @Inject private EventBus eventBus; @Override - public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { + public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { applySkin0(floodgatePlayer, skinData, true); } - @Override - public boolean hasSkin(FloodgatePlayer floodgatePlayer) { - Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId()); - - if (player == null) { - return false; - } - - GameProfile profile = ReflectionUtils.castedInvoke(player, ClassNames.GET_PROFILE_METHOD); - if (profile == null) { - 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. - for (Property textures : profile.getProperties().get("textures")) { - if (!textures.getValue().isEmpty()) { - return true; - } - } - return false; - } - private void applySkin0(FloodgatePlayer floodgatePlayer, SkinData skinData, boolean firstTry) { Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId()); // player is probably not logged in yet if (player == null) { if (firstTry) { - Bukkit.getScheduler().runTaskLater(plugin, - () -> { - if (!hasSkin(floodgatePlayer)) { - applySkin0(floodgatePlayer, skinData, false); - } - }, - 10 * 20); + Bukkit.getScheduler().runTaskLater( + plugin, + () -> applySkin0(floodgatePlayer, skinData, false), + 10 * 20 + ); } return; } @@ -100,11 +77,22 @@ 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(); - properties.removeAll("textures"); - Property property = new Property("textures", skinData.getValue(), skinData.getSignature()); - properties.put("textures", property); + SkinData currentSkin = currentSkin(properties); + + SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData); + event.setCancelled(floodgatePlayer.isLinked()); + + eventBus.fire(event); + + if (event.isCancelled()) { + return; + } + + replaceSkin(properties, event.newSkin()); // By running as a task, we don't run into async issues plugin.getServer().getScheduler().runTask(plugin, () -> { @@ -116,4 +104,19 @@ public final class SpigotSkinApplier implements SkinApplier { } }); } + + private SkinData currentSkin(PropertyMap properties) { + for (Property texture : properties.get("textures")) { + if (!texture.getValue().isEmpty()) { + return new SkinDataImpl(texture.getValue(), texture.getSignature()); + } + } + return null; + } + + private void replaceSkin(PropertyMap properties, SkinData skinData) { + properties.removeAll("textures"); + Property property = new Property("textures", skinData.value(), skinData.signature()); + properties.put("textures", property); + } } 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 b5244c3a..fb412b47 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java @@ -69,6 +69,7 @@ public final class VelocityPlatformModule extends AbstractModule { bind(CommandUtil.class).to(VelocityCommandUtil.class); bind(PlatformUtils.class).to(VelocityPlatformUtils.class); bind(FloodgateLogger.class).to(Slf4jFloodgateLogger.class); + bind(SkinApplier.class).to(VelocitySkinApplier.class); } @Provides @@ -112,12 +113,6 @@ public final class VelocityPlatformModule extends AbstractModule { return new VelocityPluginMessageRegistration(proxy); } - @Provides - @Singleton - public SkinApplier skinApplier(ProxyServer server) { - return new VelocitySkinApplier(server); - } - /* DebugAddon / PlatformInjector */ diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java b/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java index c2c66d2f..f9818cbc 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java +++ b/velocity/src/main/java/org/geysermc/floodgate/util/VelocitySkinApplier.java @@ -25,43 +25,60 @@ package org.geysermc.floodgate.util; -import com.velocitypowered.api.proxy.Player; +import com.google.inject.Inject; +import com.google.inject.Singleton; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.util.GameProfile.Property; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +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.event.EventBus; +import org.geysermc.floodgate.event.skin.SkinApplyEventImpl; import org.geysermc.floodgate.skin.SkinApplier; -import org.geysermc.floodgate.skin.SkinData; +import org.geysermc.floodgate.skin.SkinDataImpl; -@RequiredArgsConstructor +@Singleton public class VelocitySkinApplier implements SkinApplier { - private final ProxyServer server; + @Inject private ProxyServer server; + @Inject private EventBus eventBus; @Override - public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { + public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) { server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> { List properties = new ArrayList<>(player.getGameProfileProperties()); - properties.add(new Property("textures", skinData.getValue(), skinData.getSignature())); + + SkinData currentSkin = currentSkin(properties); + + SkinApplyEvent event = new SkinApplyEventImpl(floodgatePlayer, currentSkin, skinData); + event.setCancelled(floodgatePlayer.isLinked()); + + eventBus.fire(event); + + if (event.isCancelled()) { + return; + } + + replaceSkin(properties, event.newSkin()); player.setGameProfileProperties(properties); }); } - @Override - public boolean hasSkin(FloodgatePlayer floodgatePlayer) { - Optional player = server.getPlayer(floodgatePlayer.getCorrectUniqueId()); - - if (player.isPresent()) { - for (Property property : player.get().getGameProfileProperties()) { - if (property.getName().equals("textures")) { - if (!property.getValue().isEmpty()) { - return true; - } + private SkinData currentSkin(List properties) { + for (Property property : properties) { + if (property.getName().equals("textures")) { + if (!property.getValue().isEmpty()) { + return new SkinDataImpl(property.getValue(), property.getSignature()); } } } - return false; + return null; + } + + private void replaceSkin(List properties, SkinData skinData) { + properties.removeIf(property -> property.getName().equals("textures")); + properties.add(new Property("textures", skinData.value(), skinData.signature())); } }