1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Added Player properties, started working on Bungee skins, fixed errors

This commit is contained in:
Tim203
2020-12-19 22:42:25 +01:00
parent 4d6cc25315
commit dd93b98407
39 changed files with 1036 additions and 259 deletions

View File

@@ -97,6 +97,11 @@ public interface FloodgatePlayer {
*/ */
InputMode getInputMode(); InputMode getInputMode();
/**
* Returns if the Floodgate player is connected through a proxy
*/
boolean isFromProxy();
/** /**
* Returns the LinkedPlayer object if the player is linked to a Java account. * Returns the LinkedPlayer object if the player is linked to a Java account.
*/ */
@@ -115,6 +120,18 @@ public interface FloodgatePlayer {
return sendForm(formBuilder.build()); return sendForm(formBuilder.build());
} }
<T> T getProperty(PropertyKey key);
<T> T getProperty(String key);
<T> T removeProperty(PropertyKey key);
<T> T removeProperty(String key);
<T> T addProperty(PropertyKey key, Object value);
<T> T addProperty(String key, Object value);
/** /**
* Casts the FloodgatePlayer instance to a class that extends FloodgatePlayer. * Casts the FloodgatePlayer instance to a class that extends FloodgatePlayer.
* *

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2019-2020 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.player;
import lombok.Getter;
@Getter
public class PropertyKey {
/**
* Socket Address returns the InetSocketAddress of the Bedrock player
*/
public static final PropertyKey SOCKET_ADDRESS =
new PropertyKey("socket_address", false, false);
/**
* Skin Uploaded returns a JsonObject containing the value and signature of the Skin
*/
public static final PropertyKey SKIN_UPLOADED =
new PropertyKey("skin_uploaded", false, false);
private final String key;
private final boolean changeable;
private final boolean removeable;
public PropertyKey(String key, boolean changeable, boolean removeable) {
this.key = key;
this.changeable = changeable;
this.removeable = removeable;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PropertyKey) {
return key.equals(((PropertyKey) obj).key);
}
if (obj instanceof String) {
return key.equals(obj);
}
return false;
}
public AllowedResult isAddAllowed(Object obj) {
if (obj instanceof PropertyKey) {
PropertyKey propertyKey = (PropertyKey) obj;
if (key.equals(propertyKey.key)) {
if ((propertyKey.changeable == changeable || propertyKey.changeable) &&
(propertyKey.removeable == removeable || propertyKey.removeable)) {
return AllowedResult.CORRECT;
}
return AllowedResult.INVALID_TAGS;
}
return AllowedResult.NOT_EQUALS;
}
if (obj instanceof String) {
if (changeable) {
return AllowedResult.CORRECT;
}
return AllowedResult.INVALID_TAGS;
}
return AllowedResult.NOT_EQUALS;
}
public enum AllowedResult {
NOT_EQUALS,
INVALID_TAGS,
CORRECT
}
}

View File

@@ -39,9 +39,9 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>com.github.SpigotMC.BungeeCord</groupId>
<artifactId>bungeecord-protocol</artifactId> <artifactId>bungeecord-proxy</artifactId>
<version>${bungee.version}</version> <version>master-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -88,5 +88,9 @@
<id>bungeecord-repo</id> <id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url> <url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository> </repository>
<repository>
<id>jitpack</id>
<url>https://jitpack.io</url>
</repository>
</repositories> </repositories>
</project> </project>

View File

@@ -33,7 +33,7 @@ import org.geysermc.floodgate.module.BungeeAddonModule;
import org.geysermc.floodgate.module.BungeeListenerModule; import org.geysermc.floodgate.module.BungeeListenerModule;
import org.geysermc.floodgate.module.BungeePlatformModule; import org.geysermc.floodgate.module.BungeePlatformModule;
import org.geysermc.floodgate.module.CommandModule; import org.geysermc.floodgate.module.CommandModule;
import org.geysermc.floodgate.module.CommonModule; import org.geysermc.floodgate.module.ProxyCommonModule;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
public final class BungeePlugin extends Plugin { public final class BungeePlugin extends Plugin {
@@ -45,7 +45,7 @@ public final class BungeePlugin extends Plugin {
long ctm = System.currentTimeMillis(); long ctm = System.currentTimeMillis();
Injector injector = Guice.createInjector( Injector injector = Guice.createInjector(
new CommonModule(getDataFolder().toPath()), new ProxyCommonModule(getDataFolder().toPath()),
new BungeePlatformModule(this) new BungeePlatformModule(this)
); );

View File

@@ -45,6 +45,7 @@ import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
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.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
@@ -97,7 +98,12 @@ public final class BungeeDataHandler {
event.getConnection(), EXTRA_HANDSHAKE_DATA event.getConnection(), EXTRA_HANDSHAKE_DATA
); );
HandshakeResult result = handler.handle(extraData); Object channelWrapper =
ReflectionUtils.getValue(event.getConnection(), PLAYER_CHANNEL_WRAPPER);
Channel channel = ReflectionUtils.getCastedValue(channelWrapper, PLAYER_CHANNEL);
HandshakeResult result = handler.handle(channel, extraData);
switch (result.getResultType()) { switch (result.getResultType()) {
case EXCEPTION: case EXCEPTION:
event.setCancelReason(config.getDisconnect().getInvalidKey()); event.setCancelReason(config.getDisconnect().getInvalidKey());
@@ -117,7 +123,13 @@ public final class BungeeDataHandler {
} }
FloodgatePlayer player = result.getFloodgatePlayer(); FloodgatePlayer player = result.getFloodgatePlayer();
api.addEncryptedData(player.getCorrectUniqueId(), result.getHandshakeData()[1]);
String encryptedData = result.getHandshakeData()[1];
// remove skin from encrypted data if it has a skin
if (encryptedData.indexOf(0x21) != -1) {
encryptedData = encryptedData.substring(0, encryptedData.indexOf(0x21) - 1);
}
api.addEncryptedData(player.getCorrectUniqueId(), encryptedData);
event.getConnection().setOnlineMode(false); event.getConnection().setOnlineMode(false);
event.getConnection().setUniqueId(player.getCorrectUniqueId()); event.getConnection().setUniqueId(player.getCorrectUniqueId());
@@ -126,9 +138,6 @@ public final class BungeeDataHandler {
event.getConnection(), PLAYER_NAME, player.getCorrectUsername() event.getConnection(), PLAYER_NAME, player.getCorrectUsername()
); );
Object channelWrapper =
ReflectionUtils.getValue(event.getConnection(), PLAYER_CHANNEL_WRAPPER);
SocketAddress remoteAddress = SocketAddress remoteAddress =
ReflectionUtils.getCastedValue(channelWrapper, PLAYER_REMOTE_ADDRESS); ReflectionUtils.getCastedValue(channelWrapper, PLAYER_REMOTE_ADDRESS);
@@ -137,15 +146,15 @@ public final class BungeeDataHandler {
"Ignoring the player, I guess.", "Ignoring the player, I guess.",
player.getUsername(), remoteAddress.getClass().getSimpleName() player.getUsername(), remoteAddress.getClass().getSimpleName()
); );
} else { event.setCancelled(true);
int port = ((InetSocketAddress) remoteAddress).getPort(); event.setCancelReason(
ReflectionUtils.setValue( new TextComponent("remoteAddress is not an InetSocketAddress!"));
channelWrapper, PLAYER_REMOTE_ADDRESS, event.completeIntent(plugin);
new InetSocketAddress(result.getBedrockData().getIp(), port) return;
);
} }
Channel channel = ReflectionUtils.getCastedValue(channelWrapper, PLAYER_CHANNEL); InetSocketAddress correctAddress = player.getProperty(PropertyKey.SOCKET_ADDRESS);
ReflectionUtils.setValue(channelWrapper, PLAYER_REMOTE_ADDRESS, correctAddress);
channel.attr(playerAttribute).set(player); channel.attr(playerAttribute).set(player);

View File

