diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..339a92aa --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/GeyserMC.xml b/.idea/copyright/GeyserMC.xml new file mode 100644 index 00000000..729d5a1e --- /dev/null +++ b/.idea/copyright/GeyserMC.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..d8157acd --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 00000000..6298e853 --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,77 @@ + + + + parent + org.geysermc.floodgate + 1.0-SNAPSHOT + + 4.0.0 + + api + + + + nukkitx-release-repo + https://repo.nukkitx.com/maven-releases/ + + true + + + false + + + + nukkitx-snapshot-repo + https://repo.nukkitx.com/maven-snapshots/ + + false + + + true + + + + + + + org.geysermc + common + ${geyser-version} + + + io.netty + netty-transport + 4.1.49.Final + provided + + + com.google.code.findbugs + jsr305 + 3.0.2 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + + + ${outputName} + true + + + + + \ No newline at end of file diff --git a/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java b/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java new file mode 100644 index 00000000..ad194b20 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/FloodgateApi.java @@ -0,0 +1,83 @@ +/* + * 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; + +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +import java.util.UUID; + +public interface FloodgateApi { + /** + * Method to determine if the given online player is a bedrock player + * + * @param uuid The uuid of the online player + * @return true if the given online player is a Bedrock player + */ + boolean isBedrockPlayer(UUID uuid); + + /** + * Get info about the given Bedrock player + * + * @param uuid the uuid of the online Bedrock player + * @return FloodgatePlayer if the given uuid is a Bedrock player + */ + FloodgatePlayer getPlayer(UUID uuid); + + /** + * Create a valid Java player uuid of a xuid + * + * @param xuid the xuid that should be converted + * @return the created uuid based of the given xuid + */ + UUID createJavaPlayerId(long xuid); + + /** + * Checks if the uuid of the player has the {@link #createJavaPlayerId(long)} format. + * This method can't validate a linked player uuid, since that doesn't equal the format. + * Use {@link #isBedrockPlayer(UUID)} if you want to include linked accounts. + * + * @param uuid the uuid to check + * @return true if the given uuid has the correct format. + */ + boolean isFloodgateId(UUID uuid); + + /** + * Returns the instance that manages all the linking. + */ + default PlayerLink getPlayerLink() { + return InstanceHolder.getPlayerLink(); + } + + /** + * Returns the Floodgate API instance. + * This method is equal to running {@link InstanceHolder#getInstance()} + */ + static FloodgateApi getInstance() { + return InstanceHolder.getInstance(); + } +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java b/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java new file mode 100644 index 00000000..56f89f05 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/InstanceHolder.java @@ -0,0 +1,54 @@ +/* + * 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; + +import lombok.Getter; +import org.geysermc.floodgate.api.link.PlayerLink; + +import java.util.UUID; + +public final class InstanceHolder { + @Getter private static FloodgateApi instance; + @Getter private static PlayerLink playerLink; + private static UUID key; + + public static boolean setInstance(FloodgateApi floodgateApi, PlayerLink link, UUID key) { + if (instance == null) { + InstanceHolder.key = key; + } else if (!InstanceHolder.key.equals(key)) { + return false; + } + instance = floodgateApi; + playerLink = link; + return true; + } + + @SuppressWarnings("unchecked") + public static T castApi(Class cast) { + return (T) instance; + } +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java b/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java new file mode 100644 index 00000000..f0a81a4d --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/inject/InjectorAddon.java @@ -0,0 +1,68 @@ +/* + * 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.inject; + +import io.netty.channel.Channel; + +public interface InjectorAddon { + /** + * Called when injecting a specific channel + * (every client that is connected to the server has his own channel). + * Internally used for the Floodgate debugger and data handler but can also be used for + * third party things. + * + * @param channel the channel that the injector is injecting + * @param proxyToServer if the the connection is between the proxy and a server + */ + void onInject(Channel channel, boolean proxyToServer); + + /** + * Called when the player successfully logged in. + * That is the moment that most of the addons can deregister. + * Note that it is entirely optional to remove the addon from the channel, + * the injector won't force the addon to remove. + * + * @param channel the channel that the injector injected + */ + void onLoginDone(Channel channel); + + /** + * Called when Floodgate is removing the injection from the server. + * The addon should remove his traces otherwise it is likely that an error will popup after + * the server is injected again. + * + * @param channel the channel that the injector injected + */ + void onRemoveInject(Channel channel); + + /** + * If the Injector should call {@link #onInject(Channel, boolean)} + * + * @return true if it should, false otherwise + */ + boolean shouldInject(); +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java b/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java new file mode 100644 index 00000000..9f92a099 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/inject/PlatformInjector.java @@ -0,0 +1,84 @@ +/* + * 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.inject; + +/** + * The global interface of all the Platform Injectors. + * The injector can be used for various things. It is used internally for getting Floodgate + * data out of the handshake packet and for debug mode, but there is also an option to add your + * own addons. + * Note that every Floodgate platform that supports netty should implement this, + * but the platform implementation isn't required to implement this. + */ +public interface PlatformInjector { + /** + * Injects the server connection. + * This will allow various addons (like getting the Floodgate data and debug mode) to work. + * + * @return true if the connection has successfully been injected + * @throws Exception if something went wrong while injecting the server connection + */ + boolean inject() throws Exception; + + /** + * Removes the injection from the server. + * Please note that this function should only be used internally (on plugin shutdown). + * This method will also remove every added addon. + * + * @return true if the injection has successfully been removed + * @throws Exception if something went wrong while removing the injection + */ + boolean removeInjection() throws Exception; + + /** + * If the server connection is currently injected. + * + * @return true if the server connection is currently injected, returns false otherwise + */ + boolean isInjected(); + + /** + * Adds an addon to the addon list of the Floodgate Injector + * (the addon is called when Floodgate injects a channel). + * See {@link InjectorAddon} for more info. + * + * @param addon the addon to add to the addon list + * @return true if the addon has been added, false if the addon is already present + */ + boolean addAddon(InjectorAddon addon); + + /** + * Removes an addon from the addon list of the Floodgate Injector + * (the addon is called when Floodgate injects a channel). + * See {@link InjectorAddon} for more info. + * + * @param addon the class of the addon to remove from the addon list + * @param the addon type + * @return the instance that was present when removing + */ + T removeAddon(Class addon); +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequest.java b/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequest.java new file mode 100644 index 00000000..12b171f5 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/link/LinkRequest.java @@ -0,0 +1,54 @@ +package org.geysermc.floodgate.api.link; + +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +import java.util.UUID; + +public interface LinkRequest { + /** + * Returns the Java username of the linked player. + */ + String getJavaUsername(); + + /** + * Returns the Java unique id of the linked player. + */ + UUID getJavaUniqueId(); + + /** + * Returns the code that the Bedrock player has to enter in order to link the account. + */ + String getLinkCode(); + + /** + * Returns the username of player being linked. + */ + String getBedrockUsername(); + + /** + * Returns the unix time when the player link was requested. + */ + long getRequestTime(); + + /** + * If this player link request is expired. + * + * @param linkTimeout the link timeout in millis + * @return true if the difference between now and requestTime is greater then the link timout + */ + boolean isExpired(long linkTimeout); + + /** + * Checks if the given FloodgatePlayer is the player requested in this LinkRequest. + * This method will check both the real bedrock username + * {@link FloodgatePlayer#getUsername()} and the edited username + * {@link FloodgatePlayer#getJavaUsername()} and returns true if one of the two matches. + * + * @param player the player to check + * @return true if the given player is the player requested + */ + default boolean isRequestedPlayer(FloodgatePlayer player) { + return getBedrockUsername().equals(player.getUsername()) || + getBedrockUsername().equals(player.getJavaUsername()); + } +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java b/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java new file mode 100644 index 00000000..526571b4 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/link/PlayerLink.java @@ -0,0 +1,112 @@ +/* + * 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.link; + +import org.geysermc.floodgate.util.LinkedPlayer; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * The base class of the PlayerLink database implementation. + * The implementation is responsible for making a connection with the database + * and keeping that connection alive so that Floodgate (or a third party plugin) + * can check for example if a given player is linked. + */ +public interface PlayerLink { + /** + * Called by Floodgate after the initialization of the class. + * In this method the implementation should start the connection with the database and + * create the collections if they don't exist already. + */ + void load(); + + /** + * Get a linked player by the bedrock uuid + * + * @param bedrockId the uuid of the bedrock player + * @return a completable future with the {@link LinkedPlayer}. + * The future will have a null value if that Bedrock player isn't linked + */ + CompletableFuture getLinkedPlayer(UUID bedrockId); + + /** + * Tells if the given player is a linked player + * + * @param bedrockId the bedrock uuid of the linked player + * @return true if the player is a linked player + */ + CompletableFuture isLinkedPlayer(UUID bedrockId); + + /** + * Links a Java account to a Bedrock account. + * + * @param bedrockId the uuid of the Bedrock player + * @param javaId the uuid of the Java player + * @param username the username of the Java player + * @return a future holding void on success or completed exceptionally when failed + */ + CompletableFuture linkPlayer(UUID bedrockId, UUID javaId, String username); + + /** + * Unlinks a Java account from a Bedrock account. + * + * @param javaId the uuid of the Java player + * @return a future holding void on success or completed exceptionally when failed + */ + CompletableFuture unlinkPlayer(UUID javaId); + + /** + * Return if account linking is enabled. + * The difference between enabled and allowed is that 'enabled' still allows already linked + * people to join with their linked account while 'allow linking' allows people to link + * accounts using the commands. + */ + boolean isEnabled(); + + /** + * Returns the duration (in seconds) before a {@link LinkRequest} timeouts + */ + long getVerifyLinkTimeout(); + + /** + * Return if account linking is allowed. + * The difference between enabled and allowed is that 'enabled' still allows already linked + * people to join with their linked account while 'allow linking' allows people to link + * accounts using the commands. + */ + boolean isAllowLinking(); + + default boolean isEnabledAndAllowed() { + return isEnabled() && isAllowLinking(); + } + + /** + * Called when the Floodgate plugin is going to shutdown + */ + void stop(); +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java b/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java new file mode 100644 index 00000000..fbd21ebf --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/logger/FloodgateLogger.java @@ -0,0 +1,80 @@ +/* + * 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.logger; + +public interface FloodgateLogger { + String LOGGER_NAME = "Floodgate"; + + /** + * Logs an error message to the console, with 0 or more arguments. + * + * @param message the message to log to the console + * @param args the arguments to fill the missing spots in the message + */ + void error(String message, Object... args); + + /** + * Logs an error message to the console, with 0 or more arguments. + * + * @param message the message to log to the console + * @param throwable the throwable to log + * @param args the arguments to fill the missing spots in the message + */ + void error(String message, Throwable throwable, Object... args); + + /** + * Logs a warning message to the console, with 0 or more arguments. + * + * @param message the message to log to the console + * @param args the arguments to fill the missing spots in the message + */ + void warn(String message, Object... args); + + /** + * Logs an info message to the console, with 0 or more arguments. + * + * @param message the message to log to the console + * @param args the arguments to fill the missing spots in the message + */ + void info(String message, Object... args); + + /** + * Logs a debug message to the console, with 0 or more arguments. + * + * @param message the message to log to the console + * @param args the arguments to fill the missing spots in the message + */ + void debug(String message, Object... args); + + /** + * Logs a trace message to the console, with 0 or more arguments. + * + * @param message the message to log to the console + * @param args the arguments to fill the missing spots in the message + */ + void trace(String message, Object... args); +} diff --git a/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java b/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java new file mode 100644 index 00000000..09826b88 --- /dev/null +++ b/api/src/main/java/org/geysermc/floodgate/api/player/FloodgatePlayer.java @@ -0,0 +1,89 @@ +package org.geysermc.floodgate.api.player; + +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.LinkedPlayer; +import org.geysermc.floodgate.util.UiProfile; + +import java.util.UUID; + +public interface FloodgatePlayer { + /** + * Returns the Bedrock username that will be used as username on the server. + * This includes replace spaces (if enabled), username shortened and prefix appended.
+ * Note that this field is not used when the player is a {@link LinkedPlayer LinkedPlayer} + */ + String getJavaUsername(); + + /** + * Returns the uuid that will be used as UUID on the server.
+ * Note that this field is not used when the player is a {@link LinkedPlayer LinkedPlayer} + */ + UUID getJavaUniqueId(); + + /** + * Returns the uuid that the server will use as uuid of that player. + * Will return {@link #getJavaUniqueId()} when not linked or + * {@link LinkedPlayer#getJavaUniqueId()} when linked. + */ + UUID getCorrectUniqueId(); + + /** + * Returns the username the server will as username for that player. + * Will return {@link #getJavaUsername()} when not linked or + * {@link LinkedPlayer#getJavaUsername()} when linked. + */ + String getCorrectUsername(); + + /** + * Returns the version of the Bedrock client + */ + String getVersion(); + + /** + * Returns the real username of the Bedrock client. + * No prefix nor shortened nor replaced spaces. + */ + String getUsername(); + + /** + * Returns the Xbox Unique Identifier of the Bedrock client + */ + String getXuid(); + + /** + * Returns the Operation System of the Bedrock client + */ + DeviceOs getDeviceOs(); + + /** + * Returns the language code of the Bedrock client + */ + String getLanguageCode(); + + /** + * Returns the User Interface Profile of the Bedrock client + */ + UiProfile getUiProfile(); + + /** + * Returns the Input Mode of the Bedrock client + */ + InputMode getInputMode(); + + /** + * Returns the LinkedPlayer object if the player is linked to a Java account. + */ + LinkedPlayer getLinkedPlayer(); + + /** + * Casts the FloodgatePlayer instance to a class that extends FloodgatePlayer. + * + * @param The instance to cast to. + * @return The FloodgatePlayer casted to the given class + * @throws ClassCastException when it can't cast the instance to the given class + */ + default T as(Class clazz) { + return clazz.cast(this); + } +} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/BukkitPlugin.java b/bukkit/src/main/java/org/geysermc/floodgate/BukkitPlugin.java deleted file mode 100644 index 54430af0..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/BukkitPlugin.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.plugin.java.JavaPlugin; -import org.geysermc.floodgate.command.LinkAccountCommand; -import org.geysermc.floodgate.command.UnlinkAccountCommand; -import org.geysermc.floodgate.injector.BukkitInjector; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.ReflectionUtil; - -import java.util.logging.Level; - -public class BukkitPlugin extends JavaPlugin implements Listener { - @Getter private static BukkitPlugin instance; - @Getter private FloodgateConfig configuration; - @Getter private PlayerLink playerLink; - - @Override - public void onLoad() { - instance = this; - if (!getDataFolder().exists()) { - getDataFolder().mkdir(); - } - ReflectionUtil.setPrefix("net.minecraft.server." + getServer().getClass().getPackage().getName().split("\\.")[3]); - - configuration = FloodgateConfig.load(getLogger(), getDataFolder().toPath().resolve("config.yml")); - playerLink = PlayerLink.initialize(getLogger(), getDataFolder().toPath(), configuration); - } - - @Override - public void onEnable() { - try { - if (!BukkitInjector.inject()) getLogger().severe("Failed to inject the packet listener!"); - } catch (Exception e) { - getLogger().log(Level.SEVERE, "Failed to inject the packet listener!", e); - } finally { - if (!BukkitInjector.isInjected()) { - getServer().getPluginManager().disablePlugin(this); - } - } - CommandUtil commandUtil = new CommandUtil(this); - getCommand(CommandUtil.LINK_ACCOUNT_COMMAND).setExecutor(new LinkAccountCommand(playerLink, commandUtil)); - getCommand(CommandUtil.UNLINK_ACCOUNT_COMMAND).setExecutor(new UnlinkAccountCommand(playerLink, commandUtil)); - - // Register the plugin as an event listener to we get join and leave events - Bukkit.getServer().getPluginManager().registerEvents(this, this); - } - - @Override - public void onDisable() { - try { - if (!BukkitInjector.removeInjection()) getLogger().severe("Failed to remove the injection!"); - } catch (Exception e) { - getLogger().log(Level.SEVERE, "Failed to remove the injection!", e); - } - playerLink.stop(); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onAsyncPreLogin(AsyncPlayerPreLoginEvent event) { - if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - FloodgateAPI.removePlayer(event.getUniqueId(), true); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerLogin(PlayerLoginEvent event) { - if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { - FloodgateAPI.removePlayer(event.getPlayer().getUniqueId()); - return; - } - // if there was another player with the same uuid online, - // he would've been disconnected by now - FloodgatePlayer player = FloodgateAPI.getPlayer(event.getPlayer()); - if (player != null) player.setLogin(false); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerQuit(PlayerQuitEvent event) { - Player player = event.getPlayer(); - if (FloodgateAPI.removePlayer(player.getUniqueId())) { - System.out.println("Removed Bedrock player who was logged in as " + player.getName() + " " + event.getPlayer().getUniqueId()); - } - } -} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/FloodgateAPI.java b/bukkit/src/main/java/org/geysermc/floodgate/FloodgateAPI.java deleted file mode 100644 index 28386a99..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/FloodgateAPI.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.geysermc.floodgate; - -import org.bukkit.entity.Player; - -import java.util.UUID; - -public class FloodgateAPI extends AbstractFloodgateAPI { - /** - * See {@link AbstractFloodgateAPI#getPlayer(UUID)} - */ - public static FloodgatePlayer getPlayer(Player player) { - return getPlayer(player.getUniqueId()); - } - - /** - * See {@link AbstractFloodgateAPI#isBedrockPlayer(UUID)} - */ - public static boolean isBedrockPlayer(Player player) { - return isBedrockPlayer(player.getUniqueId()); - } -} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java b/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java deleted file mode 100644 index e709c57b..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java +++ /dev/null @@ -1,178 +0,0 @@ -package org.geysermc.floodgate; - -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import lombok.RequiredArgsConstructor; -import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; -import org.geysermc.floodgate.injector.BukkitInjector; -import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.ReflectionUtil; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.UUID; - -import static org.geysermc.floodgate.util.ReflectionUtil.*; - -@RequiredArgsConstructor -public class PacketHandler extends SimpleChannelInboundHandler { - private static BukkitPlugin plugin = BukkitPlugin.getInstance(); - private static HandshakeHandler handshakeHandler; - - private static Class networkManagerClass; - private static Class handshakePacketClass; - private static Field hostField; - - private static Class gameProfileClass; - private static Constructor gameProfileConstructor; - private static Field gameProfileField; - - private static Class loginStartPacketClass; - private static Class loginListenerClass; - private static Method initUUIDMethod; - - private static Class loginHandlerClass; - private static Constructor loginHandlerConstructor; - private static Method fireEventsMethod; - - private static Field packetListenerField; - private static Field protocolStateField; - private static Object readyToAcceptState; - - /* per player stuff */ - - private final ChannelFuture future; - private Object networkManager; - private FloodgatePlayer fPlayer; - private boolean bungee; - - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object packet) throws Exception { - boolean isHandhake = handshakePacketClass.isInstance(packet); - boolean isLogin = loginStartPacketClass.isInstance(packet); - - try { - if (isHandhake) { - networkManager = ctx.channel().pipeline().get("packet_handler"); - - HandshakeResult result = handshakeHandler.handle(getCastedValue(packet, hostField, String.class)); - switch (result.getResultType()) { - case SUCCESS: - break; - case INVALID_DATA_LENGTH: - plugin.getLogger().info(String.format( - plugin.getConfiguration().getMessages().getInvalidArgumentsLength(), - BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength() - )); - ctx.close(); - default: // only continue when SUCCESS - return; - } - - fPlayer = result.getFloodgatePlayer(); - BedrockData bedrockData = result.getBedrockData(); - String[] data = result.getHandshakeData(); - - if (bungee = (data.length == 6 || data.length == 7)) { - setValue(packet, hostField, data[0] + '\0' + - bedrockData.getIp() + '\0' + fPlayer.getCorrectUniqueId() + - (data.length == 7 ? '\0' + data[6] : "") - ); - } else { - // Use a spoofedUUID for initUUID (just like Bungeecord) - setValue(networkManager, "spoofedUUID", fPlayer.getCorrectUniqueId()); - // Use the player his IP for stuff instead of Geyser his IP - SocketAddress newAddress = new InetSocketAddress( - bedrockData.getIp(), - ((InetSocketAddress) ctx.channel().remoteAddress()).getPort() - ); - setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress); - } - plugin.getLogger().info("Added " + fPlayer.getCorrectUsername() + " " + fPlayer.getCorrectUniqueId()); - } else if (isLogin) { - if (!bungee) { - // we have to fake the offline player cycle - Object loginListener = packetListenerField.get(networkManager); - - // Set the player his GameProfile - Object gameProfile = gameProfileConstructor.newInstance(fPlayer.getCorrectUniqueId(), fPlayer.getCorrectUsername()); - setValue(loginListener, gameProfileField, gameProfile); - - initUUIDMethod.invoke(loginListener); // LoginListener#initUUID - fireEventsMethod.invoke(loginHandlerConstructor.newInstance(loginListener)); // new LoginHandler().fireEvents(); - setValue(loginListener, protocolStateField, readyToAcceptState); // LoginLister#protocolState = READY_TO_ACCEPT - // The tick of LoginListener will do the rest - } - } - } finally { - // don't let the packet through if the packet is the login packet - // because we want to skip the login cycle - if (!isLogin) ctx.fireChannelRead(packet); - - if (isHandhake && bungee || isLogin && !bungee || fPlayer == null) { - // remove the injection of the client because we're finished - BukkitInjector.removeInjectedClient(future, ctx.channel()); - } - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - super.exceptionCaught(ctx, cause); - cause.printStackTrace(); - } - - static { - handshakeHandler = new HandshakeHandler(plugin.getConfiguration().getPrivateKey(), false, plugin.getConfiguration().getUsernamePrefix(), plugin.getConfiguration().isReplaceSpaces()); - - networkManagerClass = getPrefixedClass("NetworkManager"); - loginStartPacketClass = getPrefixedClass("PacketLoginInStart"); - - gameProfileClass = ReflectionUtil.getClass("com.mojang.authlib.GameProfile"); - assert gameProfileClass != null; - try { - gameProfileConstructor = gameProfileClass.getConstructor(UUID.class, String.class); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - - handshakePacketClass = getPrefixedClass("PacketHandshakingInSetProtocol"); - assert handshakePacketClass != null; - hostField = getFieldOfType(handshakePacketClass, String.class, true); - - loginListenerClass = getPrefixedClass("LoginListener"); - assert loginListenerClass != null; - gameProfileField = getFieldOfType(loginListenerClass, gameProfileClass, true); - initUUIDMethod = getMethod(loginListenerClass, "initUUID"); - - for (Field field : loginListenerClass.getDeclaredFields()) { - if (field.getType().isEnum()) { - protocolStateField = field; - } - } - assert protocolStateField != null; - - Enum[] protocolStates = (Enum[]) protocolStateField.getType().getEnumConstants(); - for (Enum protocolState : protocolStates) { - if (protocolState.name().equals("READY_TO_ACCEPT")) { - readyToAcceptState = protocolState; - } - } - - Class packetListenerClass = getPrefixedClass("PacketListener"); - packetListenerField = getFieldOfType(networkManagerClass, packetListenerClass, true); - - loginHandlerClass = getPrefixedClass("LoginListener$LoginHandler"); - assert loginHandlerClass != null; - try { - loginHandlerConstructor = makeAccessible(loginHandlerClass.getDeclaredConstructor(loginListenerClass)); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - fireEventsMethod = getMethod(loginHandlerClass, "fireEvents"); - } -} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java b/bukkit/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java deleted file mode 100644 index cabde967..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.geysermc.floodgate.command; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.geysermc.floodgate.AbstractLinkAccountCommand; -import org.geysermc.floodgate.PlayerLink; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.CommonMessage; - -import java.util.UUID; - -public class LinkAccountCommand extends AbstractLinkAccountCommand implements CommandExecutor { - public LinkAccountCommand(PlayerLink link, CommandUtil commandUtil) { - super(link, commandUtil); - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage()); - return true; - } - UUID uuid = ((Player) sender).getUniqueId(); - String username = sender.getName(); - execute((Player) sender, uuid, username, args); - return true; - } -} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java b/bukkit/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java deleted file mode 100644 index 593500fc..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.geysermc.floodgate.command; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.geysermc.floodgate.AbstractUnlinkAccountCommand; -import org.geysermc.floodgate.PlayerLink; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.CommonMessage; - -import java.util.UUID; - -public class UnlinkAccountCommand extends AbstractUnlinkAccountCommand implements CommandExecutor { - public UnlinkAccountCommand(PlayerLink link, CommandUtil commandUtil) { - super(link, commandUtil); - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage()); - return true; - } - UUID uuid = ((Player) sender).getUniqueId(); - execute((Player) sender, uuid); - return true; - } -} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/injector/BukkitInjector.java b/bukkit/src/main/java/org/geysermc/floodgate/injector/BukkitInjector.java deleted file mode 100644 index 4120f813..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/injector/BukkitInjector.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.geysermc.floodgate.injector; - -import io.netty.channel.*; -import lombok.Getter; -import org.geysermc.floodgate.PacketHandler; -import org.geysermc.floodgate.util.ReflectionUtil; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.util.ArrayList; -import java.util.List; - -public class BukkitInjector { - private static Object serverConnection; - private static List injectedClients = new ArrayList<>(); - - @Getter private static boolean injected = false; - private static String injectedFieldName; - - public static boolean inject() throws Exception { - if (isInjected()) return true; - if (getServerConnection() != null) { - for (Field f : serverConnection.getClass().getDeclaredFields()) { - if (f.getType() == List.class) { - f.setAccessible(true); - boolean rightList = ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] == ChannelFuture.class; - if (!rightList) continue; - - injectedFieldName = f.getName(); - List newList = new CustomList((List) f.get(serverConnection)) { - @Override - public void onAdd(Object o) { - try { - injectClient((ChannelFuture) o); - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - - synchronized (newList) { - for (Object o : newList) { - try { - injectClient((ChannelFuture) o); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - f.set(serverConnection, newList); - injected = true; - return true; - } - } - } - return false; - } - - public static void injectClient(ChannelFuture future) { - future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - super.channelRead(ctx, msg); - - Channel channel = (Channel) msg; - channel.pipeline().addLast(new ChannelInitializer() { - @Override - protected void initChannel(Channel channel) throws Exception { - channel.pipeline().addBefore("packet_handler", "floodgate_handler", new PacketHandler(future)); - } - }); - } - }); - injectedClients.add(future); - } - - public static void removeInjectedClient(ChannelFuture future, Channel channel) { - if (channel != null) channel.pipeline().remove("floodgate_handler"); - injectedClients.remove(future); - } - - public static boolean removeInjection() throws Exception { - if (!isInjected()) return true; - for (ChannelFuture future : new ArrayList<>(injectedClients)) { - removeInjectedClient(future, null); - } - - Object serverConnection = getServerConnection(); - if (serverConnection != null) { - Field field = ReflectionUtil.getField(serverConnection.getClass(), injectedFieldName); - List list = (List) ReflectionUtil.getValue(serverConnection, field); - if (list instanceof CustomList) { - ReflectionUtil.setValue(serverConnection, field, ((CustomList) list).getOriginalList()); - } - } - injectedFieldName = null; - injected = false; - return true; - } - - public static Object getServerConnection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - if (serverConnection != null) return serverConnection; - Class minecraftServer = ReflectionUtil.getPrefixedClass("MinecraftServer"); - assert minecraftServer != null; - - Object minecraftServerInstance = ReflectionUtil.invokeStatic(minecraftServer, "getServer"); - for (Method m : minecraftServer.getDeclaredMethods()) { - if (m.getReturnType() != null) { - if (m.getReturnType().getSimpleName().equals("ServerConnection")) { - if (m.getParameterTypes().length == 0) { - serverConnection = m.invoke(minecraftServerInstance); - } - } - } - } - - return serverConnection; - } -} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/util/CommandUtil.java b/bukkit/src/main/java/org/geysermc/floodgate/util/CommandUtil.java deleted file mode 100644 index 36495e96..00000000 --- a/bukkit/src/main/java/org/geysermc/floodgate/util/CommandUtil.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.geysermc.floodgate.util; - -import lombok.AllArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.geysermc.floodgate.command.CommandMessage; - -@AllArgsConstructor -public class CommandUtil extends AbstractCommandResponseCache implements ICommandUtil { - private final Plugin plugin; - - @Override - public void sendMessage(Player player, CommandMessage message, Object... args) { - player.sendMessage(format(message, args)); - } - - @Override - public void kickPlayer(Player player, CommandMessage message, Object... args) { - // Have to run this in a non async thread so we don't get a `Asynchronous player kick!` error - Bukkit.getScheduler().runTask(plugin, () -> player.kickPlayer(format(message, args))); - } - - @Override - protected String transformMessage(String message) { - // unlike others, Bukkit doesn't have to transform a message into another class. - return message; - } -} diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml deleted file mode 100644 index f2f6bb79..00000000 --- a/bukkit/src/main/resources/plugin.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: ${project.name} -description: ${project.description} -version: ${project.version} -author: ${project.organization.name} -website: ${project.url} -main: org.geysermc.floodgate.BukkitPlugin -api-version: 1.13 -commands: - linkaccount: - description: Link your Java and Bedrock accounts - usage: /linkaccount [code] - permission: floodgate.linkaccount - permission-message: You don't have the floodgate.linkaccount permission. - unlinkaccount: - description: Unlink your Java account from your Bedrock account - usage: /unlinkaccount - permission: floodgate.unlinkaccount - permission-message: You don't have the floodgate.unlinkaccount permission. -permissions: - floodgate.linkaccount: - description: Allows players to link their Bedrock and Java accounts - default: true - floodgate.unlinkaccount: - description: Allows players to unlink their Bedrock and Java accounts - default: true diff --git a/bungee/pom.xml b/bungee/pom.xml index d4105f18..43b8b651 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -3,13 +3,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - org.geysermc - floodgate-parent + parent + org.geysermc.floodgate 1.0-SNAPSHOT 4.0.0 - floodgate-bungee + bungee @@ -28,12 +28,12 @@ net.md-5 bungeecord-protocol - 1.15-SNAPSHOT + ${bungee-version} provided - org.geysermc - floodgate-common + org.geysermc.floodgate + common ${project.version} compile diff --git a/bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java b/bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java deleted file mode 100644 index b0e9faad..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.geysermc.floodgate; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.channel.*; -import io.netty.channel.socket.ServerSocketChannel; -import io.netty.handler.codec.MessageToByteEncoder; -import lombok.RequiredArgsConstructor; -import net.md_5.bungee.protocol.MinecraftEncoder; -import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; -import org.geysermc.floodgate.util.ReflectionUtil; - -import java.lang.reflect.Field; -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -public class BungeeDebugger { - public BungeeDebugger() { - init(); - } - - private void init() { - Class pipelineUtils = ReflectionUtil.getPrefixedClass("netty.PipelineUtils"); - Field framePrepender = ReflectionUtil.getField(pipelineUtils, "framePrepender"); - ReflectionUtil.setFinalValue(null, framePrepender, new CustomVarint21LengthFieldPrepender()); - } - - @ChannelHandler.Sharable - private static class CustomVarint21LengthFieldPrepender extends Varint21LengthFieldPrepender { - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - // we're getting called before the encoder and decoder are added, - // so we have to wait with a nice while loop :D - ctx.executor().execute(() -> { - System.out.println("Channel: " + ctx.channel().isActive() + " " + ctx.channel().isOpen() + " " + ctx.channel().isRegistered()); - while (ctx.channel().isOpen()) { - System.out.println("Trying to find decoder for " + getHostString(ctx, true) + " " + getParentName(ctx, true)); - if (ctx.channel().pipeline().get(MinecraftEncoder.class) != null) { - System.out.println("Found decoder for " + getHostString(ctx, true)); - ctx.channel().pipeline().addLast( - "floodgate-debug-init", - new BungeeChannelInitializer( - ctx.channel().parent() instanceof ServerSocketChannel - ) - ); - break; - } - } - }); - } - } - - public static String getHostString(ChannelHandlerContext ctx, boolean alwaysString) { - SocketAddress address = ctx.channel().remoteAddress(); - return address != null ? ((InetSocketAddress) address).getHostString() : (alwaysString ? "null" : null); - } - - public static String getParentName(ChannelHandlerContext ctx, boolean alwaysString) { - Channel parent = ctx.channel().parent(); - return parent != null ? parent.getClass().getSimpleName() : (alwaysString ? "null" : null); - } - - public static int executorsCount(ChannelHandlerContext ctx) { - return ((MultithreadEventLoopGroup) ctx.channel().eventLoop().parent()).executorCount(); - } - - @RequiredArgsConstructor - private static class BungeeChannelInitializer extends ChannelInitializer { - private final boolean player; - - @Override - protected void initChannel(Channel channel) throws Exception { - System.out.println("Init " + (player ? "Bungee" : "Server")); - // can't add our debugger when the inbound-boss is missing - if (channel.pipeline().get("packet-encoder") == null) return; - if (channel.pipeline().get("packet-decoder") == null) return; - channel.pipeline() - .addBefore("packet-decoder", "floodgate-debug-in", new BungeeInPacketHandler(player)) - .addBefore("packet-encoder", "floodgate-debug-out", new BungeeOutPacketHandler(player)); - } - } - - @RequiredArgsConstructor - private static class BungeeInPacketHandler extends SimpleChannelInboundHandler { - private final boolean player; - - @Override - protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { - int index = msg.readerIndex(); - System.out.println((player ? "Player -> Bungee" : "Server -> Bungee") + ":\n" + ByteBufUtil.prettyHexDump(msg)); - msg.readerIndex(index); - ctx.fireChannelRead(msg.retain()); - } - } - - @RequiredArgsConstructor - private static class BungeeOutPacketHandler extends MessageToByteEncoder { - private final boolean player; - - @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { - int index = msg.readerIndex(); - System.out.println((player ? "Bungee -> Player" : "Bungee -> Server") + ":\n" + ByteBufUtil.prettyHexDump(msg)); - msg.readerIndex(index); - out.writeBytes(msg); - } - } -} diff --git a/bungee/src/main/java/org/geysermc/floodgate/BungeeFloodgateConfig.java b/bungee/src/main/java/org/geysermc/floodgate/BungeeFloodgateConfig.java deleted file mode 100644 index a352af2c..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/BungeeFloodgateConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.geysermc.floodgate; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; - -public class BungeeFloodgateConfig extends FloodgateConfig { - @JsonProperty(value = "send-floodgate-data") - @Getter private boolean sendFloodgateData; -} diff --git a/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java b/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java index 393fc5dc..c022d4d1 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java +++ b/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java @@ -1,168 +1,65 @@ +/* + * 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; -import lombok.Getter; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.LoginEvent; -import net.md_5.bungee.api.event.PlayerDisconnectEvent; -import net.md_5.bungee.api.event.PreLoginEvent; -import net.md_5.bungee.api.event.ServerConnectEvent; -import net.md_5.bungee.api.plugin.Listener; +import com.google.inject.Guice; +import com.google.inject.Injector; import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.event.EventHandler; -import net.md_5.bungee.event.EventPriority; -import net.md_5.bungee.protocol.packet.Handshake; -import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; -import org.geysermc.floodgate.HandshakeHandler.ResultType; -import org.geysermc.floodgate.command.LinkAccountCommand; -import org.geysermc.floodgate.command.UnlinkAccountCommand; -import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.CommandUtil; +import org.geysermc.floodgate.module.*; import org.geysermc.floodgate.util.ReflectionUtil; -import java.lang.reflect.Field; -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER; - -public class BungeePlugin extends Plugin implements Listener { - @Getter private static BungeePlugin instance; - private static Field extraHandshakeData; - - @Getter private BungeeFloodgateConfig config; - @Getter private PlayerLink playerLink; - private BungeeDebugger debugger; - private HandshakeHandler handshakeHandler; +public final class BungeePlugin extends Plugin { + private FloodgatePlatform platform; @Override public void onLoad() { - instance = this; - if (!getDataFolder().exists()) { - getDataFolder().mkdir(); - } - config = FloodgateConfig.load(getLogger(), getDataFolder().toPath().resolve("config.yml"), BungeeFloodgateConfig.class); - playerLink = PlayerLink.initialize(getLogger(), getDataFolder().toPath(), config); - handshakeHandler = new HandshakeHandler(config.getPrivateKey(), true, config.getUsernamePrefix(), config.isReplaceSpaces()); + ReflectionUtil.setPrefix("net.md_5.bungee"); + + long ctm = System.currentTimeMillis(); + Injector injector = Guice.createInjector( + new CommonModule(getDataFolder().toPath()), + new BungeePlatformModule(this) + ); + + long endCtm = System.currentTimeMillis(); + getLogger().info("Took " + (endCtm - ctm) + "ms to boot Floodgate"); + + // Bungeecord doesn't have a build-in function to disable plugins, + // so there is no need to have a custom Platform class like Spigot + platform = injector.getInstance(FloodgatePlatform.class); } @Override public void onEnable() { - getProxy().getPluginManager().registerListener(this, this); - if (config.isDebug()) { - debugger = new BungeeDebugger(); - } - - CommandUtil commandUtil = new CommandUtil(); - getProxy().getPluginManager().registerCommand(this, new LinkAccountCommand(playerLink, commandUtil)); - getProxy().getPluginManager().registerCommand(this, new UnlinkAccountCommand(playerLink, commandUtil)); + platform.enable(new CommandModule(), new BungeeListenerModule(), new BungeeAddonModule()); } @Override public void onDisable() { - if (config.isDebug()) { - getLogger().warning("Please note that it is not possible to reload this plugin when debug mode is enabled. At least for now"); - } - playerLink.stop(); - } - - @EventHandler(priority = EventPriority.LOW) - public void onServerConnect(ServerConnectEvent e) { - // Passes the information through to the connecting server if enabled - if (config.isSendFloodgateData() && FloodgateAPI.isBedrockPlayer(e.getPlayer())) { - Handshake handshake = ReflectionUtil.getCastedValue(e.getPlayer().getPendingConnection(), "handshake", Handshake.class); - handshake.setHost( - handshake.getHost().split("\0")[0] + '\0' + // Ensures that only the hostname remains! - FLOODGATE_IDENTIFIER + '\0' + FloodgateAPI.getEncryptedData(e.getPlayer().getUniqueId()) - ); - // Bungeecord will add his data after our data - } - } - - @EventHandler(priority = EventPriority.LOW) - public void onPreLogin(PreLoginEvent event) { - event.registerIntent(this); - getProxy().getScheduler().runAsync(this, () -> { - String extraData = ReflectionUtil.getCastedValue(event.getConnection(), extraHandshakeData, String.class); - - HandshakeResult result = handshakeHandler.handle(extraData); - switch (result.getResultType()) { - case SUCCESS: - break; - case EXCEPTION: - event.setCancelReason(config.getMessages().getInvalidKey()); - break; - case INVALID_DATA_LENGTH: - event.setCancelReason(String.format( - config.getMessages().getInvalidArgumentsLength(), - BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength() - )); - break; - } - - if (result.getResultType() != ResultType.SUCCESS) { - // only continue when SUCCESS - event.completeIntent(this); - return; - } - - FloodgatePlayer player = result.getFloodgatePlayer(); - FloodgateAPI.addEncryptedData(player.getCorrectUniqueId(), result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]); - - event.getConnection().setOnlineMode(false); - event.getConnection().setUniqueId(player.getCorrectUniqueId()); - - ReflectionUtil.setValue(event.getConnection(), "name", player.getCorrectUsername()); - Object channelWrapper = ReflectionUtil.getValue(event.getConnection(), "ch"); - SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class); - if (!(remoteAddress instanceof InetSocketAddress)) { - getLogger().info( - "Player " + player.getUsername() + " doesn't use an InetSocketAddress. " + - "It uses " + remoteAddress.getClass().getSimpleName() + ". Ignoring the player, I guess." - ); - } else { - ReflectionUtil.setValue( - channelWrapper, "remoteAddress", - new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort()) - ); - } - event.completeIntent(this); - }); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPreLoginMonitor(PreLoginEvent event) { - if (event.isCancelled()) { - FloodgateAPI.removePlayer(event.getConnection().getUniqueId(), true); - } - } - - @EventHandler - public void onLogin(LoginEvent event) { - // if there was another player with the same uuid / name online, - // he has been disconnected by now - FloodgatePlayer player = FloodgateAPI.getPlayerByConnection(event.getConnection()); - if (player != null) player.setLogin(false); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onLoginMonitor(LoginEvent event) { - if (event.isCancelled()) { - FloodgateAPI.removePlayer(event.getConnection().getUniqueId()); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerDisconnect(PlayerDisconnectEvent event) { - ProxiedPlayer player = event.getPlayer(); - if (FloodgateAPI.removePlayer(player.getUniqueId())) { - FloodgateAPI.removeEncryptedData(player.getUniqueId()); - System.out.println("Removed Bedrock player who was logged in as " + player.getName() + " " + player.getUniqueId()); - } - } - - static { - ReflectionUtil.setPrefix("net.md_5.bungee"); - Class initial_handler = ReflectionUtil.getPrefixedClass("connection.InitialHandler"); - extraHandshakeData = ReflectionUtil.getField(initial_handler, "extraDataInHandshake"); + platform.disable(); } } diff --git a/bungee/src/main/java/org/geysermc/floodgate/FloodgateAPI.java b/bungee/src/main/java/org/geysermc/floodgate/FloodgateAPI.java deleted file mode 100644 index bca6f56a..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/FloodgateAPI.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.geysermc.floodgate; - -import net.md_5.bungee.api.connection.PendingConnection; -import net.md_5.bungee.api.connection.ProxiedPlayer; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class FloodgateAPI extends AbstractFloodgateAPI { - private static Map encryptedData = new HashMap<>(); - - static void addEncryptedData(UUID uuid, String encryptedData) { - FloodgateAPI.encryptedData.put(uuid, encryptedData); // just override it I guess - } - - static void removeEncryptedData(UUID uuid) { - encryptedData.remove(uuid); - } - - static String getEncryptedData(UUID uuid) { - return encryptedData.get(uuid); - } - - /** - * See {@link AbstractFloodgateAPI#getPlayer(UUID)} - */ - public static FloodgatePlayer getPlayerByConnection(PendingConnection connection) { - return getPlayer(connection.getUniqueId()); - } - - public static boolean isBedrockPlayer(ProxiedPlayer player) { - return isBedrockPlayer(player.getUniqueId()); - } -} diff --git a/bungee/src/main/java/org/geysermc/floodgate/command/BungeeCommandRegistration.java b/bungee/src/main/java/org/geysermc/floodgate/command/BungeeCommandRegistration.java new file mode 100644 index 00000000..be4de89a --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/command/BungeeCommandRegistration.java @@ -0,0 +1,75 @@ +/* + * 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.command; + +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.geysermc.floodgate.BungeePlugin; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.platform.command.CommandRegistration; + +import java.util.UUID; + +@RequiredArgsConstructor +public final class BungeeCommandRegistration implements CommandRegistration { + private final BungeePlugin plugin; + + @Override + public void register(Command command) { + ProxyServer.getInstance().getPluginManager().registerCommand( + plugin, new BungeeCommandWrapper(command) + ); + } + + protected static class BungeeCommandWrapper extends net.md_5.bungee.api.plugin.Command { + private final Command command; + + public BungeeCommandWrapper(Command command) { + super(command.getName()); + this.command = command; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!(sender instanceof ProxiedPlayer)) { + if (command.isRequirePlayer()) { + //todo let it use the response cache + sender.sendMessage(CommonCommandMessage.NOT_A_PLAYER.getMessage()); + return; + } + command.execute(sender, args); + return; + } + + UUID uuid = ((ProxiedPlayer) sender).getUniqueId(); + String username = sender.getName(); + command.execute(sender, uuid, username, args); + } + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java b/bungee/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java deleted file mode 100644 index 40c20f9c..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.geysermc.floodgate.command; - -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.plugin.Command; -import org.geysermc.floodgate.AbstractLinkAccountCommand; -import org.geysermc.floodgate.PlayerLink; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.CommonMessage; - -import java.util.UUID; - -public class LinkAccountCommand extends Command { - private final LinkAccountCommandHandler handler; - - public LinkAccountCommand(PlayerLink playerLink, CommandUtil responseCache) { - super(CommandUtil.LINK_ACCOUNT_COMMAND); - handler = new LinkAccountCommandHandler(playerLink, responseCache); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage()); - return; - } - UUID uuid = ((ProxiedPlayer) sender).getUniqueId(); - String username = sender.getName(); - handler.execute((ProxiedPlayer) sender, uuid, username, args); - } - - private static class LinkAccountCommandHandler extends AbstractLinkAccountCommand { - public LinkAccountCommandHandler(PlayerLink link, CommandUtil commandUtil) { - super(link, commandUtil); - } - } -} diff --git a/bungee/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java b/bungee/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java deleted file mode 100644 index cfcf0c48..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.geysermc.floodgate.command; - -import net.md_5.bungee.api.CommandSender; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.plugin.Command; -import org.geysermc.floodgate.AbstractUnlinkAccountCommand; -import org.geysermc.floodgate.PlayerLink; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.CommonMessage; - -import java.util.UUID; - -public class UnlinkAccountCommand extends Command { - private final UnlinkAccountCommandHandler handler; - - public UnlinkAccountCommand(PlayerLink playerLink, CommandUtil commandUtil) { - super(CommandUtil.UNLINK_ACCOUNT_COMMAND); - handler = new UnlinkAccountCommandHandler(playerLink, commandUtil); - } - - @Override - public void execute(CommandSender sender, String[] args) { - if (!(sender instanceof ProxiedPlayer)) { - sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage()); - return; - } - UUID uuid = ((ProxiedPlayer) sender).getUniqueId(); - handler.execute((ProxiedPlayer) sender, uuid); - } - - private static class UnlinkAccountCommandHandler extends AbstractUnlinkAccountCommand { - public UnlinkAccountCommandHandler(PlayerLink link, CommandUtil commandUtil) { - super(link, commandUtil); - } - } -} diff --git a/bungee/src/main/java/org/geysermc/floodgate/handler/BungeeDataHandler.java b/bungee/src/main/java/org/geysermc/floodgate/handler/BungeeDataHandler.java new file mode 100644 index 00000000..975420ba --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/handler/BungeeDataHandler.java @@ -0,0 +1,174 @@ +/* + * 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.handler; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PreLoginEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.protocol.packet.Handshake; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.floodgate.util.ReflectionUtil; + +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.geysermc.floodgate.HandshakeHandler.*; +import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER; + +public final class BungeeDataHandler { + private static final Field EXTRA_HANDSHAKE_DATA; + private static final Field PLAYER_NAME; + private static final Field PLAYER_CHANNEL_WRAPPER; + private static final Field PLAYER_REMOTE_ADDRESS; + private static final Field CACHED_HANDSHAKE_PACKET; + + private final Plugin plugin; + private final ProxyFloodgateConfig config; + private final ProxyFloodgateApi api; + private final HandshakeHandler handler; + private final FloodgateLogger logger; + + public BungeeDataHandler(Plugin plugin, ProxyFloodgateConfig config, ProxyFloodgateApi api, + HandshakeHandler handshakeHandler, FloodgateLogger logger) { + this.plugin = plugin; + this.config = config; + this.handler = handshakeHandler; + this.api = api; + this.logger = logger; + } + + public void handle(PreLoginEvent event) { + event.registerIntent(plugin); + plugin.getProxy().getScheduler().runAsync(plugin, () -> { + String extraData = ReflectionUtil.getCastedValue( + event.getConnection(), EXTRA_HANDSHAKE_DATA + ); + + HandshakeResult result = handler.handle(extraData); + switch (result.getResultType()) { + case SUCCESS: + break; + case EXCEPTION: + event.setCancelReason(config.getMessages().getInvalidKey()); + break; + case INVALID_DATA_LENGTH: + event.setCancelReason(String.format( + config.getMessages().getInvalidArgumentsLength(), + BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength() + )); + break; + } + + // only continue when SUCCESS + if (result.getResultType() != ResultType.SUCCESS) { + event.completeIntent(plugin); + return; + } + + FloodgatePlayer player = result.getFloodgatePlayer(); + api.addEncryptedData( + player.getCorrectUniqueId(), + result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3] + ); + + event.getConnection().setOnlineMode(false); + event.getConnection().setUniqueId(player.getCorrectUniqueId()); + + ReflectionUtil.setValue( + event.getConnection(), PLAYER_NAME, player.getCorrectUsername() + ); + + Object channelWrapper = + ReflectionUtil.getValue(event.getConnection(), PLAYER_CHANNEL_WRAPPER); + + SocketAddress remoteAddress = + ReflectionUtil.getCastedValue(channelWrapper, PLAYER_REMOTE_ADDRESS); + + if (!(remoteAddress instanceof InetSocketAddress)) { + logger.info("Player {} doesn't use an InetSocketAddress. " + + "It uses {}. Ignoring the player, I guess.", + player.getUsername(), remoteAddress.getClass().getSimpleName() + ); + } else { + int port = ((InetSocketAddress) remoteAddress).getPort(); + ReflectionUtil.setValue( + channelWrapper, PLAYER_REMOTE_ADDRESS, + new InetSocketAddress(result.getBedrockData().getIp(), port) + ); + } + event.completeIntent(plugin); + }); + } + + public void handle(ServerConnectEvent event) { + ProxiedPlayer player = event.getPlayer(); + // Passes the information through to the connecting server if enabled + if (config.isSendFloodgateData() && api.isBedrockPlayer(player.getUniqueId())) { + Handshake handshake = ReflectionUtil.getCastedValue( + player.getPendingConnection(), CACHED_HANDSHAKE_PACKET + ); + + // Ensures that only the hostname remains, + // this way it can't mess up the Floodgate data format + String initialHostname = handshake.getHost().split("\0")[0]; + handshake.setHost(initialHostname + '\0' + FLOODGATE_IDENTIFIER + '\0' + + api.getEncryptedData(player.getUniqueId()) + ); + + // Bungeecord will add his data after our data + } + } + + static { + Class initialHandler = ReflectionUtil.getPrefixedClass("connection.InitialHandler"); + EXTRA_HANDSHAKE_DATA = ReflectionUtil.getField(initialHandler, "extraDataInHandshake"); + checkNotNull(EXTRA_HANDSHAKE_DATA, "extraDataInHandshake field cannot be null"); + + PLAYER_NAME = ReflectionUtil.getField(initialHandler, "name"); + checkNotNull(PLAYER_NAME, "Initial name field cannot be null"); + + Class channelWrapper = ReflectionUtil.getPrefixedClass("netty.ChannelWrapper"); + PLAYER_CHANNEL_WRAPPER = ReflectionUtil.getFieldOfType(initialHandler, channelWrapper); + checkNotNull(PLAYER_CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); + + PLAYER_REMOTE_ADDRESS = ReflectionUtil.getFieldOfType(channelWrapper, SocketAddress.class); + checkNotNull(PLAYER_REMOTE_ADDRESS, "Remote address field cannot be null"); + + Class handshakePacket = ReflectionUtil.getPrefixedClass("protocol.packet.Handshake"); + CACHED_HANDSHAKE_PACKET = ReflectionUtil.getFieldOfType(initialHandler, handshakePacket); + checkNotNull(CACHED_HANDSHAKE_PACKET, "Cached handshake packet field cannot be null"); + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java b/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java new file mode 100644 index 00000000..4c408bf5 --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/inject/bungee/BungeeInjector.java @@ -0,0 +1,143 @@ +/* + * 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.inject.bungee; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.protocol.MinecraftEncoder; +import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.util.ReflectionUtil; + +import javax.naming.OperationNotSupportedException; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.ServerSocketChannel; + +@RequiredArgsConstructor +public final class BungeeInjector extends CommonPlatformInjector { + private final FloodgateLogger logger; + @Getter + private boolean injected; + + @Override + public boolean inject() { + Class pipelineUtils = ReflectionUtil.getPrefixedClass("netty.PipelineUtils"); + Field framePrepender = ReflectionUtil.getField(pipelineUtils, "framePrepender"); + Object customPrepender = new CustomVarint21LengthFieldPrepender(this, logger); + + ReflectionUtil.setFinalValue(null, framePrepender, customPrepender); + + injected = true; + return true; + } + + @Override + public boolean removeInjection() throws Exception { + //todo implement injection removal support + throw new OperationNotSupportedException( + "Floodgate cannot remove the Bungee injection at the moment"); + } + + public void injectClient(Channel channel) { + boolean clientToProxy = channel.parent() instanceof ServerSocketChannel; + logger.info("Client to proxy? " + clientToProxy); + + channel.pipeline().addLast(new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) { + injectAddonsCall(channel, !clientToProxy); + addInjectedClient(channel); + } + }); + } + + @AllArgsConstructor + @ChannelHandler.Sharable + private static class CustomVarint21LengthFieldPrepender extends Varint21LengthFieldPrepender { + private final BungeeInjector injector; + private final FloodgateLogger logger; + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + // we're getting called before the encoder and decoder are added, + // so we have to wait a little, so we have a nice while loop here :D + //todo look if we can make this nicer + ctx.executor().execute(() -> { + logger.debug("Channel: {} {} {}", + ctx.channel().isActive(), + ctx.channel().isOpen(), + ctx.channel().isRegistered() + ); + + long ctm = System.currentTimeMillis(); + while (ctx.channel().isOpen()) { + logger.debug("Trying to find decoder for {} {}", + getHostString(ctx, true), + getParentName(ctx, true) + ); + + if (ctx.channel().pipeline().get(MinecraftEncoder.class) != null) { + logger.debug("Found decoder for {}", + getHostString(ctx, true) + ); + + injector.injectClient(ctx.channel()); + break; + } + + if (System.currentTimeMillis() - ctm > 3000) { + logger.error("Failed to find decoder for client after 3 seconds!"); + } + } + }); + } + } + + public static String getHostString(ChannelHandlerContext ctx, boolean alwaysString) { + SocketAddress address = ctx.channel().remoteAddress(); + if (address != null) { + return ((InetSocketAddress) address).getHostString(); + } + return alwaysString ? "null" : null; + } + + public static String getParentName(ChannelHandlerContext ctx, boolean alwaysString) { + Channel parent = ctx.channel().parent(); + if (parent != null) { + return parent.getClass().getSimpleName(); + } + return alwaysString ? "null" : null; + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java new file mode 100644 index 00000000..2af73209 --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java @@ -0,0 +1,86 @@ +package org.geysermc.floodgate.listener; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.LoginEvent; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PreLoginEvent; +import net.md_5.bungee.api.event.ServerConnectEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.event.EventPriority; +import org.geysermc.floodgate.FloodgatePlayerImpl; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.handler.BungeeDataHandler; + +import java.util.UUID; + +public final class BungeeListener implements Listener { + private final BungeeDataHandler dataHandler; + private final ProxyFloodgateApi api; + private final FloodgateLogger logger; + + public BungeeListener(Plugin plugin, ProxyFloodgateConfig config, ProxyFloodgateApi api, + HandshakeHandler handshakeHandler, FloodgateLogger logger) { + this.dataHandler = new BungeeDataHandler(plugin, config, api, handshakeHandler, logger); + this.api = api; + this.logger = logger; + } + + @EventHandler(priority = EventPriority.LOW) + public void onServerConnect(ServerConnectEvent event) { + dataHandler.handle(event); + } + + @EventHandler(priority = EventPriority.LOW) + public void onPreLogin(PreLoginEvent event) { + dataHandler.handle(event); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPreLoginMonitor(PreLoginEvent event) { + if (event.isCancelled()) { + api.removePlayer(event.getConnection().getUniqueId(), true); + } + } + + @EventHandler + public void onLogin(LoginEvent event) { + // if there was another player with the same uuid / name online, + // he has been disconnected by now + UUID uniqueId = event.getConnection().getUniqueId(); + FloodgatePlayer player = api.getPlayer(uniqueId); + logger.info("Login" + (player != null) + " " + uniqueId); + if (player != null) { + player.as(FloodgatePlayerImpl.class).setLogin(false); + logger.info("Floodgate player who is logged in as {} {} joined", + player.getCorrectUsername(), player.getCorrectUniqueId()); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onLoginMonitor(LoginEvent event) { + logger.info("LoginMonitor" + event.isCancelled()); + if (event.isCancelled()) { + api.removePlayer(event.getConnection().getUniqueId()); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerDisconnect(PlayerDisconnectEvent event) { + ProxiedPlayer player = event.getPlayer(); + FloodgatePlayer fPlayer; + if ((fPlayer = api.removePlayer(player.getUniqueId())) != null) { + api.removeEncryptedData(player.getUniqueId()); + logger.info( + "Floodgate player who was logged in as {} {} disconnected", + player.getName(), player.getUniqueId() + ); + } + logger.info("Disconnected " + fPlayer); + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListenerRegistration.java b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListenerRegistration.java new file mode 100644 index 00000000..909d33b1 --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListenerRegistration.java @@ -0,0 +1,44 @@ +/* + * 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.listener; + +import com.google.inject.Inject; +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.plugin.Listener; +import org.geysermc.floodgate.BungeePlugin; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; + +@RequiredArgsConstructor(onConstructor = @__(@Inject)) +public final class BungeeListenerRegistration implements ListenerRegistration { + private final BungeePlugin plugin; + + @Override + public void register(Listener listener) { + ProxyServer.getInstance().getPluginManager().registerListener(plugin, listener); + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/module/BungeeAddonModule.java b/bungee/src/main/java/org/geysermc/floodgate/module/BungeeAddonModule.java new file mode 100644 index 00000000..e2de3039 --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeeAddonModule.java @@ -0,0 +1,54 @@ +/* + * 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.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.addon.AddonManagerAddon; +import org.geysermc.floodgate.addon.DebugAddon; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.register.AddonRegister; + +public final class BungeeAddonModule extends AbstractModule { + @Override + protected void configure() { + bind(AddonRegister.class).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon managerAddon() { + return new AddonManagerAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon debugAddon() { + return new DebugAddon(); + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/module/BungeeListenerModule.java b/bungee/src/main/java/org/geysermc/floodgate/module/BungeeListenerModule.java new file mode 100644 index 00000000..2d58dd4e --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeeListenerModule.java @@ -0,0 +1,55 @@ +/* + * 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.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.ProvidesIntoSet; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.listener.BungeeListener; +import org.geysermc.floodgate.register.ListenerRegister; + +public final class BungeeListenerModule extends AbstractModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public Listener bungeeListener(Plugin plugin, ProxyFloodgateConfig config, + ProxyFloodgateApi api, HandshakeHandler handshakeHandler, + FloodgateLogger logger) { + return new BungeeListener(plugin, config, api, handshakeHandler, logger); + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java new file mode 100644 index 00000000..5ceb1f8d --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/module/BungeePlatformModule.java @@ -0,0 +1,141 @@ +/* + * 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.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +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.command.BungeeCommandRegistration; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.inject.bungee.BungeeInjector; +import org.geysermc.floodgate.listener.BungeeListenerRegistration; +import org.geysermc.floodgate.logger.JavaDefaultFloodgateLogger; +import org.geysermc.floodgate.platform.command.CommandRegistration; +import org.geysermc.floodgate.platform.command.util.CommandUtil; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.util.BungeeCommandUtil; + +@RequiredArgsConstructor +public final class BungeePlatformModule extends AbstractModule { + private final BungeePlugin plugin; + + @Override + protected void configure() { + bind(SimpleFloodgateApi.class).to(ProxyFloodgateApi.class); + } + + @Provides + @Singleton + public Plugin bungeePlugin() { + return plugin; + } + + @Provides + @Singleton + @Named("configClass") + public Class floodgateConfigClass() { + return ProxyFloodgateConfig.class; + } + + @Provides + @Singleton + public ProxyFloodgateApi proxyFloodgateApi() { + return new ProxyFloodgateApi(); + } + + @Provides + @Singleton + public FloodgateLogger floodgateLogger() { + return new JavaDefaultFloodgateLogger(plugin.getLogger()); + } + + /* + Commands / Listeners + */ + + @Provides + @Singleton + public CommandRegistration commandRegistration() { + return new BungeeCommandRegistration(plugin); + } + + @Provides + @Singleton + public CommandUtil commandUtil(FloodgateLogger logger) { + return new BungeeCommandUtil(logger); + } + + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new BungeeListenerRegistration(plugin); + } + + /* + DebugAddon / PlatformInjector + */ + + @Provides + @Singleton + public CommonPlatformInjector platformInjector(FloodgateLogger logger) { + return new BungeeInjector(logger); + } + + @Provides + @Named("packetEncoder") + public String packetEncoder() { + return "packet-encoder"; + } + + @Provides + @Named("packetDecoder") + public String packetDecoder() { + return "packet-decoder"; + } + + @Provides + @Named("packetHandler") + public String packetHandler() { + return "inbound-boss"; + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "Bungeecord"; + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java new file mode 100644 index 00000000..be271b5c --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/util/BungeeCommandUtil.java @@ -0,0 +1,65 @@ +/* + * 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 lombok.RequiredArgsConstructor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.util.CommandResponseCache; +import org.geysermc.floodgate.platform.command.util.CommandUtil; + +@RequiredArgsConstructor +public final class BungeeCommandUtil extends CommandResponseCache implements CommandUtil { + private final FloodgateLogger logger; + + @Override + public void sendMessage(Object player, CommandMessage message, Object... args) { + cast(player).sendMessage(getOrAddCachedMessage(message, args)); + } + + @Override + public void kickPlayer(Object player, CommandMessage message, Object... args) { + cast(player).disconnect(getOrAddCachedMessage(message, args)); + } + + @Override + protected BaseComponent[] transformMessage(String message) { + return TextComponent.fromLegacyText(message); + } + + protected ProxiedPlayer cast(Object player) { + try { + return (ProxiedPlayer) player; + } catch (ClassCastException exception) { + logger.error("Failed to cast {} to ProxiedPlayer", player.getClass().getName()); + throw exception; + } + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/util/CommandUtil.java b/bungee/src/main/java/org/geysermc/floodgate/util/CommandUtil.java deleted file mode 100644 index 0f8a8ca6..00000000 --- a/bungee/src/main/java/org/geysermc/floodgate/util/CommandUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.geysermc.floodgate.util; - -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import org.geysermc.floodgate.command.CommandMessage; - -public class CommandUtil extends AbstractCommandResponseCache implements ICommandUtil { - @Override - public void sendMessage(ProxiedPlayer player, CommandMessage message, Object... args) { - player.sendMessage(getOrAddCachedMessage(message, args)); - } - - @Override - public void kickPlayer(ProxiedPlayer player, CommandMessage message, Object... args) { - player.disconnect(getOrAddCachedMessage(message, args)); - } - - @Override - protected BaseComponent[] transformMessage(String message) { - return TextComponent.fromLegacyText(message); - } -} diff --git a/bungee/src/main/resources/plugin.yml b/bungee/src/main/resources/plugin.yml index efa1e6b9..e5edac3c 100644 --- a/bungee/src/main/resources/plugin.yml +++ b/bungee/src/main/resources/plugin.yml @@ -1,21 +1,5 @@ -name: ${project.name} +name: ${outputName} description: ${project.description} version: ${project.version} author: ${project.organization.name} -main: org.geysermc.floodgate.BungeePlugin - -# Configuration for player linking -player-link: - # Whether to enable the linking system. Turning this off will prevent - # players from using the linking feature even if they are already linked. - enable: false - # The type of storage system you want to use - # Currently implemented: SQLite - type: sqlite - # Whether to allow the use of /linkaccount and /unlinkaccount - # You can also use allow specific people to use the commands using the - # permissions floodgate.linkaccount and floodgate.unlinkaccount. - # This is only for linking, already connected people will stay connected - allow-linking: true - # The amount of time until a link code expires in seconds - link-code-timeout: 300 \ No newline at end of file +main: org.geysermc.floodgate.BungeePlugin \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index a1dcbbad..fe1bf758 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -3,13 +3,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - floodgate-parent - org.geysermc + parent + org.geysermc.floodgate 1.0-SNAPSHOT 4.0.0 - floodgate-common + common @@ -36,28 +36,31 @@ - org.geysermc - common - ${geyser-version} - compile + org.geysermc.floodgate + api + ${project.parent.version} - com.fasterxml.jackson.core - jackson-databind - 2.9.10.4 - compile + com.google.inject + guice + 4.2.3 + + + io.netty + netty-transport + 4.1.49.Final + provided + + + io.netty + netty-codec + 4.1.49.Final + provided com.fasterxml.jackson.dataformat jackson-dataformat-yaml 2.9.9 - compile - - - org.xerial - sqlite-jdbc - 3.30.1 - compile diff --git a/common/src/main/java/org/geysermc/floodgate/AbstractFloodgateAPI.java b/common/src/main/java/org/geysermc/floodgate/AbstractFloodgateAPI.java deleted file mode 100644 index 1d2e6073..00000000 --- a/common/src/main/java/org/geysermc/floodgate/AbstractFloodgateAPI.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.geysermc.floodgate; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -abstract class AbstractFloodgateAPI { - static final Map players = new HashMap<>(); - - /** - * Get info about the given Bedrock player - * @param uuid the uuid of the online Bedrock player - * @return FloodgatePlayer if the given uuid is a Bedrock player - */ - public static FloodgatePlayer getPlayer(UUID uuid) { - FloodgatePlayer player = players.get(uuid); - if (player != null || isFloodgateId(uuid)) return player; - // make it possible to find player by Java id (for example for a linked player) - for (FloodgatePlayer player1 : players.values()) { - if (player1.getCorrectUniqueId().equals(uuid)) { - return player1; - } - } - return null; - } - - /** - * Removes a player (should only be used internally) - * @param onlineId The UUID of the online player - * @param removeLogin true if it should remove a sessions who is still logging in - * @return true if player was a LinkedPlayer - */ - static boolean removePlayer(UUID onlineId, boolean removeLogin) { - FloodgatePlayer player = players.get(onlineId); - // the player is a non-linked player or a linked player but somehow someone tried to - // remove the player by his xuid, we have to find out - if (player != null) { - // we don't allow them to remove a player by his xuid - // because a linked player is never registered by his linked java uuid - if (player.getLinkedPlayer() != null) return false; - - // removeLogin logics - if (player.isLogin() && !removeLogin || !player.isLogin() && removeLogin) { - return false; - } - - // passed the test - players.remove(onlineId); - // was the account linked? - return player.getLinkedPlayer() != null; - } - - // we still want to be able to remove a linked-player by his linked java uuid - for (FloodgatePlayer player1 : players.values()) { - if (player1.isLogin() && !removeLogin || !player1.isLogin() && removeLogin) continue; - if (!player1.getCorrectUniqueId().equals(onlineId)) continue; - players.remove(createJavaPlayerId(Long.parseLong(player1.getXuid()))); - return true; - } - return false; - } - - /** - * {@link #removePlayer(UUID, boolean)} but with removeLogin on false - */ - static boolean removePlayer(UUID onlineId) { - return removePlayer(onlineId, false); - } - - static boolean removePlayer(FloodgatePlayer player) { - boolean removed = players.remove(createJavaPlayerId(Long.parseLong(player.getXuid())), player); - return removed && player.getLinkedPlayer() != null; - } - - /** - * Method to determine if the given online player is a bedrock player - * @param uuid The uuid of the online player - * @return true if the given online player is a Bedrock player - */ - public static boolean isBedrockPlayer(UUID uuid) { - return getPlayer(uuid) != null; - } - - /** - * Create a valid Java player uuid of a xuid - */ - public static UUID createJavaPlayerId(long xuid) { - return new UUID(0, xuid); - } - - public static boolean isFloodgateId(UUID uuid) { - return uuid.getMostSignificantBits() == 0; - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/AbstractLinkAccountCommand.java b/common/src/main/java/org/geysermc/floodgate/AbstractLinkAccountCommand.java deleted file mode 100644 index 39031fbf..00000000 --- a/common/src/main/java/org/geysermc/floodgate/AbstractLinkAccountCommand.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.floodgate.command.CommandMessage; -import org.geysermc.floodgate.util.CommonMessage; -import org.geysermc.floodgate.util.ICommandUtil; - -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.util.UUID; - -@AllArgsConstructor -public class AbstractLinkAccountCommand> { - private final Map activeLinkRequests = new HashMap<>(); - private final PlayerLink link; - @Getter(AccessLevel.PROTECTED) - private final C commandUtil; - - public void execute(P player, UUID uuid, String username, String[] args) { - if (!PlayerLink.isEnabledAndAllowed()) { - sendMessage(player, Message.LINK_REQUEST_DISABLED); - return; - } - link.isLinkedPlayer(uuid).whenComplete((linked, throwable) -> { - if (throwable != null) { - sendMessage(player, CommonMessage.IS_LINKED_ERROR); - return; - } - if (linked) { - sendMessage(player, Message.ALREADY_LINKED); - return; - } - // when the player is a Java player - if (!AbstractFloodgateAPI.isBedrockPlayer(uuid)) { - if (args.length != 1) { - sendMessage(player, Message.JAVA_USAGE); - return; - } - String code = String.format("%04d", new Random().nextInt(10000)); - String bedrockUsername = args[0]; - activeLinkRequests.put(username, new LinkRequest(username, uuid, code, bedrockUsername)); - sendMessage(player, Message.LINK_REQUEST_CREATED, bedrockUsername, username, code); - return; - } - // when the player is a Bedrock player - if (args.length != 2) { - sendMessage(player, Message.BEDROCK_USAGE); - return; - } - String javaUsername = args[0]; - String code = args[1]; - LinkRequest request = activeLinkRequests.getOrDefault(javaUsername, null); - if (request != null && request.checkGamerTag(AbstractFloodgateAPI.getPlayer(uuid))) { - if (request.getLinkCode().equals(code)) { - activeLinkRequests.remove(javaUsername); // Delete the request, whether it has expired or is successful - if (request.isExpired()) { - sendMessage(player, Message.LINK_REQUEST_EXPIRED); - return; - } - link.linkPlayer(uuid, request.getJavaUniqueId(), request.getJavaUsername()).whenComplete((aVoid, throwable1) -> { - if (throwable1 != null) { - sendMessage(player, Message.LINK_REQUEST_ERROR); - return; - } - commandUtil.kickPlayer(player, Message.LINK_REQUEST_COMPLETED, request.getJavaUsername()); - }); - return; - } - sendMessage(player, Message.INVALID_CODE); - return; - } - sendMessage(player, Message.NO_LINK_REQUESTED); - }); - } - - private void sendMessage(P player, CommandMessage message, Object... args) { - commandUtil.sendMessage(player, message, args); - } - - public enum Message implements CommandMessage { - ALREADY_LINKED( - "&cYour account is already linked!\n" + - "&cIf you want to link to a different account, run &6/unlinkaccount&c and try it again." - ), - JAVA_USAGE("&cUsage: /linkaccount "), - LINK_REQUEST_CREATED( - "&aLog in as %s on Bedrock and run &6/linkaccount %s %s\n" + - "&cWarning: Any progress on your Bedrock account will not be carried over! Save any items in your inventory first.\n" + - "&cIf you change your mind you can run &6/unlinkaccount&c to get your progess back." - ), - BEDROCK_USAGE("&cStart the process from Java! Usage: /linkaccount "), - LINK_REQUEST_EXPIRED("&cThe code you entered is expired! Run &6/linkaccount&c again on your Java account"), - LINK_REQUEST_COMPLETED("You are successfully linked to %s!\nIf you want to undo this run /unlinkaccount"), - LINK_REQUEST_ERROR("&cAn error occurred while linking. " + CommonMessage.CHECK_CONSOLE), - INVALID_CODE("&cInvalid code! Please check your code or run the &6/linkaccount&c command again on your Java account."), - NO_LINK_REQUESTED("&cThis player has not requested an account link! Please log in on Java and request one with &6/linkaccount"), - LINK_REQUEST_DISABLED("&cLinking is not enabled on this server."); - - @Getter private final String message; - - Message(String message) { - this.message = message.replace('&', COLOR_CHAR); - } - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/AbstractUnlinkAccountCommand.java b/common/src/main/java/org/geysermc/floodgate/AbstractUnlinkAccountCommand.java deleted file mode 100644 index 35035f87..00000000 --- a/common/src/main/java/org/geysermc/floodgate/AbstractUnlinkAccountCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.floodgate.command.CommandMessage; -import org.geysermc.floodgate.util.CommonMessage; -import org.geysermc.floodgate.util.ICommandUtil; - -import java.util.UUID; - -@AllArgsConstructor -public class AbstractUnlinkAccountCommand> { - private final PlayerLink link; - @Getter(AccessLevel.PROTECTED) - private final C commandUtil; - - public void execute(P player, UUID uuid) { - if (!PlayerLink.isEnabledAndAllowed()) { - sendMessage(player, Message.LINKING_NOT_ENABLED); - return; - } - link.isLinkedPlayer(uuid).whenComplete((linked, throwable) -> { - if (throwable != null) { - sendMessage(player, CommonMessage.IS_LINKED_ERROR); - return; - } - if (!linked) { - sendMessage(player, Message.NOT_LINKED); - return; - } - link.unlinkPlayer(uuid).whenComplete((aVoid, throwable1) -> - sendMessage(player, throwable1 == null ? Message.UNLINK_SUCCESS : Message.UNLINK_ERROR) - ); - }); - } - - private void sendMessage(P player, CommandMessage message, Object... args) { - commandUtil.sendMessage(player, message, args); - } - - public enum Message implements CommandMessage { - NOT_LINKED("&cYour account isn't linked"), - UNLINK_SUCCESS("&cUnlink successful! Rejoin to return to your Bedrock account"), - UNLINK_ERROR("&cAn error occurred while unlinking player! " + CommonMessage.CHECK_CONSOLE), - LINKING_NOT_ENABLED("&cLinking is not enabled on this server"); - - @Getter - private final String message; - - Message(String message) { - this.message = message.replace('&', COLOR_CHAR); - } - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java b/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java deleted file mode 100644 index ad77124f..00000000 --- a/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.geysermc.floodgate; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import lombok.Getter; -import org.geysermc.floodgate.util.EncryptionUtil; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Base64; -import java.util.logging.Level; -import java.util.logging.Logger; - -@Getter -public class FloodgateConfig { - @JsonProperty(value = "key-file-name") - private String keyFileName; - @JsonProperty(value = "disconnect") - private DisconnectMessages messages; - @JsonProperty(value = "username-prefix") - private String usernamePrefix; - @JsonProperty(value = "replace-spaces") - private boolean replaceSpaces; - - @JsonProperty(value = "player-link") - private PlayerLinkConfig playerLink; - - @JsonProperty - private boolean debug; - - @JsonIgnore - PrivateKey privateKey = null; - - @Getter - public static class DisconnectMessages { - @JsonProperty("invalid-key") - private String invalidKey; - @JsonProperty("invalid-arguments-length") - private String invalidArgumentsLength; - } - - @Getter - public static class PlayerLinkConfig { - @JsonProperty("enable") - private boolean enabled; - @JsonProperty("type") - private String type; - @JsonProperty("allow-linking") - private boolean allowLinking; - @JsonProperty("link-code-timeout") - private long linkCodeTimeout; - } - - public static FloodgateConfig load(Logger logger, Path configPath) { - return load(logger, configPath, FloodgateConfig.class); - } - - public static T load(Logger logger, Path configPath, Class configClass) { - T config = null; - try { - try { - if (!configPath.toFile().exists()) { - Files.copy(FloodgateConfig.class.getClassLoader().getResourceAsStream("config.yml"), configPath); - - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(2048); - KeyPair keyPair = generator.generateKeyPair(); - - String test = "abcdefghijklmnopqrstuvwxyz1234567890"; - - String encrypted = EncryptionUtil.encrypt(keyPair.getPublic(), test); - String decrypted = new String(EncryptionUtil.decrypt(keyPair.getPrivate(), encrypted)); - - if (!test.equals(decrypted)) { - System.out.println(test +" "+ decrypted +" "+ encrypted +" " + new String(Base64.getDecoder().decode(encrypted.split("\0")[1]))); - throw new RuntimeException( - "Testing the private and public key failed," + - "the original message isn't the same as the decrypted!" - ); - } - - Files.write(configPath.getParent().resolve("encrypted.txt"), encrypted.getBytes()); - - Files.write(configPath.getParent().resolve("public-key.pem"), keyPair.getPublic().getEncoded()); - Files.write(configPath.getParent().resolve("key.pem"), keyPair.getPrivate().getEncoded()); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error while creating config", e); - } - - config = new ObjectMapper(new YAMLFactory()).readValue( - Files.readAllBytes(configPath), configClass - ); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error while loading config", e); - } - - if (config == null) { - throw new RuntimeException("Failed to load config file! Try to delete the data folder of Floodgate"); - } - - try { - config.privateKey = EncryptionUtil.getKeyFromFile( - configPath.getParent().resolve(config.getKeyFileName()), - PrivateKey.class - ); - } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - logger.log(Level.SEVERE, "Error while reading private key", e); - } - return config; - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java b/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java new file mode 100644 index 00000000..569b8b2a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java @@ -0,0 +1,132 @@ +/* + * 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; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.name.Named; +import lombok.AccessLevel; +import lombok.Getter; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.InstanceHolder; +import org.geysermc.floodgate.api.inject.PlatformInjector; +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.loader.ConfigLoader; +import org.geysermc.floodgate.link.PlayerLinkLoader; +import org.geysermc.floodgate.module.ConfigLoadedModule; +import org.geysermc.floodgate.module.PostInitializeModule; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class FloodgatePlatform { + private static final UUID KEY = UUID.randomUUID(); + + @Getter(AccessLevel.PROTECTED) + private final FloodgateConfig config; + private final FloodgateApi api; + + @Getter(AccessLevel.PROTECTED) + private final FloodgateLogger logger; + + private final Injector guice; + + @Inject private PlatformInjector injector; + + @Inject + public FloodgatePlatform(@Named("dataDirectory") Path dataDirectory, FloodgateApi api, + ConfigLoader configLoader, PlayerLinkLoader playerLinkLoader, + HandshakeHandler handshakeHandler, FloodgateLogger logger, + Injector injector) { + this.api = api; + this.logger = logger; + + if (!Files.isDirectory(dataDirectory)) { + try { + Files.createDirectory(dataDirectory); + } catch (Exception exception) { + logger.error("Failed to create the data folder", exception); + throw new RuntimeException("Failed to create the data folder"); + } + } + + config = configLoader.load(); + + // make the config available for other classes + guice = injector.createChildInjector(new ConfigLoadedModule(config, api)); + + guice.injectMembers(playerLinkLoader); + guice.injectMembers(handshakeHandler); + + PlayerLink link = playerLinkLoader.load(); + + InstanceHolder.setInstance(api, link, KEY); + } + + public boolean enable(Module... postCreateModules) { + if (injector == null) { + getLogger().error("Failed to find the platform injector!"); + return false; + } + + try { + if (!injector.inject()) { + getLogger().error("Failed to inject the packet listener!"); + return false; + } + } catch (Exception exception) { + getLogger().error("Failed to inject the packet listener!", exception); + return false; + } + + guice.createChildInjector(new PostInitializeModule(postCreateModules)); + return true; + } + + public boolean disable() { + if (injector != null) { + try { + if (!injector.removeInjection()) { + getLogger().error("Failed to remove the injection!"); + } + } catch (Exception exception) { + getLogger().error("Failed to remove the injection!", exception); + } + } + + api.getPlayerLink().stop(); + return true; + } + + public boolean isProxy() { + return config.isProxy(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/FloodgatePlayer.java b/common/src/main/java/org/geysermc/floodgate/FloodgatePlayer.java deleted file mode 100644 index 0a7b5c2c..00000000 --- a/common/src/main/java/org/geysermc/floodgate/FloodgatePlayer.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.Getter; -import lombok.Setter; -import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.DeviceOS; - -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -@Getter -public class FloodgatePlayer { - /** - * Bedrock version of the client - */ - private String version; - /** - * Bedrock username (full version) - */ - private String username; - /** - * Bedrock username with the given identifier
- * This won't be null if it is an {@link LinkedPlayer LinkedPlayer}, but it isn't used - */ - private String javaUsername; - /** - * The Unique Identifier used at the server to identify the bedrock client.
- * Note that this field is only used when the player is not an {@link LinkedPlayer LinkedPlayer} - */ - @Setter - private UUID javaUniqueId; - /** - * The Xbox Unique Identifier - */ - private String xuid; - /** - * The operation system of the bedrock client - */ - private DeviceOS deviceOS; - /** - * The language code of the bedrock client - */ - private String languageCode; - /** - * The InputMode of the bedrock client - */ - private int inputMode; - /** - * The LinkedPlayer object if the player is linked to Java account. - */ - private LinkedPlayer linkedPlayer; - - /** - * Returns true if the player is still logging in - */ - @Setter - private boolean login = true; - - FloodgatePlayer(BedrockData data, String prefix, boolean replaceSpaces) { - xuid = data.getXuid(); - version = data.getVersion(); - username = data.getUsername(); - javaUsername = prefix + data.getUsername().substring(0, Math.min(data.getUsername().length(), 16 - prefix.length())); - if (replaceSpaces) { - javaUsername = javaUsername.replaceAll(" ", "_"); - } - deviceOS = DeviceOS.getById(data.getDeviceId()); - languageCode = data.getLanguageCode(); - inputMode = data.getInputMode(); - javaUniqueId = AbstractFloodgateAPI.createJavaPlayerId(Long.parseLong(data.getXuid())); - // every implementation (Bukkit, Bungee and Velocity) all run this async, - // so we can block this thread - if (PlayerLink.isEnabledAndAllowed()) { - linkedPlayer = fetchLinkedPlayer(); - } - } - - public UUID getCorrectUniqueId() { - return linkedPlayer != null ? linkedPlayer.javaUniqueId : javaUniqueId; - } - - public String getCorrectUsername() { - return linkedPlayer != null ? linkedPlayer.javaUsername : javaUsername; - } - - /** - * This will return the LinkedPlayer object if the player is linked.
- * Please note that the LinkedPlayer will be loaded (sync) when used for the first time.
- * This method also checks if linking is enabled - * @return LinkedPlayer or null if the player isn't linked or linking isn't enabled - * @see #fetchLinkedPlayerAsync() for the async alternative - */ - public LinkedPlayer fetchLinkedPlayer() { - if (!PlayerLink.isEnabledAndAllowed()) return null; - try { - return PlayerLink.getInstance().getLinkedPlayer(javaUniqueId).get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - return null; - } - } - - /** - * Async alternative to {@link #fetchLinkedPlayer()}. - * - * @see #fetchLinkedPlayer() for the sync version - * @return a completable future of the fetched link player - */ - public CompletableFuture fetchLinkedPlayerAsync() { - return PlayerLink.isEnabledAndAllowed() ? - PlayerLink.getInstance().getLinkedPlayer(javaUniqueId) : - CompletableFuture.completedFuture(null); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/FloodgatePlayerImpl.java b/common/src/main/java/org/geysermc/floodgate/FloodgatePlayerImpl.java new file mode 100644 index 00000000..c4e3ab51 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/FloodgatePlayerImpl.java @@ -0,0 +1,143 @@ +/* + * 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; + +import lombok.Getter; +import lombok.Setter; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.InstanceHolder; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.util.*; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +@Getter +public final class FloodgatePlayerImpl implements FloodgatePlayer { + private final String version; + private final String username; + private final String javaUsername; + //todo maybe add a map for platform specific things + private final UUID javaUniqueId; + private final String xuid; + private final DeviceOs deviceOs; + private final String languageCode; + private final UiProfile uiProfile; + private final InputMode inputMode; + private final String ip; + private final LinkedPlayer linkedPlayer; + + /** + * Returns true if the player is still logging in + */ + @Setter + private boolean login = true; + + FloodgatePlayerImpl(BedrockData data, String prefix, boolean replaceSpaces) { + FloodgateApi api = FloodgateApi.getInstance(); + version = data.getVersion(); + username = data.getUsername(); + + int usernameLength = Math.min(data.getUsername().length(), 16 - prefix.length()); + String editedUsername = prefix + data.getUsername().substring(0, usernameLength); + + if (replaceSpaces) { + editedUsername = editedUsername.replaceAll(" ", "_"); + } + javaUsername = editedUsername; + javaUniqueId = api.createJavaPlayerId(Long.parseLong(data.getXuid())); + + xuid = data.getXuid(); + deviceOs = DeviceOs.getById(data.getDeviceOs()); + languageCode = data.getLanguageCode(); + uiProfile = UiProfile.getById(data.getUiProfile()); + inputMode = InputMode.getById(data.getInputMode()); + ip = data.getIp(); + + // we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one) + if (data.hasPlayerLink()) { + linkedPlayer = data.getLinkedPlayer(); + return; + } + + // every implementation (Bukkit, Bungee and Velocity) run this constructor async, so we + // should be fine doing this synchronised. + linkedPlayer = fetchLinkedPlayer(api.getPlayerLink()); + + if (linkedPlayer != null && api instanceof ProxyFloodgateApi) { + // oh oh, now our encrypted data is incorrect. Updating... + InstanceHolder.castApi(ProxyFloodgateApi.class) + .updateEncryptedData(getCorrectUniqueId(), toBedrockData()); + } + } + + 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. + * Please note that this method loads the LinkedPlayer synchronously. + * + * @return LinkedPlayer or null if the player isn't linked or linking isn't enabled + * @see #fetchLinkedPlayerAsync(PlayerLink) for the asynchronously alternative + */ + public LinkedPlayer fetchLinkedPlayer(PlayerLink link) { + if (!link.isEnabledAndAllowed()) return null; + try { + return link.getLinkedPlayer(javaUniqueId).get(); + } catch (InterruptedException | ExecutionException exception) { + exception.printStackTrace(); + return null; + } + } + + /** + * Fetch and return the LinkedPlayer object associated to the player if the player is linked. + * + * @return a future holding the LinkedPlayer or null if the player isn't linked or when + * linking isn't enabled + * @see #fetchLinkedPlayer(PlayerLink) for the sync version + */ + public CompletableFuture fetchLinkedPlayerAsync(PlayerLink link) { + return link.isEnabledAndAllowed() ? + link.getLinkedPlayer(javaUniqueId) : + CompletableFuture.completedFuture(null); + } + + public BedrockData toBedrockData() { + return new BedrockData(version, username, xuid, deviceOs.ordinal(), languageCode, + uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java b/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java index 792dace9..081df282 100644 --- a/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java +++ b/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java @@ -1,6 +1,36 @@ +/* + * 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; +import com.google.inject.Inject; import lombok.*; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -10,22 +40,27 @@ import javax.crypto.NoSuchPaddingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.util.UUID; +import static com.google.common.base.Preconditions.checkNotNull; import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH; import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER; -public class HandshakeHandler { +@RequiredArgsConstructor +public final class HandshakeHandler { + private final SimpleFloodgateApi api; private PrivateKey privateKey; - private boolean bungee; + private boolean proxy; + private String usernamePrefix; private boolean replaceSpaces; - public HandshakeHandler(@NonNull PrivateKey privateKey, boolean bungee, String usernamePrefix, boolean replaceSpaces) { - this.privateKey = privateKey; - this.bungee = bungee; - this.usernamePrefix = usernamePrefix; - this.replaceSpaces = replaceSpaces; + @Inject + public void init(FloodgateConfig config) { + this.privateKey = config.getPrivateKey(); + checkNotNull(privateKey, "Floodgate key cannot be null"); + this.proxy = config.isProxy(); + this.usernamePrefix = config.getUsernamePrefix(); + this.replaceSpaces = config.isReplaceSpaces(); } public HandshakeResult handle(@NonNull String handshakeData) { @@ -33,7 +68,8 @@ public class HandshakeHandler { String[] data = handshakeData.split("\0"); boolean isBungeeData = data.length == 6 || data.length == 7; - if (bungee && isBungeeData || !isBungeeData && data.length != 4 || !data[1].equals(FLOODGATE_IDENTIFIER)) { + if (proxy && isBungeeData || !isBungeeData && data.length != 4 + || !data[1].equals(FLOODGATE_IDENTIFIER)) { return ResultType.NOT_FLOODGATE_DATA.getCachedResult(); } @@ -45,30 +81,25 @@ public class HandshakeHandler { return ResultType.INVALID_DATA_LENGTH.getCachedResult(); } - FloodgatePlayer player = new FloodgatePlayer(bedrockData, usernamePrefix, replaceSpaces); - // javaUniqueId will always be (at this point) the xuid but converted into an uuid form - AbstractFloodgateAPI.players.put(player.getJavaUniqueId(), player); - - // Get the UUID from the bungee instance to fix linked account UUIDs being wrong - if (isBungeeData) { - String uuid = data[5].replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); - player.setJavaUniqueId(UUID.fromString(uuid)); - } + FloodgatePlayer player = + new FloodgatePlayerImpl(bedrockData, usernamePrefix, replaceSpaces); + api.addPlayer(player.getJavaUniqueId(), player); return new HandshakeResult(ResultType.SUCCESS, data, bedrockData, player); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { - e.printStackTrace(); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | + IllegalBlockSizeException | BadPaddingException exception) { + exception.printStackTrace(); return ResultType.EXCEPTION.getCachedResult(); } } @AllArgsConstructor(access = AccessLevel.PROTECTED) - @Getter @ToString + @Getter public static class HandshakeResult { - private ResultType resultType; - private String[] handshakeData; - private BedrockData bedrockData; - private FloodgatePlayer floodgatePlayer; + private final ResultType resultType; + private final String[] handshakeData; + private final BedrockData bedrockData; + private final FloodgatePlayer floodgatePlayer; } public enum ResultType { @@ -77,7 +108,8 @@ public class HandshakeHandler { INVALID_DATA_LENGTH, SUCCESS; - @Getter private HandshakeResult cachedResult; + @Getter + private final HandshakeResult cachedResult; ResultType() { cachedResult = new HandshakeResult(this, null, null, null); diff --git a/common/src/main/java/org/geysermc/floodgate/LinkRequest.java b/common/src/main/java/org/geysermc/floodgate/LinkRequest.java deleted file mode 100644 index f51842bf..00000000 --- a/common/src/main/java/org/geysermc/floodgate/LinkRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.Getter; - -import java.time.Instant; -import java.util.UUID; - -@Getter -public class LinkRequest { - /** - * The Java username of the linked player - */ - private String javaUsername; - /** - * The Java UUID of the linked player - */ - private UUID javaUniqueId; - /** - * The link code - */ - private String linkCode; - /** - * The username of player being linked - */ - private String bedrockUsername; - /** - * The time when the link was requested - */ - private long unixTime; - - LinkRequest(String username, UUID uuid, String code, String beUsername) { - javaUniqueId = uuid; - javaUsername = username; - linkCode = code; - bedrockUsername = beUsername; - unixTime = Instant.now().getEpochSecond(); - } - - public boolean isExpired() { - long timePassed = Instant.now().getEpochSecond() - unixTime; - return timePassed > PlayerLink.getVerifyLinkTimeout(); - } - - public boolean checkGamerTag(FloodgatePlayer player) { - // Accept the request whether the prefix was used or not - return bedrockUsername.equals(player.getUsername()) || bedrockUsername.equals(player.getJavaUsername()); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/LinkedPlayer.java deleted file mode 100644 index c148c430..00000000 --- a/common/src/main/java/org/geysermc/floodgate/LinkedPlayer.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -import java.util.UUID; - -@AllArgsConstructor(access = AccessLevel.PACKAGE) -@Getter @Setter -public class LinkedPlayer { - /** - * The Java username of the linked player - */ - public String javaUsername; - /** - * The Java UUID of the linked player - */ - public UUID javaUniqueId; - /** - * The UUID of the Bedrock player - */ - public UUID bedrockId; -} diff --git a/common/src/main/java/org/geysermc/floodgate/PlayerLink.java b/common/src/main/java/org/geysermc/floodgate/PlayerLink.java deleted file mode 100644 index 8a75998e..00000000 --- a/common/src/main/java/org/geysermc/floodgate/PlayerLink.java +++ /dev/null @@ -1,94 +0,0 @@ -package org.geysermc.floodgate; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.floodgate.link.SQLitePlayerLink; - -import java.nio.file.Path; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Supplier; -import java.util.logging.Logger; - -public abstract class PlayerLink { - @Getter private static PlayerLink instance; - @Getter private static boolean enabled; - @Getter private static long verifyLinkTimeout; - @Getter private static boolean allowLinking; - - @Getter private final ExecutorService executorService = Executors.newFixedThreadPool(11); - @Getter private Logger logger; - - protected abstract void load(Path dataFolder); - - public abstract CompletableFuture getLinkedPlayer(UUID bedrockId); - - public abstract CompletableFuture isLinkedPlayer(UUID bedrockId); - - public abstract CompletableFuture linkPlayer(UUID bedrockId, UUID uuid, String username); - - public abstract CompletableFuture unlinkPlayer(UUID uuid); - - public static PlayerLink initialize(Logger logger, Path dataFolder, FloodgateConfig config) { - if (PlayerLink.instance == null) { - FloodgateConfig.PlayerLinkConfig linkConfig = config.getPlayerLink(); - ImplementationType type = ImplementationType.getByName(linkConfig.getType()); - if (type == null) { - logger.severe("Failed to find an implementation for type: " + linkConfig.getType()); - return null; - } - PlayerLink.instance = type.instanceSupplier.get(); - PlayerLink.enabled = linkConfig.isEnabled(); - PlayerLink.verifyLinkTimeout = linkConfig.getLinkCodeTimeout(); - PlayerLink.allowLinking = linkConfig.isAllowLinking(); - instance.logger = logger; - instance.load(dataFolder); - return instance; - } - return instance; - } - - /** - * Shutdown the thread pool and invalidates the PlayerLink instance - */ - public void stop() { - instance = null; - executorService.shutdown(); - } - - protected LinkedPlayer createLinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { - return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); - } - - public static boolean isEnabledAndAllowed() { - return enabled && allowLinking; - } - - @AllArgsConstructor - @Getter - public enum ImplementationType { - SQLITE(SQLitePlayerLink::new); - - private Supplier instanceSupplier; - - public static final ImplementationType[] VALUES = values(); - - public static ImplementationType getByName(String implementationName) { - String uppercase = implementationName.toUpperCase(); - for (ImplementationType type : VALUES) { - if (type.name().equals(uppercase)) { - return type; - } - } - return null; - } - } - - public static CompletableFuture failedFuture(Throwable exception) { - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(exception); - return future; - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/addon/AddonManagerAddon.java b/common/src/main/java/org/geysermc/floodgate/addon/AddonManagerAddon.java new file mode 100644 index 00000000..3258a7e0 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/addon/AddonManagerAddon.java @@ -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.addon; + +import com.google.inject.Inject; +import io.netty.channel.Channel; +import org.geysermc.floodgate.addon.addonmanager.AddonManagerHandler; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.inject.CommonPlatformInjector; + +public final class AddonManagerAddon implements InjectorAddon { + @Inject private CommonPlatformInjector injector; + + @Override + public void onInject(Channel channel, boolean proxyToServer) { + channel.pipeline().addLast( + "floodgate_addon", new AddonManagerHandler(injector, channel) + ); + } + + @Override + public void onLoginDone(Channel channel) { + onRemoveInject(channel); + } + + @Override + public void onRemoveInject(Channel channel) { + channel.pipeline().remove("floodgate_addon"); + } + + @Override + public boolean shouldInject() { + return true; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/addon/DebugAddon.java b/common/src/main/java/org/geysermc/floodgate/addon/DebugAddon.java new file mode 100644 index 00000000..09f67325 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/addon/DebugAddon.java @@ -0,0 +1,82 @@ +/* + * 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.addon; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import io.netty.channel.Channel; +import org.geysermc.floodgate.addon.debug.ChannelInDebugHandler; +import org.geysermc.floodgate.addon.debug.ChannelOutDebugHandler; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.FloodgateConfig; + +public final class DebugAddon implements InjectorAddon { + @Inject private FloodgateConfig config; + @Inject private FloodgateLogger logger; + + @Inject + @Named("implementationName") + private String implementationName; + + @Inject + @Named("packetEncoder") + private String packetEncoder; + + @Inject + @Named("packetDecoder") + private String packetDecoder; + + @Override + public void onInject(Channel channel, boolean proxyToServer) { + //todo enable debug mode in our logger + channel.pipeline().addBefore( + packetEncoder, "floodgate_debug_out", + new ChannelOutDebugHandler(implementationName, !proxyToServer, logger) + ).addBefore( + packetDecoder, "floodgate_debug_in", + new ChannelInDebugHandler(logger, implementationName, !proxyToServer) + ); + } + + @Override + public void onLoginDone(Channel channel) { + onRemoveInject(channel); + } + + @Override + public void onRemoveInject(Channel channel) { + channel.pipeline().remove("floodgate_debug_out"); + channel.pipeline().remove("floodgate_debug_in"); + } + + @Override + public boolean shouldInject() { + logger.info("Is debug? " + config.isDebug()); + return config.isDebug(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/addon/addonmanager/AddonManagerHandler.java b/common/src/main/java/org/geysermc/floodgate/addon/addonmanager/AddonManagerHandler.java new file mode 100644 index 00000000..f18ced16 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/addon/addonmanager/AddonManagerHandler.java @@ -0,0 +1,71 @@ +/* + * 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.addon.addonmanager; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.inject.CommonPlatformInjector; + +@RequiredArgsConstructor +public final class AddonManagerHandler extends MessageToByteEncoder { + private final CommonPlatformInjector injector; + private final Channel channel; + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) { + injector.removeAddonsCall(channel); + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) { + int index = msg.readerIndex(); + // LoginSuccess packet = 2 + if (readVarInt(msg) == 2) { + injector.loginSuccessCall(channel); + } + msg.readerIndex(index); + out.writeBytes(msg); + } + + private int readVarInt(ByteBuf buffer) { + int out = 0; + int count = 0; + byte current; + do { + current = buffer.readByte(); + out |= (current & 0x7F) << (count++ * 7); + + if (count > 5) { + throw new RuntimeException("VarInt is bigger then allowed"); + } + } while ((current & 0x80) != 0); + return out; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/addon/debug/ChannelInDebugHandler.java b/common/src/main/java/org/geysermc/floodgate/addon/debug/ChannelInDebugHandler.java new file mode 100644 index 00000000..adf5d8c7 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/addon/debug/ChannelInDebugHandler.java @@ -0,0 +1,52 @@ +/* + * 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.addon.debug; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +public final class ChannelInDebugHandler extends SimpleChannelInboundHandler { + private final String message; + private final FloodgateLogger logger; + + public ChannelInDebugHandler(FloodgateLogger logger, String implementationType, + boolean player) { + this.logger = logger; + this.message = (player ? "Player ->" : "Server ->") + ' ' + implementationType; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + int index = msg.readerIndex(); + logger.info("{}:\n{}", message, ByteBufUtil.prettyHexDump(msg)); + msg.readerIndex(index); + ctx.fireChannelRead(msg.retain()); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/addon/debug/ChannelOutDebugHandler.java b/common/src/main/java/org/geysermc/floodgate/addon/debug/ChannelOutDebugHandler.java new file mode 100644 index 00000000..4e53ea00 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/addon/debug/ChannelOutDebugHandler.java @@ -0,0 +1,53 @@ +/* + * 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.addon.debug; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +public final class ChannelOutDebugHandler extends MessageToByteEncoder { + private final String direction; + private final FloodgateLogger logger; + + public ChannelOutDebugHandler(String implementationType, boolean player, + FloodgateLogger logger) { + this.direction = implementationType + (player ? " -> Player" : " -> Server"); + this.logger = logger; + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) { + int index = msg.readerIndex(); + logger.info("{}:\n{}", direction, ByteBufUtil.prettyHexDump(msg)); + + msg.readerIndex(index); + out.writeBytes(msg); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java b/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java new file mode 100644 index 00000000..1d33a986 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/api/ProxyFloodgateApi.java @@ -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.api; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.floodgate.util.EncryptionUtil; + +import java.security.PrivateKey; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class ProxyFloodgateApi extends SimpleFloodgateApi { + private final Map encryptedData = new HashMap<>(); + private PrivateKey key; + + @Inject + public void init(@Named("floodgateKey") PrivateKey key) { + this.key = key; + } + + public String getEncryptedData(UUID uuid) { + return encryptedData.get(uuid); + } + + public void addEncryptedData(UUID uuid, String encryptedData) { + this.encryptedData.put(uuid, encryptedData); // just override already existing data I guess + } + + public void removeEncryptedData(UUID uuid) { + encryptedData.remove(uuid); + } + + public void updateEncryptedData(UUID uuid, BedrockData bedrockData) { + //todo move away from public/private key system + try { + String data = EncryptionUtil.encryptBedrockData(key, bedrockData); + addEncryptedData(uuid, data); + } catch (Exception exception) { +// throw new IllegalStateException("We failed to update the BedrockData, " + +// "but we can't continue without the updated version!", exception); + System.out.println("We failed to update the BedrockData, " + + "but we can't continue without the updated version!"); + exception.printStackTrace(); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java b/common/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java new file mode 100644 index 00000000..e6451266 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/api/SimpleFloodgateApi.java @@ -0,0 +1,136 @@ +/* + * 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; + +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.FloodgatePlayerImpl; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@RequiredArgsConstructor +public class SimpleFloodgateApi implements FloodgateApi { + private final Map players = new HashMap<>(); + + @Override + public boolean isBedrockPlayer(UUID uuid) { + return getPlayer(uuid) != null; + } + + @Override + public FloodgatePlayer getPlayer(UUID uuid) { + FloodgatePlayer player = players.get(uuid); + // bedrock players are always stored by their xuid, + // so we return the instance if we know that the given uuid is a Floodgate uuid + if (player != null || isFloodgateId(uuid)) { + return player; + } + + // make it possible to find player by Java id (linked players) + for (FloodgatePlayer player1 : players.values()) { + if (player1.getCorrectUniqueId().equals(uuid)) { + return player1; + } + } + return null; + } + + @Override + public UUID createJavaPlayerId(long xuid) { + return new UUID(0, xuid); + } + + @Override + public boolean isFloodgateId(UUID uuid) { + return uuid.getMostSignificantBits() == 0; + } + + public FloodgatePlayer addPlayer(UUID uuid, FloodgatePlayer player) { + return players.put(uuid, player); + } + + /** + * Removes a player (should only be used internally) + * + * @param onlineId The UUID of the online player + * @param removeLogin true if it should remove a sessions who is still logging in + * @return the FloodgatePlayer the player was logged in with + */ + @Nullable + public FloodgatePlayer removePlayer(UUID onlineId, boolean removeLogin) { + FloodgatePlayer player = players.get(onlineId); + // the player is a non-linked player or a linked player but somehow someone tried to + // remove the player by his xuid, we have to find out + if (player != null) { + // we don't allow them to remove a player by his xuid + // because a linked player is never registered by his linked java uuid + if (player.getLinkedPlayer() != null) return null; + + // removeLogin logic + if (!shouldRemove(player, removeLogin)) return null; + + // passed the test + players.remove(onlineId); + // was the account linked? + return player; + } + + // we still want to be able to remove a linked-player by his linked java uuid + for (FloodgatePlayer player1 : players.values()) { + if (!shouldRemove(player1, removeLogin)) continue; + if (!player1.getCorrectUniqueId().equals(onlineId)) continue; + players.remove(player1.getJavaUniqueId()); + return player1; + } + + return null; + } + + protected boolean shouldRemove(FloodgatePlayer player, boolean removeLogin) { + FloodgatePlayerImpl impl = player.as(FloodgatePlayerImpl.class); + return impl.isLogin() && removeLogin || !impl.isLogin() && !removeLogin; + } + + /** + * Equivalant of {@link #removePlayer(UUID, boolean)} but with removeLogin = false. + */ + public FloodgatePlayer removePlayer(UUID onlineId) { + return removePlayer(onlineId, false); + } + + /** + * Equivalent of {@link #removePlayer(UUID, boolean)} except that it removes a + * FloodgatePlayer instance directly. + */ + public boolean removePlayer(FloodgatePlayer player) { + boolean removed = players.remove(player.getJavaUniqueId(), player); + return removed && player.getLinkedPlayer() != null; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/command/CommandMessage.java b/common/src/main/java/org/geysermc/floodgate/command/CommandMessage.java deleted file mode 100644 index 84dc7b4a..00000000 --- a/common/src/main/java/org/geysermc/floodgate/command/CommandMessage.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.geysermc.floodgate.command; - -public interface CommandMessage { - char COLOR_CHAR = '\u00A7'; - String getMessage(); -} diff --git a/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java b/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java new file mode 100644 index 00000000..2d86caec --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/command/CommonCommandMessage.java @@ -0,0 +1,50 @@ +/* + * 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.command; + +import lombok.Getter; +import org.geysermc.floodgate.platform.command.CommandMessage; + +/** + * Messages (or part of messages) that are used in two or more commands and thus are 'commonly used' + */ +public enum CommonCommandMessage implements CommandMessage { + NOT_A_PLAYER("Please head over to your Minecraft Account and link from there."), + CHECK_CONSOLE("Please check the console for more info!"), + IS_LINKED_ERROR("&cError while checking if the given player is linked. " + CHECK_CONSOLE); + + @Getter private final String message; + + CommonCommandMessage(String message) { + this.message = message.replace('&', COLOR_CHAR); + } + + @Override + public String toString() { + return getMessage(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java b/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java new file mode 100644 index 00000000..6a7d57dd --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java @@ -0,0 +1,171 @@ +/* + * 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.command; + +import com.google.inject.Inject; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.link.LinkRequest; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.link.LinkRequestImpl; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.util.CommandUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +@NoArgsConstructor +public final class LinkAccountCommand implements Command { + private final Map activeLinkRequests = new HashMap<>(); + + @Inject private FloodgateApi api; + @Inject private CommandUtil commandUtil; + + @Override + public void execute(Object player, UUID uuid, String username, String[] args) { + PlayerLink link = api.getPlayerLink(); + if (!link.isEnabledAndAllowed()) { + sendMessage(player, Message.LINK_REQUEST_DISABLED); + return; + } + + link.isLinkedPlayer(uuid).whenComplete((linked, throwable) -> { + if (throwable != null) { + sendMessage(player, CommonCommandMessage.IS_LINKED_ERROR); + return; + } + + if (linked) { + sendMessage(player, Message.ALREADY_LINKED); + return; + } + + // when the player is a Java player + if (!api.isBedrockPlayer(uuid)) { + if (args.length != 1) { + sendMessage(player, Message.JAVA_USAGE); + return; + } + + String code = String.format("%04d", new Random().nextInt(10000)); + String bedrockUsername = args[0]; + + LinkRequest linkRequest = + new LinkRequestImpl(username, uuid, code, bedrockUsername); + + activeLinkRequests.put(username, linkRequest); + sendMessage(player, Message.LINK_REQUEST_CREATED, bedrockUsername, username, code); + return; + } + + // when the player is a Bedrock player + + if (args.length != 2) { + sendMessage(player, Message.BEDROCK_USAGE); + return; + } + + String javaUsername = args[0]; + String code = args[1]; + LinkRequest request = activeLinkRequests.getOrDefault(javaUsername, null); + if (request == null || !request.isRequestedPlayer(api.getPlayer(uuid))) { + sendMessage(player, Message.NO_LINK_REQUESTED); + return; + } + + if (!request.getLinkCode().equals(code)) { + sendMessage(player, Message.INVALID_CODE); + return; + } + + // Delete the request, whether it has expired or is successful + activeLinkRequests.remove(javaUsername); + if (request.isExpired(link.getVerifyLinkTimeout())) { + sendMessage(player, Message.LINK_REQUEST_EXPIRED); + return; + } + + link.linkPlayer(uuid, request.getJavaUniqueId(), request.getJavaUsername()) + .whenComplete((aVoid, error) -> { + if (error != null) { + sendMessage(player, Message.LINK_REQUEST_ERROR); + return; + } + commandUtil.kickPlayer( + player, Message.LINK_REQUEST_COMPLETED, request.getJavaUsername() + ); + }); + }); + } + + @Override + public String getName() { + return "linkaccount"; + } + + @Override + public String getPermission() { + return "floodgate.linkaccount"; + } + + @Override + public boolean isRequirePlayer() { + return true; + } + + private void sendMessage(Object player, CommandMessage message, Object... args) { + commandUtil.sendMessage(player, message, args); + } + + public enum Message implements CommandMessage { + ALREADY_LINKED("&cYour account is already linked!\n" + + "&cIf you want to link to a different account, run &6/unlinkaccount&c and try it again." + ), + JAVA_USAGE("&cUsage: /linkaccount "), + LINK_REQUEST_CREATED("&aLog in as {} on Bedrock and run &6/linkaccount {} {}\n" + + "&cWarning: Any progress on your Bedrock account will not be carried over! Save any items in your inventory first.\n" + + "&cIf you change your mind you can run &6/unlinkaccount&c to get your progess back." + ), + BEDROCK_USAGE("&cStart the process from Java! Usage: /linkaccount "), + LINK_REQUEST_EXPIRED("&cThe code you entered is expired! Run &6/linkaccount&c again on your Java account"), + LINK_REQUEST_COMPLETED("You are successfully linked to {}!\nIf you want to undo this run /unlinkaccount"), + LINK_REQUEST_ERROR("&cAn error occurred while linking. " + CommonCommandMessage.CHECK_CONSOLE), + INVALID_CODE("&cInvalid code! Please check your code or run the &6/linkaccount&c command again on your Java account."), + NO_LINK_REQUESTED("&cThis player has not requested an account link! Please log in on Java and request one with &6/linkaccount"), + LINK_REQUEST_DISABLED("&cLinking is not enabled on this server."); + + @Getter private final String message; + + Message(String message) { + this.message = message.replace('&', COLOR_CHAR); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java b/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java new file mode 100644 index 00000000..9699edef --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java @@ -0,0 +1,104 @@ +/* + * 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.command; + +import com.google.inject.Inject; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.util.CommandUtil; + +import java.util.UUID; + +@NoArgsConstructor +public final class UnlinkAccountCommand implements Command { + @Inject private FloodgateApi api; + @Inject private CommandUtil commandUtil; + + @Override + public void execute(Object player, UUID uuid, String username, String... args) { + PlayerLink link = api.getPlayerLink(); + if (!link.isEnabledAndAllowed()) { + sendMessage(player, Message.LINKING_NOT_ENABLED); + return; + } + + link.isLinkedPlayer(uuid).whenComplete((linked, throwable) -> { + if (throwable != null) { + sendMessage(player, CommonCommandMessage.IS_LINKED_ERROR); + return; + } + + if (!linked) { + sendMessage(player, Message.NOT_LINKED); + return; + } + + link.unlinkPlayer(uuid).whenComplete((aVoid, throwable1) -> + sendMessage(player, throwable1 == null ? + Message.UNLINK_SUCCESS : + Message.UNLINK_ERROR + ) + ); + }); + } + + @Override + public String getName() { + return "unlinkaccount"; + } + + @Override + public String getPermission() { + return "floodgate.unlinkaccount"; + } + + @Override + public boolean isRequirePlayer() { + return true; + } + + private void sendMessage(Object player, CommandMessage message, Object... args) { + commandUtil.sendMessage(player, message, args); + } + + public enum Message implements CommandMessage { + NOT_LINKED("&cYour account isn't linked"), + UNLINK_SUCCESS("&cUnlink successful! Rejoin to return to your Bedrock account"), + UNLINK_ERROR("&cAn error occurred while unlinking player! " + CommonCommandMessage.CHECK_CONSOLE), + LINKING_NOT_ENABLED("&cLinking is not enabled on this server"); + + @Getter private final String message; + + Message(String message) { + this.message = message.replace('&', COLOR_CHAR); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java b/common/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java new file mode 100644 index 00000000..006dbf0e --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/config/FloodgateConfig.java @@ -0,0 +1,95 @@ +/* + * 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.config; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.security.PrivateKey; + +/** + * The global Floodgate configuration file used in every platform. + * Some platforms have their own addition to the global configuration like + * {@link ProxyFloodgateConfig} for the proxies. + */ +@Getter +public class FloodgateConfig { + @JsonProperty(value = "key-file-name") + private String keyFileName; + @JsonProperty(value = "username-prefix") + private String usernamePrefix; + @JsonProperty(value = "replace-spaces") + private boolean replaceSpaces; + + @JsonProperty(value = "disconnect") + private DisconnectMessages messages; + + @JsonProperty(value = "player-link") + private PlayerLinkConfig playerLink; + + @JsonProperty + private boolean debug; + + @JsonProperty("config-version") + private boolean configVersion; + + @JsonIgnore + private PrivateKey privateKey = null; + + @Getter + public static class DisconnectMessages { + @JsonProperty("invalid-key") + private String invalidKey; + @JsonProperty("invalid-arguments-length") + private String invalidArgumentsLength; + } + + @Getter + public static class PlayerLinkConfig { + @JsonProperty("enable") + private boolean enabled; + @JsonProperty("allow-linking") + private boolean allowLinking; + @JsonProperty("link-code-timeout") + private long linkCodeTimeout; + @JsonProperty("type") + private String type; + @JsonProperty("auto-download") + private boolean autoDownload; + } + + public void setPrivateKey(PrivateKey privateKey) { + if (this.privateKey == null) { + this.privateKey = privateKey; + } + } + + public boolean isProxy() { + return this instanceof ProxyFloodgateConfig; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/config/ProxyFloodgateConfig.java b/common/src/main/java/org/geysermc/floodgate/config/ProxyFloodgateConfig.java new file mode 100644 index 00000000..c83e41a7 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/config/ProxyFloodgateConfig.java @@ -0,0 +1,38 @@ +/* + * 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.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +/** + * The Floodgate configuration used by proxy platforms, currently Velocity and Bungeecord. + */ +public final class ProxyFloodgateConfig extends FloodgateConfig { + @JsonProperty(value = "send-floodgate-data") + @Getter private boolean sendFloodgateData; +} diff --git a/common/src/main/java/org/geysermc/floodgate/config/loader/ConfigLoader.java b/common/src/main/java/org/geysermc/floodgate/config/loader/ConfigLoader.java new file mode 100644 index 00000000..38a7b1df --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/config/loader/ConfigLoader.java @@ -0,0 +1,139 @@ +/* + * 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.config.loader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.config.updater.ConfigUpdater; +import org.geysermc.floodgate.util.EncryptionUtil; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; + +@RequiredArgsConstructor +public class ConfigLoader { + private final Path dataFolder; + private final Class configClass; + private final ConfigUpdater updater; + + private final FloodgateLogger logger; + + @SuppressWarnings("unchecked") + public T load() { + Path configPath = dataFolder.resolve("config.yml"); + + String defaultConfigName = "config.yml"; + boolean proxy = ProxyFloodgateConfig.class.isAssignableFrom(configClass); + if (proxy) { + defaultConfigName = "proxy-" + defaultConfigName; + } + + Path defaultConfigPath; + try { + defaultConfigPath = Paths.get("./" + defaultConfigName); + } catch (RuntimeException exception) { + logger.error("Failed to get the default config location", exception); + throw new RuntimeException("Failed to get the default config location"); + } + + boolean newConfig = !Files.exists(configPath); + try { + if (newConfig) { + Files.copy(defaultConfigPath, configPath); + + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); + KeyPair pair = generator.generateKeyPair(); + + String test = "abcdefghijklmnopqrstuvwxyz0123456789"; + + String encrypted = EncryptionUtil.encrypt(pair.getPublic(), test); + String decrypted = new String(EncryptionUtil.decrypt(pair.getPrivate(), encrypted)); + + if (!test.equals(decrypted)) { + logger.error("Whoops, we tested the generated Floodgate keys but " + + "the decrypted test message doesn't match the original.\n" + + "Original message: " + test + "." + + "Decrypted message: " + decrypted + ".\n" + + "The encrypted message itself: " + encrypted + ); + throw new RuntimeException( + "Tested the generated public and private key but, " + + "the decrypted message doesn't match the original!" + ); + } + + Files.write(dataFolder.resolve("public-key.pem"), pair.getPublic().getEncoded()); + Files.write(dataFolder.resolve("key.pem"), pair.getPrivate().getEncoded()); + } + } catch (Exception exception) { + logger.error("Error while creating config", exception); + } + + T configInstance; + try { + // check and update if the config is outdated + if (!newConfig) { + updater.update(defaultConfigPath); + } + + configInstance = (T) new ObjectMapper(new YAMLFactory()) + .readValue(Files.readAllBytes(configPath), configClass); + } catch (ClassCastException exception) { + logger.error("Provided class {} cannot be cast to the required return type", + configClass.getName()); + + throw new RuntimeException("Failed to load cast the config! " + + "Try to contact the platform developer"); + } catch (Exception exception) { + logger.error("Error while loading config", exception); + throw new RuntimeException("Failed to load the config! Try to delete the config file"); + } + + try { + PrivateKey key = EncryptionUtil.getKeyFromFile( + dataFolder.resolve(configInstance.getKeyFileName()), PrivateKey.class); + configInstance.setPrivateKey(key); + } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException exception) { + logger.error("Error while reading private key", exception); + throw new RuntimeException("Failed to read the private key!"); + } + + return configInstance; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/config/updater/ConfigFileUpdater.java b/common/src/main/java/org/geysermc/floodgate/config/updater/ConfigFileUpdater.java new file mode 100644 index 00000000..26735c69 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/config/updater/ConfigFileUpdater.java @@ -0,0 +1,129 @@ +/* + * 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.config.updater; + +import com.google.inject.Inject; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ConfigFileUpdater { + @Inject + private FloodgateLogger logger; + + /** + * Simple config file updater. + * Please note that all the keys should be unique and that this system wasn't made for complex + * configurations. + * + * @param configLocation the location of the Floodgate config + * @param currentVersion the key value map of the current config + * @param renames name changes introduced in this version. new (key) to old (value) + * @param defaultConfigLocation the location of the default Floodgate config + * @throws IOException if an I/O error occurs + */ + public void update(Path configLocation, Map currentVersion, + Map renames, Path defaultConfigLocation) throws IOException { + List notFound = new ArrayList<>(); + List newConfig = Files.readAllLines(defaultConfigLocation); + + String line; + for (int i = 0; i < newConfig.size(); i++) { + line = newConfig.get(i); + // we don't have to check comments + if (line.startsWith("#")) continue; + + int splitIndex = line.indexOf(':'); + // if the line has a 'key: value' structure + if (splitIndex != -1) { + String nameUntrimmed = line.substring(0, splitIndex); + String name = nameUntrimmed.trim(); + Object value; + + logger.info(name); + if (renames.containsKey(name)) { + value = currentVersion.get(renames.get(name)); + } else { + value = currentVersion.get(name); + } + + if (value == null) { + notFound.add(name); + continue; + } + + if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("\"") || !v.endsWith("\"")) { + value = "\"" + value + "\""; + } + } + + logger.debug(nameUntrimmed + " has been changed to " + value); + newConfig.set(i, nameUntrimmed + ": " + value); + } + } + + Files.deleteIfExists(configLocation.getParent().resolve("config-old.yml")); + Files.copy(configLocation, configLocation.getParent().resolve("config-old.yml")); + Files.write(configLocation, newConfig); + + logger.info("Successfully updated the config file! " + + "Your old config has been moved to config-old.yml"); + + if (notFound.size() > 0) { + StringBuilder messageBuilder = new StringBuilder( + "Please note that the following keys we not found in the old config and " + + "are now using the default Floodgate config value. " + + "Missing/new keys: " + ); + + boolean first = true; + for (String value : notFound) { + if (!first) { + messageBuilder.append(", "); + } + + messageBuilder.append(value); + + String renamed = renames.get(value); + if (renamed != null) { + messageBuilder.append(" to ").append(renamed); + } + + first = false; + } + + logger.info(messageBuilder.toString()); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/config/updater/ConfigUpdater.java b/common/src/main/java/org/geysermc/floodgate/config/updater/ConfigUpdater.java new file mode 100644 index 00000000..4ffaef7b --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/config/updater/ConfigUpdater.java @@ -0,0 +1,90 @@ +/* + * 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.config.updater; + +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.yaml.snakeyaml.Yaml; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; + +@RequiredArgsConstructor +public class ConfigUpdater { + private final Path dataFolder; + private final ConfigFileUpdater fileUpdater; + private final FloodgateLogger logger; + + public void update(Path defaultConfigLocation) { + Path configLocation = dataFolder.resolve("config.yml"); + + BufferedReader configReader; + try { + configReader = Files.newBufferedReader(configLocation); + } catch (IOException exception) { + logger.error("Error while opening the config file", exception); + throw new RuntimeException("Failed to update config"); + } + + Map config = new Yaml().load(configReader); + // currently unused, but can be used when a config name has been changed + Map renames = new HashMap<>(0); + + Object versionElement = config.get("config-version"); + // not a pre-rewrite config + if (versionElement != null) { + checkArgument( + versionElement instanceof Integer, + "Config version should be an integer. Did someone mess with the config?" + ); + + int version = (int) versionElement; + checkArgument( + version == 1, + "Config is newer then possible on this version! Expected 1, got " + version + ); + + // config is already up-to-date + if (version == 1) { + return; + } + } + + try { + fileUpdater.update(configLocation, config, renames, defaultConfigLocation); + } catch (IOException exception) { + logger.error("Error while updating the config file", exception); + throw new RuntimeException("Failed to update config"); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java b/common/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java new file mode 100644 index 00000000..b8325884 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/inject/CommonPlatformInjector.java @@ -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.inject; + +import io.netty.channel.Channel; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.inject.PlatformInjector; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public abstract class CommonPlatformInjector implements PlatformInjector { + private final Set injectedClients = new HashSet<>(); + private final Map, InjectorAddon> addons = new HashMap<>(); + + protected boolean addInjectedClient(Channel channel) { + return injectedClients.add(channel); + } + + protected boolean removeInjectedClient(Channel channel) { + return injectedClients.remove(channel); + } + + @Override + public boolean addAddon(InjectorAddon addon) { + return addons.putIfAbsent(addon.getClass(), addon) == null; + } + + @Override + @SuppressWarnings("unchecked") + public T removeAddon(Class addon) { + return (T) addons.remove(addon); + } + + /** + * Method to loop through all the addons and call + * {@link InjectorAddon#onInject(Channel, boolean)} if + * {@link InjectorAddon#shouldInject()}. + * + * @param channel the channel to inject + * @param proxyToServer true if the proxy is connecting to a server or false when the player + * is connecting to the proxy or false when the platform isn't a proxy + */ + public void injectAddonsCall(Channel channel, boolean proxyToServer) { + for (InjectorAddon addon : addons.values()) { + if (addon.shouldInject()) { + addon.onInject(channel, proxyToServer); + } + } + } + + /** + * Method to loop through all the addons and call + * {@link InjectorAddon#onLoginDone(Channel)} if + * {@link InjectorAddon#shouldInject()}. + * + * @param channel the channel that was injected + */ + public void loginSuccessCall(Channel channel) { + for (InjectorAddon addon : addons.values()) { + if (addon.shouldInject()) { + addon.onLoginDone(channel); + } + } + } + + /** + * Method to loop through all the addons and call + * {@link InjectorAddon#onRemoveInject(Channel)} if + * {@link InjectorAddon#shouldInject()}. + * + * @param channel the channel that was injected + */ + public void removeAddonsCall(Channel channel) { + for (InjectorAddon addon : addons.values()) { + if (addon.shouldInject()) { + addon.onRemoveInject(channel); + } + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java b/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java new file mode 100644 index 00000000..78c44433 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/link/CommonPlayerLink.java @@ -0,0 +1,62 @@ +/* + * 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.link; + +import com.google.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.FloodgateConfig; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public abstract class CommonPlayerLink implements PlayerLink { + @Getter private boolean enabled; + @Getter private boolean allowLinking; + @Getter private long verifyLinkTimeout; + + @Getter(AccessLevel.PROTECTED) + private final ExecutorService executorService = Executors.newFixedThreadPool(11); + + @Inject @Getter(AccessLevel.PROTECTED) + private FloodgateLogger logger; + + @Inject + private void init(FloodgateConfig config) { + FloodgateConfig.PlayerLinkConfig linkConfig = config.getPlayerLink(); + enabled = linkConfig.isEnabled(); + allowLinking = linkConfig.isAllowLinking(); + verifyLinkTimeout = linkConfig.getLinkCodeTimeout(); + } + + @Override + public void stop() { + executorService.shutdown(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java b/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java new file mode 100644 index 00000000..a41cdd2f --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/link/DisabledPlayerLink.java @@ -0,0 +1,84 @@ +/* + * 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.link; + +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.util.LinkedPlayer; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * Simple class used when PlayerLinking is disabled. + * This class has been made because Floodgate doesn't have a default PlayerLink implementation + * anymore and {@link FloodgateApi#getPlayerLink()} returning null} is also not an option. + */ +final class DisabledPlayerLink implements PlayerLink { + @Override + public void load() { + } + + @Override + public CompletableFuture getLinkedPlayer(UUID bedrockId) { + return null; + } + + @Override + public CompletableFuture isLinkedPlayer(UUID bedrockId) { + return null; + } + + @Override + public CompletableFuture linkPlayer(UUID bedrockId, UUID javaId, String username) { + return null; + } + + @Override + public CompletableFuture unlinkPlayer(UUID javaId) { + return null; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public long getVerifyLinkTimeout() { + return -1; + } + + @Override + public boolean isAllowLinking() { + return false; + } + + @Override + public void stop() { + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/link/LinkRequestImpl.java b/common/src/main/java/org/geysermc/floodgate/link/LinkRequestImpl.java new file mode 100644 index 00000000..4a5c7405 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/link/LinkRequestImpl.java @@ -0,0 +1,56 @@ +/* + * 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.link; + +import lombok.Getter; +import org.geysermc.floodgate.api.link.LinkRequest; + +import java.time.Instant; +import java.util.UUID; + +@Getter +public final class LinkRequestImpl implements LinkRequest { + private final String javaUsername; + private final UUID javaUniqueId; + private final String linkCode; + private final String bedrockUsername; + private final long requestTime; + + public LinkRequestImpl(String javaUsername, UUID javaUniqueId, + String linkCode, String bedrockUsername) { + this.javaUniqueId = javaUniqueId; + this.javaUsername = javaUsername; + this.linkCode = linkCode; + this.bedrockUsername = bedrockUsername; + requestTime = Instant.now().getEpochSecond(); + } + + public boolean isExpired(long linkTimeout) { + long timePassed = Instant.now().getEpochSecond() - requestTime; + return timePassed > linkTimeout; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java b/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java new file mode 100644 index 00000000..3f62db6f --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/link/PlayerLinkLoader.java @@ -0,0 +1,132 @@ +/* + * 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.link; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.name.Named; +import org.geysermc.floodgate.api.link.PlayerLink; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.FloodgateConfig; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +public class PlayerLinkLoader { + @Named("dataDirectory") + @Inject private Path dataDirectory; + @Inject private Injector injector; + @Inject private FloodgateConfig config; + + @Inject private FloodgateLogger logger; + + public PlayerLink load() { + FloodgateConfig.PlayerLinkConfig linkConfig = config.getPlayerLink(); + if (!linkConfig.isEnabled()) { + return new DisabledPlayerLink(); + } + + List files; + try { + files = Files.list(dataDirectory) + .filter(path -> Files.isRegularFile(path) && path.toString().endsWith("jar")) + .collect(Collectors.toList()); + } catch (IOException exception) { + logger.error("Failed to list possible database implementations", exception); + return null; + } + + if (files.size() == 0) { + logger.error("Failed to find a database implementation"); + return null; + } + + Path implementationPath = files.get(0); + + // We only want to load one database implementation + String type = linkConfig.getType().toLowerCase(); + if (files.size() > 1) { + implementationPath = null; + for (Path path : files) { + if (path.getFileName().toString().toLowerCase().contains(type)) { + implementationPath = path; + } + } + if (implementationPath == null) { + logger.error("Failed to find an implementation for type: {}", + linkConfig.getType()); + return null; + } + } + + Class mainClass; + try { + URL pluginUrl = implementationPath.toUri().toURL(); + URLClassLoader classLoader = new URLClassLoader( + new URL[]{pluginUrl}, + PlayerLinkLoader.class.getClassLoader() + ); + + InputStream linkImplConfigStream = classLoader.getResourceAsStream("config.json"); + requireNonNull(linkImplConfigStream, "Database implementation should contain a config"); + + JsonObject linkImplConfig = new Gson().fromJson( + new InputStreamReader(linkImplConfigStream), JsonObject.class + ); + + String mainClassName = linkImplConfig.get("mainClass").getAsString(); + mainClass = (Class) classLoader.loadClass(mainClassName); + } catch (ClassCastException exception) { + logger.error("The database implementation ({}) doesn't extend the PlayerLink class!", + implementationPath.getFileName().toString()); + return null; + } catch (Exception exception) { + logger.error("Error while loading database jar", exception); + return null; + } + + try { + PlayerLink instance = injector.getInstance(mainClass); + instance.load(); + return instance; + } catch (Exception exception) { + logger.error("Error while initializing database jar", exception); + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/link/SQLitePlayerLink.java b/common/src/main/java/org/geysermc/floodgate/link/SQLitePlayerLink.java deleted file mode 100644 index c394f416..00000000 --- a/common/src/main/java/org/geysermc/floodgate/link/SQLitePlayerLink.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.geysermc.floodgate.link; - -import lombok.Getter; -import org.geysermc.floodgate.LinkedPlayer; -import org.geysermc.floodgate.PlayerLink; - -import java.nio.file.Path; -import java.sql.*; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.logging.Level; - -public class SQLitePlayerLink extends PlayerLink { - @Getter private Connection connection; - - @Override - public void load(Path dataFolder) { - Path databasePath = dataFolder.resolve("linked-players.db"); - getLogger().info("Loading Floodgate linked player database..."); - try { - Class.forName("org.sqlite.JDBC"); - - // create a database connection - connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath.toString()); - Statement statement = connection.createStatement(); - statement.setQueryTimeout(30); // set timeout to 30 sec. - statement.executeUpdate("create table if not exists LinkedPlayers (bedrockId string, javaUniqueId string, javaUsername string)"); - } catch (ClassNotFoundException e) { - getLogger().severe("The required class to load the SQLite database wasn't found"); - } catch (SQLException e) { - getLogger().log(Level.SEVERE, "Error while loading database", e); - } - } - - @Override - public CompletableFuture getLinkedPlayer(UUID bedrockId) { - // TODO: make it work with Java player UUIDs - return CompletableFuture.supplyAsync(() -> { - try { - PreparedStatement query = connection.prepareStatement("select * from LinkedPlayers where bedrockId = ?"); - query.setString(1, bedrockId.toString()); - ResultSet result = query.executeQuery(); - if (!result.next()) return null; - - String javaUsername = result.getString("javaUsername"); - UUID javaUniqueId = UUID.fromString(result.getString("javaUniqueId")); - return createLinkedPlayer(javaUsername, javaUniqueId, bedrockId); - } catch (SQLException | NullPointerException e) { - getLogger().log(Level.SEVERE, "Error while getting LinkedPlayer", e); - throw new CompletionException("Error while getting LinkedPlayer", e); - } - }, getExecutorService()); - } - - @Override - public CompletableFuture isLinkedPlayer(UUID bedrockId) { - return CompletableFuture.supplyAsync(() -> { - try { - PreparedStatement query = connection.prepareStatement("select javaUniqueId from LinkedPlayers where bedrockId = ? or javaUniqueId = ?"); - query.setString(1, bedrockId.toString()); - query.setString(2, bedrockId.toString()); - ResultSet result = query.executeQuery(); - return result.next(); - } catch (SQLException | NullPointerException e) { - getLogger().log(Level.SEVERE, "Error while checking if player is a LinkedPlayer", e); - throw new CompletionException("Error while checking if player is a LinkedPlayer", e); - } - }, getExecutorService()); - } - - @Override - public CompletableFuture linkPlayer(UUID bedrockId, UUID uuid, String username) { - return CompletableFuture.runAsync(() -> { - try { - PreparedStatement query = connection.prepareStatement("insert into LinkedPlayers values(?, ?, ?)"); - query.setString(1, bedrockId.toString()); - query.setString(2, uuid.toString()); - query.setString(3, username); - query.executeUpdate(); - } catch (SQLException | NullPointerException e) { - getLogger().log(Level.SEVERE, "Error while linking player", e); - throw new CompletionException("Error while linking player", e); - } - }, getExecutorService()); - } - - @Override - public CompletableFuture unlinkPlayer(UUID uuid) { - return CompletableFuture.runAsync(() -> { - try { - PreparedStatement query = connection.prepareStatement("delete from LinkedPlayers where javaUniqueId = ? or bedrockId = ?"); - query.setString(1, uuid.toString()); - query.setString(2, uuid.toString()); - query.executeUpdate(); - } catch (SQLException | NullPointerException e) { - getLogger().log(Level.SEVERE, "Error while unlinking player", e); - throw new CompletionException("Error while unlinking player", e); - } - }, getExecutorService()); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/logger/JavaDefaultFloodgateLogger.java b/common/src/main/java/org/geysermc/floodgate/logger/JavaDefaultFloodgateLogger.java new file mode 100644 index 00000000..f0ba377d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/logger/JavaDefaultFloodgateLogger.java @@ -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.logger; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.geysermc.floodgate.api.logger.FloodgateLogger; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.geysermc.floodgate.util.MessageFormatter.format; + +@AllArgsConstructor +@NoArgsConstructor +public final class JavaDefaultFloodgateLogger implements FloodgateLogger { + private Logger logger = Logger.getLogger(LOGGER_NAME); + + @Override + public void error(String message, Object... args) { + logger.severe(format(message, args)); + } + + @Override + public void error(String message, Throwable throwable, Object... args) { + logger.log(Level.SEVERE, format(message, args), throwable); + } + + @Override + public void warn(String message, Object... args) { + logger.warning(format(message, args)); + } + + @Override + public void info(String message, Object... args) { + logger.info(format(message, args)); + } + + @Override + public void debug(String message, Object... args) { + logger.fine(format(message, args)); + } + + @Override + public void trace(String message, Object... args) { + logger.finer(format(message, args)); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/module/CommandModule.java b/common/src/main/java/org/geysermc/floodgate/module/CommandModule.java new file mode 100644 index 00000000..7561a8d9 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/module/CommandModule.java @@ -0,0 +1,54 @@ +/* + * 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.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.command.LinkAccountCommand; +import org.geysermc.floodgate.command.UnlinkAccountCommand; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.register.CommandRegister; + +public final class CommandModule extends AbstractModule { + @Override + protected void configure() { + bind(CommandRegister.class).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public Command linkAccountCommand() { + return new LinkAccountCommand(); + } + + @Singleton + @ProvidesIntoSet + public Command unlinkAccountCommand() { + return new UnlinkAccountCommand(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java b/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java new file mode 100644 index 00000000..31149502 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/module/CommonModule.java @@ -0,0 +1,99 @@ +/* + * 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.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import io.netty.util.AttributeKey; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.inject.PlatformInjector; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.config.loader.ConfigLoader; +import org.geysermc.floodgate.config.updater.ConfigFileUpdater; +import org.geysermc.floodgate.config.updater.ConfigUpdater; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.link.PlayerLinkLoader; + +import java.nio.file.Path; + +@RequiredArgsConstructor +public final class CommonModule extends AbstractModule { + private final Path dataDirectory; + + @Override + protected void configure() { + bind(FloodgateApi.class).to(SimpleFloodgateApi.class); + bind(PlatformInjector.class).to(CommonPlatformInjector.class); + } + + @Provides + @Singleton + @Named("playerAttribute") + public AttributeKey playerAttribute() { + return AttributeKey.newInstance("floodgate-player"); + } + + @Provides + @Singleton + @Named("dataDirectory") + public Path dataDirectory() { + return dataDirectory; + } + + @Provides + @Singleton + public ConfigLoader configLoader(@Named("configClass") Class configClass, + ConfigUpdater configUpdater, FloodgateLogger logger) { + return new ConfigLoader(dataDirectory, configClass, configUpdater, logger); + } + + @Provides + @Singleton + public ConfigUpdater configUpdater(ConfigFileUpdater configFileUpdater, + FloodgateLogger logger) { + return new ConfigUpdater(dataDirectory, configFileUpdater, logger); + } + + @Provides + @Singleton + public PlayerLinkLoader playerLinkLoader() { + return new PlayerLinkLoader(); + } + + @Provides + @Singleton + public HandshakeHandler handshakeHandler(SimpleFloodgateApi api) { + return new HandshakeHandler(api); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/module/ConfigLoadedModule.java b/common/src/main/java/org/geysermc/floodgate/module/ConfigLoadedModule.java new file mode 100644 index 00000000..dc42e5ed --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/module/ConfigLoadedModule.java @@ -0,0 +1,66 @@ +/* + * 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.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; + +import java.security.PrivateKey; + +@RequiredArgsConstructor +public final class ConfigLoadedModule extends AbstractModule { + private final FloodgateConfig config; + private final FloodgateApi api; + + @Override + protected void configure() { + if (config instanceof ProxyFloodgateConfig) { + bind(ProxyFloodgateConfig.class).toInstance((ProxyFloodgateConfig) config); + // The ProxyFloodgateApi needs the floodgateKey + requestInjection(api); + } + } + + @Provides + @Singleton + public FloodgateConfig floodgateConfig() { + return config; + } + + @Provides + @Singleton + @Named("floodgateKey") + public PrivateKey floodgateKey() { + return config.getPrivateKey(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/module/PostInitializeModule.java b/common/src/main/java/org/geysermc/floodgate/module/PostInitializeModule.java new file mode 100644 index 00000000..ce2813df --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/module/PostInitializeModule.java @@ -0,0 +1,44 @@ +/* + * 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.AbstractModule; +import com.google.inject.Module; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public final class PostInitializeModule extends AbstractModule { + private final Module[] postCreateModules; + + @Override + protected void configure() { + //todo move this to FloodgatePlatform itself + for (Module module : postCreateModules) { + install(module); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/Command.java b/common/src/main/java/org/geysermc/floodgate/platform/command/Command.java new file mode 100644 index 00000000..7b85ff5a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/Command.java @@ -0,0 +1,87 @@ +/* + * 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.platform.command; + +import org.geysermc.floodgate.platform.command.util.CommandUtil; + +import java.util.UUID; + +/** + * The base class for every Floodgate command. + */ +public interface Command { + /** + * Should be implemented when {@link #isRequirePlayer()} is true + * or when the source is a player. + * + * @param player the player instance (used for example in combination with + * {@link CommandUtil#kickPlayer(Object, CommandMessage, Object...)} + * @param uuid the uuid of the player + * @param username the username of the player + * @param args the arguments of the command + */ + default void execute(Object player, UUID uuid, String username, String... args) { + } + + /** + * Should be implemented when {@link #isRequirePlayer()} is false. + * + * @param source the CommandSource (Velocity) or CommandExecutor (Bungee and Bukkit) that + * executed this command + * @param args the arguments of the command + */ + default void execute(Object source, String... args) { + if (isRequirePlayer()) { + throw new RuntimeException( + "Cannot execute this command since it requires a player" + ); + } + } + + /** + * The command name that should be registered and used by the CommandSource. + * + * @return the name of the command that should be registered + */ + String getName(); + + /** + * The permission that is required to execute the specific command. + * Should return null when there is no permission required. + * + * @return the permission required to execute the command + */ + String getPermission(); + + /** + * If the Command requires a Player to execute this command + * or if it doesn't matter if (for example) the console executes the command. + * + * @return true if this command can only be executed by a player + */ + boolean isRequirePlayer(); +} diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/CommandMessage.java b/common/src/main/java/org/geysermc/floodgate/platform/command/CommandMessage.java new file mode 100644 index 00000000..7c84da7c --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/CommandMessage.java @@ -0,0 +1,55 @@ +/* + * 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.platform.command; + +import org.geysermc.floodgate.platform.command.util.CommandResponseCache; +import org.geysermc.floodgate.util.MessageFormatter; + +/** + * CommandMessage is the interface of a message that can be send to a command source after + * executing a command. Those messages are generally implemented using enums, so that they are + * only created once and can be used over and over again. This in combination with + * {@link CommandResponseCache message caching} should make this system quite fast. + */ +public interface CommandMessage { + char COLOR_CHAR = '\u00A7'; + + /** + * Returns the message attached to the enum identifier + */ + String getMessage(); + + /** + * This method will format a message by putting the arguments on the missing spots. + * + * @param args the arguments to fill in at the missing spots + * @return the formatted message + */ + default String format(Object... args) { + return MessageFormatter.format(getMessage(), args); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/CommandRegistration.java b/common/src/main/java/org/geysermc/floodgate/platform/command/CommandRegistration.java new file mode 100644 index 00000000..8ac4f6bf --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/CommandRegistration.java @@ -0,0 +1,42 @@ +/* + * 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.platform.command; + +/** + * This class is responsible for registering commands to the command register of the platform + * that is currently in use. So that the commands only have to be written once + * (in the common module) and can be used across all platforms without the need of adding platform + * specific commands. + */ +public interface CommandRegistration { + /** + * This method will register the specified command. + * + * @param command the command to register + */ + void register(Command command); +} diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/util/CommandResponseCache.java b/common/src/main/java/org/geysermc/floodgate/platform/command/util/CommandResponseCache.java new file mode 100644 index 00000000..58ab5afc --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/util/CommandResponseCache.java @@ -0,0 +1,80 @@ +/* + * 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.platform.command.util; + +import org.geysermc.floodgate.platform.command.CommandMessage; + +import java.util.HashMap; +import java.util.Map; + +/** + * A class used in all platforms to cache simple messages (messages without arguments) to improve + * execution speed. + * + * @param the message type (of the platform) that is send to the player. + * For example BaseComponent[] on Bungeecord. Please note that not every platform has + * something like that (for example Bukkit) and then a String should be used instead. + */ +public abstract class CommandResponseCache { + private final Map cachedResponses = new HashMap<>(0); + + /** + * Transforms a string (raw input) into a format that can be send to the player. + * + * @param message the message to transform + * @return the transformed message + */ + protected abstract T transformMessage(String message); + + /** + * Get the cached message or (if it isn't cached) {@link #transformMessage(String) transform + * the message} and add it to the cached messages. + * Please note that the transformed message will only be added to the cached messages when the + * transformed message has zero arguments, and thus can be cached. + * + * @param message the command message + * @param args the arguments + * @return the transformed (and maybe cached) message. + */ + public T getOrAddCachedMessage(CommandMessage message, Object... args) { + if (args != null && args.length > 0) { + return transformMessage(format(message, args)); + } + if (!cachedResponses.containsKey(message)) { + T components = transformMessage(message.getMessage()); + cachedResponses.put(message, components); + return components; + } + return cachedResponses.get(message); + } + + protected String format(CommandMessage message, Object... args) { + return args != null && args.length > 0 ? + message.format(args) : + message.getMessage(); + } +} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/platform/command/util/CommandUtil.java b/common/src/main/java/org/geysermc/floodgate/platform/command/util/CommandUtil.java new file mode 100644 index 00000000..4e785b10 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/platform/command/util/CommandUtil.java @@ -0,0 +1,53 @@ +/* + * 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.platform.command.util; + +import org.geysermc.floodgate.platform.command.CommandMessage; + +/** + * An interface used across all Floodgate platforms to simple stuff in commands like kicking + * players and sending player messages independent of the Floodgate platform implementation. + */ +public interface CommandUtil { + /** + * Send a message to the specified player, no matter what platform Floodgate is running on. + * + * @param player the player to send the message to + * @param message the command message + * @param args the arguments + */ + void sendMessage(Object player, CommandMessage message, Object... args); + + /** + * Same as {@link CommandUtil#sendMessage(Object, CommandMessage, Object...)} except it kicks the player. + * + * @param player the player to send the message to + * @param message the command message + * @param args the arguments + */ + void kickPlayer(Object player, CommandMessage message, Object... args); +} diff --git a/common/src/main/java/org/geysermc/floodgate/platform/listener/ListenerRegistration.java b/common/src/main/java/org/geysermc/floodgate/platform/listener/ListenerRegistration.java new file mode 100644 index 00000000..b52ae10d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/platform/listener/ListenerRegistration.java @@ -0,0 +1,43 @@ +/* + * 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.platform.listener; + +/** + * This class is responsible for registering listeners to the listener manager of the platform + * that is currently in use. Unfortunately due to the major differences between the platforms + * (when it comes to listeners) every Floodgate platform has to implement their own listeners. + * + * @param the platform-specific listener class + */ +public interface ListenerRegistration { + /** + * This method will register the specified listener. + * + * @param listener the listener to register + */ + void register(T listener); +} diff --git a/common/src/main/java/org/geysermc/floodgate/register/AddonRegister.java b/common/src/main/java/org/geysermc/floodgate/register/AddonRegister.java new file mode 100644 index 00000000..9f2e9a4a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/register/AddonRegister.java @@ -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.register; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.inject.PlatformInjector; + +import java.util.Set; + +public final class AddonRegister { + @Inject private Injector guice; + @Inject private PlatformInjector injector; + + @Inject + public void registerAddons(Set addons) { + for (InjectorAddon addon : addons) { + guice.injectMembers(addon); + injector.addAddon(addon); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/register/CommandRegister.java b/common/src/main/java/org/geysermc/floodgate/register/CommandRegister.java new file mode 100644 index 00000000..997b3a96 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/register/CommandRegister.java @@ -0,0 +1,49 @@ +/* + * 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.register; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.platform.command.CommandRegistration; + +import java.util.Set; + +@RequiredArgsConstructor(onConstructor = @__(@Inject)) +public final class CommandRegister { + private final CommandRegistration registration; + private final Injector guice; + + @Inject + public void registerCommands(Set foundCommands) { + for (Command command : foundCommands) { + guice.injectMembers(command); + registration.register(command); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/register/ListenerRegister.java b/common/src/main/java/org/geysermc/floodgate/register/ListenerRegister.java new file mode 100644 index 00000000..33bea659 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/register/ListenerRegister.java @@ -0,0 +1,45 @@ +/* + * 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.register; + +import com.google.inject.Inject; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; + +import java.util.Set; + +@RequiredArgsConstructor(onConstructor = @__(@Inject)) +public final class ListenerRegister { + private final ListenerRegistration registration; + + @Inject + public void registerListeners(Set foundListeners) { + for (T listener : foundListeners) { + registration.register(listener); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/AbstractCommandResponseCache.java b/common/src/main/java/org/geysermc/floodgate/util/AbstractCommandResponseCache.java deleted file mode 100644 index 015599f4..00000000 --- a/common/src/main/java/org/geysermc/floodgate/util/AbstractCommandResponseCache.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.geysermc.floodgate.util; - -import org.geysermc.floodgate.command.CommandMessage; - -import java.util.HashMap; -import java.util.Map; - -/** - * @param Message Type stored. For example BaseComponent[] for Bungeecord - */ -public abstract class AbstractCommandResponseCache { - private final Map cachedResponses = new HashMap<>(0); - - /** - * Transforms a string (raw input) into a format that can be sent to the player - * @param message he message to transform - * @return the transformed message - */ - protected abstract T transformMessage(String message); - - /** - * If the message has no arguments:
- * If cached: return cached message.
- * If not cached: transform it, add the message to cache and return the message.
- * It will only transform the message if the message has one or more arguments - * @param message the command message - * @param args the arguments - * @return the message; caches if not already - */ - public T getOrAddCachedMessage(CommandMessage message, Object... args) { - if (args != null && args.length > 0) { - return transformMessage(format(message, args)); - } - if (!cachedResponses.containsKey(message)) { - T components = transformMessage(message.getMessage()); - cachedResponses.put(message, components); - return components; - } - return cachedResponses.get(message); - } - - protected String format(CommandMessage message, Object... args) { - String msg = message.getMessage(); - return args != null && args.length > 0 ? String.format(msg, args) : msg; - } -} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/util/CommonMessage.java b/common/src/main/java/org/geysermc/floodgate/util/CommonMessage.java deleted file mode 100644 index 5f93d0da..00000000 --- a/common/src/main/java/org/geysermc/floodgate/util/CommonMessage.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.geysermc.floodgate.util; - -import lombok.Getter; -import org.geysermc.floodgate.command.CommandMessage; - -public enum CommonMessage implements CommandMessage { - NOT_A_PLAYER("Please head over to your Minecraft Account and link from there."), - CHECK_CONSOLE("Please check the console for more info!"), - IS_LINKED_ERROR("&cError while checking if the given player is linked. " + CHECK_CONSOLE); - - @Getter private final String message; - - CommonMessage(String message) { - this.message = message.replace('&', COLOR_CHAR); - } - - @Override - public String toString() { - return getMessage(); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/ICommandUtil.java b/common/src/main/java/org/geysermc/floodgate/util/ICommandUtil.java deleted file mode 100644 index a970f8b4..00000000 --- a/common/src/main/java/org/geysermc/floodgate/util/ICommandUtil.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.geysermc.floodgate.util; - -import org.geysermc.floodgate.command.CommandMessage; - -public interface ICommandUtil

{ - String LINK_ACCOUNT_COMMAND = "linkaccount"; - String UNLINK_ACCOUNT_COMMAND = "unlinkaccount"; - - /** - * Send the specified player a message - * - * @param player the player to send the message to - * @param message the command message - * @param args the arguments - */ - void sendMessage(P player, CommandMessage message, Object... args); - - /** - * Same as {@link ICommandUtil#sendMessage(Object, CommandMessage, Object...)} except it kicks the player. - * - * @param player the player to send the message to - * @param message the command message - * @param args the arguments - */ - void kickPlayer(P player, CommandMessage message, Object... args); -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/MessageFormatter.java b/common/src/main/java/org/geysermc/floodgate/util/MessageFormatter.java new file mode 100644 index 00000000..d84e12c6 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/MessageFormatter.java @@ -0,0 +1,79 @@ +/* + * 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; + +public final class MessageFormatter { + private static final String DELIM_STR = "{}"; + private static final int DELIM_LENGTH = DELIM_STR.length(); + + public static String format(String message, Object... arguments) { + // simple variant of slf4j's parameters. + if (arguments == null || arguments.length == 0) return message; + + String[] args = new String[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + args[i] = arguments[i].toString(); + } + + int previousIndex = -1; + int currentIndex; + StringBuilder stringBuilder = new StringBuilder( + message.length() + getArgsContentLength(args) + ); + + for (String argument : args) { + currentIndex = message.indexOf(DELIM_STR, previousIndex); + if (currentIndex == -1) { + // no parameter places left in message, + // we'll ignore the remaining parameters and return the message + if (previousIndex == -1) return message; + else { + stringBuilder.append(message.substring(previousIndex)); + return stringBuilder.toString(); + } + } + + if (previousIndex == -1) stringBuilder.append(message, 0, currentIndex); + else stringBuilder.append(message, previousIndex, currentIndex); + stringBuilder.append(argument); + + // we finished this argument, so we're past the current delimiter + previousIndex = currentIndex + DELIM_LENGTH; + } + + if (previousIndex != message.length()) + stringBuilder.append(message, previousIndex, message.length()); + return stringBuilder.toString(); + } + + public static int getArgsContentLength(String... args) { + int length = 0; + for (String arg : args) + length += arg.length(); + return length; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java index 4f13270f..66aef2e8 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java @@ -1,28 +1,55 @@ +/* + * 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 lombok.Getter; import lombok.Setter; import java.lang.reflect.*; -public class ReflectionUtil { +public final class ReflectionUtil { /** * Prefix without dot
* Example net.minecraft.server.v1_8R3.PacketHandhakingInSetProtocol will become:
* net.minecraft.server.v1_8R3 */ - @Getter @Setter - private static String prefix = null; + @Setter private static String prefix = null; + + private static Field modifiersField = null; public static Class getPrefixedClass(String className) { - return getClass(prefix +"."+ className); + return getClass(prefix + "." + className); } public static Class getClass(String className) { try { return Class.forName(className); - } catch (ClassNotFoundException e) { - e.printStackTrace(); + } catch (ClassNotFoundException exception) { + exception.printStackTrace(); + // class is not found, so we return null return null; } } @@ -30,15 +57,26 @@ public class ReflectionUtil { public static Field getField(Class clazz, String fieldName, boolean isPublic) { try { - return isPublic ? clazz.getField(fieldName) : clazz.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { + if (isPublic) { + return clazz.getField(fieldName); + } + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException exception) { + // field is not found, so we return null return null; } } public static Field getField(Class clazz, String fieldName) { Field field = getField(clazz, fieldName, false); - return field != null ? field : getField(clazz, fieldName, true); + if (field != null) { + return field; + } + return getField(clazz, fieldName, true); + } + + public static Field getFieldOfType(Class clazz, Class fieldType) { + return getFieldOfType(clazz, fieldType, true); } public static Field getFieldOfType(Class clazz, Class fieldType, boolean declared) { @@ -54,24 +92,33 @@ public class ReflectionUtil { makeAccessible(field); try { return field.get(instance); - } catch (IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); + } catch (IllegalArgumentException | IllegalAccessException exception) { + exception.printStackTrace(); return null; } } + /** + * This method is equal to running:
+ * {@link #getValue(Object, Field) getValue}(instance, + * {@link #getField(Class, String) getField}(instance.getClass(), fieldName)) + */ public static Object getValue(Object instance, String fieldName) { return getValue(instance, getField(instance.getClass(), fieldName)); } @SuppressWarnings("unchecked") - public static T getCastedValue(Object instance, Field field, Class returnType) { + public static T getCastedValue(Object instance, Field field) { return (T) getValue(instance, field); } - @SuppressWarnings("unchecked") - public static T getCastedValue(Object instance, String fieldName, Class returnType) { - return (T) getValue(instance, getField(instance.getClass(), fieldName)); + /** + * This method is equal to running:
+ * {@link #getCastedValue(Object, Field) getCastedValue}(instance, + * {@link #getField(Class, String) getField}(instance.getClass(), fieldName)) + */ + public static T getCastedValue(Object instance, String fieldName) { + return getCastedValue(instance, getField(instance.getClass(), fieldName)); } public static boolean setValue(Object instance, String fieldName, Object value) { @@ -86,8 +133,8 @@ public class ReflectionUtil { makeAccessible(field); try { field.set(instance, value); - } catch (IllegalAccessException e) { - e.printStackTrace(); + } catch (IllegalAccessException exception) { + exception.printStackTrace(); } } @@ -95,69 +142,110 @@ public class ReflectionUtil { try { makeAccessible(field); - Field modifiersField = null; int modifiers = field.getModifiers(); if (Modifier.isFinal(modifiers)) { - try { - modifiersField = Field.class.getDeclaredField("modifiers"); - } catch (NoSuchFieldException e) { - // Java 12 compatibility, thanks to https://github.com/powermock/powermock/pull/1010 - Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - makeAccessible(getDeclaredFields0); - Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); - for (Field classField : fields) { - if ("modifiers".equals(classField.getName())) { - modifiersField = classField; - break; - } - } - } - assert modifiersField != null; + Field modifiersField = getModifiersField(); makeAccessible(modifiersField); modifiersField.setInt(field, modifiers & ~Modifier.FINAL); } setValue(instance, field, value); return true; - } catch (Exception e) { - e.printStackTrace(); + } catch (Exception exception) { + exception.printStackTrace(); return false; } } - public static Method getMethod(Class clazz, String method, Class... args) { + public static Field getModifiersField(){ + if (modifiersField != null) return modifiersField; + try { - return clazz.getMethod(method, args); - } catch (NoSuchMethodException e) { - e.printStackTrace(); + modifiersField = Field.class.getDeclaredField("modifiers"); + } catch (NoSuchFieldException exception) { + // Java 12 compatibility, thanks to https://github.com/powermock/powermock/pull/1010 + try { + Method getDeclaredFields0 = Class.class.getDeclaredMethod( + "getDeclaredFields0", boolean.class + ); + makeAccessible(getDeclaredFields0); + + Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); + for (Field field : fields) { + if ("modifiers".equals(field.getName())) { + modifiersField = field; + break; + } + } + } catch (Exception exception1) { + exception1.printStackTrace(); + return null; + } + } + return modifiersField; + } + + public static Method getMethod(Class clazz, String method, + boolean isPublic, Class... args) { + try { + return isPublic ? clazz.getMethod(method, args) : clazz.getDeclaredMethod(method, args); + } catch (NoSuchMethodException exception) { + // method is not found, so we return null return null; } } - public static Object invoke(Object instance, Method method) { + public static Method getMethod(Class clazz, String methodName, Class... args) { + Method method = getMethod(clazz, methodName, false, args); + if (method != null) { + return method; + } + return getMethod(clazz, methodName, true, args); + } + + public static Method getMethod(Object instance, String method, Class... args) { + return getMethod(instance.getClass(), method, args); + } + + public static Object invoke(Object instance, Method method, Object... args) { + makeAccessible(method); try { - return method.invoke(instance); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); + return method.invoke(instance, args); + } catch (IllegalAccessException | InvocationTargetException exception) { + exception.printStackTrace(); return null; } } + public static Object invoke(Object instance, String method) { + return invoke(instance, getMethod(instance.getClass(), method)); + } + @SuppressWarnings("unchecked") - public static T invokeCasted(Object instance, Method method, Class cast) { - return (T) invoke(instance, method); + public static T castedInvoke(Object instance, Method method, Object... args) { + try { + return (T) invoke(instance, method, args); + } catch (NullPointerException exception) { + return null; + } + } + + public static T castedInvoke(Object instance, String method) { + return castedInvoke(instance, getMethod(instance.getClass(), method)); } public static Object invokeStatic(Class clazz, String method) { try { - return getMethod(clazz, method).invoke(null); - } catch (IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); + return invoke(null, getMethod(clazz, method)); + } catch (NullPointerException exception) { + exception.printStackTrace(); return null; } } public static T makeAccessible(T accessibleObject) { - if (!accessibleObject.isAccessible()) accessibleObject.setAccessible(true); + if (!accessibleObject.isAccessible()) { + accessibleObject.setAccessible(true); + } return accessibleObject; } } diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 255ef4f3..9cb41687 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -17,16 +17,13 @@ disconnect: invalid-key: Please connect through the official Geyser # The disconnect message Geyser users should get when connecting # to the server with the correct key but not with the correct data format - invalid-arguments-length: Expected {0} arguments, got {1}. Is Geyser up-to-date? + invalid-arguments-length: Expected {} arguments, got {}. Is Geyser up-to-date? # Configuration for player linking player-link: # Whether to enable the linking system. Turning this off will prevent # players from using the linking feature even if they are already linked. enable: false - # The type of storage system you want to use - # Currently implemented: SQLite - type: sqlite # Whether to allow the use of /linkaccount and /unlinkaccount # You can also use allow specific people to use the commands using the # permissions floodgate.linkaccount and floodgate.unlinkaccount. @@ -34,3 +31,13 @@ player-link: allow-linking: true # The amount of time until a link code expires in seconds link-code-timeout: 300 + # The database type you want to use. + # The option is only used when auto-download is enabled or when + # there are more then one database implementations found in the config directory + type: sqlite + # Automatically download the database type you want to use + # This doesn't include updating automatically (I don't expect the database implementations to be updated frequently) + auto-download: true + +# Do not change this +config-version: 1 diff --git a/bungee/src/main/resources/config.yml b/common/src/main/resources/proxy-config.yml similarity index 73% rename from bungee/src/main/resources/config.yml rename to common/src/main/resources/proxy-config.yml index d127d524..87d797af 100644 --- a/bungee/src/main/resources/config.yml +++ b/common/src/main/resources/proxy-config.yml @@ -11,7 +11,8 @@ username-prefix: "*" # Should spaces be replaced with '_' in bedrock usernames? replace-spaces: true -# Should Bungeecord send the bedrock player data to the servers it is connecting to? +# todo impl args like {proxy} +# Should {proxy} send the bedrock player data to the servers it is connecting to? # This requires Floodgate to be installed on the servers. # You'll get kicked if you don't use the plugin. The default value is false because of it send-floodgate-data: false @@ -22,16 +23,13 @@ disconnect: invalid-key: Please connect through the official Geyser # The disconnect message Geyser users should get when connecting # to the server with the correct key but not with the correct data format - invalid-arguments-length: Expected {0} arguments, got {1}. Is Geyser up-to-date? + invalid-arguments-length: Expected {} arguments, got {}. Is Geyser up-to-date? # Configuration for player linking player-link: # Whether to enable the linking system. Turning this off will prevent # players from using the linking feature even if they are already linked. enable: false - # The type of storage system you want to use - # Currently implemented: SQLite - type: sqlite # Whether to allow the use of /linkaccount and /unlinkaccount # You can also use allow specific people to use the commands using the # permissions floodgate.linkaccount and floodgate.unlinkaccount. @@ -39,4 +37,14 @@ player-link: allow-linking: true # The amount of time until a link code expires in seconds link-code-timeout: 300 - + # The database type you want to use. + # The option is only used when auto-download is enabled or when + # there are more then one database implementations found in the config directory + type: sqlite + # Automatically download the database type you want to use + # This doesn't include updating automatically + # (because the database implementations won't be updated frequently) + auto-download: true + +# Do not change this +config-version: 1 diff --git a/database/pom.xml b/database/pom.xml new file mode 100644 index 00000000..c1858179 --- /dev/null +++ b/database/pom.xml @@ -0,0 +1,52 @@ + + + + + + parent + org.geysermc.floodgate + 1.0-SNAPSHOT + + 4.0.0 + + org.geysermc.floodgate.database + parent + pom + 1.0-SNAPSHOT + + database + + + sqlite + + + + floodgate-${project.name}-database + + \ No newline at end of file diff --git a/database/sqlite/pom.xml b/database/sqlite/pom.xml new file mode 100644 index 00000000..216badf5 --- /dev/null +++ b/database/sqlite/pom.xml @@ -0,0 +1,88 @@ + + + + + + parent + org.geysermc.floodgate.database + 1.0-SNAPSHOT + + 4.0.0 + + sqlite + + + + org.xerial + sqlite-jdbc + 3.30.1 + compile + + + org.geysermc.floodgate + common + 1.0-SNAPSHOT + provided + + + org.projectlombok + lombok + 1.18.12 + provided + + + + + + + src/main/resources/ + true + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + + + ${outputName} + true + + + + + \ No newline at end of file diff --git a/database/sqlite/src/main/java/org/geysermc/floodgate/database/SqliteDatabase.java b/database/sqlite/src/main/java/org/geysermc/floodgate/database/SqliteDatabase.java new file mode 100644 index 00000000..58fef691 --- /dev/null +++ b/database/sqlite/src/main/java/org/geysermc/floodgate/database/SqliteDatabase.java @@ -0,0 +1,138 @@ +/* + * 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.database; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.geysermc.floodgate.link.CommonPlayerLink; +import org.geysermc.floodgate.util.LinkedPlayer; + +import java.nio.file.Path; +import java.sql.*; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +public class SqliteDatabase extends CommonPlayerLink { + private Connection connection; + @Inject @Named("dataDirectory") + private Path dataDirectory; + + @Override + public void load() { + Path databasePath = dataDirectory.resolve("linked-players.db"); + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath.toString()); + Statement statement = connection.createStatement(); + statement.setQueryTimeout(30); // set timeout to 30 sec. + statement.executeUpdate("create table if not exists LinkedPlayers (bedrockId string, javaUniqueId string, javaUsername string)"); + } catch (ClassNotFoundException ignored) { + getLogger().error("The required class to load the SQLite database wasn't found"); + } catch (SQLException exception) { + getLogger().error("Error while loading database", exception); + } + } + + @Override + public void stop() { + super.stop(); + try { + connection.close(); + } catch (SQLException exception) { + getLogger().error("Error while closing database connection", exception); + } + } + + @Override + public CompletableFuture getLinkedPlayer(UUID bedrockId) { + return CompletableFuture.supplyAsync(() -> { + try { + PreparedStatement query = connection.prepareStatement("select * from LinkedPlayers where bedrockId = ?"); + query.setString(1, bedrockId.toString()); + ResultSet result = query.executeQuery(); + if (!result.next()) return null; + + String javaUsername = result.getString("javaUsername"); + UUID javaUniqueId = UUID.fromString(result.getString("javaUniqueId")); + return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); + } catch (SQLException | NullPointerException exception) { + getLogger().error("Error while getting LinkedPlayer", exception); + throw new CompletionException("Error while getting LinkedPlayer", exception); + } + }, getExecutorService()); + } + + @Override + public CompletableFuture isLinkedPlayer(UUID bedrockId) { + return CompletableFuture.supplyAsync(() -> { + try { + PreparedStatement query = connection.prepareStatement("select javaUniqueId from LinkedPlayers where bedrockId = ? or javaUniqueId = ?"); + query.setString(1, bedrockId.toString()); + query.setString(2, bedrockId.toString()); + ResultSet result = query.executeQuery(); + return result.next(); + } catch (SQLException | NullPointerException exception) { + getLogger().error("Error while checking if player is a LinkedPlayer", exception); + throw new CompletionException( + "Error while checking if player is a LinkedPlayer", exception + ); + } + }, getExecutorService()); + } + + @Override + public CompletableFuture linkPlayer(UUID bedrockId, UUID javaId, String username) { + return CompletableFuture.runAsync(() -> { + try { + PreparedStatement query = connection.prepareStatement("insert into LinkedPlayers values(?, ?, ?)"); + query.setString(1, bedrockId.toString()); + query.setString(2, javaId.toString()); + query.setString(3, username); + query.executeUpdate(); + } catch (SQLException | NullPointerException exception) { + getLogger().error("Error while linking player", exception); + throw new CompletionException("Error while linking player", exception); + } + }, getExecutorService()); + } + + @Override + public CompletableFuture unlinkPlayer(UUID javaId) { + return CompletableFuture.runAsync(() -> { + try { + PreparedStatement query = connection.prepareStatement("delete from LinkedPlayers where javaUniqueId = ? or bedrockId = ?"); + query.setString(1, javaId.toString()); + query.setString(2, javaId.toString()); + query.executeUpdate(); + } catch (SQLException | NullPointerException exception) { + getLogger().error("Error while unlinking player", exception); + throw new CompletionException("Error while unlinking player", exception); + } + }, getExecutorService()); + } +} diff --git a/database/sqlite/src/main/resources/config.json b/database/sqlite/src/main/resources/config.json new file mode 100644 index 00000000..8d17bcbc --- /dev/null +++ b/database/sqlite/src/main/resources/config.json @@ -0,0 +1,3 @@ +{ + "mainClass": "org.geysermc.floodgate.database.SqliteDatabase" +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5671356d..2264049f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,19 +4,21 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.geysermc - floodgate-parent + org.geysermc.floodgate + parent 1.0-SNAPSHOT - bungee + api common - bukkit + spigot + bungee velocity + database pom floodgate Allows Bedrock players to join Java edition servers while keeping online mode - https://github.com/GeyserMC/Floodgate/ + https://github.com/GeyserMC/Floodgate 1.0-SNAPSHOT @@ -24,7 +26,7 @@ 1.15-SNAPSHOT 1.1.0-SNAPSHOT - ${project.name} + floodgate-${project.name} UTF-8 UTF-8 1.8 diff --git a/bukkit/pom.xml b/spigot/pom.xml similarity index 89% rename from bukkit/pom.xml rename to spigot/pom.xml index cb63fe58..d708d39c 100644 --- a/bukkit/pom.xml +++ b/spigot/pom.xml @@ -3,13 +3,13 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - org.geysermc - floodgate-parent + org.geysermc.floodgate + parent 1.0-SNAPSHOT 4.0.0 - floodgate-bukkit + spigot @@ -38,10 +38,9 @@ provided - org.geysermc - floodgate-common + org.geysermc.floodgate + common ${project.version} - compile diff --git a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java new file mode 100644 index 00000000..d1f2c9f5 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java @@ -0,0 +1,62 @@ +/* + * 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; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.name.Named; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.api.FloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.loader.ConfigLoader; +import org.geysermc.floodgate.link.PlayerLinkLoader; + +import java.nio.file.Path; + +public final class SpigotPlatform extends FloodgatePlatform { + @Inject private JavaPlugin plugin; + + @Inject + public SpigotPlatform(@Named("dataDirectory") Path dataDirectory, FloodgateApi api, + ConfigLoader configLoader, PlayerLinkLoader playerLinkLoader, + HandshakeHandler handshakeHandler, FloodgateLogger logger, + Injector injector) { + super(dataDirectory, api, configLoader, playerLinkLoader, + handshakeHandler, logger, injector); + } + + @Override + public boolean enable(Module... postCreateModules) { + boolean success = super.enable(postCreateModules); + if (!success) { + Bukkit.getPluginManager().disablePlugin(plugin); + } + return success; + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java new file mode 100644 index 00000000..2df7cbd4 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java @@ -0,0 +1,64 @@ +/* + * 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; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.module.*; +import org.geysermc.floodgate.util.ReflectionUtil; + +public final class SpigotPlugin extends JavaPlugin { + private SpigotPlatform platform; + + @Override + public void onLoad() { + String minecraftVersion = getServer().getClass().getPackage().getName().split("\\.")[3]; + ReflectionUtil.setPrefix("net.minecraft.server." + minecraftVersion); + + long ctm = System.currentTimeMillis(); + Injector injector = Guice.createInjector( + new CommonModule(getDataFolder().toPath()), + new SpigotPlatformModule(this) + ); + + long endCtm = System.currentTimeMillis(); + getLogger().info("Took " + (endCtm - ctm) + "ms to boot Floodgate"); + + platform = injector.getInstance(SpigotPlatform.class); + } + + @Override + public void onEnable() { + platform.enable(new CommandModule(), new SpigotListenerModule(), new SpigotAddonModule()); + } + + @Override + public void onDisable() { + platform.disable(); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java new file mode 100644 index 00000000..fc23cba8 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataAddon.java @@ -0,0 +1,67 @@ +/* + * 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.addon.data; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import io.netty.channel.Channel; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.config.FloodgateConfig; + +public final class SpigotDataAddon implements InjectorAddon { + @Inject private HandshakeHandler handshakeHandler; + @Inject private FloodgateConfig config; + @Inject private FloodgateLogger logger; + + @Inject @Named("packetHandler") + private String packetHandlerName; + + @Override + public void onInject(Channel channel, boolean proxyToServer) { + channel.pipeline().addBefore( + packetHandlerName, "floodgate_data_handler", + new SpigotDataHandler(handshakeHandler, config, logger) + ); + } + + @Override + public void onLoginDone(Channel channel) { + onRemoveInject(channel); + } + + @Override + public void onRemoveInject(Channel channel) { + channel.pipeline().remove("floodgate_data_handler"); + } + + @Override + public boolean shouldInject() { + return true; + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java new file mode 100644 index 00000000..af1b1e90 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java @@ -0,0 +1,248 @@ +/* + * 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.addon.data; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.floodgate.util.ReflectionUtil; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.geysermc.floodgate.util.ReflectionUtil.*; + +@RequiredArgsConstructor +public final class SpigotDataHandler extends SimpleChannelInboundHandler { + private static final Field SOCKET_ADDRESS; + private static final Class HANDSHAKE_PACKET; + private static final Field HANDSHAKE_HOST; + + private static final Class GAME_PROFILE; + private static final Constructor GAME_PROFILE_CONSTRUCTOR; + private static final Field LOGIN_PROFILE; + + private static final Class LOGIN_START_PACKET; + private static final Class LOGIN_LISTENER; + private static final Method INIT_UUID; + + private static final Class LOGIN_HANDLER; + private static final Constructor LOGIN_HANDLER_CONSTRUCTOR; + private static final Method FIRE_LOGIN_EVENTS; + + private static final Field PACKET_LISTENER; + private static final Field PROTOCOL_STATE; + private static final Object READY_TO_ACCEPT_PROTOCOL_STATE; + + /* per player stuff */ + private final HandshakeHandler handshakeHandler; + private final FloodgateConfig config; + private final FloodgateLogger logger; + + private Object networkManager; + private FloodgatePlayer fPlayer; + private boolean bungee; + + private boolean done; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object packet) throws Exception { + // we're done but we're not yet removed from the connection + if (done) { + ctx.fireChannelRead(packet); + return; + } + + boolean isHandshake = HANDSHAKE_PACKET.isInstance(packet); + boolean isLogin = LOGIN_START_PACKET.isInstance(packet); + + try { + if (isHandshake) { + networkManager = ctx.channel().pipeline().get("packet_handler"); + + String handshakeValue = getCastedValue(packet, HANDSHAKE_HOST); + HandshakeResult result = handshakeHandler.handle(handshakeValue); + switch (result.getResultType()) { + case SUCCESS: + break; + case INVALID_DATA_LENGTH: + int dataLength = result.getBedrockData().getDataLength(); + logger.info(config.getMessages().getInvalidArgumentsLength(), + BedrockData.EXPECTED_LENGTH, dataLength); + ctx.close(); + return; + default: // only continue when SUCCESS + return; + } + + fPlayer = result.getFloodgatePlayer(); + BedrockData bedrockData = result.getBedrockData(); + String[] data = result.getHandshakeData(); + + if (bungee = (data.length == 6 || data.length == 7)) { + setValue(packet, HANDSHAKE_HOST, data[0] + '\0' + + bedrockData.getIp() + '\0' + fPlayer.getCorrectUniqueId() + + (data.length == 7 ? '\0' + data[6] : "") + ); + } else { + // Use a spoofedUUID for initUUID (just like Bungeecord) + setValue(networkManager, "spoofedUUID", fPlayer.getCorrectUniqueId()); + // Use the player his IP for stuff instead of Geyser his IP + SocketAddress newAddress = new InetSocketAddress( + bedrockData.getIp(), + ((InetSocketAddress) ctx.channel().remoteAddress()).getPort() + ); + + setValue(networkManager, SOCKET_ADDRESS, newAddress); + } + } else if (isLogin) { + if (!bungee) { + // we have to fake the offline player cycle + Object loginListener = PACKET_LISTENER.get(networkManager); + + // Set the player his GameProfile + Object gameProfile = GAME_PROFILE_CONSTRUCTOR.newInstance( + fPlayer.getCorrectUniqueId(), fPlayer.getCorrectUsername() + ); + setValue(loginListener, LOGIN_PROFILE, gameProfile); + + // Just like on Spigot: + + // LoginListener#initUUID + // new LoginHandler().fireEvents(); + // LoginListener#protocolState = READY_TO_ACCEPT + + // and the tick of LoginListener will do the rest + + INIT_UUID.invoke(loginListener); + FIRE_LOGIN_EVENTS.invoke(LOGIN_HANDLER_CONSTRUCTOR.newInstance(loginListener)); + setValue(loginListener, PROTOCOL_STATE, READY_TO_ACCEPT_PROTOCOL_STATE); + } + } + } finally { + // don't let the packet through if the packet is the login packet + // because we want to skip the login cycle + if (!isLogin) ctx.fireChannelRead(packet); + + if (isHandshake && bungee || isLogin && !bungee || fPlayer == null) { + // we're done, we'll just wait for the loginSuccessCall + done = true; + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + cause.printStackTrace(); + } + + static { + Class networkManager = getPrefixedClass("NetworkManager"); + checkNotNull(networkManager, "NetworkManager class cannot be null"); + + SOCKET_ADDRESS = getFieldOfType(networkManager, SocketAddress.class, false); + checkNotNull(SOCKET_ADDRESS, "SocketAddress field cannot be null"); + + HANDSHAKE_PACKET = getPrefixedClass("PacketHandshakingInSetProtocol"); + checkNotNull(HANDSHAKE_PACKET, "PacketHandshakingInSetProtocol cannot be null"); + HANDSHAKE_HOST = getFieldOfType(HANDSHAKE_PACKET, String.class, true); + checkNotNull(HANDSHAKE_HOST, "Host field from handshake packet cannot be null"); + + LOGIN_START_PACKET = getPrefixedClass("PacketLoginInStart"); + checkNotNull(LOGIN_START_PACKET, "PacketLoginInStart cannot be null"); + + GAME_PROFILE = ReflectionUtil.getClass("com.mojang.authlib.GameProfile"); + checkNotNull(GAME_PROFILE, "GameProfile class cannot be null"); + + Constructor gameProfileConstructor = null; + try { + gameProfileConstructor = GAME_PROFILE.getConstructor(UUID.class, String.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + GAME_PROFILE_CONSTRUCTOR = gameProfileConstructor; + checkNotNull(GAME_PROFILE_CONSTRUCTOR, "GameProfileConstructor cannot be null"); + + LOGIN_LISTENER = getPrefixedClass("LoginListener"); + checkNotNull(LOGIN_LISTENER, "LoginListener cannot be null"); + LOGIN_PROFILE = getFieldOfType(LOGIN_LISTENER, GAME_PROFILE, true); + checkNotNull(LOGIN_PROFILE, "Profile from LoginListener cannot be null"); + INIT_UUID = getMethod(LOGIN_LISTENER, "initUUID"); + checkNotNull(INIT_UUID, "initUUID from LoginListener cannot be null"); + + Field protocolStateField = null; + for (Field field : LOGIN_LISTENER.getDeclaredFields()) { + if (field.getType().isEnum()) { + protocolStateField = field; + } + } + PROTOCOL_STATE = protocolStateField; + checkNotNull(PROTOCOL_STATE, "Protocol state field from LoginListener cannot be null"); + + Enum[] protocolStates = (Enum[]) PROTOCOL_STATE.getType().getEnumConstants(); + Object readyToAcceptState = null; + for (Enum protocolState : protocolStates) { + if (protocolState.name().equals("READY_TO_ACCEPT")) { + readyToAcceptState = protocolState; + } + } + READY_TO_ACCEPT_PROTOCOL_STATE = readyToAcceptState; + checkNotNull(READY_TO_ACCEPT_PROTOCOL_STATE, + "Ready to accept state from Protocol state cannot be null"); + + Class packetListenerClass = getPrefixedClass("PacketListener"); + PACKET_LISTENER = getFieldOfType(networkManager, packetListenerClass, true); + checkNotNull(PACKET_LISTENER, "PacketListener cannot be null"); + + LOGIN_HANDLER = getPrefixedClass("LoginListener$LoginHandler"); + checkNotNull(LOGIN_HANDLER, "LoginHandler cannot be null"); + + Constructor loginHandlerConstructor = null; + try { + loginHandlerConstructor = makeAccessible(LOGIN_HANDLER.getDeclaredConstructor(LOGIN_LISTENER)); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + LOGIN_HANDLER_CONSTRUCTOR = loginHandlerConstructor; + checkNotNull(LOGIN_HANDLER_CONSTRUCTOR, "LoginHandler constructor cannot be null"); + + FIRE_LOGIN_EVENTS = getMethod(LOGIN_HANDLER, "fireEvents"); + checkNotNull(FIRE_LOGIN_EVENTS, "fireEvents from LoginHandler cannot be null"); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/command/SpigotCommandRegistration.java b/spigot/src/main/java/org/geysermc/floodgate/command/SpigotCommandRegistration.java new file mode 100644 index 00000000..58ef9426 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/command/SpigotCommandRegistration.java @@ -0,0 +1,76 @@ +/* + * 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.command; + +import lombok.RequiredArgsConstructor; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.platform.command.CommandRegistration; + +import java.util.UUID; + +@RequiredArgsConstructor +public final class SpigotCommandRegistration implements CommandRegistration { + private final JavaPlugin plugin; + private final FloodgateLogger logger; + + @Override + public void register(Command command) { + try { + plugin.getCommand(command.getName()) + .setExecutor(new SpigotCommandWrapper(command)); + } catch (ClassCastException e) { + logger.error("Failed to cast plugin object to JavaPlugin", e); + } + } + + @RequiredArgsConstructor + protected static class SpigotCommandWrapper implements CommandExecutor { + private final Command command; + + @Override + public boolean onCommand(CommandSender source, org.bukkit.command.Command cmd, String label, String[] args) { + if (!(source instanceof Player)) { + if (command.isRequirePlayer()) { + source.sendMessage(CommonCommandMessage.NOT_A_PLAYER.getMessage()); + return true; + } + command.execute(source, args); + return true; + } + + UUID uuid = ((Player) source).getUniqueId(); + String username = source.getName(); + command.execute(source, uuid, username, args); + return true; + } + } +} diff --git a/bukkit/src/main/java/org/geysermc/floodgate/injector/CustomList.java b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/CustomList.java similarity index 70% rename from bukkit/src/main/java/org/geysermc/floodgate/injector/CustomList.java rename to spigot/src/main/java/org/geysermc/floodgate/inject/spigot/CustomList.java index a3a21b47..68ae5544 100644 --- a/bukkit/src/main/java/org/geysermc/floodgate/injector/CustomList.java +++ b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/CustomList.java @@ -1,4 +1,30 @@ -package org.geysermc.floodgate.injector; +/* + * 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.inject.spigot; import lombok.AllArgsConstructor; import lombok.Getter; @@ -9,6 +35,7 @@ import java.util.List; import java.util.ListIterator; @AllArgsConstructor +@SuppressWarnings({"unchecked", "rawtypes"}) public abstract class CustomList implements List { @Getter private final List originalList; diff --git a/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java new file mode 100644 index 00000000..02647263 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/inject/spigot/SpigotInjector.java @@ -0,0 +1,162 @@ +/* + * 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.inject.spigot; + +import io.netty.channel.*; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.util.ReflectionUtil; + +import java.lang.reflect.*; +import java.util.*; + +@RequiredArgsConstructor +public final class SpigotInjector extends CommonPlatformInjector { + private Object serverConnection; + private final Set injectedClients = new HashSet<>(); + + @Getter private boolean injected = false; + private String injectedFieldName; + + @Override + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public boolean inject() throws Exception { + if (isInjected()) { + return true; + } + + if (getServerConnection() != null) { + for (Field f : serverConnection.getClass().getDeclaredFields()) { + if (f.getType() == List.class) { + f.setAccessible(true); + + ParameterizedType parameterType = ((ParameterizedType) f.getGenericType()); + Type listType = parameterType.getActualTypeArguments()[0]; + + // the list we search has ChannelFuture as type + if (listType != ChannelFuture.class) { + continue; + } + + injectedFieldName = f.getName(); + List newList = new CustomList((List) f.get(serverConnection)) { + @Override + public void onAdd(Object o) { + try { + injectClient((ChannelFuture) o); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + + // inject existing + synchronized (newList) { + for (Object o : newList) { + try { + injectClient((ChannelFuture) o); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + f.set(serverConnection, newList); + injected = true; + return true; + } + } + } + return false; + } + + public void injectClient(ChannelFuture future) { + future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + super.channelRead(ctx, msg); + + Channel channel = (Channel) msg; + channel.pipeline().addLast(new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) { + injectAddonsCall(channel, false); + injectedClients.add(channel); + } + }); + } + }); + } + + @Override + public boolean removeInjection() throws Exception { + if (!isInjected()) { + return true; + } + + // remove injection from clients + for (Channel channel : injectedClients) { + removeAddonsCall(channel); + } + injectedClients.clear(); + + // and change the list back to the original + Object serverConnection = getServerConnection(); + if (serverConnection != null) { + Field field = ReflectionUtil.getField(serverConnection.getClass(), injectedFieldName); + List list = (List) ReflectionUtil.getValue(serverConnection, field); + if (list instanceof CustomList) { + CustomList customList = (CustomList) list; + ReflectionUtil.setValue(serverConnection, field, customList.getOriginalList()); + customList.clear(); + customList.addAll(list); + } + } + + injectedFieldName = null; + injected = false; + return true; + } + + public Object getServerConnection() throws IllegalAccessException, InvocationTargetException { + if (serverConnection != null) return serverConnection; + Class minecraftServer = ReflectionUtil.getPrefixedClass("MinecraftServer"); + assert minecraftServer != null; + + Object minecraftServerInstance = ReflectionUtil.invokeStatic(minecraftServer, "getServer"); + for (Method m : minecraftServer.getDeclaredMethods()) { + if (m.getReturnType().getSimpleName().equals("ServerConnection")) { + if (m.getParameterTypes().length == 0) { + serverConnection = m.invoke(minecraftServerInstance); + } + } + } + + return serverConnection; + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java new file mode 100644 index 00000000..6d1b1256 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListener.java @@ -0,0 +1,84 @@ +/* + * 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.listener; + +import lombok.RequiredArgsConstructor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.geysermc.floodgate.FloodgatePlayerImpl; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +import java.util.UUID; + +@RequiredArgsConstructor +public final class SpigotListener implements Listener { + private final SimpleFloodgateApi api; + private final FloodgateLogger logger; + + @EventHandler(priority = EventPriority.MONITOR) + public void onAsyncPreLogin(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + api.removePlayer(event.getUniqueId(), true); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerLogin(PlayerLoginEvent event) { + UUID uniqueId = event.getPlayer().getUniqueId(); + if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) { + api.removePlayer(uniqueId); + return; + } + + // if there was another player with the same uuid online, + // he would've been disconnected by now + FloodgatePlayer player = api.getPlayer(uniqueId); + if (player != null) { + player.as(FloodgatePlayerImpl.class).setLogin(false); + logger.info("Floodgate player who is logged in as {} {} joined", + player.getCorrectUsername(), player.getCorrectUniqueId()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + if (api.removePlayer(player.getUniqueId()) != null) { + logger.info( + "Floodgate player who was logged in as {} {} disconnected", + player.getName(), player.getUniqueId() + ); + } + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListenerRegistration.java b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListenerRegistration.java new file mode 100644 index 00000000..719f9c35 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/listener/SpigotListenerRegistration.java @@ -0,0 +1,44 @@ +/* + * 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.listener; + +import com.google.inject.Inject; +import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; + +@RequiredArgsConstructor(onConstructor = @__(@Inject)) +public final class SpigotListenerRegistration implements ListenerRegistration { + private final JavaPlugin plugin; + + @Override + public void register(Listener listener) { + Bukkit.getServer().getPluginManager().registerEvents(listener, plugin); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotAddonModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotAddonModule.java new file mode 100644 index 00000000..22547c47 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotAddonModule.java @@ -0,0 +1,61 @@ +/* + * 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.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.addon.AddonManagerAddon; +import org.geysermc.floodgate.addon.data.SpigotDataAddon; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.addon.DebugAddon; +import org.geysermc.floodgate.register.AddonRegister; + +public final class SpigotAddonModule extends AbstractModule { + @Override + protected void configure() { + bind(AddonRegister.class).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon managerAddon() { + return new AddonManagerAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon dataAddon() { + return new SpigotDataAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon debugAddon() { + return new DebugAddon(); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java new file mode 100644 index 00000000..7cd0b095 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotListenerModule.java @@ -0,0 +1,50 @@ +/* + * 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.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.bukkit.event.Listener; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.listener.SpigotListener; +import org.geysermc.floodgate.register.ListenerRegister; + +public final class SpigotListenerModule extends AbstractModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public Listener spigotListener(SimpleFloodgateApi api, FloodgateLogger logger) { + return new SpigotListener(api, logger); + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java new file mode 100644 index 00000000..60218087 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/module/SpigotPlatformModule.java @@ -0,0 +1,134 @@ +/* + * 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.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import lombok.RequiredArgsConstructor; +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.SpigotPlugin; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.command.SpigotCommandRegistration; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.inject.spigot.SpigotInjector; +import org.geysermc.floodgate.listener.SpigotListenerRegistration; +import org.geysermc.floodgate.logger.JavaDefaultFloodgateLogger; +import org.geysermc.floodgate.platform.command.CommandRegistration; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.platform.command.util.CommandUtil; +import org.geysermc.floodgate.util.SpigotCommandUtil; + +@RequiredArgsConstructor +public final class SpigotPlatformModule extends AbstractModule { + private final SpigotPlugin plugin; + + @Provides + @Singleton + public JavaPlugin javaPlugin() { + return plugin; + } + + @Provides + @Singleton + @Named("configClass") + public Class floodgateConfigClass() { + return FloodgateConfig.class; + } + + @Provides + @Singleton + public SimpleFloodgateApi floodgateApi() { + return new SimpleFloodgateApi(); + } + + @Provides + @Singleton + public FloodgateLogger floodgateLogger() { + return new JavaDefaultFloodgateLogger(plugin.getLogger()); + } + + /* + Commands / Listeners + */ + + @Provides + @Singleton + public CommandRegistration commandRegistration(FloodgateLogger logger) { + return new SpigotCommandRegistration(plugin, logger); + } + + @Provides + @Singleton + public CommandUtil commandUtil(FloodgateLogger logger) { + return new SpigotCommandUtil(plugin, logger); + } + + @Provides + @Singleton + public ListenerRegistration listenerRegistration() { + return new SpigotListenerRegistration(plugin); + } + + /* + DebugAddon / PlatformInjector + */ + + @Provides + @Singleton + public CommonPlatformInjector platformInjector() { + return new SpigotInjector(); + } + + @Provides + @Named("packetEncoder") + public String packetEncoder() { + return "encoder"; + } + + @Provides + @Named("packetDecoder") + public String packetDecoder() { + return "decoder"; + } + + @Provides + @Named("packetHandler") + public String packetHandler() { + return "packet_handler"; + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "Spigot"; + } +} diff --git a/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java new file mode 100644 index 00000000..f8833805 --- /dev/null +++ b/spigot/src/main/java/org/geysermc/floodgate/util/SpigotCommandUtil.java @@ -0,0 +1,69 @@ +/* + * 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 lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.util.CommandResponseCache; +import org.geysermc.floodgate.platform.command.util.CommandUtil; + +@RequiredArgsConstructor +public final class SpigotCommandUtil extends CommandResponseCache implements CommandUtil { + private final JavaPlugin plugin; + private final FloodgateLogger logger; + + @Override + public void sendMessage(Object player, CommandMessage message, Object... args) { + cast(player).sendMessage(format(message, args)); + } + + @Override + public void kickPlayer(Object player, CommandMessage message, Object... args) { + // Have to run this in the main thread so we don't get a `Asynchronous player kick!` error + Bukkit.getScheduler().runTask(plugin, + () -> cast(player).kickPlayer(format(message, args))); + } + + @Override + protected String transformMessage(String message) { + // unlike others, Bukkit doesn't have to transform a message into another class. + return message; + } + + protected Player cast(Object instance) { + try { + return (Player) instance; + } catch (ClassCastException exception) { + logger.error("Failed to cast {} to Player", instance.getClass().getName()); + throw exception; + } + } +} diff --git a/spigot/src/main/resources/plugin.yml b/spigot/src/main/resources/plugin.yml new file mode 100644 index 00000000..36dc6876 --- /dev/null +++ b/spigot/src/main/resources/plugin.yml @@ -0,0 +1,10 @@ +name: ${outputName} +description: ${project.description} +version: ${project.version} +author: ${project.organization.name} +website: ${project.url} +main: org.geysermc.floodgate.SpigotPlugin +api-version: 1.13 +commands: #todo maybe add the commands directly to the commandmap? + linkaccount: + unlinkaccount: \ No newline at end of file diff --git a/velocity/pom.xml b/velocity/pom.xml index 4a7eefb4..a18569b5 100644 --- a/velocity/pom.xml +++ b/velocity/pom.xml @@ -3,14 +3,14 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - org.geysermc - floodgate-parent + parent + org.geysermc.floodgate 1.0-SNAPSHOT 4.0.0 ${parent.url} - floodgate-velocity + velocity @@ -36,12 +36,6 @@ ${velocity-version} provided - - org.javassist - javassist - 3.26.0-GA - compile - io.netty netty-all @@ -49,8 +43,8 @@ provided - org.geysermc - floodgate-common + org.geysermc.floodgate + common ${project.version} compile diff --git a/velocity/src/main/java/org/geysermc/floodgate/FloodgateAPI.java b/velocity/src/main/java/org/geysermc/floodgate/FloodgateAPI.java deleted file mode 100644 index bb7cca9a..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/FloodgateAPI.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.geysermc.floodgate; - -import com.velocitypowered.api.proxy.Player; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class FloodgateAPI extends AbstractFloodgateAPI { - private static Map encryptedData = new HashMap<>(); - - static void addEncryptedData(UUID uuid, String encryptedData) { - FloodgateAPI.encryptedData.put(uuid, encryptedData); // just override it I guess - } - - static void removeEncryptedData(UUID uuid) { - encryptedData.remove(uuid); - } - - public static String getEncryptedData(UUID uuid) { - return encryptedData.get(uuid); - } - - /** - * See {@link AbstractFloodgateAPI#getPlayer(UUID)} - */ - public static FloodgatePlayer getPlayer(Player player) { - return getPlayer(player.getUniqueId()); - } - - /** - * See {@link AbstractFloodgateAPI#isBedrockPlayer(UUID)} - */ - public static boolean isBedrockPlayer(Player player) { - return isBedrockPlayer(player.getUniqueId()); - } -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/VelocityFloodgateConfig.java b/velocity/src/main/java/org/geysermc/floodgate/VelocityFloodgateConfig.java deleted file mode 100644 index 3e49c0de..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/VelocityFloodgateConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.geysermc.floodgate; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; - -public class VelocityFloodgateConfig extends FloodgateConfig { - @JsonProperty(value = "send-floodgate-data") - @Getter - private boolean sendFloodgateData; -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java b/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java index be2ecebd..a9475f0e 100644 --- a/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java +++ b/velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java @@ -1,233 +1,64 @@ +/* + * 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; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.google.inject.Inject; -import com.velocitypowered.api.event.PostOrder; +import com.google.inject.Injector; import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent; -import com.velocitypowered.api.event.connection.DisconnectEvent; -import com.velocitypowered.api.event.connection.PreLoginEvent; -import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; -import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; -import com.velocitypowered.api.proxy.InboundConnection; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.api.util.GameProfile; -import io.netty.channel.Channel; -import io.netty.util.AttributeKey; -import lombok.Getter; -import net.kyori.text.TextComponent; -import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; -import org.geysermc.floodgate.command.LinkAccountCommand; -import org.geysermc.floodgate.command.UnlinkAccountCommand; -import org.geysermc.floodgate.injector.VelocityInjector; -import org.geysermc.floodgate.util.CommandUtil; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import org.geysermc.floodgate.module.*; import org.geysermc.floodgate.util.ReflectionUtil; +import org.slf4j.Logger; -import java.io.File; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.nio.file.Path; -import static org.geysermc.floodgate.util.ReflectionUtil.*; - -public class VelocityPlugin { - @Getter private VelocityFloodgateConfig config; - @Getter private PlayerLink playerLink; - private HandshakeHandler handshakeHandler; - - private Set workingSet; - private Cache playerCache; - private Cache playersToKick; - - private final ProxyServer server; - private final Logger logger; - private boolean injectSucceed; +public final class VelocityPlugin { + private final FloodgatePlatform platform; @Inject - public VelocityPlugin(ProxyServer server, Logger logger) { - this.server = server; - // we're too late if we would do this in the init event - injectSucceed = false; - try { - injectSucceed = VelocityInjector.inject(this); - } catch (Exception e) { - e.printStackTrace(); - } - this.logger = logger; - if (!injectSucceed) return; + public VelocityPlugin(@DataDirectory Path dataDirectory, Injector guice, Logger logger) { + ReflectionUtil.setPrefix("com.velocitypowered.proxy"); - this.workingSet = new HashSet<>(); - this.playersToKick = CacheBuilder.newBuilder() - .maximumSize(3000) - .expireAfterWrite(50, TimeUnit.SECONDS) - .build(); - this.playerCache = CacheBuilder.newBuilder() - .maximumSize(500) - .expireAfterAccess(50, TimeUnit.SECONDS) - .build(); + long ctm = System.currentTimeMillis(); + Injector injector = guice.createChildInjector( + new CommonModule(dataDirectory), + new VelocityPlatformModule() + ); + + long endCtm = System.currentTimeMillis(); + logger.info("Took " + (endCtm - ctm) + "ms to boot Floodgate"); + + platform = injector.getInstance(FloodgatePlatform.class); } @Subscribe public void onProxyInitialization(ProxyInitializeEvent event) { - if (injectSucceed) { - logger.info("Floodgate injection process succeeded!"); - } else { - logger.severe("Failed to inject! Floodgate won't do anything."); - return; - } - - File dataFolder = new File("plugins/floodgate/"); - if (!dataFolder.exists()) { - dataFolder.mkdir(); - } - - config = FloodgateConfig.load(logger, dataFolder.toPath().resolve("config.yml"), VelocityFloodgateConfig.class); - playerLink = PlayerLink.initialize(logger, dataFolder.toPath(), config); - handshakeHandler = new HandshakeHandler(config.getPrivateKey(), true, config.getUsernamePrefix(), config.isReplaceSpaces()); - - CommandUtil commandUtil = new CommandUtil(); - server.getCommandManager().register(CommandUtil.LINK_ACCOUNT_COMMAND, new LinkAccountCommand(playerLink, commandUtil)); - server.getCommandManager().register(CommandUtil.UNLINK_ACCOUNT_COMMAND, new UnlinkAccountCommand(playerLink, commandUtil)); - } - - @Subscribe(order = PostOrder.EARLY) - public void onConnectionHandshake(ConnectionHandshakeEvent event) { - if (!injectSucceed) return; - workingSet.add(event.getConnection()); - - try { - Object cachedHandshake = ReflectionUtil.getValue(event.getConnection(), handshakeField); - String handshakeData = ReflectionUtil.getCastedValue(cachedHandshake, handshakeAddressField, String.class); - - HandshakeResult result = handshakeHandler.handle(handshakeData); - switch (result.getResultType()) { - case SUCCESS: - break; - case EXCEPTION: - playersToKick.put(event.getConnection(), config.getMessages().getInvalidKey()); - return; - case INVALID_DATA_LENGTH: - playersToKick.put(event.getConnection(), config.getMessages().getInvalidArgumentsLength()); - return; - default: - return; - } - - FloodgatePlayer player = result.getFloodgatePlayer(); - FloodgateAPI.addEncryptedData(player.getCorrectUniqueId(), result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]); - playerCache.put(event.getConnection(), player); - logger.info("Added " + player.getCorrectUsername() + " " + player.getCorrectUniqueId()); - } catch (Exception e) { - e.printStackTrace(); - } finally { - workingSet.remove(event.getConnection()); - } - } - - @Subscribe(order = PostOrder.EARLY) - public void onPreLogin(PreLoginEvent event) { - if (!injectSucceed) return; - - if (workingSet.contains(event.getConnection())) { - int count = 70; - // 70 * 70 / 1000 = 4.9 seconds to get removed from working cache - while (count-- != 0 && workingSet.contains(event.getConnection())) { - // should be 'just fine' because the event system is multithreaded - try { - Thread.sleep(70); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - if (workingSet.contains(event.getConnection())) { - String message = "Took more then 4.9 seconds (after PreLoginEvent) to finish Handshake data for "+event.getUsername(); - logger.warning(message); - event.setResult(PreLoginComponentResult.denied(TextComponent.of(message))); - return; - } - } - - FloodgatePlayer player = playerCache.getIfPresent(event.getConnection()); - if (player != null) { - System.out.println(event.getUsername()); - event.setResult(PreLoginComponentResult.forceOfflineMode()); - - // we can't rely on Velocity when it comes to kicking the old players, so with this system we only - // have to check if the connection (which is already closed at that time) has a FloodgatePlayer as attribute - try { - Object mcConnection = initialMinecraftConnectionField.get(event.getConnection()); - Channel channel = (Channel) channelField.get(mcConnection); - channel.attr(playerAttribute).set(player); - return; - } catch (Exception e) { - logger.log(Level.SEVERE, "Failed to add FloodgatePlayer to player", e); - playersToKick.put(event.getConnection(), "Failed to add FloodgatePlayer to player"); - } - } - - String message = playersToKick.getIfPresent(event.getConnection()); - if (message != null) { - playersToKick.invalidate(event.getConnection()); - event.setResult(PreLoginComponentResult.denied(TextComponent.of(message))); - } - } - - @Subscribe(order = PostOrder.EARLY) - public void onGameProfileRequest(GameProfileRequestEvent event) { - if (!injectSucceed) return; - - FloodgatePlayer player = playerCache.getIfPresent(event.getConnection()); - if (player != null) { - playerCache.invalidate(event.getConnection()); - event.setGameProfile(new GameProfile(player.getCorrectUniqueId(), player.getCorrectUsername(), new ArrayList<>())); - } - } - - @Subscribe(order = PostOrder.LAST) - public void onDisconnect(DisconnectEvent event) { - Player player = event.getPlayer(); - - try { - Object minecraftConnection = minecraftConnectionField.get(player); - Channel channel = (Channel) channelField.get(minecraftConnection); - FloodgatePlayer fPlayer = channel.attr(playerAttribute).get(); - if (fPlayer != null) { - FloodgateAPI.removePlayer(fPlayer); - FloodgateAPI.removeEncryptedData(event.getPlayer().getUniqueId()); - logger.info("Removed Bedrock player who was logged in as " + player.getUsername() + " " + player.getUniqueId()); - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Failed to remove the player", e); - } - } - - private Field handshakeField; - private Field handshakeAddressField; - - private final AttributeKey playerAttribute = AttributeKey.newInstance("floodgate-player"); - private Field initialMinecraftConnectionField; - private Field minecraftConnectionField; - private Field channelField; - - public void initReflection() { - ReflectionUtil.setPrefix("com.velocitypowered.proxy"); - Class IIC = getPrefixedClass("connection.client.InitialInboundConnection"); - Class HandshakePacket = getPrefixedClass("protocol.packet.Handshake"); - - handshakeField = getField(IIC, "handshake"); - handshakeAddressField = getField(HandshakePacket, "serverAddress"); - - Class minecraftConnection = getPrefixedClass("connection.MinecraftConnection"); - initialMinecraftConnectionField = getFieldOfType(IIC, minecraftConnection, true); - Class connectedPlayer = getPrefixedClass("connection.client.ConnectedPlayer"); - minecraftConnectionField = getFieldOfType(connectedPlayer, minecraftConnection, true); - channelField = getFieldOfType(minecraftConnection, Channel.class, true); + platform.enable(new CommandModule(), new VelocityListenerModule(), + new VelocityAddonModule()); } } diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java new file mode 100644 index 00000000..91a17bb8 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityDataAddon.java @@ -0,0 +1,101 @@ +/* + * 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.addon.data; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import io.netty.channel.Channel; +import io.netty.channel.EventLoop; +import io.netty.util.AttributeKey; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; + +import java.util.HashMap; +import java.util.Map; + +public final class VelocityDataAddon implements InjectorAddon { + @Inject private HandshakeHandler handshakeHandler; + @Inject private ProxyFloodgateConfig config; + @Inject private ProxyFloodgateApi api; + @Inject private FloodgateLogger logger; + + @Inject + @Named("packetHandler") + private String packetHandler; + + @Inject + @Named("packetEncoder") + private String packetEncoder; + + @Inject + @Named("playerAttribute") + private AttributeKey playerAttribute; + + @Inject + @Named("kickMessageAttribute") + private AttributeKey kickMessageAttribute; + + private final Map playerMap = new HashMap<>(); + + @Override + public void onInject(Channel channel, boolean proxyToServer) { + if (proxyToServer) { + channel.pipeline().addAfter( + packetEncoder, "floodgate_data_handler", + new VelocityServerDataHandler(config, api, playerMap) + ); + } else { + // The handler is already added so we should add our handler before it + channel.pipeline().addBefore( + packetHandler, "floodgate_data_handler", + new VelocityProxyDataHandler( + config, api, handshakeHandler, playerAttribute, + kickMessageAttribute, playerMap, logger + ) + ); + } + } + + @Override + public void onLoginDone(Channel channel) { + onRemoveInject(channel); + } + + @Override + public void onRemoveInject(Channel channel) { + channel.pipeline().remove("floodgate_data_handler"); + } + + @Override + public boolean shouldInject() { + return true; + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java new file mode 100644 index 00000000..b0eda845 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityProxyDataHandler.java @@ -0,0 +1,120 @@ +/* + * 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.addon.data; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.EventLoop; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.AttributeKey; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.HandshakeHandler; +import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; + +import java.lang.reflect.Field; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.geysermc.floodgate.util.ReflectionUtil.*; + +@RequiredArgsConstructor +public final class VelocityProxyDataHandler extends SimpleChannelInboundHandler { + private static final Field HANDSHAKE; + private static final Class HANDSHAKE_PACKET; + private static final Field HANDSHAKE_SERVER_ADDRESS; + + private final ProxyFloodgateConfig config; + private final ProxyFloodgateApi api; + private final HandshakeHandler handshakeHandler; + + private final AttributeKey playerAttribute; + private final AttributeKey kickMessageAttribute; + private final Map playerMap; + + private final FloodgateLogger logger; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + // we're only interested in the Handshake packet + if (!HANDSHAKE_PACKET.isInstance(msg)) { + ctx.fireChannelRead(msg); + return; + } + + handleClientToProxy(ctx, msg); + ctx.fireChannelRead(msg); + } + + private void handleClientToProxy(ChannelHandlerContext ctx, Object packet) { + String address = getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS); + + HandshakeResult result = handshakeHandler.handle(address); + switch (result.getResultType()) { + case SUCCESS: + break; + case EXCEPTION: + ctx.channel().attr(kickMessageAttribute).set(config.getMessages().getInvalidKey()); + case INVALID_DATA_LENGTH: + ctx.channel().attr(kickMessageAttribute) + .set(config.getMessages().getInvalidArgumentsLength()); + return; + default: + return; + } + + FloodgatePlayer player = result.getFloodgatePlayer(); + + // we can't rely on Velocity when it comes to kicking the old players, so with this + // system we only have to check if the connection (which is already closed at that time) + // has the FloodgatePlayer attribute + ctx.channel().attr(playerAttribute).set(player); + playerMap.put(ctx.channel().eventLoop(), player); + + api.addEncryptedData(player.getCorrectUniqueId(), + result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]); + logger.info("Floodgate player who is logged in as {} {} joined", + player.getCorrectUsername(), player.getCorrectUniqueId()); + } + + static { + Class initialInboundConnection = + getPrefixedClass("connection.client.InitialInboundConnection"); + checkNotNull(initialInboundConnection, "InitialInboundConnection class cannot be null"); + + HANDSHAKE = getField(initialInboundConnection, "handshake"); + checkNotNull(HANDSHAKE, "Handshake field cannot be null"); + + HANDSHAKE_PACKET = getPrefixedClass("protocol.packet.Handshake"); + checkNotNull(HANDSHAKE_PACKET, "Handshake packet class cannot be null"); + + HANDSHAKE_SERVER_ADDRESS = getField(HANDSHAKE_PACKET, "serverAddress"); + checkNotNull(HANDSHAKE_SERVER_ADDRESS, "Address field of the Handshake packet cannot be null"); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java new file mode 100644 index 00000000..37a534e6 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/addon/data/VelocityServerDataHandler.java @@ -0,0 +1,100 @@ +/* + * 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.addon.data; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.EventLoop; +import io.netty.handler.codec.MessageToMessageEncoder; +import io.netty.util.ReferenceCountUtil; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER; +import static org.geysermc.floodgate.util.ReflectionUtil.*; + +@RequiredArgsConstructor +public class VelocityServerDataHandler extends MessageToMessageEncoder { + private static final Class HANDSHAKE_PACKET; + private static final Field HANDSHAKE_SERVER_ADDRESS; + + private final ProxyFloodgateConfig config; + private final ProxyFloodgateApi api; + + private final Map playerMap; + + @Override + protected void encode(ChannelHandlerContext ctx, Object packet, List out) { + ReferenceCountUtil.retain(packet); + if (!HANDSHAKE_PACKET.isInstance(packet) || !config.isSendFloodgateData()) { + System.out.println(HANDSHAKE_PACKET.isInstance(packet)+" "+config.isSendFloodgateData()); + out.add(packet); + return; + } + + String address = getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS); + + // works because the EventLoop is shared between the Player and the ServerConnection + // todo check if it actually works (by using multiple accounts and log if the map + // overrides older ones) + FloodgatePlayer player = playerMap.get(ctx.channel().eventLoop()); + + // player is not a Floodgate player + if (player == null) { + System.out.println("Not Floodgate player"); + out.add(packet); + return; + } + + String encryptedData = api.getEncryptedData(player.getCorrectUniqueId()); + checkArgument(encryptedData != null, "Encrypted data cannot be null"); + + // use the same system that we use on bungee, our data goes before all the other data + String[] split = address.split("\0"); + String remaining = address.substring(split[0].length()); + + setValue(packet, HANDSHAKE_SERVER_ADDRESS, + split[0] + '\0' + FLOODGATE_IDENTIFIER + '\0' + encryptedData + remaining); + System.out.println("done"); + out.add(packet); + } + + static { + HANDSHAKE_PACKET = getPrefixedClass("protocol.packet.Handshake"); + checkNotNull(HANDSHAKE_PACKET, "Handshake packet class cannot be null"); + + HANDSHAKE_SERVER_ADDRESS = getField(HANDSHAKE_PACKET, "serverAddress"); + checkNotNull(HANDSHAKE_SERVER_ADDRESS, "Address field of the Handshake packet cannot be null"); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java b/velocity/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java deleted file mode 100644 index 9bcc9220..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/command/LinkAccountCommand.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.geysermc.floodgate.command; - -import com.velocitypowered.api.command.Command; -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.proxy.Player; -import org.geysermc.floodgate.AbstractLinkAccountCommand; -import org.geysermc.floodgate.PlayerLink; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.CommonMessage; - -import java.util.UUID; - -public class LinkAccountCommand extends AbstractLinkAccountCommand implements Command { - public LinkAccountCommand(PlayerLink link, CommandUtil commandUtil) { - super(link, commandUtil); - } - - @Override - public void execute(CommandSource sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage(getCommandUtil().getOrAddCachedMessage(CommonMessage.NOT_A_PLAYER)); - return; - } - UUID uuid = ((Player) sender).getUniqueId(); - String username = ((Player) sender).getUsername(); - execute((Player) sender, uuid, username, args); - } -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java b/velocity/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java deleted file mode 100644 index 899ebb73..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/command/UnlinkAccountCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.geysermc.floodgate.command; - -import com.velocitypowered.api.command.Command; -import com.velocitypowered.api.command.CommandSource; -import com.velocitypowered.api.proxy.Player; -import org.geysermc.floodgate.AbstractUnlinkAccountCommand; -import org.geysermc.floodgate.PlayerLink; -import org.geysermc.floodgate.util.CommandUtil; -import org.geysermc.floodgate.util.CommonMessage; - -import java.util.UUID; - -public class UnlinkAccountCommand extends AbstractUnlinkAccountCommand implements Command { - public UnlinkAccountCommand(PlayerLink link, CommandUtil commandUtil) { - super(link, commandUtil); - } - - @Override - public void execute(CommandSource sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage(getCommandUtil().getOrAddCachedMessage(CommonMessage.NOT_A_PLAYER)); - return; - } - UUID uuid = ((Player) sender).getUniqueId(); - execute((Player) sender, uuid); - } -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/command/VelocityCommandRegistration.java b/velocity/src/main/java/org/geysermc/floodgate/command/VelocityCommandRegistration.java new file mode 100644 index 00000000..03430167 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/command/VelocityCommandRegistration.java @@ -0,0 +1,74 @@ +/* + * 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.command; + +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.platform.command.Command; +import org.geysermc.floodgate.platform.command.CommandRegistration; +import org.geysermc.floodgate.util.VelocityCommandUtil; + +import java.util.UUID; + +@RequiredArgsConstructor +public final class VelocityCommandRegistration implements CommandRegistration { + private final CommandManager commandManager; + private final VelocityCommandUtil commandUtil; + + @Override + public void register(Command command) { + commandManager.register(command.getName(), + new VelocityCommandWrapper(commandUtil, command)); + } + + @RequiredArgsConstructor + protected static final class VelocityCommandWrapper + implements com.velocitypowered.api.command.Command { + private final VelocityCommandUtil commandUtil; + private final Command command; + + @Override + public void execute(CommandSource source, @NonNull String[] args) { + if (!(source instanceof Player)) { + if (command.isRequirePlayer()) { + commandUtil.sendMessage(source, CommonCommandMessage.NOT_A_PLAYER); + return; + } + command.execute(source, args); + return; + } + + Player player = (Player) source; + UUID uuid = player.getUniqueId(); + String username = player.getUsername(); + command.execute(source, uuid, username, args); + } + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java b/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java new file mode 100644 index 00000000..968057f2 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/inject/velocity/VelocityInjector.java @@ -0,0 +1,100 @@ +/* + * 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.inject.velocity; + +import com.velocitypowered.api.proxy.ProxyServer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.inject.CommonPlatformInjector; + +import javax.naming.OperationNotSupportedException; +import java.lang.reflect.Method; + +import static org.geysermc.floodgate.util.ReflectionUtil.*; + +@RequiredArgsConstructor +public final class VelocityInjector extends CommonPlatformInjector { + private final ProxyServer server; + @Getter private boolean injected = false; + + @SuppressWarnings("rawtypes") + public boolean inject() { + if (isInjected()) return true; + + Object connectionManager = getValue(server, "cm"); + + // Client <-> Proxy + + Object serverInitializerHolder = getValue(connectionManager, "serverChannelInitializer"); + ChannelInitializer serverInitializer = castedInvoke(serverInitializerHolder, "get"); + + Method serverSetter = getMethod(serverInitializerHolder, "set", ChannelInitializer.class); + invoke(serverInitializerHolder, serverSetter, + new VelocityChannelInitializer(this, serverInitializer, false)); + + // Proxy <-> Server + + Object backendInitializerHolder = getValue(connectionManager,"backendChannelInitializer"); + ChannelInitializer backendInitializer = castedInvoke(backendInitializerHolder, "get"); + + Method backendSetter = getMethod(backendInitializerHolder, "set", ChannelInitializer.class); + invoke(backendInitializerHolder, backendSetter, + new VelocityChannelInitializer(this, backendInitializer, true)); + return injected = true; + } + + @Override + public boolean removeInjection() throws Exception { + //todo implement injection removal support + throw new OperationNotSupportedException( + "Floodgate cannot remove the Velocity injection at the moment"); + } + + @RequiredArgsConstructor + @SuppressWarnings("rawtypes") + private static final class VelocityChannelInitializer extends ChannelInitializer { + private static Method initChannel; + + private final VelocityInjector injector; + private final ChannelInitializer original; + private final boolean proxyToServer; + + @Override + protected void initChannel(Channel channel) { + invoke(original, initChannel, channel); + + injector.injectAddonsCall(channel, proxyToServer); + injector.addInjectedClient(channel); + } + + static { + initChannel = getMethod(ChannelInitializer.class, "initChannel", Channel.class); + } + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/injector/VelocityInjector.java b/velocity/src/main/java/org/geysermc/floodgate/injector/VelocityInjector.java deleted file mode 100644 index d46f5a02..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/injector/VelocityInjector.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.geysermc.floodgate.injector; - -import com.velocitypowered.api.proxy.Player; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtField; -import javassist.Modifier; -import javassist.bytecode.Descriptor; -import lombok.Getter; -import org.geysermc.floodgate.FloodgateAPI; -import org.geysermc.floodgate.VelocityPlugin; -import org.geysermc.floodgate.util.ReflectionUtil; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.function.BiConsumer; - -import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER; - -public class VelocityInjector { - @Getter private static boolean injected = false; - - public static boolean inject(VelocityPlugin plugin) throws Exception { - if (isInjected()) return true; - // Tnx Velocity for making it difficult x) - // But I made my way in >:) - // Please note that doing this is not recommended - - // This method will edit the source code of MinecraftConnection to allow us to edit any outgoing packet. - - ClassPool classPool = ClassPool.getDefault(); - CtClass ctClass = classPool.get("com.velocitypowered.proxy.connection.MinecraftConnection"); - ctClass.defrost(); - - // create dataConsumer field - CtClass biConsumerClass = classPool.get("java.util.function.BiConsumer"); - CtField dataConsumer = new CtField(biConsumerClass, "dataConsumer", ctClass); - dataConsumer.setModifiers(Modifier.PUBLIC | Modifier.STATIC); - ctClass.addField(dataConsumer); - - // override write and delayedWrite in MinecraftConnection - String voidObjectDescriptor = Descriptor.ofMethod(CtClass.voidType, new CtClass[] { classPool.get("java.lang.Object") }); - String codeToInsert = - "if (msg != null && dataConsumer != null && msg instanceof com.velocitypowered.proxy.protocol.MinecraftPacket) {\n" + - " dataConsumer.accept(this, (com.velocitypowered.proxy.protocol.MinecraftPacket)msg);\n" + - "}\n"; - - ctClass.getMethod("write", voidObjectDescriptor).insertAt(1, codeToInsert); - ctClass.getMethod("delayedWrite", voidObjectDescriptor).insertAt(1, codeToInsert); - - Class clazz = ctClass.toClass(); - - // The most important part is done, - // So now we call initReflection to let us use getPrefixedClass - plugin.initReflection(); - - // handshake handle stuff - Class handshakePacket = ReflectionUtil.getPrefixedClass("protocol.packet.Handshake"); - Field serverAddress = ReflectionUtil.getField(handshakePacket, "serverAddress"); - - Field sessionHandlerField = ReflectionUtil.getField(clazz, "sessionHandler"); - Class loginHandler = ReflectionUtil.getPrefixedClass("connection.backend.LoginSessionHandler"); - - Field serverConnField = ReflectionUtil.getField(loginHandler, "serverConn"); - - Class serverConnection = ReflectionUtil.getPrefixedClass("connection.backend.VelocityServerConnection"); - Method playerGetMethod = ReflectionUtil.getMethod(serverConnection, "getPlayer"); - assert playerGetMethod != null; - - // create and set our custom made Consumer - - BiConsumer biConsumer = (minecraftConnection, packet) -> { - // This consumer is only called when the server is sending data (outgoing). - // So when we get a handshake packet it'll be Velocity trying to connect to a server. - // And that is exactly what we need, to edit the outgoing handshake packet to include Floodgate data - if (plugin.getConfig().isSendFloodgateData()) { - try { - if (handshakePacket.isInstance(packet)) { - String address = ReflectionUtil.getCastedValue(packet, serverAddress, String.class); - - Object sessionHandler = ReflectionUtil.getValue(minecraftConnection, sessionHandlerField); - if (loginHandler.isInstance(sessionHandler)) { - Object serverConn = ReflectionUtil.getValue(sessionHandler, serverConnField); - Player connectedPlayer = ReflectionUtil.invokeCasted(serverConn, playerGetMethod, Player.class); - - String encryptedData = FloodgateAPI.getEncryptedData(connectedPlayer.getUniqueId()); - if (encryptedData == null) return; // we know enough, this is not a Floodgate player - - String[] splitted = address.split("\0"); - String remaining = address.substring(splitted[0].length()); - ReflectionUtil.setValue(packet, serverAddress, splitted[0] + '\0' + FLOODGATE_IDENTIFIER + '\0' + encryptedData + remaining); - // keep the same system as we have on Bungeecord. Our data is before the Bungee data - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - - Class minecraftConnection = ReflectionUtil.getPrefixedClass("connection.MinecraftConnection"); - ReflectionUtil.setValue(null, ReflectionUtil.getField(minecraftConnection, "dataConsumer"), biConsumer); - return true; - } -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java new file mode 100644 index 00000000..7d0e51b5 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java @@ -0,0 +1,145 @@ +/* + * 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.listener; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.PreLoginEvent; +import com.velocitypowered.api.event.player.GameProfileRequestEvent; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.util.GameProfile; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import net.kyori.adventure.text.TextComponent; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import static org.geysermc.floodgate.util.ReflectionUtil.*; + +public final class VelocityListener { + private static final Field INITIAL_MINECRAFT_CONNECTION; + private static final Field MINECRAFT_CONNECTION; + private static final Field CHANNEL; + + private final ProxyFloodgateApi api; + private final AttributeKey playerAttribute; + private final AttributeKey kickMessageAttribute; + private final FloodgateLogger logger; + + private final Cache playerCache; + + public VelocityListener(ProxyFloodgateApi api, + AttributeKey playerAttribute, + AttributeKey kickMessageAttribute, + FloodgateLogger logger) { + this.api = api; + this.playerAttribute = playerAttribute; + this.kickMessageAttribute = kickMessageAttribute; + this.logger = logger; + + this.playerCache = CacheBuilder.newBuilder() + .maximumSize(500) + .expireAfterAccess(20, TimeUnit.SECONDS) + .build(); + } + + @Subscribe(order = PostOrder.EARLY) + public void onPreLogin(PreLoginEvent event) { + FloodgatePlayer player = null; + String kickMessage; + try { + Object mcConnection = getValue(event.getConnection(), INITIAL_MINECRAFT_CONNECTION); + Channel channel = getCastedValue(mcConnection, CHANNEL); + + player = channel.attr(playerAttribute).get(); + if (player != null) { + event.setResult(PreLoginEvent.PreLoginComponentResult.forceOfflineMode()); + } + + kickMessage = channel.attr(kickMessageAttribute).get(); + } catch (Exception exception) { + logger.error("Failed get the FloodgatePlayer from the player's channel", exception); + kickMessage = "Failed to get the FloodgatePlayer from the players's Channel"; + } + + if (kickMessage != null) { + event.setResult(PreLoginEvent.PreLoginComponentResult.denied(TextComponent.of(kickMessage))); + return; + } + + if (player != null) { + playerCache.put(event.getConnection(), player); + } + } + + @Subscribe(order = PostOrder.EARLY) + public void onGameProfileRequest(GameProfileRequestEvent event) { + FloodgatePlayer player = playerCache.getIfPresent(event.getConnection()); + if (player != null) { + playerCache.invalidate(event.getConnection()); + event.setGameProfile(new GameProfile( + player.getCorrectUniqueId(), player.getCorrectUsername(), new ArrayList<>())); + } + } + + @Subscribe(order = PostOrder.LAST) + public void onDisconnect(DisconnectEvent event) { + Player player = event.getPlayer(); + try { + Object minecraftConnection = getValue(player, MINECRAFT_CONNECTION); + Channel channel = getCastedValue(minecraftConnection, CHANNEL); + FloodgatePlayer fPlayer = channel.attr(playerAttribute).get(); + + if (fPlayer != null && api.removePlayer(fPlayer)) { + api.removeEncryptedData(event.getPlayer().getUniqueId()); + logger.info("Removed Bedrock player who was logged in as {} {} ", + player.getUsername(), player.getUniqueId()); + } + } catch (Exception exception) { + logger.error("Failed to remove the player", exception); + } + } + + static { + Class initialConnection = getPrefixedClass("connection.client.InitialInboundConnection"); + + Class minecraftConnection = getPrefixedClass("connection.MinecraftConnection"); + INITIAL_MINECRAFT_CONNECTION = getFieldOfType(initialConnection, minecraftConnection, true); + Class connectedPlayer = getPrefixedClass("connection.client.ConnectedPlayer"); + MINECRAFT_CONNECTION = getFieldOfType(connectedPlayer, minecraftConnection, true); + CHANNEL = getFieldOfType(minecraftConnection, Channel.class, true); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListenerRegistration.java b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListenerRegistration.java new file mode 100644 index 00000000..a3ed8401 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListenerRegistration.java @@ -0,0 +1,43 @@ +/* + * 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.listener; + +import com.velocitypowered.api.event.EventManager; +import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.VelocityPlugin; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; + +@RequiredArgsConstructor +public final class VelocityListenerRegistration implements ListenerRegistration { + private final EventManager eventManager; + private final VelocityPlugin plugin; + + @Override + public void register(Object listener) { + eventManager.register(plugin, listener); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jLogger.java b/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jLogger.java new file mode 100644 index 00000000..99aaf6dc --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/logger/Slf4jLogger.java @@ -0,0 +1,71 @@ +/* + * 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.logger; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.geysermc.floodgate.util.MessageFormatter.format; + +@AllArgsConstructor +@NoArgsConstructor +public final class Slf4jLogger implements FloodgateLogger { + private Logger logger = LoggerFactory.getLogger(LOGGER_NAME); + + @Override + public void error(String message, Object... args) { + logger.error(message, args); + } + + @Override + public void error(String message, Throwable throwable, Object... args) { + logger.error(format(message, args), throwable); + } + + @Override + public void warn(String message, Object... args) { + logger.warn(message, args); + } + + @Override + public void info(String message, Object... args) { + logger.info(message, args); + } + + @Override + public void debug(String message, Object... args) { + logger.debug(message, args); + } + + @Override + public void trace(String message, Object... args) { + logger.trace(message, args); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityAddonModule.java b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityAddonModule.java new file mode 100644 index 00000000..366ead08 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityAddonModule.java @@ -0,0 +1,61 @@ +/* + * 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.AbstractModule; +import com.google.inject.Singleton; +import com.google.inject.multibindings.ProvidesIntoSet; +import org.geysermc.floodgate.addon.AddonManagerAddon; +import org.geysermc.floodgate.addon.DebugAddon; +import org.geysermc.floodgate.addon.data.VelocityDataAddon; +import org.geysermc.floodgate.api.inject.InjectorAddon; +import org.geysermc.floodgate.register.AddonRegister; + +public final class VelocityAddonModule extends AbstractModule { + @Override + protected void configure() { + bind(AddonRegister.class).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon managerAddon() { + return new AddonManagerAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon dataAddon() { + return new VelocityDataAddon(); + } + + @Singleton + @ProvidesIntoSet + public InjectorAddon debugAddon() { + return new DebugAddon(); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityListenerModule.java b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityListenerModule.java new file mode 100644 index 00000000..6d3ca869 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityListenerModule.java @@ -0,0 +1,52 @@ +/* + * 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.*; +import com.google.inject.multibindings.ProvidesIntoSet; +import com.google.inject.name.Named; +import io.netty.util.AttributeKey; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.api.player.FloodgatePlayer; +import org.geysermc.floodgate.listener.VelocityListener; +import org.geysermc.floodgate.register.ListenerRegister; + +public final class VelocityListenerModule extends AbstractModule { + @Override + protected void configure() { + bind(new TypeLiteral>() {}).asEagerSingleton(); + } + + @Singleton + @ProvidesIntoSet + public Object velocityListener(ProxyFloodgateApi api, FloodgateLogger logger, + @Named("playerAttribute") AttributeKey playerAttr, + @Named("kickMessageAttribute") AttributeKey kickMessageAttr) { + return new VelocityListener(api, playerAttr, kickMessageAttr, logger); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java new file mode 100644 index 00000000..264a17b8 --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/module/VelocityPlatformModule.java @@ -0,0 +1,144 @@ +/* + * 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.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.proxy.ProxyServer; +import io.netty.util.AttributeKey; +import org.geysermc.floodgate.VelocityPlugin; +import org.geysermc.floodgate.api.ProxyFloodgateApi; +import org.geysermc.floodgate.api.SimpleFloodgateApi; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.command.VelocityCommandRegistration; +import org.geysermc.floodgate.config.FloodgateConfig; +import org.geysermc.floodgate.config.ProxyFloodgateConfig; +import org.geysermc.floodgate.inject.CommonPlatformInjector; +import org.geysermc.floodgate.inject.velocity.VelocityInjector; +import org.geysermc.floodgate.listener.VelocityListenerRegistration; +import org.geysermc.floodgate.logger.Slf4jLogger; +import org.geysermc.floodgate.platform.command.CommandRegistration; +import org.geysermc.floodgate.platform.command.util.CommandUtil; +import org.geysermc.floodgate.platform.listener.ListenerRegistration; +import org.geysermc.floodgate.util.VelocityCommandUtil; +import org.slf4j.Logger; + +public final class VelocityPlatformModule extends AbstractModule { + @Override + protected void configure() { + bind(SimpleFloodgateApi.class).to(ProxyFloodgateApi.class); + bind(CommandUtil.class).to(VelocityCommandUtil.class); + } + + @Provides + @Singleton + @Named("configClass") + public Class floodgateConfigClass() { + return ProxyFloodgateConfig.class; + } + + @Provides + @Singleton + public ProxyFloodgateApi proxyFloodgateApi() { + return new ProxyFloodgateApi(); + } + + @Provides + @Singleton + public FloodgateLogger floodgateLogger(Logger logger) { + return new Slf4jLogger(logger); + } + + /* + Commands / Listeners + */ + + @Provides + @Singleton + public CommandRegistration commandRegistration(CommandManager commandManager, + VelocityCommandUtil commandUtil) { + return new VelocityCommandRegistration(commandManager, commandUtil); + } + + @Provides + @Singleton + public VelocityCommandUtil commandUtil(FloodgateLogger logger) { + return new VelocityCommandUtil(logger); + } + + @Provides + @Singleton + public ListenerRegistration listenerRegistration(EventManager eventManager, + VelocityPlugin plugin) { + return new VelocityListenerRegistration(eventManager, plugin); + } + + /* + DebugAddon / PlatformInjector + */ + + @Provides + @Singleton + public CommonPlatformInjector platformInjector(ProxyServer server) { + return new VelocityInjector(server); + } + + @Provides + @Named("packetEncoder") + public String packetEncoder() { + return "minecraft-encoder"; + } + + @Provides + @Named("packetDecoder") + public String packetDecoder() { + return "minecraft-decoder"; + } + + @Provides + @Named("packetHandler") + public String packetHandler() { + return "handler"; + } + + @Provides + @Named("implementationName") + public String implementationName() { + return "Velocity"; + } + + @Provides + @Singleton + @Named("kickMessageAttribute") + public AttributeKey kickMessageAttribute() { + return AttributeKey.newInstance("floodgate-kick-message"); + } +} diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/CommandUtil.java b/velocity/src/main/java/org/geysermc/floodgate/util/CommandUtil.java deleted file mode 100644 index 93fbabf4..00000000 --- a/velocity/src/main/java/org/geysermc/floodgate/util/CommandUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.geysermc.floodgate.util; - -import com.velocitypowered.api.proxy.Player; -import net.kyori.text.TextComponent; -import net.kyori.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.floodgate.command.CommandMessage; - -public class CommandUtil extends AbstractCommandResponseCache implements ICommandUtil { - @Override - public void sendMessage(Player player, CommandMessage message, Object... args) { - player.sendMessage(getOrAddCachedMessage(message, args)); - } - - @Override - public void kickPlayer(Player player, CommandMessage message, Object... args) { - player.disconnect(getOrAddCachedMessage(message, args)); - } - - @Override - protected TextComponent transformMessage(String message) { - return LegacyComponentSerializer.legacy().deserialize(message); - } -} diff --git a/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java new file mode 100644 index 00000000..b396a91a --- /dev/null +++ b/velocity/src/main/java/org/geysermc/floodgate/util/VelocityCommandUtil.java @@ -0,0 +1,65 @@ +/* + * 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 com.velocitypowered.api.proxy.Player; +import lombok.RequiredArgsConstructor; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.geysermc.floodgate.api.logger.FloodgateLogger; +import org.geysermc.floodgate.platform.command.CommandMessage; +import org.geysermc.floodgate.platform.command.util.CommandResponseCache; +import org.geysermc.floodgate.platform.command.util.CommandUtil; + +@RequiredArgsConstructor +public final class VelocityCommandUtil extends CommandResponseCache implements CommandUtil { + private final FloodgateLogger logger; + + @Override + public void sendMessage(Object player, CommandMessage message, Object... args) { + cast(player).sendMessage(getOrAddCachedMessage(message, args)); + } + + @Override + public void kickPlayer(Object player, CommandMessage message, Object... args) { + cast(player).disconnect(getOrAddCachedMessage(message, args)); + } + + @Override + protected TextComponent transformMessage(String message) { + return LegacyComponentSerializer.legacy().deserialize(message); + } + + protected Player cast(Object instance) { + try { + return (Player) instance; + } catch (ClassCastException exception) { + logger.error("Failed to cast {} to Player", instance.getClass().getName()); + throw exception; + } + } +} diff --git a/velocity/src/main/resources/config.yml b/velocity/src/main/resources/config.yml deleted file mode 100644 index f0485db9..00000000 --- a/velocity/src/main/resources/config.yml +++ /dev/null @@ -1,41 +0,0 @@ -# In Floodgate bedrock player data is send encrypted -# The following value should point to the key Floodgate generated. -# The public key should be used for the Geyser(s) and the private key for the Floodgate(s) -key-file-name: key.pem - -# Floodgate prepends a prefix to bedrock usernames to avoid conflicts -# However, certain conflicts can cause issues with some plugins so this prefix is configurable using the property below -# It is recommended to use a prefix that does not contain alphanumerical to avoid the possibility of duplicate usernames. -username-prefix: "*" - -# Should spaces be replaced with '_' in bedrock usernames? -replace-spaces: true - -# Should Velocity send the bedrock player data to the servers it is connecting to? -# This requires Floodgate to be installed on the servers. -# You'll get kicked if you don't use the plugin. The default value is false because of it -send-floodgate-data: false - -disconnect: - # The disconnect message Geyser users should get when connecting - # to the server with an invalid key - invalid-key: Please connect through the official Geyser - # The disconnect message Geyser users should get when connecting - # to the server with the correct key but not with the correct data format - invalid-arguments-length: Expected {0} arguments, got {1}. Is Geyser up-to-date? - -# Configuration for player linking -player-link: - # Whether to enable the linking system. Turning this off will prevent - # players from using the linking feature even if they are already linked. - enable: false - # The type of storage system you want to use - # Currently implemented: SQLite - type: sqlite - # Whether to allow the use of /linkaccount and /unlinkaccount - # You can also use allow specific people to use the commands using the - # permissions floodgate.linkaccount and floodgate.unlinkaccount. - # This is only for linking, already connected people will stay connected - allow-linking: true - # The amount of time until a link code expires in seconds - link-code-timeout: 300 \ No newline at end of file diff --git a/velocity/src/main/resources/velocity-plugin.json b/velocity/src/main/resources/velocity-plugin.json index 6bdc8e29..872b82e7 100644 --- a/velocity/src/main/resources/velocity-plugin.json +++ b/velocity/src/main/resources/velocity-plugin.json @@ -1 +1 @@ -{"id": "floodgate", "name": "${project.artifactId}", "version": "${project.version}", "description": "${project.description}", "url": "${project.url}", "authors": ["${project.organization.name}"], "main": "org.geysermc.floodgate.VelocityPlugin"} \ No newline at end of file +{"id": "${project.parent.name}", "name": "${outputName}", "version": "${project.version}", "description": "${project.description}", "url": "${project.url}", "authors": ["${project.organization.name}"], "main": "org.geysermc.floodgate.VelocityPlugin"} \ No newline at end of file