1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +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 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<String> getGamertagFor(long xuid);
default FloodgateEventBus getEventBus() {
return InstanceHolder.getEventBus();
}
/**
* Returns the instance that manages all the linking.
*/

View File

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

View File

@@ -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
*/

View File

@@ -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<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 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 {

View File

@@ -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<Object, EventSubscriber<?>> {
public final class EventBus extends EventBusImpl<Object, FloodgateSubscriber<?>>
implements FloodgateEventBus {
@Override
protected <H, T, B extends Subscriber<T>> B makeSubscription(
@NonNull Class<T> eventClass,

View File

@@ -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<E> extends SubscriberImpl<E> {
public final class EventSubscriber<E> extends SubscriberImpl<E> implements FloodgateSubscriber<E> {
EventSubscriber(
@NonNull Class<E> eventClass,
@NonNull Consumer<E> handler,

View File

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

View File

@@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.event;
package org.geysermc.floodgate.event.lifecycle;
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.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

View File

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

View File

@@ -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")

View File

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

View File

@@ -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<String, PropertyKey> stringToPropertyKey;
static FloodgatePlayerImpl from(BedrockData data, HandshakeData handshakeData) {
FloodgateApi api = InstanceHolder.getApi();
FloodgateApi api = FloodgateApi.getInstance();
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());

View File

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

View File

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

View File

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

View File

@@ -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<SkinUploadSocket> 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) -> {

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

@@ -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
*/

View File

@@ -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<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);
});
}
@Override
public boolean hasSkin(FloodgatePlayer floodgatePlayer) {
Optional<Player> 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<Property> 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<Property> properties, SkinData skinData) {
properties.removeIf(property -> property.getName().equals("textures"));
properties.add(new Property("textures", skinData.value(), skinData.signature()));
}
}