1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-22 00:11:37 +00:00

Added a SkinApplyEvent that can cancel/edit the to be applied skin

This commit is contained in:
Tim203
2022-12-28 02:11:26 +01:00
parent 2c92e3e215
commit 913c85c154
29 changed files with 425 additions and 235 deletions

View File

@@ -30,6 +30,7 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder; 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.link.PlayerLink;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.unsafe.Unsafe; import org.geysermc.floodgate.api.unsafe.Unsafe;
@@ -148,6 +149,10 @@ public interface FloodgateApi {
*/ */
CompletableFuture<String> getGamertagFor(long xuid); CompletableFuture<String> getGamertagFor(long xuid);
default FloodgateEventBus getEventBus() {
return InstanceHolder.getEventBus();
}
/** /**
* Returns the instance that manages all the linking. * Returns the instance that manages all the linking.
*/ */

View File

@@ -27,6 +27,7 @@ package org.geysermc.floodgate.api;
import java.util.UUID; import java.util.UUID;
import lombok.Getter; import lombok.Getter;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers; import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.link.PlayerLink;
@@ -35,6 +36,7 @@ import org.geysermc.floodgate.api.packet.PacketHandlers;
public final class InstanceHolder { public final class InstanceHolder {
@Getter private static FloodgateApi api; @Getter private static FloodgateApi api;
@Getter private static PlayerLink playerLink; @Getter private static PlayerLink playerLink;
@Getter private static FloodgateEventBus eventBus;
@Getter private static PlatformInjector injector; @Getter private static PlatformInjector injector;
@Getter private static PacketHandlers packetHandlers; @Getter private static PacketHandlers packetHandlers;
@@ -44,11 +46,12 @@ public final class InstanceHolder {
public static boolean set( public static boolean set(
FloodgateApi floodgateApi, FloodgateApi floodgateApi,
PlayerLink link, PlayerLink link,
FloodgateEventBus floodgateEventBus,
PlatformInjector platformInjector, PlatformInjector platformInjector,
PacketHandlers packetHandlers, PacketHandlers packetHandlers,
HandshakeHandlers handshakeHandlers, HandshakeHandlers handshakeHandlers,
UUID key) { UUID key
) {
if (storedKey != null) { if (storedKey != null) {
if (!storedKey.equals(key)) { if (!storedKey.equals(key)) {
return false; return false;
@@ -59,14 +62,10 @@ public final class InstanceHolder {
api = floodgateApi; api = floodgateApi;
playerLink = link; playerLink = link;
eventBus = floodgateEventBus;
injector = platformInjector; injector = platformInjector;
InstanceHolder.packetHandlers = packetHandlers; InstanceHolder.packetHandlers = packetHandlers;
InstanceHolder.handshakeHandlers = handshakeHandlers; InstanceHolder.handshakeHandlers = handshakeHandlers;
return true; return true;
} }
@SuppressWarnings("unchecked")
public static <T extends FloodgateApi> T castApi(Class<T> cast) {
return (T) api;
}
} }

View File

@@ -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<Object, FloodgateSubscriber<?>> {
}

View File

@@ -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<T> extends Subscriber<T> {
}

View File

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

View File

@@ -48,7 +48,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.skin.SkinApplier; 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.LanguageManager;
import org.geysermc.floodgate.util.ReflectionUtils; 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 // To fix the February 2 2022 Mojang authentication changes
if (!config.isSendFloodgateData()) { if (!config.isSendFloodgateData()) {
FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId()); FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId());
if (player != null && !player.isLinked() && !skinApplier.hasSkin(player)) { if (player != null && !player.isLinked()) {
skinApplier.applySkin(player, new SkinData("", "")); skinApplier.applySkin(player, new SkinDataImpl("", ""));
} }
} }
} }

View File

@@ -70,6 +70,7 @@ public final class BungeePlatformModule extends AbstractModule {
bind(PlatformUtils.class).to(BungeePlatformUtils.class); bind(PlatformUtils.class).to(BungeePlatformUtils.class);
bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger()); bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger());
bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class); bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class);
bind(SkinApplier.class).to(BungeeSkinApplier.class);
} }
@Provides @Provides
@@ -121,12 +122,6 @@ public final class BungeePlatformModule extends AbstractModule {
return new BungeePluginMessageRegistration(); return new BungeePluginMessageRegistration();
} }
@Provides
@Singleton
public SkinApplier skinApplier(FloodgateLogger logger) {
return new BungeeSkinApplier(logger);
}
/* /*
DebugAddon / PlatformInjector DebugAddon / PlatformInjector
*/ */