@@ -33,6 +33,7 @@ import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PreLoginEvent; import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority; import net.md_5.bungee.event.EventPriority;
@@ -40,7 +41,10 @@ import org.geysermc.floodgate.FloodgatePlayerImpl;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
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.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.handler.BungeeDataHandler; import org.geysermc.floodgate.handler.BungeeDataHandler;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
public final class BungeeListener implements Listener { public final class BungeeListener implements Listener {
@@ -49,6 +53,10 @@ public final class BungeeListener implements Listener {
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@Inject private ProxyFloodgateConfig config;
@Inject private PluginMessageHandler pluginMessageHandler;
@Inject private SkinHandler skinHandler;
@Inject @Inject
public void init(Injector injector) { public void init(Injector injector) {
dataHandler = injector.getInstance(BungeeDataHandler.class); dataHandler = injector.getInstance(BungeeDataHandler.class);
@@ -59,6 +67,24 @@ public final class BungeeListener implements Listener {
dataHandler.handleServerConnect(event.getPlayer()); dataHandler.handleServerConnect(event.getPlayer());
} }
@EventHandler
public void onServerConnected(ServerConnectedEvent event) {
ProxiedPlayer player = event.getPlayer();
FloodgatePlayer floodgatePlayer = api.getPlayer(player.getUniqueId());
if (floodgatePlayer == null) {
return;
}
// send skin request to server if data forwarding allows that
if (config.isSendFloodgateData()) {
pluginMessageHandler.sendSkinRequest(player.getUniqueId(),
floodgatePlayer.getRawSkin());
} else {
//todo also a Proxy SkinHandler to keep stuff clean?
skinHandler.handleSkinUploadFor(floodgatePlayer, null);
}
}
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
public void onPreLogin(PreLoginEvent event) { public void onPreLogin(PreLoginEvent event) {
dataHandler.handlePreLogin(event); dataHandler.handlePreLogin(event);

View File

@@ -33,14 +33,9 @@ import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.floodgate.BungeePlugin; import org.geysermc.floodgate.BungeePlugin;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.BungeeCommandRegistration; import org.geysermc.floodgate.command.BungeeCommandRegistration;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.inject.bungee.BungeeInjector; import org.geysermc.floodgate.inject.bungee.BungeeInjector;
import org.geysermc.floodgate.listener.BungeeListenerRegistration; import org.geysermc.floodgate.listener.BungeeListenerRegistration;
@@ -50,6 +45,9 @@ import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.pluginmessage.BungeePluginMessageHandler; import org.geysermc.floodgate.pluginmessage.BungeePluginMessageHandler;
import org.geysermc.floodgate.pluginmessage.BungeeSkinApplier;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.util.BungeeCommandUtil; import org.geysermc.floodgate.util.BungeeCommandUtil;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
@@ -57,31 +55,12 @@ import org.geysermc.floodgate.util.LanguageManager;
public final class BungeePlatformModule extends AbstractModule { public final class BungeePlatformModule extends AbstractModule {
private final BungeePlugin plugin; private final BungeePlugin plugin;
@Override
protected void configure() {
bind(SimpleFloodgateApi.class).to(ProxyFloodgateApi.class);
}
@Provides @Provides
@Singleton @Singleton
public Plugin bungeePlugin() { public Plugin bungeePlugin() {
return plugin; return plugin;
} }
@Provides
@Singleton
@Named("configClass")
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return ProxyFloodgateConfig.class;
}
@Provides
@Singleton
public ProxyFloodgateApi proxyFloodgateApi(PluginMessageHandler pluginMessageHandler,
FloodgateCipher cipher) {
return new ProxyFloodgateApi(pluginMessageHandler, cipher);
}
@Provides @Provides
@Singleton @Singleton
public FloodgateLogger floodgateLogger(LanguageManager languageManager) { public FloodgateLogger floodgateLogger(LanguageManager languageManager) {
@@ -117,6 +96,18 @@ public final class BungeePlatformModule extends AbstractModule {
return new BungeePluginMessageHandler(configHolder); return new BungeePluginMessageHandler(configHolder);
} }
@Provides
@Singleton
public SkinApplier skinApplier(FloodgateLogger logger) {
return new BungeeSkinApplier(logger);
}
@Provides
@Singleton
public SkinHandler skinHandler(SkinApplier skinApplier, FloodgateLogger logger) {
return new SkinHandler(skinApplier, logger);
}
/* /*
DebugAddon / PlatformInjector DebugAddon / PlatformInjector
*/ */

View File

@@ -25,10 +25,12 @@
package org.geysermc.floodgate.pluginmessage; package org.geysermc.floodgate.pluginmessage;
import static org.geysermc.floodgate.util.MessageFormatter.format; import com.google.gson.JsonObject;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.UUID; import java.util.UUID;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
@@ -40,15 +42,22 @@ import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import org.geysermc.cumulus.Form; import org.geysermc.cumulus.Form;
import org.geysermc.floodgate.api.FloodgateApi;
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.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.RawSkin;
public final class BungeePluginMessageHandler extends PluginMessageHandler implements Listener { public final class BungeePluginMessageHandler extends PluginMessageHandler implements Listener {
private ProxyServer proxy; private ProxyServer proxy;
private FloodgateLogger logger; private FloodgateLogger logger;
private String formChannel; private String formChannel;
private String skinChannel;
private FloodgateApi api;
private SkinApplier skinApplier;
public BungeePluginMessageHandler(FloodgateConfigHolder configHolder) { public BungeePluginMessageHandler(FloodgateConfigHolder configHolder) {
super(configHolder); super(configHolder);
@@ -57,10 +66,14 @@ public final class BungeePluginMessageHandler extends PluginMessageHandler imple
@Inject // called because this is a listener as well @Inject // called because this is a listener as well
public void init(Plugin plugin, FloodgateLogger logger, public void init(Plugin plugin, FloodgateLogger logger,
@Named("formChannel") String formChannel, @Named("formChannel") String formChannel,
@Named("skinChannel") String skinChannel) { @Named("skinChannel") String skinChannel,
FloodgateApi api, SkinApplier skinApplier) {
this.proxy = plugin.getProxy(); this.proxy = plugin.getProxy();
this.logger = logger; this.logger = logger;
this.formChannel = formChannel; this.formChannel = formChannel;
this.skinChannel = skinChannel;
this.api = api;
this.skinApplier = skinApplier;
proxy.registerChannel(formChannel); proxy.registerChannel(formChannel);
proxy.registerChannel(skinChannel); proxy.registerChannel(skinChannel);
@@ -69,6 +82,7 @@ public final class BungeePluginMessageHandler extends PluginMessageHandler imple
@EventHandler @EventHandler
public void onPluginMessage(PluginMessageEvent event) { public void onPluginMessage(PluginMessageEvent event) {
Connection source = event.getSender(); Connection source = event.getSender();
if (event.getTag().equals(formChannel)) { if (event.getTag().equals(formChannel)) {
if (source instanceof Server) { if (source instanceof Server) {
// send it to the client // send it to the client
@@ -79,8 +93,7 @@ public final class BungeePluginMessageHandler extends PluginMessageHandler imple
if (source instanceof ProxiedPlayer) { if (source instanceof ProxiedPlayer) {
byte[] data = event.getData(); byte[] data = event.getData();
if (data.length < 2) { if (data.length < 2) {
logger.error("Invalid form response! Closing connection"); logKick(source, "Invalid form response!");
source.disconnect(new TextComponent("Invalid form response!"));
return; return;
} }
@@ -95,12 +108,73 @@ public final class BungeePluginMessageHandler extends PluginMessageHandler imple
event.setCancelled(true); event.setCancelled(true);
if (!callResponseConsumer(data)) { if (!callResponseConsumer(data)) {
logger.error(format( logger.error("Couldn't find stored form with id {} for player {}",
"Couldn't find stored form with id {} for player {}", formId, ((ProxiedPlayer) source).getName());
formId, ((ProxiedPlayer) source).getName()));
} }
} }
return;
} }
if (event.getTag().equals(skinChannel)) {
byte[] data = event.getData();
if (data.length < 1) {
logKick(source, "Got invalid Skin request/response.");
return;
}
boolean request = data[0] == 1;
if (!request && data.length < 2) {
logKick(source, "Got invalid Skin response.");
return;
}
if (source instanceof Server) {
if (request) {
logKick(source, "Got Skin request from Server?");
return;
}
UUID playerUniqueId = ((ProxiedPlayer) event.getReceiver()).getUniqueId();
FloodgatePlayer floodgatePlayer = api.getPlayer(playerUniqueId);
if (floodgatePlayer == null) {
logKick(source, "Server issued Skin request for non-Floodgate player.");
return;
}
// 1 = failed, 0 = successful.
// we'll try it again on the next server if it failed
if (data[1] != 0) {
return;
}
JsonObject response;
try {
Reader reader = new InputStreamReader(
new ByteArrayInputStream(event.getData()));
response = GSON.fromJson(reader, JsonObject.class);
} catch (Throwable throwable) {
logger.error("Failed to read Skin response", throwable);
return;
}
skinApplier.applySkin(floodgatePlayer, UploadResult.success(response));
return;
}
// Players (Geyser) can't send requests nor responses
if (source instanceof ProxiedPlayer) {
logKick(source, "Got Skin " + (request ? "request" : "response") + " from Player?");
}
}
}
private void logKick(Connection source, String reason) {
logger.error(reason + " Closing connection");
source.disconnect(new TextComponent(reason));
} }
@Override @Override
@@ -114,12 +188,12 @@ public final class BungeePluginMessageHandler extends PluginMessageHandler imple
} }
@Override @Override
public boolean sendSkinRequest(UUID player, RawSkin skin) { public boolean sendSkinRequest(UUID uuid, RawSkin skin) {
return false; //todo ProxiedPlayer player = proxy.getPlayer(uuid);
} if (player != null) {
player.sendData(skinChannel, createSkinRequestData(skin.encode()));
@Override return true;
public void sendSkinResponse(UUID player, String response) { }
return false;
} }
} }

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2019-2020 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.pluginmessage;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
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.connection.LoginResult.Property;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
@RequiredArgsConstructor
public final class BungeeSkinApplier implements SkinApplier {
private final FloodgateLogger logger;
@Override
public void applySkin(FloodgatePlayer uuid, UploadResult result) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId());
InitialHandler handler;
try {
handler = (InitialHandler) player.getPendingConnection();
} catch (Exception exception) {
logger.error("Incompatible Bungeecord fork detected", exception);
return;
}
LoginResult loginResult = handler.getLoginProfile();
// 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 = new LoginResult(null, null, null);
}
JsonObject response = result.getResponse();
Property property = new Property(
"textures",
response.get("value").getAsString(),
response.get("signature").getAsString()
);
loginResult.setProperties(new Property[]{property});
}
}

View File

@@ -53,6 +53,11 @@
<artifactId>api</artifactId> <artifactId>api</artifactId>
<version>${project.parent.version}</version> <version>${project.parent.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.geysermc.cumulus</groupId>
<artifactId>cumulus</artifactId>
<version>${cumulus.version}</version>
</dependency>
<!-- <dependency> todo --> <!-- <dependency> todo -->
<!-- <groupId>com.mojang</groupId>--> <!-- <groupId>com.mojang</groupId>-->
<!-- <artifactId>brigadier</artifactId>--> <!-- <artifactId>brigadier</artifactId>-->
@@ -64,12 +69,6 @@
<version>1.27</version> <version>1.27</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -41,7 +41,6 @@ import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.config.loader.ConfigLoader; import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.module.ConfigLoadedModule;
import org.geysermc.floodgate.module.PostInitializeModule; import org.geysermc.floodgate.module.PostInitializeModule;
public class FloodgatePlatform { public class FloodgatePlatform {
@@ -56,16 +55,16 @@ public class FloodgatePlatform {
@Inject @Inject
public FloodgatePlatform(FloodgateApi api, PlatformInjector platformInjector, public FloodgatePlatform(FloodgateApi api, PlatformInjector platformInjector,
FloodgateLogger logger) { FloodgateLogger logger, Injector guice) {
this.api = api; this.api = api;
this.injector = platformInjector; this.injector = platformInjector;
this.logger = logger; this.logger = logger;
this.guice = guice;
} }
@Inject @Inject
public void init(@Named("dataDirectory") Path dataDirectory, ConfigLoader configLoader, public void init(@Named("dataDirectory") Path dataDirectory, ConfigLoader configLoader,
PlayerLinkLoader playerLinkLoader, FloodgateConfigHolder configHolder, PlayerLinkLoader playerLinkLoader, FloodgateConfigHolder configHolder) {
Injector injector) {
if (!Files.isDirectory(dataDirectory)) { if (!Files.isDirectory(dataDirectory)) {
try { try {
@@ -84,9 +83,6 @@ public class FloodgatePlatform {
configHolder.set(config); configHolder.set(config);
PlayerLink link = playerLinkLoader.load(); PlayerLink link = playerLinkLoader.load();
// make the config available for other classes (who are injected later on)
guice = injector.createChildInjector(new ConfigLoadedModule(config));
InstanceHolder.setInstance(api, link, this.injector, KEY); InstanceHolder.setInstance(api, link, this.injector, KEY);
} }

View File

@@ -25,6 +25,8 @@
package org.geysermc.floodgate; package org.geysermc.floodgate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -37,6 +39,7 @@ import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
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.player.PropertyKey;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
@@ -48,11 +51,11 @@ import org.geysermc.floodgate.util.UiProfile;
@Getter @Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("unchecked")
public final class FloodgatePlayerImpl implements FloodgatePlayer { public final class FloodgatePlayerImpl implements FloodgatePlayer {
private final String version; private final String version;
private final String username; private final String username;
private final String javaUsername; private final String javaUsername;
//todo maybe add a map for platform specific things
private final UUID javaUniqueId; private final UUID javaUniqueId;
private final String xuid; private final String xuid;
private final DeviceOs deviceOs; private final DeviceOs deviceOs;
@@ -60,10 +63,13 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
private final UiProfile uiProfile; private final UiProfile uiProfile;
private final InputMode inputMode; private final InputMode inputMode;
private final String ip; private final String ip;
private final boolean fromProxy; //todo remove hasBungeeData private final boolean fromProxy;
private final LinkedPlayer linkedPlayer; private final LinkedPlayer linkedPlayer;
private final RawSkin rawSkin; private final RawSkin rawSkin;
@Getter(AccessLevel.PRIVATE)
public Map<PropertyKey, Object> propertyKeyToValue;
@Getter(AccessLevel.PRIVATE)
private Map<String, PropertyKey> stringToPropertyKey;
/** /**
* Returns true if the player is still logging in * Returns true if the player is still logging in
*/ */
@@ -87,6 +93,12 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
UiProfile uiProfile = UiProfile.getById(data.getUiProfile()); UiProfile uiProfile = UiProfile.getById(data.getUiProfile());
InputMode inputMode = InputMode.getById(data.getInputMode()); InputMode inputMode = InputMode.getById(data.getInputMode());
// RawSkin must be removed from the encrypted data
if (api instanceof ProxyFloodgateApi) {
InstanceHolder.castApi(ProxyFloodgateApi.class)
.updateEncryptedData(javaUniqueId, data);
}
LinkedPlayer linkedPlayer; LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one) // we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
@@ -103,7 +115,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
deviceOs, data.getLanguageCode(), uiProfile, inputMode, data.getIp(), deviceOs, data.getLanguageCode(), uiProfile, inputMode, data.getIp(),
data.isFromProxy(), linkedPlayer, skin); data.isFromProxy(), linkedPlayer, skin);
// oh oh, after fetching the linkedPlayer our encrypted data is incorrect. // encrypted data has been changed after fetching the linkedPlayer
// We have to update it... // We have to update it...
if (linkedPlayer != null && api instanceof ProxyFloodgateApi) { if (linkedPlayer != null && api instanceof ProxyFloodgateApi) {
InstanceHolder.castApi(ProxyFloodgateApi.class) InstanceHolder.castApi(ProxyFloodgateApi.class)
@@ -112,14 +124,6 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
return player; return player;
} }
public UUID getCorrectUniqueId() {
return linkedPlayer != null ? linkedPlayer.getJavaUniqueId() : javaUniqueId;
}
public String getCorrectUsername() {
return linkedPlayer != null ? linkedPlayer.getJavaUsername() : javaUsername;
}
/** /**
* Fetch and return the LinkedPlayer object associated to the player if the player is linked. * Fetch and return the LinkedPlayer object associated to the player if the player is linked.
* Please note that this method loads the LinkedPlayer synchronously. * Please note that this method loads the LinkedPlayer synchronously.
@@ -154,9 +158,108 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
CompletableFuture.completedFuture(null); CompletableFuture.completedFuture(null);
} }
public UUID getCorrectUniqueId() {
return linkedPlayer != null ? linkedPlayer.getJavaUniqueId() : javaUniqueId;
}
public String getCorrectUsername() {
return linkedPlayer != null ? linkedPlayer.getJavaUsername() : javaUsername;
}
public BedrockData toBedrockData() { public BedrockData toBedrockData() {
return BedrockData.of( return BedrockData.of(
version, username, xuid, deviceOs.ordinal(), languageCode, version, username, xuid, deviceOs.ordinal(), languageCode,
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, fromProxy); uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, fromProxy);
} }
public <T> T getProperty(PropertyKey key) {
if (propertyKeyToValue == null) {
return null;
}
return (T) propertyKeyToValue.get(key);
}
public <T> T getProperty(String key) {
if (stringToPropertyKey == null) {
return null;
}
return getProperty(stringToPropertyKey.get(key));
}
public <T> T removeProperty(String key) {
if (stringToPropertyKey == null) {
return null;
}
PropertyKey propertyKey = stringToPropertyKey.get(key);
if (propertyKey == null || !propertyKey.isRemoveable()) {
return null;
}
return (T) propertyKeyToValue.remove(propertyKey);
}
public <T> T removeProperty(PropertyKey key) {
if (stringToPropertyKey == null) {
return null;
}
PropertyKey propertyKey = stringToPropertyKey.get(key.getKey());
if (propertyKey == null || !propertyKey.equals(key) || !propertyKey.isRemoveable()) {
return null;
}
return (T) propertyKeyToValue.remove(key);
}
public <T> T addProperty(PropertyKey key, Object value) {
if (stringToPropertyKey == null) {
stringToPropertyKey = new HashMap<>();
propertyKeyToValue = new HashMap<>();
stringToPropertyKey.put(key.getKey(), key);
propertyKeyToValue.put(key, value);
return null;
}
PropertyKey propertyKey = stringToPropertyKey.get(key.getKey());
if (propertyKey != null && propertyKey.equals(key) && key.isChangeable()) {
stringToPropertyKey.put(key.getKey(), key);
return (T) propertyKeyToValue.put(key, value);
}
return (T) stringToPropertyKey.computeIfAbsent(key.getKey(), (keyString) -> {
propertyKeyToValue.put(key, value);
return key;
});
}
public <T> T addProperty(String key, Object value) {
PropertyKey propertyKey = new PropertyKey(key, true, true);
if (stringToPropertyKey == null) {
stringToPropertyKey = new HashMap<>();
propertyKeyToValue = new HashMap<>();
stringToPropertyKey.put(key, propertyKey);
propertyKeyToValue.put(propertyKey, value);
return null;
}
PropertyKey currentPropertyKey = stringToPropertyKey.get(key);
// key is always changeable if it passes this if statement
if (currentPropertyKey != null && currentPropertyKey.equals(propertyKey)) {
stringToPropertyKey.put(key, propertyKey);
return (T) propertyKeyToValue.put(propertyKey, value);
}
return (T) stringToPropertyKey.computeIfAbsent(key, (keyString) -> {
propertyKeyToValue.put(propertyKey, value);
return propertyKey;
});
}
} }

View File

@@ -28,6 +28,8 @@ package org.geysermc.floodgate;
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH; import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import io.netty.channel.Channel;
import java.net.InetSocketAddress;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -35,6 +37,7 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.SimpleFloodgateApi;
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.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.FloodgateCipher;
@@ -48,7 +51,7 @@ public final class HandshakeHandler {
private final FloodgateCipher cipher; private final FloodgateCipher cipher;
private final FloodgateConfigHolder configHolder; private final FloodgateConfigHolder configHolder;
public HandshakeResult handle(@NonNull String handshakeData) { public HandshakeResult handle(Channel channel, @NonNull String handshakeData) {
try { try {
String[] dataArray = handshakeData.split("\0"); String[] dataArray = handshakeData.split("\0");
@@ -95,11 +98,13 @@ public final class HandshakeHandler {
rawSkin = RawSkin.decode(rawSkinData); rawSkin = RawSkin.decode(rawSkinData);
} }
System.out.println(rawSkin);
FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, rawSkin, configHolder); FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, rawSkin, configHolder);
api.addPlayer(player.getJavaUniqueId(), player); api.addPlayer(player.getJavaUniqueId(), player);
int port = ((InetSocketAddress) channel.remoteAddress()).getPort();
InetSocketAddress socketAddress = new InetSocketAddress(bedrockData.getIp(), port);
player.addProperty(PropertyKey.SOCKET_ADDRESS, socketAddress);
return new HandshakeResult(ResultType.SUCCESS, dataArray, bedrockData, player); return new HandshakeResult(ResultType.SUCCESS, dataArray, bedrockData, player);
} catch (InvalidFormatException formatException) { } catch (InvalidFormatException formatException) {
// only header exceptions should return 'not floodgate data', // only header exceptions should return 'not floodgate data',

View File

@@ -27,10 +27,10 @@ package org.geysermc.floodgate.addon;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import org.geysermc.floodgate.addon.addonmanager.AddonManagerHandler; import org.geysermc.floodgate.addon.addonmanager.AddonManagerHandler;
import org.geysermc.floodgate.api.inject.InjectorAddon; import org.geysermc.floodgate.api.inject.InjectorAddon;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.util.Utils;
public final class AddonManagerAddon implements InjectorAddon { public final class AddonManagerAddon implements InjectorAddon {
@Inject private CommonPlatformInjector injector; @Inject private CommonPlatformInjector injector;
@@ -47,10 +47,7 @@ public final class AddonManagerAddon implements InjectorAddon {
@Override @Override
public void onRemoveInject(Channel channel) { public void onRemoveInject(Channel channel) {
ChannelHandler handler = channel.pipeline().get("floodgate_addon"); Utils.removeHandler(channel.pipeline(), "floodgate_addon");
if (handler != null) {
channel.pipeline().remove(handler);
}
} }
@Override @Override

View File

@@ -34,6 +34,7 @@ import org.geysermc.floodgate.addon.debug.ChannelOutDebugHandler;
import org.geysermc.floodgate.api.inject.InjectorAddon; import org.geysermc.floodgate.api.inject.InjectorAddon;
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.util.Utils;
public final class DebugAddon implements InjectorAddon { public final class DebugAddon implements InjectorAddon {
@Inject private FloodgateConfig config; @Inject private FloodgateConfig config;
@@ -71,8 +72,8 @@ public final class DebugAddon implements InjectorAddon {
public void onRemoveInject(Channel channel) { public void onRemoveInject(Channel channel) {
ChannelPipeline pipeline = channel.pipeline(); ChannelPipeline pipeline = channel.pipeline();
pipeline.remove("floodgate_debug_out"); Utils.removeHandler(pipeline, "floodgate_debug_out");
pipeline.remove("floodgate_debug_in"); Utils.removeHandler(pipeline, "floodgate_debug_in");
} }
@Override @Override

View File

@@ -29,9 +29,10 @@ import static java.util.Objects.requireNonNull;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.inject.Inject; import com.google.inject.AbstractModule;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.google.inject.name.Named; import com.google.inject.Provides;
import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@@ -124,8 +125,17 @@ public final class PlayerLinkLoader {
return null; return null;
} }
// allow the FloodgateConfig to be used directly instead of the FloodgateConfigHolder
Injector child = injector.createChildInjector(new AbstractModule() {
@Provides
@Singleton
public FloodgateConfig floodgateConfig() {
return config;
}
});
try { try {
PlayerLink instance = injector.getInstance(mainClass); PlayerLink instance = child.getInstance(mainClass);
instance.load(); instance.load();
return instance; return instance;
} catch (Exception exception) { } catch (Exception exception) {

View File

@@ -54,7 +54,7 @@ import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class CommonModule extends AbstractModule { public class CommonModule extends AbstractModule {
private final Path dataDirectory; private final Path dataDirectory;
@Override @Override

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020 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.module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.nio.file.Path;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinHandler;
public final class ProxyCommonModule extends CommonModule {
public ProxyCommonModule(Path dataDirectory) {
super(dataDirectory);
}
@Override
protected void configure() {
super.configure();
bind(SimpleFloodgateApi.class).to(ProxyFloodgateApi.class);
}
@Provides
@Singleton
@Named("configClass")
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return ProxyFloodgateConfig.class;
}
@Provides
@Singleton
public ProxyFloodgateApi proxyFloodgateApi(PluginMessageHandler pluginMessageHandler,
FloodgateCipher cipher) {
return new ProxyFloodgateApi(pluginMessageHandler, cipher);
}
@Provides
@Singleton
public SkinHandler skinHandler(SkinApplier skinApplier, FloodgateLogger logger) {
return new SkinHandler(skinApplier, logger);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2019-2020 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.module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.nio.file.Path;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.skin.ServerSkinHandler;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinHandler;
public final class ServerCommonModule extends CommonModule {
public ServerCommonModule(Path dataDirectory) {
super(dataDirectory);
}
@Override
protected void configure() {
super.configure();
bind(SkinHandler.class).to(ServerSkinHandler.class);
}
@Provides
@Singleton
@Named("configClass")
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return FloodgateConfig.class;
}
@Provides
@Singleton
public SimpleFloodgateApi floodgateApi(PluginMessageHandler pluginMessageHandler) {
return new SimpleFloodgateApi(pluginMessageHandler);
}
@Provides
@Singleton
public ServerSkinHandler skinHandler(SkinApplier skinApplier, FloodgateLogger logger,
PluginMessageHandler pluginMessageHandler) {
return new ServerSkinHandler(skinApplier, logger, pluginMessageHandler);
}
}

View File

@@ -26,8 +26,10 @@
package org.geysermc.floodgate.platform.pluginmessage; package org.geysermc.floodgate.platform.pluginmessage;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.gson.Gson;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.geysermc.cumulus.Form; import org.geysermc.cumulus.Form;
@@ -35,6 +37,7 @@ import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.RawSkin;
public abstract class PluginMessageHandler { public abstract class PluginMessageHandler {
protected static final Gson GSON = new Gson();
protected final Short2ObjectMap<Form> storedForms = new Short2ObjectOpenHashMap<>(); protected final Short2ObjectMap<Form> storedForms = new Short2ObjectOpenHashMap<>();
private final AtomicInteger nextFormId = new AtomicInteger(0); private final AtomicInteger nextFormId = new AtomicInteger(0);
private final FloodgateConfigHolder configHolder; private final FloodgateConfigHolder configHolder;
@@ -45,9 +48,13 @@ public abstract class PluginMessageHandler {
public abstract boolean sendForm(UUID player, Form form); public abstract boolean sendForm(UUID player, Form form);
public abstract boolean sendSkinRequest(UUID player, RawSkin skin); public boolean sendSkinRequest(UUID player, RawSkin skin) {
return false; // Non-proxy implementations don't send requests
}
public abstract void sendSkinResponse(UUID player, String response); public void sendSkinResponse(UUID player, boolean failed, String response) {
return; // Proxy implementations don't send responses
}
protected byte[] createFormData(Form form) { protected byte[] createFormData(Form form) {
short formId = getNextFormId(); short formId = getNextFormId();
@@ -66,11 +73,40 @@ public abstract class PluginMessageHandler {
return data; return data;
} }
protected boolean callResponseConsumer(byte[] data) { protected byte[] createSkinRequestData(byte[] data) {
// data format:
// 0 = is request
// remaining = request data
byte[] output = new byte[data.length + 1];
output[0] = 1;
System.arraycopy(data, 0, output, 1, data.length);
return output;
}
protected byte[] createSkinResponseData(boolean failed, String data) {
// data format:
// 0 = is request
// 1 = has failed
// remaining = response data
byte[] rawData = data.getBytes(StandardCharsets.UTF_8);
byte[] output = new byte[rawData.length + 2];
output[0] = 0;
output[1] = (byte) (failed ? 1 : 0);
System.arraycopy(rawData, 0, output, 2, rawData.length);
return output;
}
public boolean callResponseConsumer(byte[] data) {
Form storedForm = storedForms.remove(getFormId(data)); Form storedForm = storedForms.remove(getFormId(data));
if (storedForm != null) { if (storedForm != null) {
storedForm.getResponseHandler().accept( String responseData = new String(data, 2, data.length -2, Charsets.UTF_8);
new String(data, 2, data.length - 2, Charsets.UTF_8)); storedForm.getResponseHandler().accept(responseData);
return true; return true;
} }
return false; return false;

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2020 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.skin;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.util.RawSkin;
public final class ServerSkinHandler extends SkinHandler {
private final PluginMessageHandler pluginMessageHandler;
public ServerSkinHandler(SkinApplier skinApplier,
FloodgateLogger logger,
PluginMessageHandler pluginMessageHandler) {
super(skinApplier, logger);
this.pluginMessageHandler = pluginMessageHandler;
}
public void handleSkinUploadFor(FloodgatePlayer player) {
handleSkinUploadFor(player, player.getRawSkin());
}
public void handleSkinUploadFor(FloodgatePlayer player, RawSkin rawSkin) {
handleSkinUploadFor(player, rawSkin,
(failed, response) -> {
if (player.isFromProxy()) {
pluginMessageHandler.sendSkinResponse(
player.getCorrectUniqueId(),
failed,
response
);
}
});
}
}

View File

@@ -23,29 +23,11 @@
* @link https://github.com/GeyserMC/Floodgate * @link https://github.com/GeyserMC/Floodgate
*/ */
package org.geysermc.floodgate.module; package org.geysermc.floodgate.skin;
import com.google.inject.AbstractModule; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import com.google.inject.Provides; import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
import com.google.inject.Singleton;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
@RequiredArgsConstructor public interface SkinApplier {
public final class ConfigLoadedModule extends AbstractModule { void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result);
private final FloodgateConfig config;
@Override
protected void configure() {
if (config instanceof ProxyFloodgateConfig) {
bind(ProxyFloodgateConfig.class).toInstance((ProxyFloodgateConfig) config);
}
}
@Provides
@Singleton
public FloodgateConfig floodgateConfig() {
return config;
}
} }

View File

@@ -25,50 +25,66 @@
package org.geysermc.floodgate.skin; package org.geysermc.floodgate.skin;
import static org.geysermc.floodgate.util.MessageFormatter.format; import java.util.function.BiConsumer;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
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.platform.pluginmessage.PluginMessageHandler; import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult; import org.geysermc.floodgate.util.RawSkin;
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class SkinHandler { public class SkinHandler {
private final SkinUploader uploader = new SkinUploader(); private final SkinUploader uploader = new SkinUploader();
private final PluginMessageHandler messageHandler; private final SkinApplier skinApplier;
private final FloodgateLogger logger; private final FloodgateLogger logger;
public final void handleSkinUploadFor(FloodgatePlayer player) { public final void handleSkinUploadFor(FloodgatePlayer player,
uploader.uploadSkin(player.getRawSkin()) BiConsumer<Boolean, String> consumer) {
handleSkinUploadFor(player, player.getRawSkin(), consumer);
}
public final void handleSkinUploadFor(FloodgatePlayer player,
RawSkin rawSkin,
BiConsumer<Boolean, String> consumer) {
if (player == null || rawSkin == null) {
if (consumer != null) {
consumer.accept(true, "Skin or Player is null");
}
return;
}
uploader.uploadSkin(rawSkin)
.whenComplete((uploadResult, throwable) -> { .whenComplete((uploadResult, throwable) -> {
if (throwable != null) { if (throwable != null) {
logger.error( logger.error(
"Failed to upload player skin for " + player.getCorrectUsername(), "Failed to upload player skin for " + player.getCorrectUsername(),
throwable); throwable);
messageHandler.sendSkinResponse( if (consumer != null) {
player.getJavaUniqueId(), throwable.getMessage()); consumer.accept(true, throwable.getMessage());
}
return; return;
} }
if (uploadResult.getError() != null) { if (uploadResult.getError() != null) {
logger.error(format( logger.error("Error while uploading player skin for {}: {}",
"Error while uploading player skin for {}: {}", player.getCorrectUsername(), uploadResult.getError());
player.getCorrectUsername(), uploadResult.getError()));
messageHandler.sendSkinResponse( if (consumer != null) {
player.getJavaUniqueId(), uploadResult.getError()); consumer.accept(true, uploadResult.getError());
}
return; return;
} }
logger.info("Skin upload successful for " + player.getCorrectUsername()); logger.info("Skin upload successful for " + player.getCorrectUsername());
logger.info(uploadResult.getResponse().toString()); logger.info(uploadResult.getResponse().toString());
messageHandler.sendSkinResponse(
player.getJavaUniqueId(), uploadResult.getResponse().toString()); if (consumer != null) {
applySkin(player, uploadResult); consumer.accept(false, uploadResult.getResponse().toString());
}
player.addProperty(PropertyKey.SKIN_UPLOADED, uploadResult.getResponse());
skinApplier.applySkin(player, uploadResult);
}); });
} }
protected abstract void applySkin(FloodgatePlayer player, UploadResult result);
} }

View File

@@ -30,6 +30,7 @@ import java.awt.image.BufferedImage;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.annotation.Nonnull;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -44,7 +45,7 @@ public final class SkinUploader {
private final Executor requestExecutor = Executors.newSingleThreadExecutor(); private final Executor requestExecutor = Executors.newSingleThreadExecutor();
private long nextResult = 0; private long nextResult = 0;
public CompletableFuture<UploadResult> uploadSkin(RawSkin rawSkin) { public CompletableFuture<UploadResult> uploadSkin(@Nonnull RawSkin rawSkin) {
return CompletableFuture.supplyAsync(() -> uploadSkinInner(rawSkin, 0), requestExecutor); return CompletableFuture.supplyAsync(() -> uploadSkinInner(rawSkin, 0), requestExecutor);
} }
@@ -149,5 +150,9 @@ public final class SkinUploader {
return new UploadResult(httpCode, null, false, model, skinUrl, capeUrl, response); return new UploadResult(httpCode, null, false, model, skinUrl, capeUrl, response);
} }
public static UploadResult success(JsonObject response) {
return new UploadResult(200, null, false, null, null, null, response);
}
} }
} }

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2019-2020 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.util;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
public class Utils {
/**
* This method is used in Addons.<br> Most addons can be removed once the player associated to
* the channel has been logged in, but they should also be removed once the inject is removed.
* Because of how Netty works it will throw an exception and we don't want that. This method
* removes those handlers safely.
*
* @param pipeline the pipeline
* @param handler the name of the handler to remove
*/
public static void removeHandler(ChannelPipeline pipeline, String handler) {
ChannelHandler channelHandler = pipeline.get(handler);
if (channelHandler != null) {
pipeline.remove(channelHandler);
}
}
}

View File

@@ -45,6 +45,7 @@
<properties> <properties>
<geyser.version>1.2.0-SNAPSHOT</geyser.version> <geyser.version>1.2.0-SNAPSHOT</geyser.version>
<cumulus.version>1.0-SNAPSHOT</cumulus.version>
<spigot.version>1.13-R0.1-SNAPSHOT</spigot.version> <spigot.version>1.13-R0.1-SNAPSHOT</spigot.version>
<bungee.version>1.15-SNAPSHOT</bungee.version> <bungee.version>1.15-SNAPSHOT</bungee.version>
<velocity.version>1.1.0</velocity.version> <velocity.version>1.1.0</velocity.version>

View File

@@ -26,20 +26,23 @@
package org.geysermc.floodgate; package org.geysermc.floodgate;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module; import com.google.inject.Module;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.inject.PlatformInjector; import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageRegister;
public final class SpigotPlatform extends FloodgatePlatform { public final class SpigotPlatform extends FloodgatePlatform {
@Inject private JavaPlugin plugin; @Inject private JavaPlugin plugin;
@Inject private Injector guice;
@Inject @Inject
public SpigotPlatform(FloodgateApi api, PlatformInjector platformInjector, public SpigotPlatform(FloodgateApi api, PlatformInjector platformInjector,
FloodgateLogger logger) { FloodgateLogger logger, Injector injector) {
super(api, platformInjector, logger); super(api, platformInjector, logger, injector);
} }
@Override @Override
@@ -47,7 +50,9 @@ public final class SpigotPlatform extends FloodgatePlatform {
boolean success = super.enable(postInitializeModules); boolean success = super.enable(postInitializeModules);
if (!success) { if (!success) {
Bukkit.getPluginManager().disablePlugin(plugin); Bukkit.getPluginManager().disablePlugin(plugin);
return false;
} }
return success; guice.getInstance(SpigotPluginMessageRegister.class).register();
return true;
} }
} }

View File

@@ -30,7 +30,7 @@ import com.google.inject.Injector;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.module.CommandModule; import org.geysermc.floodgate.module.CommandModule;
import org.geysermc.floodgate.module.CommonModule; import org.geysermc.floodgate.module.ServerCommonModule;
import org.geysermc.floodgate.module.SpigotAddonModule; import org.geysermc.floodgate.module.SpigotAddonModule;
import org.geysermc.floodgate.module.SpigotListenerModule; import org.geysermc.floodgate.module.SpigotListenerModule;
import org.geysermc.floodgate.module.SpigotPlatformModule; import org.geysermc.floodgate.module.SpigotPlatformModule;
@@ -46,7 +46,7 @@ public final class SpigotPlugin extends JavaPlugin {
long ctm = System.currentTimeMillis(); long ctm = System.currentTimeMillis();
Injector injector = Guice.createInjector( Injector injector = Guice.createInjector(
new CommonModule(getDataFolder().toPath()), new ServerCommonModule(getDataFolder().toPath()),
new SpigotPlatformModule(this) new SpigotPlatformModule(this)
); );

View File

@@ -34,6 +34,7 @@ import org.geysermc.floodgate.api.inject.InjectorAddon;
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.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.Utils;
public final class SpigotDataAddon implements InjectorAddon { public final class SpigotDataAddon implements InjectorAddon {
@Inject private HandshakeHandler handshakeHandler; @Inject private HandshakeHandler handshakeHandler;
@@ -63,7 +64,7 @@ public final class SpigotDataAddon implements InjectorAddon {
@Override @Override
public void onRemoveInject(Channel channel) { public void onRemoveInject(Channel channel) {
channel.pipeline().remove("floodgate_data_handler"); Utils.removeHandler(channel.pipeline(), "floodgate_data_handler");
} }
@Override @Override

View File

@@ -48,6 +48,7 @@ import org.geysermc.floodgate.HandshakeHandler;
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
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.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
@@ -180,7 +181,7 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
networkManager = ctx.channel().pipeline().get("packet_handler"); networkManager = ctx.channel().pipeline().get("packet_handler");
String handshakeValue = getCastedValue(packet, HANDSHAKE_HOST); String handshakeValue = getCastedValue(packet, HANDSHAKE_HOST);
HandshakeResult result = handshakeHandler.handle(handshakeValue); HandshakeResult result = handshakeHandler.handle(ctx.channel(), handshakeValue);
switch (result.getResultType()) { switch (result.getResultType()) {
case SUCCESS: case SUCCESS:
break; break;
@@ -204,6 +205,8 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
String[] data = result.getHandshakeData(); String[] data = result.getHandshakeData();
bungeeData = isBungeeData(); bungeeData = isBungeeData();
InetSocketAddress correctAddress = fPlayer.getProperty(PropertyKey.SOCKET_ADDRESS);
if (bungeeData) { if (bungeeData) {
setValue(packet, HANDSHAKE_HOST, data[0] + '\0' + setValue(packet, HANDSHAKE_HOST, data[0] + '\0' +
bedrockData.getIp() + '\0' + bedrockData.getIp() + '\0' +
@@ -213,12 +216,7 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
// Use a spoofedUUID for initUUID (just like Bungeecord) // Use a spoofedUUID for initUUID (just like Bungeecord)
setValue(networkManager, "spoofedUUID", fPlayer.getCorrectUniqueId()); setValue(networkManager, "spoofedUUID", fPlayer.getCorrectUniqueId());
// Use the player his IP for stuff instead of Geyser his IP // Use the player his IP for stuff instead of Geyser his IP
SocketAddress newAddress = new InetSocketAddress( setValue(networkManager, SOCKET_ADDRESS, correctAddress);
bedrockData.getIp(),
((InetSocketAddress) ctx.channel().remoteAddress()).getPort()
);
setValue(networkManager, SOCKET_ADDRESS, newAddress);
} }
} else if (isLogin) { } else if (isLogin) {
if (!bungeeData) { if (!bungeeData) {

View File

@@ -38,12 +38,12 @@ import org.geysermc.floodgate.FloodgatePlayerImpl;
import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.SimpleFloodgateApi;
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.skin.SkinHandler; import org.geysermc.floodgate.skin.ServerSkinHandler;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
public final class SpigotListener implements Listener { public final class SpigotListener implements Listener {
@Inject private SimpleFloodgateApi api; @Inject private SimpleFloodgateApi api;
@Inject private SkinHandler skinHandler; @Inject private ServerSkinHandler skinHandler;
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;

View File

@@ -33,10 +33,9 @@ import lombok.RequiredArgsConstructor;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.SpigotPlugin; import org.geysermc.floodgate.SpigotPlugin;
import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.SpigotCommandRegistration; import org.geysermc.floodgate.command.SpigotCommandRegistration;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.inject.spigot.SpigotInjector; import org.geysermc.floodgate.inject.spigot.SpigotInjector;
@@ -47,8 +46,10 @@ import org.geysermc.floodgate.platform.command.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageHandler; import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageHandler;
import org.geysermc.floodgate.pluginmessage.SpigotSkinHandler; import org.geysermc.floodgate.pluginmessage.SpigotPluginMessageRegister;
import org.geysermc.floodgate.skin.SkinHandler; import org.geysermc.floodgate.pluginmessage.SpigotSkinApplier;
import org.geysermc.floodgate.skin.ServerSkinHandler;
import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.SpigotCommandUtil; import org.geysermc.floodgate.util.SpigotCommandUtil;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
@@ -57,25 +58,17 @@ import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
public final class SpigotPlatformModule extends AbstractModule { public final class SpigotPlatformModule extends AbstractModule {
private final SpigotPlugin plugin; private final SpigotPlugin plugin;
@Override
protected void configure() {
bind(PluginMessageHandler.class).to(SpigotPluginMessageHandler.class);
}
@Provides @Provides
@Singleton @Singleton
public JavaPlugin javaPlugin() { public JavaPlugin javaPlugin() {
return plugin; return plugin;
} }
@Provides
@Singleton
@Named("configClass")
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return FloodgateConfig.class;
}
@Provides
@Singleton
public SimpleFloodgateApi floodgateApi(PluginMessageHandler pluginMessageHandler) {
return new SimpleFloodgateApi(pluginMessageHandler);
}
@Provides @Provides
@Singleton @Singleton
public FloodgateLogger floodgateLogger(LanguageManager languageManager) { public FloodgateLogger floodgateLogger(LanguageManager languageManager) {
@@ -90,7 +83,8 @@ public final class SpigotPlatformModule extends AbstractModule {
@Singleton @Singleton
public CommandRegistration commandRegistration( public CommandRegistration commandRegistration(
SpigotVersionSpecificMethods versionSpecificMethods, SpigotVersionSpecificMethods versionSpecificMethods,
CommandUtil commandUtil) { CommandUtil commandUtil
) {
return new SpigotCommandRegistration(versionSpecificMethods, plugin, commandUtil); return new SpigotCommandRegistration(versionSpecificMethods, plugin, commandUtil);
} }
@@ -106,24 +100,6 @@ public final class SpigotPlatformModule extends AbstractModule {
return new SpigotListenerRegistration(plugin); return new SpigotListenerRegistration(plugin);
} }
@Provides
@Singleton
public PluginMessageHandler pluginMessageHandler(FloodgateConfigHolder configHolder,
@Named("formChannel") String formChannel,
@Named("skinChannel") String skinChannel) {
return new SpigotPluginMessageHandler(configHolder, plugin, formChannel, skinChannel);
}
@Provides
@Singleton
public SkinHandler skinHandler(PluginMessageHandler messageHandler, FloodgateLogger logger,
SpigotVersionSpecificMethods versionSpecificMethods,
FloodgateConfigHolder configHolder) {
return new SpigotSkinHandler(
messageHandler, logger, versionSpecificMethods, plugin, configHolder
);
}
/* /*
DebugAddon / PlatformInjector DebugAddon / PlatformInjector
*/ */
@@ -157,4 +133,47 @@ public final class SpigotPlatformModule extends AbstractModule {
public String implementationName() { public String implementationName() {
return "Spigot"; return "Spigot";
} }
/*
Others
*/
@Provides
@Singleton
public SpigotPluginMessageHandler pluginMessageHandler(
FloodgateConfigHolder configHolder,
@Named("formChannel") String formChannel,
@Named("skinChannel") String skinChannel
) {
return new SpigotPluginMessageHandler(configHolder, plugin, formChannel, skinChannel);
}
@Provides
@Singleton
public SpigotPluginMessageRegister pluginMessageRegister(
FloodgateApi api,
@Named("formChannel") String formChannel,
@Named("skinChannel") String skinChannel,
SpigotPluginMessageHandler pluginMessageHandler,
ServerSkinHandler skinHandler,
FloodgateLogger logger
) {
return new SpigotPluginMessageRegister(plugin, api, formChannel, skinChannel,
pluginMessageHandler, skinHandler, logger);
}
@Provides
@Singleton
public SkinApplier skinApplier(
SpigotVersionSpecificMethods versionSpecificMethods,
FloodgateConfigHolder configHolder
) {
return new SpigotSkinApplier(versionSpecificMethods, plugin, configHolder);
}
@Provides
@Singleton
public SpigotVersionSpecificMethods versionSpecificMethods() {
return new SpigotVersionSpecificMethods(plugin);
}
} }

View File

@@ -25,47 +25,28 @@
package org.geysermc.floodgate.pluginmessage; package org.geysermc.floodgate.pluginmessage;
import com.google.common.base.Charsets;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.Messenger;
import org.geysermc.cumulus.Form; import org.geysermc.cumulus.Form;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler; import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.util.RawSkin;
public class SpigotPluginMessageHandler extends PluginMessageHandler { public class SpigotPluginMessageHandler extends PluginMessageHandler {
private final JavaPlugin plugin; private final JavaPlugin plugin;
private final String formChannel; private final String formChannel;
private final String skinChannel; private final String skinChannel;
public SpigotPluginMessageHandler(FloodgateConfigHolder configHolder, JavaPlugin plugin, public SpigotPluginMessageHandler(
String formChannel, String skinChannel) { FloodgateConfigHolder configHolder,
JavaPlugin plugin,
String formChannel,
String skinChannel
) {
super(configHolder); super(configHolder);
this.plugin = plugin; this.plugin = plugin;
this.formChannel = formChannel; this.formChannel = formChannel;
this.skinChannel = skinChannel; this.skinChannel = skinChannel;
Messenger messenger = plugin.getServer().getMessenger();
// form
messenger.registerIncomingPluginChannel(
plugin, formChannel,
(channel, player, message) -> callResponseConsumer(message));
messenger.registerOutgoingPluginChannel(plugin, formChannel);
// skin
messenger.registerIncomingPluginChannel(
plugin, skinChannel,
(channel, player, message) -> {
String origin =
FloodgateApi.getInstance().getPlayer(player.getUniqueId()) != null
? "Geyser" : "player";
System.out.println("Got skin from " + origin + "!");
}
);
} }
@Override @Override
@@ -81,22 +62,9 @@ public class SpigotPluginMessageHandler extends PluginMessageHandler {
} }
@Override @Override
public boolean sendSkinRequest(UUID playerId, RawSkin skin) { public void sendSkinResponse(UUID playerId, boolean failed, String response) {
try { try {
byte[] skinData = skin.toString().getBytes(Charsets.UTF_8); byte[] responseData = createSkinResponseData(failed, response);
Bukkit.getPlayer(playerId).sendPluginMessage(plugin, skinChannel, skinData);
//todo use json or something to split request and response
} catch (Exception exception) {
exception.printStackTrace();
return false;
}
return true;
}
@Override
public void sendSkinResponse(UUID playerId, String response) {
try {
byte[] responseData = response.getBytes(Charsets.UTF_8);
Bukkit.getPlayer(playerId).sendPluginMessage(plugin, skinChannel, responseData); Bukkit.getPlayer(playerId).sendPluginMessage(plugin, skinChannel, responseData);
} catch (Exception exception) { } catch (Exception exception) {
exception.printStackTrace(); exception.printStackTrace();

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2019-2020 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.pluginmessage;
import lombok.RequiredArgsConstructor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.Messenger;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.skin.ServerSkinHandler;
import org.geysermc.floodgate.util.Base64Utils;
import org.geysermc.floodgate.util.RawSkin;
@RequiredArgsConstructor
public class SpigotPluginMessageRegister {
private final JavaPlugin plugin;
private final FloodgateApi api;
private final String formChannel;
private final String skinChannel;
private final SpigotPluginMessageHandler pluginMessageHandler;
private final ServerSkinHandler skinHandler;
private final FloodgateLogger logger;
public void register() {
Messenger messenger = plugin.getServer().getMessenger();
// form
messenger.registerIncomingPluginChannel(
plugin, formChannel,
(channel, player, message) ->
pluginMessageHandler.callResponseConsumer(message));
messenger.registerOutgoingPluginChannel(plugin, formChannel);
// skin
messenger.registerIncomingPluginChannel(
plugin, skinChannel,
(channel, player, message) -> {
//todo make a Proxy and a Server class for this?
FloodgatePlayer floodgatePlayer = api.getPlayer(player.getUniqueId());
if (floodgatePlayer == null) {
logKick(player, "Non-Floodgate player sent a Skin plugin message.");
return;
}
// non-proxy servers can only handle requests (from proxies)
if (!floodgatePlayer.isFromProxy()) {
logKick(player, "Cannot receive Skin request from Player.");
return;
}
// 1 byte for isRequest and 9 for RawSkin itself
if (message.length < Base64Utils.getEncodedLength(9 + 1)) {
logKick(player, "Skin request data has to be at least 10 byte long.");
return;
}
boolean request = message[0] == 1;
if (!request) {
logKick(player, "Proxy sent a response instead of a request?");
return;
}
RawSkin rawSkin = null;
try {
rawSkin = RawSkin.decode(message, 1);
} catch (Exception exception) {
logger.error("Failed to decode RawSkin", exception);
}
// we let it continue since SkinHandler sends the plugin message for us
skinHandler.handleSkinUploadFor(floodgatePlayer, rawSkin);
}
);
}
private void logKick(Player player, String reason) {
logger.error(reason + " Closing connection for " + player.getName());
player.kickPlayer(reason);
}
}

View File

@@ -33,16 +33,14 @@ import java.lang.reflect.Method;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.geysermc.floodgate.SpigotPlugin; import org.geysermc.floodgate.SpigotPlugin;
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.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult; import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
public class SpigotSkinHandler extends SkinHandler { public final class SpigotSkinApplier implements SkinApplier {
private static final Method GET_PROFILE_METHOD; private static final Method GET_PROFILE_METHOD;
static { static {
@@ -56,17 +54,18 @@ public class SpigotSkinHandler extends SkinHandler {
private final SpigotPlugin plugin; private final SpigotPlugin plugin;
private final FloodgateConfigHolder configHolder; private final FloodgateConfigHolder configHolder;
public SpigotSkinHandler(PluginMessageHandler messageHandler, FloodgateLogger logger, public SpigotSkinApplier(
SpigotVersionSpecificMethods versionSpecificMethods, SpigotVersionSpecificMethods versionSpecificMethods,
SpigotPlugin plugin, FloodgateConfigHolder configHolder) { SpigotPlugin plugin,
super(messageHandler, logger); FloodgateConfigHolder configHolder
) {
this.versionSpecificMethods = versionSpecificMethods; this.versionSpecificMethods = versionSpecificMethods;
this.plugin = plugin; this.plugin = plugin;
this.configHolder = configHolder; this.configHolder = configHolder;
} }
@Override @Override
protected void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result) { public void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result) {
Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId()); Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId());
GameProfile profile = ReflectionUtils.castedInvoke(player, GET_PROFILE_METHOD); GameProfile profile = ReflectionUtils.castedInvoke(player, GET_PROFILE_METHOD);
@@ -89,7 +88,7 @@ public class SpigotSkinHandler extends SkinHandler {
if (configHolder.get().isApplySkinDirectly()) { if (configHolder.get().isApplySkinDirectly()) {
plugin.getServer().getScheduler().runTask(plugin, () -> { plugin.getServer().getScheduler().runTask(plugin, () -> {
for (Player p : Bukkit.getOnlinePlayers()) { for (Player p : Bukkit.getOnlinePlayers()) {
if (p != player) { if (p != player && p.canSee(player)) {
versionSpecificMethods.hidePlayer(p, player); versionSpecificMethods.hidePlayer(p, player);
versionSpecificMethods.showPlayer(p, player); versionSpecificMethods.showPlayer(p, player);
} }

View File

@@ -33,7 +33,7 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
import java.nio.file.Path; import java.nio.file.Path;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.module.CommandModule; import org.geysermc.floodgate.module.CommandModule;
import org.geysermc.floodgate.module.CommonModule; import org.geysermc.floodgate.module.ProxyCommonModule;
import org.geysermc.floodgate.module.VelocityAddonModule; import org.geysermc.floodgate.module.VelocityAddonModule;
import org.geysermc.floodgate.module.VelocityListenerModule; import org.geysermc.floodgate.module.VelocityListenerModule;
import org.geysermc.floodgate.module.VelocityPlatformModule; import org.geysermc.floodgate.module.VelocityPlatformModule;
@@ -48,7 +48,7 @@ public final class VelocityPlugin {
long ctm = System.currentTimeMillis(); long ctm = System.currentTimeMillis();
Injector injector = guice.createChildInjector( Injector injector = guice.createChildInjector(
new CommonModule(dataDirectory), new ProxyCommonModule(dataDirectory),
new VelocityPlatformModule() new VelocityPlatformModule()
); );

View File

@@ -35,6 +35,7 @@ import org.geysermc.floodgate.api.inject.InjectorAddon;
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.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.util.Utils;
public final class VelocityDataAddon implements InjectorAddon { public final class VelocityDataAddon implements InjectorAddon {
@Inject private HandshakeHandler handshakeHandler; @Inject private HandshakeHandler handshakeHandler;
@@ -84,7 +85,7 @@ public final class VelocityDataAddon implements InjectorAddon {
@Override @Override
public void onRemoveInject(Channel channel) { public void onRemoveInject(Channel channel) {
channel.pipeline().remove("floodgate_data_handler"); Utils.removeHandler(channel.pipeline(), "floodgate_data_handler");
} }
@Override @Override

View File

@@ -89,7 +89,7 @@ public final class VelocityProxyDataHandler extends SimpleChannelInboundHandler<
private void handleClientToProxy(ChannelHandlerContext ctx, Object packet) { private void handleClientToProxy(ChannelHandlerContext ctx, Object packet) {
String address = getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS); String address = getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS);
HandshakeResult result = handshakeHandler.handle(address); HandshakeResult result = handshakeHandler.handle(ctx.channel(), address);
switch (result.getResultType()) { switch (result.getResultType()) {
case SUCCESS: case SUCCESS:
break; break;

View File

@@ -25,8 +25,6 @@
package org.geysermc.floodgate.listener; package org.geysermc.floodgate.listener;
import static org.geysermc.floodgate.util.MessageFormatter.format;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
@@ -98,9 +96,8 @@ public class VelocityPluginMessageHandler extends PluginMessageHandler {
event.setResult(ForwardResult.handled()); event.setResult(ForwardResult.handled());
if (!callResponseConsumer(data)) { if (!callResponseConsumer(data)) {
logger.error(format( logger.error("Couldn't find stored form with id {} for player {}",
"Couldn't find stored form with id {} for player {}", formId, ((Player) source).getUsername());
formId, ((Player) source).getUsername()));
} }
} }
} }
@@ -119,9 +116,4 @@ public class VelocityPluginMessageHandler extends PluginMessageHandler {
public boolean sendSkinRequest(UUID player, RawSkin skin) { public boolean sendSkinRequest(UUID player, RawSkin skin) {
return false; //todo return false; //todo
} }
@Override
public void sendSkinResponse(UUID player, String response) {
}
} }