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 extends PlayerLink> 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 extends FloodgateConfig> 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 extends PlayerLink> 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 extends PlayerLink>) 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 extends FloodgateConfig> 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 extends FloodgateConfig> 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 extends FloodgateConfig> 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