View File

@@ -26,68 +26,55 @@
package org.geysermc.floodgate.pluginmessage; package org.geysermc.floodgate.pluginmessage;
import static com.google.common.base.Preconditions.checkNotNull; 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.getFieldOfType;
import static org.geysermc.floodgate.util.ReflectionUtils.getMethodByName;
import java.lang.reflect.Array; import com.google.inject.Inject;
import java.lang.reflect.Constructor; import com.google.inject.Singleton;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.util.ArrayList;
import lombok.RequiredArgsConstructor; import java.util.List;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.Property; 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.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; 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.SkinApplier;
import org.geysermc.floodgate.skin.SkinData; import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
@RequiredArgsConstructor @Singleton
public final class BungeeSkinApplier implements SkinApplier { public final class BungeeSkinApplier implements SkinApplier {
private static final Constructor<?> LOGIN_RESULT_CONSTRUCTOR;
private static final Field LOGIN_RESULT_FIELD; 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 { 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); LOGIN_RESULT_FIELD = getFieldOfType(InitialHandler.class, LoginResult.class);
checkNotNull(LOGIN_RESULT_FIELD, "LoginResult field cannot be null"); 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 @Override
public void applySkin(FloodgatePlayer uuid, SkinData skinData) { public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId()); ProxiedPlayer player = server.getPlayer(floodgatePlayer.getCorrectUniqueId());
if (player == null) { if (player == null) {
return; return;
} }
InitialHandler handler = getHandler(player); InitialHandler handler;
if (handler == null) { try {
handler = (InitialHandler) player.getPendingConnection();
} catch (Exception exception) {
logger.error("Incompatible Bungeecord fork detected", exception);
return; return;
} }
@@ -95,57 +82,46 @@ public final class BungeeSkinApplier implements SkinApplier {
// expected to be null since LoginResult is the data from hasJoined, // expected to be null since LoginResult is the data from hasJoined,
// which Floodgate players don't have // which Floodgate players don't have
if (loginResult == null) { if (loginResult == null) {
// id and name are unused and properties will be overridden // id and name are unused
loginResult = (LoginResult) ReflectionUtils.newInstance( loginResult = new LoginResult(null, null, new Property[0]);
LOGIN_RESULT_CONSTRUCTOR, null, null, null
);
ReflectionUtils.setValue(handler, LOGIN_RESULT_FIELD, loginResult); ReflectionUtils.setValue(handler, LOGIN_RESULT_FIELD, loginResult);
} }
Object property = ReflectionUtils.newInstance( Property[] properties = loginResult.getProperties();
PROPERTY_CONSTRUCTOR,
"textures", skinData.getValue(), skinData.getSignature()
);
Object propertyArray = Array.newInstance(PROPERTY_CLASS, 1); SkinData currentSkin = currentSkin(properties);
Array.set(propertyArray, 0, property);
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;
} }
@Override loginResult.setProperties(replaceSkin(properties, event.newSkin()));
public boolean hasSkin(FloodgatePlayer fPlayer) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(fPlayer.getCorrectUniqueId());
if (player == null) {
return false;
} }
InitialHandler handler = getHandler(player); private SkinData currentSkin(Property[] properties) {
if (handler == null) { for (Property property : properties) {
return false;
}
LoginResult loginResult = handler.getLoginProfile();
if (loginResult == null) {
return false;
}
for (Property property : loginResult.getProperties()) {
if (property.getName().equals("textures")) { if (property.getName().equals("textures")) {
if (!property.getValue().isEmpty()) { if (!property.getValue().isEmpty()) {
return true; return new SkinDataImpl(property.getValue(), property.getSignature());
} }
} }
} }
return false;
}
private InitialHandler getHandler(ProxiedPlayer player) {
try {
return (InitialHandler) player.getPendingConnection();
} catch (Exception exception) {
logger.error("Incompatible Bungeecord fork detected", exception);
return null; return null;
} }
private Property[] replaceSkin(Property[] properties, SkinData skinData) {
List<Property> 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]);
} }
} }

View File

@@ -31,14 +31,15 @@ import com.google.inject.Module;
import java.util.UUID; import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder; 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.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.packet.PacketHandlers; import org.geysermc.floodgate.api.packet.PacketHandlers;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.event.EventBus; import org.geysermc.floodgate.event.EventBus;
import org.geysermc.floodgate.event.PostEnableEvent; import org.geysermc.floodgate.event.lifecycle.PostEnableEvent;
import org.geysermc.floodgate.event.ShutdownEvent; import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
import org.geysermc.floodgate.module.PostInitializeModule; import org.geysermc.floodgate.module.PostInitializeModule;
public class FloodgatePlatform { public class FloodgatePlatform {
@@ -52,9 +53,13 @@ public class FloodgatePlatform {
public void init( public void init(
FloodgateApi api, FloodgateApi api,
PlayerLink link, PlayerLink link,
FloodgateEventBus eventBus,
PacketHandlers packetHandlers, PacketHandlers packetHandlers,
HandshakeHandlers handshakeHandlers) { HandshakeHandlers handshakeHandlers
InstanceHolder.set(api, link, this.injector, packetHandlers, handshakeHandlers, KEY); ) {
InstanceHolder.set(
api, link, eventBus, this.injector, packetHandlers, handshakeHandlers, KEY
);
} }
public void enable(Module... postInitializeModules) throws RuntimeException { public void enable(Module... postInitializeModules) throws RuntimeException {

View File

@@ -25,6 +25,7 @@
package org.geysermc.floodgate.event; package org.geysermc.floodgate.event;
import com.google.inject.Singleton;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.NonNull; 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.bus.impl.EventBusImpl;
import org.geysermc.event.subscribe.Subscribe; import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.event.subscribe.Subscriber; import org.geysermc.event.subscribe.Subscriber;
import org.geysermc.floodgate.api.event.FloodgateEventBus;
import org.geysermc.floodgate.api.event.FloodgateSubscriber;
@Singleton
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public final class EventBus extends EventBusImpl<Object, EventSubscriber<?>> { public final class EventBus extends EventBusImpl<Object, FloodgateSubscriber<?>>
implements FloodgateEventBus {
@Override @Override
protected <H, T, B extends Subscriber<T>> B makeSubscription( protected <H, T, B extends Subscriber<T>> B makeSubscription(
@NonNull Class<T> eventClass, @NonNull Class<T> eventClass,

View File

@@ -30,8 +30,9 @@ import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.PostOrder; import org.geysermc.event.PostOrder;
import org.geysermc.event.subscribe.impl.SubscriberImpl; import org.geysermc.event.subscribe.impl.SubscriberImpl;
import org.geysermc.floodgate.api.event.FloodgateSubscriber;
public final class EventSubscriber<E> extends SubscriberImpl<E> { public final class EventSubscriber<E> extends SubscriberImpl<E> implements FloodgateSubscriber<E> {
EventSubscriber( EventSubscriber(
@NonNull Class<E> eventClass, @NonNull Class<E> eventClass,
@NonNull Consumer<E> handler, @NonNull Consumer<E> handler,

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.event; package org.geysermc.floodgate.event.lifecycle;
public class PostEnableEvent { public class PostEnableEvent {
} }

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.event; package org.geysermc.floodgate.event.lifecycle;
public class ShutdownEvent { public class ShutdownEvent {
} }

View File

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

View File

@@ -43,7 +43,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.database.config.DatabaseConfig; import org.geysermc.floodgate.database.config.DatabaseConfig;
import org.geysermc.floodgate.database.config.DatabaseConfigLoader; 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; import org.geysermc.floodgate.util.InjectorHolder;
@Listener @Listener

View File

@@ -51,7 +51,7 @@ import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfig.PlayerLinkConfig; 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.Constants;
import org.geysermc.floodgate.util.InjectorHolder; import org.geysermc.floodgate.util.InjectorHolder;
import org.geysermc.floodgate.util.Utils; import org.geysermc.floodgate.util.Utils;

View File

@@ -43,6 +43,7 @@ import org.geysermc.event.PostOrder;
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl; import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi; 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.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink; 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.FloodgateCipher;
import org.geysermc.floodgate.crypto.KeyProducer; import org.geysermc.floodgate.crypto.KeyProducer;
import org.geysermc.floodgate.event.EventBus; 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.event.util.ListenerAnnotationMatcher;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.link.PlayerLinkHolder; import org.geysermc.floodgate.link.PlayerLinkHolder;
import org.geysermc.floodgate.packet.PacketHandlersImpl; import org.geysermc.floodgate.packet.PacketHandlersImpl;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploadManager; import org.geysermc.floodgate.skin.SkinUploadManager;
import org.geysermc.floodgate.util.Constants; import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.HttpClient; import org.geysermc.floodgate.util.HttpClient;
@@ -77,6 +77,7 @@ public class CommonModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(EventBus.class).toInstance(eventBus); bind(EventBus.class).toInstance(eventBus);
bind(FloodgateEventBus.class).to(EventBus.class);
// register every class that has the Listener annotation // register every class that has the Listener annotation
bindListener(new ListenerAnnotationMatcher(), new TypeListener() { bindListener(new ListenerAnnotationMatcher(), new TypeListener() {
@Override @Override
@@ -164,17 +165,6 @@ public class CommonModule extends AbstractModule {
return new PluginMessageManager(); 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 @Provides
@Singleton @Singleton
@Named("gitBranch") @Named("gitBranch")

View File

@@ -41,7 +41,7 @@ import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe; import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.util.Permission; 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.AnnouncementData;
import org.geysermc.floodgate.news.data.BuildSpecificData; import org.geysermc.floodgate.news.data.BuildSpecificData;
import org.geysermc.floodgate.news.data.CheckAfterData; import org.geysermc.floodgate.news.data.CheckAfterData;

View File

@@ -32,7 +32,6 @@ import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData; import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -72,7 +71,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
private Map<String, PropertyKey> stringToPropertyKey; private Map<String, PropertyKey> stringToPropertyKey;
static FloodgatePlayerImpl from(BedrockData data, HandshakeData handshakeData) { static FloodgatePlayerImpl from(BedrockData data, HandshakeData handshakeData) {
FloodgateApi api = InstanceHolder.getApi(); FloodgateApi api = FloodgateApi.getInstance();
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid()); UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());

View File

@@ -25,7 +25,6 @@
package org.geysermc.floodgate.pluginmessage.channel; package org.geysermc.floodgate.pluginmessage.channel;
import com.google.gson.JsonObject;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
@@ -36,7 +35,7 @@ import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannel; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinData; import org.geysermc.floodgate.skin.SkinDataImpl;
public class SkinChannel implements PluginMessageChannel { public class SkinChannel implements PluginMessageChannel {
@Inject private FloodgateApi api; @Inject private FloodgateApi api;
@@ -89,18 +88,10 @@ public class SkinChannel implements PluginMessageChannel {
return Result.kick("Got invalid skin data"); return Result.kick("Got invalid skin data");
} }
if (floodgatePlayer.isLinked() || skinApplier.hasSkin(floodgatePlayer)) {
return Result.handled();
}
String value = split[0]; String value = split[0];
String signature = split[1]; String signature = split[1];
JsonObject result = new JsonObject(); SkinDataImpl skinData = new SkinDataImpl(value, signature);
result.addProperty("value", value);
result.addProperty("signature", signature);
SkinData skinData = new SkinData(value, signature);
floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, skinData); floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, skinData);
skinApplier.applySkin(floodgatePlayer, skinData); skinApplier.applySkin(floodgatePlayer, skinData);

View File

@@ -25,6 +25,8 @@
package org.geysermc.floodgate.skin; 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; import org.geysermc.floodgate.api.player.FloodgatePlayer;
public interface SkinApplier { public interface SkinApplier {
@@ -34,14 +36,5 @@ public interface SkinApplier {
* @param floodgatePlayer player to apply skin to * @param floodgatePlayer player to apply skin to
* @param skinData data for skin to apply to player * @param skinData data for skin to apply to player
*/ */
void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData); void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull 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);
} }

View File

@@ -26,22 +26,33 @@
package org.geysermc.floodgate.skin; package org.geysermc.floodgate.skin;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.Getter; import java.util.Objects;
import lombok.RequiredArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.api.event.skin.SkinApplyEvent.SkinData;
@Getter public class SkinDataImpl implements SkinData {
@RequiredArgsConstructor
public class SkinData {
private final String value; private final String value;
private final String signature; 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) { public static SkinData from(JsonObject data) {
if (data.has("signature") && !data.get("signature").isJsonNull()) { return new SkinDataImpl(
return new SkinData(
data.get("value").getAsString(), data.get("value").getAsString(),
data.get("signature").getAsString() data.get("signature").getAsString()
); );
} }
return new SkinData(data.get("value").getAsString(), null);
@Override
public @NonNull String value() {
return value;
}
@Override
public @NonNull String signature() {
return signature;
} }
} }

View File

@@ -25,25 +25,26 @@
package org.geysermc.floodgate.skin; 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.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import org.geysermc.event.Listener; import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe; import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.event.ShutdownEvent; import org.geysermc.floodgate.event.lifecycle.ShutdownEvent;
@Listener @Listener
@AllArgsConstructor @Singleton
public final class SkinUploadManager { public final class SkinUploadManager {
private final Int2ObjectMap<SkinUploadSocket> connections = private final Int2ObjectMap<SkinUploadSocket> connections =
Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>());
private final FloodgateApi api; @Inject private FloodgateApi api;
private final SkinApplier applier; @Inject private SkinApplier applier;
private final FloodgateLogger logger; @Inject private FloodgateLogger logger;
public void addConnectionIfNeeded(int id, String verifyCode) { public void addConnectionIfNeeded(int id, String verifyCode) {
connections.computeIfAbsent(id, (ignored) -> { connections.computeIfAbsent(id, (ignored) -> {

View File

@@ -35,6 +35,7 @@ import java.net.URI;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import lombok.Getter; import lombok.Getter;
import org.geysermc.floodgate.api.FloodgateApi; 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.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey; import org.geysermc.floodgate.api.player.PropertyKey;
@@ -61,8 +62,8 @@ final class SkinUploadSocket extends WebSocketClient {
SkinUploadManager uploadManager, SkinUploadManager uploadManager,
FloodgateApi api, FloodgateApi api,
SkinApplier applier, SkinApplier applier,
FloodgateLogger logger) { FloodgateLogger logger
) {
super(getWebsocketUri(id, verifyCode)); super(getWebsocketUri(id, verifyCode));
this.id = id; this.id = id;
this.verifyCode = verifyCode; this.verifyCode = verifyCode;
@@ -83,7 +84,7 @@ final class SkinUploadSocket extends WebSocketClient {
} }
@Override @Override
public void onOpen(ServerHandshake handshakedata) { public void onOpen(ServerHandshake ignored) {
setConnectionLostTimeout(11); setConnectionLostTimeout(11);
} }
@@ -114,10 +115,14 @@ final class SkinUploadSocket extends WebSocketClient {
player.getCorrectUsername()); player.getCorrectUsername());
return; return;
} }
if (!player.isLinked() && !applier.hasSkin(player)) {
SkinData skinData = SkinData.from(message.getAsJsonObject("data")); SkinData skinData = SkinDataImpl.from(message.getAsJsonObject("data"));
player.addProperty(PropertyKey.SKIN_UPLOADED, skinData);
applier.applySkin(player, skinData); 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);
} }
} }
break; break;

View File

@@ -32,7 +32,7 @@ import org.geysermc.event.Listener;
import org.geysermc.event.subscribe.Subscribe; import org.geysermc.event.subscribe.Subscribe;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.event.PostEnableEvent; import org.geysermc.floodgate.event.lifecycle.PostEnableEvent;
@AutoBind @AutoBind
@Listener @Listener

View File

@@ -61,9 +61,11 @@ public final class SpigotPlatformModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(SpigotPlugin.class).toInstance(plugin);
bind(PlatformUtils.class).to(SpigotPlatformUtils.class); bind(PlatformUtils.class).to(SpigotPlatformUtils.class);
bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger()); bind(Logger.class).annotatedWith(Names.named("logger")).toInstance(plugin.getLogger());
bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class); bind(FloodgateLogger.class).to(JavaUtilFloodgateLogger.class);
bind(SkinApplier.class).to(SpigotSkinApplier.class);
} }
@Provides @Provides
@@ -142,12 +144,6 @@ public final class SpigotPlatformModule extends AbstractModule {
return new SpigotPluginMessageRegistration(plugin); return new SpigotPluginMessageRegistration(plugin);
} }
@Provides
@Singleton
public SkinApplier skinApplier(SpigotVersionSpecificMethods versionSpecificMethods) {
return new SpigotSkinApplier(versionSpecificMethods, plugin);
}
@Provides @Provides
@Singleton @Singleton
public SpigotVersionSpecificMethods versionSpecificMethods() { public SpigotVersionSpecificMethods versionSpecificMethods() {

View File

@@ -25,71 +25,48 @@
package org.geysermc.floodgate.pluginmessage; package org.geysermc.floodgate.pluginmessage;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.properties.PropertyMap;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.floodgate.SpigotPlugin; 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.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.SkinApplier;
import org.geysermc.floodgate.skin.SkinData; import org.geysermc.floodgate.skin.SkinDataImpl;
import org.geysermc.floodgate.util.ClassNames; import org.geysermc.floodgate.util.ClassNames;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
@Singleton
public final class SpigotSkinApplier implements SkinApplier { public final class SpigotSkinApplier implements SkinApplier {
private final SpigotVersionSpecificMethods versionSpecificMethods; @Inject private SpigotVersionSpecificMethods versionSpecificMethods;
private final SpigotPlugin plugin; @Inject private SpigotPlugin plugin;
@Inject private EventBus eventBus;
public SpigotSkinApplier(
SpigotVersionSpecificMethods versionSpecificMethods,
SpigotPlugin plugin) {
this.versionSpecificMethods = versionSpecificMethods;
this.plugin = plugin;
}
@Override @Override
public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) {
applySkin0(floodgatePlayer, skinData, true); 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) { private void applySkin0(FloodgatePlayer floodgatePlayer, SkinData skinData, boolean firstTry) {
Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId()); Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId());
// player is probably not logged in yet // player is probably not logged in yet
if (player == null) { if (player == null) {
if (firstTry) { if (firstTry) {
Bukkit.getScheduler().runTaskLater(plugin, Bukkit.getScheduler().runTaskLater(
() -> { plugin,
if (!hasSkin(floodgatePlayer)) { () -> applySkin0(floodgatePlayer, skinData, false),
applySkin0(floodgatePlayer, skinData, false); 10 * 20
} );
},
10 * 20);
} }
return; return;
} }
@@ -100,11 +77,22 @@ public final class SpigotSkinApplier implements SkinApplier {
throw new IllegalStateException("The GameProfile cannot be null! " + player.getName()); 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(); PropertyMap properties = profile.getProperties();
properties.removeAll("textures"); SkinData currentSkin = currentSkin(properties);
Property property = new Property("textures", skinData.getValue(), skinData.getSignature());
properties.put("textures", property); 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 // By running as a task, we don't run into async issues
plugin.getServer().getScheduler().runTask(plugin, () -> { 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);
}
} }

View File

@@ -69,6 +69,7 @@ public final class VelocityPlatformModule extends AbstractModule {
bind(CommandUtil.class).to(VelocityCommandUtil.class); bind(CommandUtil.class).to(VelocityCommandUtil.class);
bind(PlatformUtils.class).to(VelocityPlatformUtils.class); bind(PlatformUtils.class).to(VelocityPlatformUtils.class);
bind(FloodgateLogger.class).to(Slf4jFloodgateLogger.class); bind(FloodgateLogger.class).to(Slf4jFloodgateLogger.class);
bind(SkinApplier.class).to(VelocitySkinApplier.class);
} }
@Provides @Provides
@@ -112,12 +113,6 @@ public final class VelocityPlatformModule extends AbstractModule {
return new VelocityPluginMessageRegistration(proxy); return new VelocityPluginMessageRegistration(proxy);
} }
@Provides
@Singleton
public SkinApplier skinApplier(ProxyServer server) {
return new VelocitySkinApplier(server);
}
/* /*
DebugAddon / PlatformInjector DebugAddon / PlatformInjector
*/ */

View File

@@ -25,43 +25,60 @@
package org.geysermc.floodgate.util; 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.proxy.ProxyServer;
import com.velocitypowered.api.util.GameProfile.Property; import com.velocitypowered.api.util.GameProfile.Property;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import org.checkerframework.checker.nullness.qual.NonNull;
import lombok.RequiredArgsConstructor; 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.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.SkinApplier;
import org.geysermc.floodgate.skin.SkinData; import org.geysermc.floodgate.skin.SkinDataImpl;
@RequiredArgsConstructor @Singleton
public class VelocitySkinApplier implements SkinApplier { public class VelocitySkinApplier implements SkinApplier {
private final ProxyServer server; @Inject private ProxyServer server;
@Inject private EventBus eventBus;
@Override @Override
public void applySkin(FloodgatePlayer floodgatePlayer, SkinData skinData) { public void applySkin(@NonNull FloodgatePlayer floodgatePlayer, @NonNull SkinData skinData) {
server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> { server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> {
List<Property> properties = new ArrayList<>(player.getGameProfileProperties()); List<Property> 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); player.setGameProfileProperties(properties);
}); });
} }
@Override private SkinData currentSkin(List<Property> properties) {
public boolean hasSkin(FloodgatePlayer floodgatePlayer) { for (Property property : properties) {
Optional<Player> player = server.getPlayer(floodgatePlayer.getCorrectUniqueId());
if (player.isPresent()) {
for (Property property : player.get().getGameProfileProperties()) {
if (property.getName().equals("textures")) { if (property.getName().equals("textures")) {
if (!property.getValue().isEmpty()) { if (!property.getValue().isEmpty()) {
return true; return new SkinDataImpl(property.getValue(), property.getSignature());
} }
} }
} }
return null;
} }
return false;
private void replaceSkin(List<Property> properties, SkinData skinData) {
properties.removeIf(property -> property.getName().equals("textures"));
properties.add(new Property("textures", skinData.value(), skinData.signature()));
} }
} }