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

Initial Floodgate 2.0 commit

This commit is contained in:
Tim203
2020-07-29 13:00:54 +02:00
parent d44377032b
commit f440fb76b0
136 changed files with 7417 additions and 2425 deletions

9
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="100" />
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

6
.idea/copyright/GeyserMC.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright (c) 2019-&amp;#36;today.year GeyserMC. http://geysermc.org&#10; &#10; Permission is hereby granted, free of charge, to any person obtaining a copy&#10; of this software and associated documentation files (the &quot;Software&quot;), to deal&#10; in the Software without restriction, including without limitation the rights&#10; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell&#10; copies of the Software, and to permit persons to whom the Software is&#10; furnished to do so, subject to the following conditions:&#10; &#10; The above copyright notice and this permission notice shall be included in&#10; all copies or substantial portions of the Software.&#10; &#10; THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR&#10; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,&#10; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE&#10; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER&#10; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,&#10; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN&#10; THE SOFTWARE.&#10; &#10; @author GeyserMC&#10; @link https://github.com/GeyserMC/Floodgate&#10; " />
<option name="myName" value="GeyserMC" />
</copyright>
</component>

7
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="CopyrightManager">
<settings default="GeyserMC">
<module2copyright>
<element module="all" copyright="Geyser" />
</module2copyright>
</settings>
</component>

77
api/pom.xml Normal file
View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<parent>
<artifactId>parent</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api</artifactId>
<repositories>
<repository>
<id>nukkitx-release-repo</id>
<url>https://repo.nukkitx.com/maven-releases/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>nukkitx-snapshot-repo</id>
<url>https://repo.nukkitx.com/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>common</artifactId>
<version>${geyser-version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>4.1.49.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<finalName>${outputName}</finalName>
<shadedArtifactAttached>true</shadedArtifactAttached>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.api;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.util.UUID;
public interface FloodgateApi {
/**
* Method to determine if the given <b>online</b> player is a bedrock player
*
* @param uuid The uuid of the <b>online</b> player
* @return true if the given <b>online</b> player is a Bedrock player
*/
boolean isBedrockPlayer(UUID uuid);
/**
* Get info about the given Bedrock player
*
* @param uuid the uuid of the <b>online</b> Bedrock player
* @return FloodgatePlayer if the given uuid is a Bedrock player
*/
FloodgatePlayer getPlayer(UUID uuid);
/**
* Create a valid Java player uuid of a xuid
*
* @param xuid the xuid that should be converted
* @return the created uuid based of the given xuid
*/
UUID createJavaPlayerId(long xuid);
/**
* Checks if the uuid of the player has the {@link #createJavaPlayerId(long)} format.
* This method can't validate a linked player uuid, since that doesn't equal the format.
* Use {@link #isBedrockPlayer(UUID)} if you want to include linked accounts.
*
* @param uuid the uuid to check
* @return true if the given uuid has the correct format.
*/
boolean isFloodgateId(UUID uuid);
/**
* Returns the instance that manages all the linking.
*/
default PlayerLink getPlayerLink() {
return InstanceHolder.getPlayerLink();
}
/**
* Returns the Floodgate API instance.
* This method is equal to running {@link InstanceHolder#getInstance()}
*/
static FloodgateApi getInstance() {
return InstanceHolder.getInstance();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.api;
import lombok.Getter;
import org.geysermc.floodgate.api.link.PlayerLink;
import java.util.UUID;
public final class InstanceHolder {
@Getter private static FloodgateApi instance;
@Getter private static PlayerLink playerLink;
private static UUID key;
public static boolean setInstance(FloodgateApi floodgateApi, PlayerLink link, UUID key) {
if (instance == null) {
InstanceHolder.key = key;
} else if (!InstanceHolder.key.equals(key)) {
return false;
}
instance = floodgateApi;
playerLink = link;
return true;
}
@SuppressWarnings("unchecked")
public static <T extends FloodgateApi> T castApi(Class<T> cast) {
return (T) instance;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.api.inject;
import io.netty.channel.Channel;
public interface InjectorAddon {
/**
* Called when injecting a specific channel
* (every client that is connected to the server has his own channel).
* Internally used for the Floodgate debugger and data handler but can also be used for
* third party things.
*
* @param channel the channel that the injector is injecting
* @param proxyToServer if the the connection is between the proxy and a server
*/
void onInject(Channel channel, boolean proxyToServer);
/**
* Called when the player successfully logged in.
* That is the moment that most of the addons can deregister.
* Note that it is entirely optional to remove the addon from the channel,
* the injector won't force the addon to remove.
*
* @param channel the channel that the injector injected
*/
void onLoginDone(Channel channel);
/**
* Called when Floodgate is removing the injection from the server.
* The addon should remove his traces otherwise it is likely that an error will popup after
* the server is injected again.
*
* @param channel the channel that the injector injected
*/
void onRemoveInject(Channel channel);
/**
* If the Injector should call {@link #onInject(Channel, boolean)}
*
* @return true if it should, false otherwise
*/
boolean shouldInject();
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.api.inject;
/**
* The global interface of all the Platform Injectors.
* The injector can be used for various things. It is used internally for getting Floodgate
* data out of the handshake packet and for debug mode, but there is also an option to add your
* own addons.
* Note that every Floodgate platform that supports netty should implement this,
* but the platform implementation isn't required to implement this.
*/
public interface PlatformInjector {
/**
* Injects the server connection.
* This will allow various addons (like getting the Floodgate data and debug mode) to work.
*
* @return true if the connection has successfully been injected
* @throws Exception if something went wrong while injecting the server connection
*/
boolean inject() throws Exception;
/**
* Removes the injection from the server.
* Please note that this function should only be used internally (on plugin shutdown).
* This method will also remove every added addon.
*
* @return true if the injection has successfully been removed
* @throws Exception if something went wrong while removing the injection
*/
boolean removeInjection() throws Exception;
/**
* If the server connection is currently injected.
*
* @return true if the server connection is currently injected, returns false otherwise
*/
boolean isInjected();
/**
* Adds an addon to the addon list of the Floodgate Injector
* (the addon is called when Floodgate injects a channel).
* See {@link InjectorAddon} for more info.
*
* @param addon the addon to add to the addon list
* @return true if the addon has been added, false if the addon is already present
*/
boolean addAddon(InjectorAddon addon);
/**
* Removes an addon from the addon list of the Floodgate Injector
* (the addon is called when Floodgate injects a channel).
* See {@link InjectorAddon} for more info.
*
* @param addon the class of the addon to remove from the addon list
* @param <T> the addon type
* @return the instance that was present when removing
*/
<T extends InjectorAddon> T removeAddon(Class<T> addon);
}

View File

@@ -0,0 +1,54 @@
package org.geysermc.floodgate.api.link;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.util.UUID;
public interface LinkRequest {
/**
* Returns the Java username of the linked player.
*/
String getJavaUsername();
/**
* Returns the Java unique id of the linked player.
*/
UUID getJavaUniqueId();
/**
* Returns the code that the Bedrock player has to enter in order to link the account.
*/
String getLinkCode();
/**
* Returns the username of player being linked.
*/
String getBedrockUsername();
/**
* Returns the unix time when the player link was requested.
*/
long getRequestTime();
/**
* If this player link request is expired.
*
* @param linkTimeout the link timeout in millis
* @return true if the difference between now and requestTime is greater then the link timout
*/
boolean isExpired(long linkTimeout);
/**
* Checks if the given FloodgatePlayer is the player requested in this LinkRequest.
* This method will check both the real bedrock username
* {@link FloodgatePlayer#getUsername()} and the edited username
* {@link FloodgatePlayer#getJavaUsername()} and returns true if one of the two matches.
*
* @param player the player to check
* @return true if the given player is the player requested
*/
default boolean isRequestedPlayer(FloodgatePlayer player) {
return getBedrockUsername().equals(player.getUsername()) ||
getBedrockUsername().equals(player.getJavaUsername());
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.api.link;
import org.geysermc.floodgate.util.LinkedPlayer;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* The base class of the PlayerLink database implementation.
* The implementation is responsible for making a connection with the database
* and keeping that connection alive so that Floodgate (or a third party plugin)
* can check for example if a given player is linked.
*/
public interface PlayerLink {
/**
* Called by Floodgate after the initialization of the class.
* In this method the implementation should start the connection with the database and
* create the collections if they don't exist already.
*/
void load();
/**
* Get a linked player by the bedrock uuid
*
* @param bedrockId the uuid of the bedrock player
* @return a completable future with the {@link LinkedPlayer}.
* The future will have a null value if that Bedrock player isn't linked
*/
CompletableFuture<LinkedPlayer> getLinkedPlayer(UUID bedrockId);
/**
* Tells if the given player is a linked player
*
* @param bedrockId the bedrock uuid of the linked player
* @return true if the player is a linked player
*/
CompletableFuture<Boolean> isLinkedPlayer(UUID bedrockId);
/**
* Links a Java account to a Bedrock account.
*
* @param bedrockId the uuid of the Bedrock player
* @param javaId the uuid of the Java player
* @param username the username of the Java player
* @return a future holding void on success or completed exceptionally when failed
*/
CompletableFuture<Void> linkPlayer(UUID bedrockId, UUID javaId, String username);
/**
* Unlinks a Java account from a Bedrock account.
*
* @param javaId the uuid of the Java player
* @return a future holding void on success or completed exceptionally when failed
*/
CompletableFuture<Void> unlinkPlayer(UUID javaId);
/**
* Return if account linking is enabled.
* The difference between enabled and allowed is that 'enabled' still allows already linked
* people to join with their linked account while 'allow linking' allows people to link
* accounts using the commands.
*/
boolean isEnabled();
/**
* Returns the duration (in seconds) before a {@link LinkRequest} timeouts
*/
long getVerifyLinkTimeout();
/**
* Return if account linking is allowed.
* The difference between enabled and allowed is that 'enabled' still allows already linked
* people to join with their linked account while 'allow linking' allows people to link
* accounts using the commands.
*/
boolean isAllowLinking();
default boolean isEnabledAndAllowed() {
return isEnabled() && isAllowLinking();
}
/**
* Called when the Floodgate plugin is going to shutdown
*/
void stop();
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.api.logger;
public interface FloodgateLogger {
String LOGGER_NAME = "Floodgate";
/**
* Logs an error message to the console, with 0 or more arguments.
*
* @param message the message to log to the console
* @param args the arguments to fill the missing spots in the message
*/
void error(String message, Object... args);
/**
* Logs an error message to the console, with 0 or more arguments.
*
* @param message the message to log to the console
* @param throwable the throwable to log
* @param args the arguments to fill the missing spots in the message
*/
void error(String message, Throwable throwable, Object... args);
/**
* Logs a warning message to the console, with 0 or more arguments.
*
* @param message the message to log to the console
* @param args the arguments to fill the missing spots in the message
*/
void warn(String message, Object... args);
/**
* Logs an info message to the console, with 0 or more arguments.
*
* @param message the message to log to the console
* @param args the arguments to fill the missing spots in the message
*/
void info(String message, Object... args);
/**
* Logs a debug message to the console, with 0 or more arguments.
*
* @param message the message to log to the console
* @param args the arguments to fill the missing spots in the message
*/
void debug(String message, Object... args);
/**
* Logs a trace message to the console, with 0 or more arguments.
*
* @param message the message to log to the console
* @param args the arguments to fill the missing spots in the message
*/
void trace(String message, Object... args);
}

View File

@@ -0,0 +1,89 @@
package org.geysermc.floodgate.api.player;
import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.UiProfile;
import java.util.UUID;
public interface FloodgatePlayer {
/**
* Returns the Bedrock username that will be used as username on the server.
* This includes replace spaces (if enabled), username shortened and prefix appended.<br>
* Note that this field is not used when the player is a {@link LinkedPlayer LinkedPlayer}
*/
String getJavaUsername();
/**
* Returns the uuid that will be used as UUID on the server.<br>
* Note that this field is not used when the player is a {@link LinkedPlayer LinkedPlayer}
*/
UUID getJavaUniqueId();
/**
* Returns the uuid that the server will use as uuid of that player.
* Will return {@link #getJavaUniqueId()} when not linked or
* {@link LinkedPlayer#getJavaUniqueId()} when linked.
*/
UUID getCorrectUniqueId();
/**
* Returns the username the server will as username for that player.
* Will return {@link #getJavaUsername()} when not linked or
* {@link LinkedPlayer#getJavaUsername()} when linked.
*/
String getCorrectUsername();
/**
* Returns the version of the Bedrock client
*/
String getVersion();
/**
* Returns the real username of the Bedrock client.
* No prefix nor shortened nor replaced spaces.
*/
String getUsername();
/**
* Returns the Xbox Unique Identifier of the Bedrock client
*/
String getXuid();
/**
* Returns the Operation System of the Bedrock client
*/
DeviceOs getDeviceOs();
/**
* Returns the language code of the Bedrock client
*/
String getLanguageCode();
/**
* Returns the User Interface Profile of the Bedrock client
*/
UiProfile getUiProfile();
/**
* Returns the Input Mode of the Bedrock client
*/
InputMode getInputMode();
/**
* Returns the LinkedPlayer object if the player is linked to a Java account.
*/
LinkedPlayer getLinkedPlayer();
/**
* Casts the FloodgatePlayer instance to a class that extends FloodgatePlayer.
*
* @param <T> The instance to cast to.
* @return The FloodgatePlayer casted to the given class
* @throws ClassCastException when it can't cast the instance to the given class
*/
default <T extends FloodgatePlayer> T as(Class<T> clazz) {
return clazz.cast(this);
}
}

View File

@@ -1,93 +0,0 @@
package org.geysermc.floodgate;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.command.LinkAccountCommand;
import org.geysermc.floodgate.command.UnlinkAccountCommand;
import org.geysermc.floodgate.injector.BukkitInjector;
import org.geysermc.floodgate.util.CommandUtil;
import org.geysermc.floodgate.util.ReflectionUtil;
import java.util.logging.Level;
public class BukkitPlugin extends JavaPlugin implements Listener {
@Getter private static BukkitPlugin instance;
@Getter private FloodgateConfig configuration;
@Getter private PlayerLink playerLink;
@Override
public void onLoad() {
instance = this;
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
}
ReflectionUtil.setPrefix("net.minecraft.server." + getServer().getClass().getPackage().getName().split("\\.")[3]);
configuration = FloodgateConfig.load(getLogger(), getDataFolder().toPath().resolve("config.yml"));
playerLink = PlayerLink.initialize(getLogger(), getDataFolder().toPath(), configuration);
}
@Override
public void onEnable() {
try {
if (!BukkitInjector.inject()) getLogger().severe("Failed to inject the packet listener!");
} catch (Exception e) {
getLogger().log(Level.SEVERE, "Failed to inject the packet listener!", e);
} finally {
if (!BukkitInjector.isInjected()) {
getServer().getPluginManager().disablePlugin(this);
}
}
CommandUtil commandUtil = new CommandUtil(this);
getCommand(CommandUtil.LINK_ACCOUNT_COMMAND).setExecutor(new LinkAccountCommand(playerLink, commandUtil));
getCommand(CommandUtil.UNLINK_ACCOUNT_COMMAND).setExecutor(new UnlinkAccountCommand(playerLink, commandUtil));
// Register the plugin as an event listener to we get join and leave events
Bukkit.getServer().getPluginManager().registerEvents(this, this);
}
@Override
public void onDisable() {
try {
if (!BukkitInjector.removeInjection()) getLogger().severe("Failed to remove the injection!");
} catch (Exception e) {
getLogger().log(Level.SEVERE, "Failed to remove the injection!", e);
}
playerLink.stop();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onAsyncPreLogin(AsyncPlayerPreLoginEvent event) {
if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
FloodgateAPI.removePlayer(event.getUniqueId(), true);
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerLogin(PlayerLoginEvent event) {
if (event.getResult() != PlayerLoginEvent.Result.ALLOWED) {
FloodgateAPI.removePlayer(event.getPlayer().getUniqueId());
return;
}
// if there was another player with the same uuid online,
// he would've been disconnected by now
FloodgatePlayer player = FloodgateAPI.getPlayer(event.getPlayer());
if (player != null) player.setLogin(false);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (FloodgateAPI.removePlayer(player.getUniqueId())) {
System.out.println("Removed Bedrock player who was logged in as " + player.getName() + " " + event.getPlayer().getUniqueId());
}
}
}

View File

@@ -1,21 +0,0 @@
package org.geysermc.floodgate;
import org.bukkit.entity.Player;
import java.util.UUID;
public class FloodgateAPI extends AbstractFloodgateAPI {
/**
* See {@link AbstractFloodgateAPI#getPlayer(UUID)}
*/
public static FloodgatePlayer getPlayer(Player player) {
return getPlayer(player.getUniqueId());
}
/**
* See {@link AbstractFloodgateAPI#isBedrockPlayer(UUID)}
*/
public static boolean isBedrockPlayer(Player player) {
return isBedrockPlayer(player.getUniqueId());
}
}

View File

@@ -1,178 +0,0 @@
package org.geysermc.floodgate;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.injector.BukkitInjector;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.ReflectionUtil;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
import static org.geysermc.floodgate.util.ReflectionUtil.*;
@RequiredArgsConstructor
public class PacketHandler extends SimpleChannelInboundHandler<Object> {
private static BukkitPlugin plugin = BukkitPlugin.getInstance();
private static HandshakeHandler handshakeHandler;
private static Class<?> networkManagerClass;
private static Class<?> handshakePacketClass;
private static Field hostField;
private static Class<?> gameProfileClass;
private static Constructor<?> gameProfileConstructor;
private static Field gameProfileField;
private static Class<?> loginStartPacketClass;
private static Class<?> loginListenerClass;
private static Method initUUIDMethod;
private static Class<?> loginHandlerClass;
private static Constructor<?> loginHandlerConstructor;
private static Method fireEventsMethod;
private static Field packetListenerField;
private static Field protocolStateField;
private static Object readyToAcceptState;
/* per player stuff */
private final ChannelFuture future;
private Object networkManager;
private FloodgatePlayer fPlayer;
private boolean bungee;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object packet) throws Exception {
boolean isHandhake = handshakePacketClass.isInstance(packet);
boolean isLogin = loginStartPacketClass.isInstance(packet);
try {
if (isHandhake) {
networkManager = ctx.channel().pipeline().get("packet_handler");
HandshakeResult result = handshakeHandler.handle(getCastedValue(packet, hostField, String.class));
switch (result.getResultType()) {
case SUCCESS:
break;
case INVALID_DATA_LENGTH:
plugin.getLogger().info(String.format(
plugin.getConfiguration().getMessages().getInvalidArgumentsLength(),
BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength()
));
ctx.close();
default: // only continue when SUCCESS
return;
}
fPlayer = result.getFloodgatePlayer();
BedrockData bedrockData = result.getBedrockData();
String[] data = result.getHandshakeData();
if (bungee = (data.length == 6 || data.length == 7)) {
setValue(packet, hostField, data[0] + '\0' +
bedrockData.getIp() + '\0' + fPlayer.getCorrectUniqueId() +
(data.length == 7 ? '\0' + data[6] : "")
);
} else {
// Use a spoofedUUID for initUUID (just like Bungeecord)
setValue(networkManager, "spoofedUUID", fPlayer.getCorrectUniqueId());
// Use the player his IP for stuff instead of Geyser his IP
SocketAddress newAddress = new InetSocketAddress(
bedrockData.getIp(),
((InetSocketAddress) ctx.channel().remoteAddress()).getPort()
);
setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress);
}
plugin.getLogger().info("Added " + fPlayer.getCorrectUsername() + " " + fPlayer.getCorrectUniqueId());
} else if (isLogin) {
if (!bungee) {
// we have to fake the offline player cycle
Object loginListener = packetListenerField.get(networkManager);
// Set the player his GameProfile
Object gameProfile = gameProfileConstructor.newInstance(fPlayer.getCorrectUniqueId(), fPlayer.getCorrectUsername());
setValue(loginListener, gameProfileField, gameProfile);
initUUIDMethod.invoke(loginListener); // LoginListener#initUUID
fireEventsMethod.invoke(loginHandlerConstructor.newInstance(loginListener)); // new LoginHandler().fireEvents();
setValue(loginListener, protocolStateField, readyToAcceptState); // LoginLister#protocolState = READY_TO_ACCEPT
// The tick of LoginListener will do the rest
}
}
} finally {
// don't let the packet through if the packet is the login packet
// because we want to skip the login cycle
if (!isLogin) ctx.fireChannelRead(packet);
if (isHandhake && bungee || isLogin && !bungee || fPlayer == null) {
// remove the injection of the client because we're finished
BukkitInjector.removeInjectedClient(future, ctx.channel());
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
}
static {
handshakeHandler = new HandshakeHandler(plugin.getConfiguration().getPrivateKey(), false, plugin.getConfiguration().getUsernamePrefix(), plugin.getConfiguration().isReplaceSpaces());
networkManagerClass = getPrefixedClass("NetworkManager");
loginStartPacketClass = getPrefixedClass("PacketLoginInStart");
gameProfileClass = ReflectionUtil.getClass("com.mojang.authlib.GameProfile");
assert gameProfileClass != null;
try {
gameProfileConstructor = gameProfileClass.getConstructor(UUID.class, String.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
handshakePacketClass = getPrefixedClass("PacketHandshakingInSetProtocol");
assert handshakePacketClass != null;
hostField = getFieldOfType(handshakePacketClass, String.class, true);
loginListenerClass = getPrefixedClass("LoginListener");
assert loginListenerClass != null;
gameProfileField = getFieldOfType(loginListenerClass, gameProfileClass, true);
initUUIDMethod = getMethod(loginListenerClass, "initUUID");
for (Field field : loginListenerClass.getDeclaredFields()) {
if (field.getType().isEnum()) {
protocolStateField = field;
}
}
assert protocolStateField != null;
Enum<?>[] protocolStates = (Enum<?>[]) protocolStateField.getType().getEnumConstants();
for (Enum<?> protocolState : protocolStates) {
if (protocolState.name().equals("READY_TO_ACCEPT")) {
readyToAcceptState = protocolState;
}
}
Class<?> packetListenerClass = getPrefixedClass("PacketListener");
packetListenerField = getFieldOfType(networkManagerClass, packetListenerClass, true);
loginHandlerClass = getPrefixedClass("LoginListener$LoginHandler");
assert loginHandlerClass != null;
try {
loginHandlerConstructor = makeAccessible(loginHandlerClass.getDeclaredConstructor(loginListenerClass));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
fireEventsMethod = getMethod(loginHandlerClass, "fireEvents");
}
}

View File

@@ -1,30 +0,0 @@
package org.geysermc.floodgate.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.AbstractLinkAccountCommand;
import org.geysermc.floodgate.PlayerLink;
import org.geysermc.floodgate.util.CommandUtil;
import org.geysermc.floodgate.util.CommonMessage;
import java.util.UUID;
public class LinkAccountCommand extends AbstractLinkAccountCommand<Player, CommandUtil> implements CommandExecutor {
public LinkAccountCommand(PlayerLink link, CommandUtil commandUtil) {
super(link, commandUtil);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage());
return true;
}
UUID uuid = ((Player) sender).getUniqueId();
String username = sender.getName();
execute((Player) sender, uuid, username, args);
return true;
}
}

View File

@@ -1,29 +0,0 @@
package org.geysermc.floodgate.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.AbstractUnlinkAccountCommand;
import org.geysermc.floodgate.PlayerLink;
import org.geysermc.floodgate.util.CommandUtil;
import org.geysermc.floodgate.util.CommonMessage;
import java.util.UUID;
public class UnlinkAccountCommand extends AbstractUnlinkAccountCommand<Player, CommandUtil> implements CommandExecutor {
public UnlinkAccountCommand(PlayerLink link, CommandUtil commandUtil) {
super(link, commandUtil);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage());
return true;
}
UUID uuid = ((Player) sender).getUniqueId();
execute((Player) sender, uuid);
return true;
}
}

View File

@@ -1,122 +0,0 @@
package org.geysermc.floodgate.injector;
import io.netty.channel.*;
import lombok.Getter;
import org.geysermc.floodgate.PacketHandler;
import org.geysermc.floodgate.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
public class BukkitInjector {
private static Object serverConnection;
private static List<ChannelFuture> injectedClients = new ArrayList<>();
@Getter private static boolean injected = false;
private static String injectedFieldName;
public static boolean inject() throws Exception {
if (isInjected()) return true;
if (getServerConnection() != null) {
for (Field f : serverConnection.getClass().getDeclaredFields()) {
if (f.getType() == List.class) {
f.setAccessible(true);
boolean rightList = ((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0] == ChannelFuture.class;
if (!rightList) continue;
injectedFieldName = f.getName();
List<?> newList = new CustomList((List<?>) f.get(serverConnection)) {
@Override
public void onAdd(Object o) {
try {
injectClient((ChannelFuture) o);
} catch (Exception e) {
e.printStackTrace();
}
}
};
synchronized (newList) {
for (Object o : newList) {
try {
injectClient((ChannelFuture) o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
f.set(serverConnection, newList);
injected = true;
return true;
}
}
}
return false;
}
public static void injectClient(ChannelFuture future) {
future.channel().pipeline().addFirst("floodgate-init", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
Channel channel = (Channel) msg;
channel.pipeline().addLast(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addBefore("packet_handler", "floodgate_handler", new PacketHandler(future));
}
});
}
});
injectedClients.add(future);
}
public static void removeInjectedClient(ChannelFuture future, Channel channel) {
if (channel != null) channel.pipeline().remove("floodgate_handler");
injectedClients.remove(future);
}
public static boolean removeInjection() throws Exception {
if (!isInjected()) return true;
for (ChannelFuture future : new ArrayList<>(injectedClients)) {
removeInjectedClient(future, null);
}
Object serverConnection = getServerConnection();
if (serverConnection != null) {
Field field = ReflectionUtil.getField(serverConnection.getClass(), injectedFieldName);
List<?> list = (List<?>) ReflectionUtil.getValue(serverConnection, field);
if (list instanceof CustomList) {
ReflectionUtil.setValue(serverConnection, field, ((CustomList) list).getOriginalList());
}
}
injectedFieldName = null;
injected = false;
return true;
}
public static Object getServerConnection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
if (serverConnection != null) return serverConnection;
Class<?> minecraftServer = ReflectionUtil.getPrefixedClass("MinecraftServer");
assert minecraftServer != null;
Object minecraftServerInstance = ReflectionUtil.invokeStatic(minecraftServer, "getServer");
for (Method m : minecraftServer.getDeclaredMethods()) {
if (m.getReturnType() != null) {
if (m.getReturnType().getSimpleName().equals("ServerConnection")) {
if (m.getParameterTypes().length == 0) {
serverConnection = m.invoke(minecraftServerInstance);
}
}
}
}
return serverConnection;
}
}

View File

@@ -1,29 +0,0 @@
package org.geysermc.floodgate.util;
import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.geysermc.floodgate.command.CommandMessage;
@AllArgsConstructor
public class CommandUtil extends AbstractCommandResponseCache<String> implements ICommandUtil<Player> {
private final Plugin plugin;
@Override
public void sendMessage(Player player, CommandMessage message, Object... args) {
player.sendMessage(format(message, args));
}
@Override
public void kickPlayer(Player player, CommandMessage message, Object... args) {
// Have to run this in a non async thread so we don't get a `Asynchronous player kick!` error
Bukkit.getScheduler().runTask(plugin, () -> player.kickPlayer(format(message, args)));
}
@Override
protected String transformMessage(String message) {
// unlike others, Bukkit doesn't have to transform a message into another class.
return message;
}
}

View File

@@ -1,25 +0,0 @@
name: ${project.name}
description: ${project.description}
version: ${project.version}
author: ${project.organization.name}
website: ${project.url}
main: org.geysermc.floodgate.BukkitPlugin
api-version: 1.13
commands:
linkaccount:
description: Link your Java and Bedrock accounts
usage: /linkaccount [code]
permission: floodgate.linkaccount
permission-message: You don't have the floodgate.linkaccount permission.
unlinkaccount:
description: Unlink your Java account from your Bedrock account
usage: /unlinkaccount
permission: floodgate.unlinkaccount
permission-message: You don't have the floodgate.unlinkaccount permission.
permissions:
floodgate.linkaccount:
description: Allows players to link their Bedrock and Java accounts
default: true
floodgate.unlinkaccount:
description: Allows players to unlink their Bedrock and Java accounts
default: true

View File

@@ -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">
<parent>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-parent</artifactId>
<artifactId>parent</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>floodgate-bungee</artifactId>
<artifactId>bungee</artifactId>
<repositories>
<repository>
@@ -28,12 +28,12 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-protocol</artifactId>
<version>1.15-SNAPSHOT</version>
<version>${bungee-version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-common</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>

View File

@@ -1,108 +0,0 @@
package org.geysermc.floodgate;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
import org.geysermc.floodgate.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class BungeeDebugger {
public BungeeDebugger() {
init();
}
private void init() {
Class<?> pipelineUtils = ReflectionUtil.getPrefixedClass("netty.PipelineUtils");
Field framePrepender = ReflectionUtil.getField(pipelineUtils, "framePrepender");
ReflectionUtil.setFinalValue(null, framePrepender, new CustomVarint21LengthFieldPrepender());
}
@ChannelHandler.Sharable
private static class CustomVarint21LengthFieldPrepender extends Varint21LengthFieldPrepender {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// we're getting called before the encoder and decoder are added,
// so we have to wait with a nice while loop :D
ctx.executor().execute(() -> {
System.out.println("Channel: " + ctx.channel().isActive() + " " + ctx.channel().isOpen() + " " + ctx.channel().isRegistered());
while (ctx.channel().isOpen()) {
System.out.println("Trying to find decoder for " + getHostString(ctx, true) + " " + getParentName(ctx, true));
if (ctx.channel().pipeline().get(MinecraftEncoder.class) != null) {
System.out.println("Found decoder for " + getHostString(ctx, true));
ctx.channel().pipeline().addLast(
"floodgate-debug-init",
new BungeeChannelInitializer(
ctx.channel().parent() instanceof ServerSocketChannel
)
);
break;
}
}
});
}
}
public static String getHostString(ChannelHandlerContext ctx, boolean alwaysString) {
SocketAddress address = ctx.channel().remoteAddress();
return address != null ? ((InetSocketAddress) address).getHostString() : (alwaysString ? "null" : null);
}
public static String getParentName(ChannelHandlerContext ctx, boolean alwaysString) {
Channel parent = ctx.channel().parent();
return parent != null ? parent.getClass().getSimpleName() : (alwaysString ? "null" : null);
}
public static int executorsCount(ChannelHandlerContext ctx) {
return ((MultithreadEventLoopGroup) ctx.channel().eventLoop().parent()).executorCount();
}
@RequiredArgsConstructor
private static class BungeeChannelInitializer extends ChannelInitializer<Channel> {
private final boolean player;
@Override
protected void initChannel(Channel channel) throws Exception {
System.out.println("Init " + (player ? "Bungee" : "Server"));
// can't add our debugger when the inbound-boss is missing
if (channel.pipeline().get("packet-encoder") == null) return;
if (channel.pipeline().get("packet-decoder") == null) return;
channel.pipeline()
.addBefore("packet-decoder", "floodgate-debug-in", new BungeeInPacketHandler(player))
.addBefore("packet-encoder", "floodgate-debug-out", new BungeeOutPacketHandler(player));
}
}
@RequiredArgsConstructor
private static class BungeeInPacketHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final boolean player;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
int index = msg.readerIndex();
System.out.println((player ? "Player -> Bungee" : "Server -> Bungee") + ":\n" + ByteBufUtil.prettyHexDump(msg));
msg.readerIndex(index);
ctx.fireChannelRead(msg.retain());
}
}
@RequiredArgsConstructor
private static class BungeeOutPacketHandler extends MessageToByteEncoder<ByteBuf> {
private final boolean player;
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
int index = msg.readerIndex();
System.out.println((player ? "Bungee -> Player" : "Bungee -> Server") + ":\n" + ByteBufUtil.prettyHexDump(msg));
msg.readerIndex(index);
out.writeBytes(msg);
}
}
}

View File

@@ -1,9 +0,0 @@
package org.geysermc.floodgate;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
public class BungeeFloodgateConfig extends FloodgateConfig {
@JsonProperty(value = "send-floodgate-data")
@Getter private boolean sendFloodgateData;
}

View File

@@ -1,168 +1,65 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate;
import lombok.Getter;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import com.google.inject.Guice;
import com.google.inject.Injector;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.md_5.bungee.protocol.packet.Handshake;
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.HandshakeHandler.ResultType;
import org.geysermc.floodgate.command.LinkAccountCommand;
import org.geysermc.floodgate.command.UnlinkAccountCommand;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.CommandUtil;
import org.geysermc.floodgate.module.*;
import org.geysermc.floodgate.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER;
public class BungeePlugin extends Plugin implements Listener {
@Getter private static BungeePlugin instance;
private static Field extraHandshakeData;
@Getter private BungeeFloodgateConfig config;
@Getter private PlayerLink playerLink;
private BungeeDebugger debugger;
private HandshakeHandler handshakeHandler;
public final class BungeePlugin extends Plugin {
private FloodgatePlatform platform;
@Override
public void onLoad() {
instance = this;
if (!getDataFolder().exists()) {
getDataFolder().mkdir();
}
config = FloodgateConfig.load(getLogger(), getDataFolder().toPath().resolve("config.yml"), BungeeFloodgateConfig.class);
playerLink = PlayerLink.initialize(getLogger(), getDataFolder().toPath(), config);
handshakeHandler = new HandshakeHandler(config.getPrivateKey(), true, config.getUsernamePrefix(), config.isReplaceSpaces());
ReflectionUtil.setPrefix("net.md_5.bungee");
long ctm = System.currentTimeMillis();
Injector injector = Guice.createInjector(
new CommonModule(getDataFolder().toPath()),
new BungeePlatformModule(this)
);
long endCtm = System.currentTimeMillis();
getLogger().info("Took " + (endCtm - ctm) + "ms to boot Floodgate");
// Bungeecord doesn't have a build-in function to disable plugins,
// so there is no need to have a custom Platform class like Spigot
platform = injector.getInstance(FloodgatePlatform.class);
}
@Override
public void onEnable() {
getProxy().getPluginManager().registerListener(this, this);
if (config.isDebug()) {
debugger = new BungeeDebugger();
}
CommandUtil commandUtil = new CommandUtil();
getProxy().getPluginManager().registerCommand(this, new LinkAccountCommand(playerLink, commandUtil));
getProxy().getPluginManager().registerCommand(this, new UnlinkAccountCommand(playerLink, commandUtil));
platform.enable(new CommandModule(), new BungeeListenerModule(), new BungeeAddonModule());
}
@Override
public void onDisable() {
if (config.isDebug()) {
getLogger().warning("Please note that it is not possible to reload this plugin when debug mode is enabled. At least for now");
}
playerLink.stop();
}
@EventHandler(priority = EventPriority.LOW)
public void onServerConnect(ServerConnectEvent e) {
// Passes the information through to the connecting server if enabled
if (config.isSendFloodgateData() && FloodgateAPI.isBedrockPlayer(e.getPlayer())) {
Handshake handshake = ReflectionUtil.getCastedValue(e.getPlayer().getPendingConnection(), "handshake", Handshake.class);
handshake.setHost(
handshake.getHost().split("\0")[0] + '\0' + // Ensures that only the hostname remains!
FLOODGATE_IDENTIFIER + '\0' + FloodgateAPI.getEncryptedData(e.getPlayer().getUniqueId())
);
// Bungeecord will add his data after our data
}
}
@EventHandler(priority = EventPriority.LOW)
public void onPreLogin(PreLoginEvent event) {
event.registerIntent(this);
getProxy().getScheduler().runAsync(this, () -> {
String extraData = ReflectionUtil.getCastedValue(event.getConnection(), extraHandshakeData, String.class);
HandshakeResult result = handshakeHandler.handle(extraData);
switch (result.getResultType()) {
case SUCCESS:
break;
case EXCEPTION:
event.setCancelReason(config.getMessages().getInvalidKey());
break;
case INVALID_DATA_LENGTH:
event.setCancelReason(String.format(
config.getMessages().getInvalidArgumentsLength(),
BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength()
));
break;
}
if (result.getResultType() != ResultType.SUCCESS) {
// only continue when SUCCESS
event.completeIntent(this);
return;
}
FloodgatePlayer player = result.getFloodgatePlayer();
FloodgateAPI.addEncryptedData(player.getCorrectUniqueId(), result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]);
event.getConnection().setOnlineMode(false);
event.getConnection().setUniqueId(player.getCorrectUniqueId());
ReflectionUtil.setValue(event.getConnection(), "name", player.getCorrectUsername());
Object channelWrapper = ReflectionUtil.getValue(event.getConnection(), "ch");
SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class);
if (!(remoteAddress instanceof InetSocketAddress)) {
getLogger().info(
"Player " + player.getUsername() + " doesn't use an InetSocketAddress. " +
"It uses " + remoteAddress.getClass().getSimpleName() + ". Ignoring the player, I guess."
);
} else {
ReflectionUtil.setValue(
channelWrapper, "remoteAddress",
new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort())
);
}
event.completeIntent(this);
});
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPreLoginMonitor(PreLoginEvent event) {
if (event.isCancelled()) {
FloodgateAPI.removePlayer(event.getConnection().getUniqueId(), true);
}
}
@EventHandler
public void onLogin(LoginEvent event) {
// if there was another player with the same uuid / name online,
// he has been disconnected by now
FloodgatePlayer player = FloodgateAPI.getPlayerByConnection(event.getConnection());
if (player != null) player.setLogin(false);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onLoginMonitor(LoginEvent event) {
if (event.isCancelled()) {
FloodgateAPI.removePlayer(event.getConnection().getUniqueId());
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
ProxiedPlayer player = event.getPlayer();
if (FloodgateAPI.removePlayer(player.getUniqueId())) {
FloodgateAPI.removeEncryptedData(player.getUniqueId());
System.out.println("Removed Bedrock player who was logged in as " + player.getName() + " " + player.getUniqueId());
}
}
static {
ReflectionUtil.setPrefix("net.md_5.bungee");
Class<?> initial_handler = ReflectionUtil.getPrefixedClass("connection.InitialHandler");
extraHandshakeData = ReflectionUtil.getField(initial_handler, "extraDataInHandshake");
platform.disable();
}
}

View File

@@ -1,35 +0,0 @@
package org.geysermc.floodgate;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class FloodgateAPI extends AbstractFloodgateAPI {
private static Map<UUID, String> encryptedData = new HashMap<>();
static void addEncryptedData(UUID uuid, String encryptedData) {
FloodgateAPI.encryptedData.put(uuid, encryptedData); // just override it I guess
}
static void removeEncryptedData(UUID uuid) {
encryptedData.remove(uuid);
}
static String getEncryptedData(UUID uuid) {
return encryptedData.get(uuid);
}
/**
* See {@link AbstractFloodgateAPI#getPlayer(UUID)}
*/
public static FloodgatePlayer getPlayerByConnection(PendingConnection connection) {
return getPlayer(connection.getUniqueId());
}
public static boolean isBedrockPlayer(ProxiedPlayer player) {
return isBedrockPlayer(player.getUniqueId());
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.command;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.geysermc.floodgate.BungeePlugin;
import org.geysermc.floodgate.platform.command.Command;
import org.geysermc.floodgate.platform.command.CommandRegistration;
import java.util.UUID;
@RequiredArgsConstructor
public final class BungeeCommandRegistration implements CommandRegistration {
private final BungeePlugin plugin;
@Override
public void register(Command command) {
ProxyServer.getInstance().getPluginManager().registerCommand(
plugin, new BungeeCommandWrapper(command)
);
}
protected static class BungeeCommandWrapper extends net.md_5.bungee.api.plugin.Command {
private final Command command;
public BungeeCommandWrapper(Command command) {
super(command.getName());
this.command = command;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) {
if (command.isRequirePlayer()) {
//todo let it use the response cache
sender.sendMessage(CommonCommandMessage.NOT_A_PLAYER.getMessage());
return;
}
command.execute(sender, args);
return;
}
UUID uuid = ((ProxiedPlayer) sender).getUniqueId();
String username = sender.getName();
command.execute(sender, uuid, username, args);
}
}
}

View File

@@ -1,37 +0,0 @@
package org.geysermc.floodgate.command;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import org.geysermc.floodgate.AbstractLinkAccountCommand;
import org.geysermc.floodgate.PlayerLink;
import org.geysermc.floodgate.util.CommandUtil;
import org.geysermc.floodgate.util.CommonMessage;
import java.util.UUID;
public class LinkAccountCommand extends Command {
private final LinkAccountCommandHandler handler;
public LinkAccountCommand(PlayerLink playerLink, CommandUtil responseCache) {
super(CommandUtil.LINK_ACCOUNT_COMMAND);
handler = new LinkAccountCommandHandler(playerLink, responseCache);
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) {
sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage());
return;
}
UUID uuid = ((ProxiedPlayer) sender).getUniqueId();
String username = sender.getName();
handler.execute((ProxiedPlayer) sender, uuid, username, args);
}
private static class LinkAccountCommandHandler extends AbstractLinkAccountCommand<ProxiedPlayer, CommandUtil> {
public LinkAccountCommandHandler(PlayerLink link, CommandUtil commandUtil) {
super(link, commandUtil);
}
}
}

View File

@@ -1,36 +0,0 @@
package org.geysermc.floodgate.command;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import org.geysermc.floodgate.AbstractUnlinkAccountCommand;
import org.geysermc.floodgate.PlayerLink;
import org.geysermc.floodgate.util.CommandUtil;
import org.geysermc.floodgate.util.CommonMessage;
import java.util.UUID;
public class UnlinkAccountCommand extends Command {
private final UnlinkAccountCommandHandler handler;
public UnlinkAccountCommand(PlayerLink playerLink, CommandUtil commandUtil) {
super(CommandUtil.UNLINK_ACCOUNT_COMMAND);
handler = new UnlinkAccountCommandHandler(playerLink, commandUtil);
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) {
sender.sendMessage(CommonMessage.NOT_A_PLAYER.getMessage());
return;
}
UUID uuid = ((ProxiedPlayer) sender).getUniqueId();
handler.execute((ProxiedPlayer) sender, uuid);
}
private static class UnlinkAccountCommandHandler extends AbstractUnlinkAccountCommand<ProxiedPlayer, CommandUtil> {
public UnlinkAccountCommandHandler(PlayerLink link, CommandUtil commandUtil) {
super(link, commandUtil);
}
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.handler;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.packet.Handshake;
import org.geysermc.floodgate.HandshakeHandler;
import org.geysermc.floodgate.HandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.geysermc.floodgate.HandshakeHandler.*;
import static org.geysermc.floodgate.util.BedrockData.FLOODGATE_IDENTIFIER;
public final class BungeeDataHandler {
private static final Field EXTRA_HANDSHAKE_DATA;
private static final Field PLAYER_NAME;
private static final Field PLAYER_CHANNEL_WRAPPER;
private static final Field PLAYER_REMOTE_ADDRESS;
private static final Field CACHED_HANDSHAKE_PACKET;
private final Plugin plugin;
private final ProxyFloodgateConfig config;
private final ProxyFloodgateApi api;
private final HandshakeHandler handler;
private final FloodgateLogger logger;
public BungeeDataHandler(Plugin plugin, ProxyFloodgateConfig config, ProxyFloodgateApi api,
HandshakeHandler handshakeHandler, FloodgateLogger logger) {
this.plugin = plugin;
this.config = config;
this.handler = handshakeHandler;
this.api = api;
this.logger = logger;
}
public void handle(PreLoginEvent event) {
event.registerIntent(plugin);
plugin.getProxy().getScheduler().runAsync(plugin, () -> {
String extraData = ReflectionUtil.getCastedValue(
event.getConnection(), EXTRA_HANDSHAKE_DATA
);
HandshakeResult result = handler.handle(extraData);
switch (result.getResultType()) {
case SUCCESS:
break;
case EXCEPTION:
event.setCancelReason(config.getMessages().getInvalidKey());
break;
case INVALID_DATA_LENGTH:
event.setCancelReason(String.format(
config.getMessages().getInvalidArgumentsLength(),
BedrockData.EXPECTED_LENGTH, result.getBedrockData().getDataLength()
));
break;
}
// only continue when SUCCESS
if (result.getResultType() != ResultType.SUCCESS) {
event.completeIntent(plugin);
return;
}
FloodgatePlayer player = result.getFloodgatePlayer();
api.addEncryptedData(
player.getCorrectUniqueId(),
result.getHandshakeData()[2] + '\0' + result.getHandshakeData()[3]
);
event.getConnection().setOnlineMode(false);
event.getConnection().setUniqueId(player.getCorrectUniqueId());
ReflectionUtil.setValue(
event.getConnection(), PLAYER_NAME, player.getCorrectUsername()
);
Object channelWrapper =
ReflectionUtil.getValue(event.getConnection(), PLAYER_CHANNEL_WRAPPER);
SocketAddress remoteAddress =
ReflectionUtil.getCastedValue(channelWrapper, PLAYER_REMOTE_ADDRESS);
if (!(remoteAddress instanceof InetSocketAddress)) {
logger.info("Player {} doesn't use an InetSocketAddress. " +
"It uses {}. Ignoring the player, I guess.",
player.getUsername(), remoteAddress.getClass().getSimpleName()
);
} else {
int port = ((InetSocketAddress) remoteAddress).getPort();
ReflectionUtil.setValue(
channelWrapper, PLAYER_REMOTE_ADDRESS,
new InetSocketAddress(result.getBedrockData().getIp(), port)
);
}
event.completeIntent(plugin);
});
}
public void handle(ServerConnectEvent event) {
ProxiedPlayer player = event.getPlayer();
// Passes the information through to the connecting server if enabled
if (config.isSendFloodgateData() && api.isBedrockPlayer(player.getUniqueId())) {
Handshake handshake = ReflectionUtil.getCastedValue(
player.getPendingConnection(), CACHED_HANDSHAKE_PACKET
);
// Ensures that only the hostname remains,
// this way it can't mess up the Floodgate data format
String initialHostname = handshake.getHost().split("\0")[0];
handshake.setHost(initialHostname + '\0' + FLOODGATE_IDENTIFIER + '\0' +
api.getEncryptedData(player.getUniqueId())
);
// Bungeecord will add his data after our data
}
}
static {
Class<?> initialHandler = ReflectionUtil.getPrefixedClass("connection.InitialHandler");
EXTRA_HANDSHAKE_DATA = ReflectionUtil.getField(initialHandler, "extraDataInHandshake");
checkNotNull(EXTRA_HANDSHAKE_DATA, "extraDataInHandshake field cannot be null");
PLAYER_NAME = ReflectionUtil.getField(initialHandler, "name");
checkNotNull(PLAYER_NAME, "Initial name field cannot be null");
Class<?> channelWrapper = ReflectionUtil.getPrefixedClass("netty.ChannelWrapper");
PLAYER_CHANNEL_WRAPPER = ReflectionUtil.getFieldOfType(initialHandler, channelWrapper);
checkNotNull(PLAYER_CHANNEL_WRAPPER, "ChannelWrapper field cannot be null");
PLAYER_REMOTE_ADDRESS = ReflectionUtil.getFieldOfType(channelWrapper, SocketAddress.class);
checkNotNull(PLAYER_REMOTE_ADDRESS, "Remote address field cannot be null");
Class<?> handshakePacket = ReflectionUtil.getPrefixedClass("protocol.packet.Handshake");
CACHED_HANDSHAKE_PACKET = ReflectionUtil.getFieldOfType(initialHandler, handshakePacket);
checkNotNull(CACHED_HANDSHAKE_PACKET, "Cached handshake packet field cannot be null");
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.inject.bungee;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.util.ReflectionUtil;
import javax.naming.OperationNotSupportedException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.ServerSocketChannel;
@RequiredArgsConstructor
public final class BungeeInjector extends CommonPlatformInjector {
private final FloodgateLogger logger;
@Getter
private boolean injected;
@Override
public boolean inject() {
Class<?> pipelineUtils = ReflectionUtil.getPrefixedClass("netty.PipelineUtils");
Field framePrepender = ReflectionUtil.getField(pipelineUtils, "framePrepender");
Object customPrepender = new CustomVarint21LengthFieldPrepender(this, logger);
ReflectionUtil.setFinalValue(null, framePrepender, customPrepender);
injected = true;
return true;
}
@Override
public boolean removeInjection() throws Exception {
//todo implement injection removal support
throw new OperationNotSupportedException(
"Floodgate cannot remove the Bungee injection at the moment");
}
public void injectClient(Channel channel) {
boolean clientToProxy = channel.parent() instanceof ServerSocketChannel;
logger.info("Client to proxy? " + clientToProxy);
channel.pipeline().addLast(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
injectAddonsCall(channel, !clientToProxy);
addInjectedClient(channel);
}
});
}
@AllArgsConstructor
@ChannelHandler.Sharable
private static class CustomVarint21LengthFieldPrepender extends Varint21LengthFieldPrepender {
private final BungeeInjector injector;
private final FloodgateLogger logger;
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
// we're getting called before the encoder and decoder are added,
// so we have to wait a little, so we have a nice while loop here :D
//todo look if we can make this nicer
ctx.executor().execute(() -> {
logger.debug("Channel: {} {} {}",
ctx.channel().isActive(),
ctx.channel().isOpen(),
ctx.channel().isRegistered()
);
long ctm = System.currentTimeMillis();
while (ctx.channel().isOpen()) {
logger.debug("Trying to find decoder for {} {}",
getHostString(ctx, true),
getParentName(ctx, true)
);
if (ctx.channel().pipeline().get(MinecraftEncoder.class) != null) {
logger.debug("Found decoder for {}",
getHostString(ctx, true)
);
injector.injectClient(ctx.channel());
break;
}
if (System.currentTimeMillis() - ctm > 3000) {
logger.error("Failed to find decoder for client after 3 seconds!");
}
}
});
}
}
public static String getHostString(ChannelHandlerContext ctx, boolean alwaysString) {
SocketAddress address = ctx.channel().remoteAddress();
if (address != null) {
return ((InetSocketAddress) address).getHostString();
}
return alwaysString ? "null" : null;
}
public static String getParentName(ChannelHandlerContext ctx, boolean alwaysString) {
Channel parent = ctx.channel().parent();
if (parent != null) {
return parent.getClass().getSimpleName();
}
return alwaysString ? "null" : null;
}
}

View File

@@ -0,0 +1,86 @@
package org.geysermc.floodgate.listener;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import org.geysermc.floodgate.FloodgatePlayerImpl;
import org.geysermc.floodgate.HandshakeHandler;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.handler.BungeeDataHandler;
import java.util.UUID;
public final class BungeeListener implements Listener {
private final BungeeDataHandler dataHandler;
private final ProxyFloodgateApi api;
private final FloodgateLogger logger;
public BungeeListener(Plugin plugin, ProxyFloodgateConfig config, ProxyFloodgateApi api,
HandshakeHandler handshakeHandler, FloodgateLogger logger) {
this.dataHandler = new BungeeDataHandler(plugin, config, api, handshakeHandler, logger);
this.api = api;
this.logger = logger;
}
@EventHandler(priority = EventPriority.LOW)
public void onServerConnect(ServerConnectEvent event) {
dataHandler.handle(event);
}
@EventHandler(priority = EventPriority.LOW)
public void onPreLogin(PreLoginEvent event) {
dataHandler.handle(event);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPreLoginMonitor(PreLoginEvent event) {
if (event.isCancelled()) {
api.removePlayer(event.getConnection().getUniqueId(), true);
}
}
@EventHandler
public void onLogin(LoginEvent event) {
// if there was another player with the same uuid / name online,
// he has been disconnected by now
UUID uniqueId = event.getConnection().getUniqueId();
FloodgatePlayer player = api.getPlayer(uniqueId);
logger.info("Login" + (player != null) + " " + uniqueId);
if (player != null) {
player.as(FloodgatePlayerImpl.class).setLogin(false);
logger.info("Floodgate player who is logged in as {} {} joined",
player.getCorrectUsername(), player.getCorrectUniqueId());
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onLoginMonitor(LoginEvent event) {
logger.info("LoginMonitor" + event.isCancelled());
if (event.isCancelled()) {
api.removePlayer(event.getConnection().getUniqueId());
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
ProxiedPlayer player = event.getPlayer();
FloodgatePlayer fPlayer;
if ((fPlayer = api.removePlayer(player.getUniqueId())) != null) {
api.removeEncryptedData(player.getUniqueId());
logger.info(
"Floodgate player who was logged in as {} {} disconnected",
player.getName(), player.getUniqueId()
);
}
logger.info("Disconnected " + fPlayer);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.listener;
import com.google.inject.Inject;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Listener;
import org.geysermc.floodgate.BungeePlugin;
import org.geysermc.floodgate.platform.listener.ListenerRegistration;
@RequiredArgsConstructor(onConstructor = @__(@Inject))
public final class BungeeListenerRegistration implements ListenerRegistration<Listener> {
private final BungeePlugin plugin;
@Override
public void register(Listener listener) {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, listener);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.module;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
import com.google.inject.multibindings.ProvidesIntoSet;
import org.geysermc.floodgate.addon.AddonManagerAddon;
import org.geysermc.floodgate.addon.DebugAddon;
import org.geysermc.floodgate.api.inject.InjectorAddon;
import org.geysermc.floodgate.register.AddonRegister;
public final class BungeeAddonModule extends AbstractModule {
@Override
protected void configure() {
bind(AddonRegister.class).asEagerSingleton();
}
@Singleton
@ProvidesIntoSet
public InjectorAddon managerAddon() {
return new AddonManagerAddon();
}
@Singleton
@ProvidesIntoSet
public InjectorAddon debugAddon() {
return new DebugAddon();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.module;
import com.google.inject.AbstractModule;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.ProvidesIntoSet;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.floodgate.HandshakeHandler;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.listener.BungeeListener;
import org.geysermc.floodgate.register.ListenerRegister;
public final class BungeeListenerModule extends AbstractModule {
@Override
protected void configure() {
bind(new TypeLiteral<ListenerRegister<Listener>>() {}).asEagerSingleton();
}
@Singleton
@ProvidesIntoSet
public Listener bungeeListener(Plugin plugin, ProxyFloodgateConfig config,
ProxyFloodgateApi api, HandshakeHandler handshakeHandler,
FloodgateLogger logger) {
return new BungeeListener(plugin, config, api, handshakeHandler, logger);
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.module;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import org.geysermc.floodgate.BungeePlugin;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.command.BungeeCommandRegistration;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.inject.bungee.BungeeInjector;
import org.geysermc.floodgate.listener.BungeeListenerRegistration;
import org.geysermc.floodgate.logger.JavaDefaultFloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandRegistration;
import org.geysermc.floodgate.platform.command.util.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.util.BungeeCommandUtil;
@RequiredArgsConstructor
public final class BungeePlatformModule extends AbstractModule {
private final BungeePlugin plugin;
@Override
protected void configure() {
bind(SimpleFloodgateApi.class).to(ProxyFloodgateApi.class);
}
@Provides
@Singleton
public Plugin bungeePlugin() {
return plugin;
}
@Provides
@Singleton
@Named("configClass")
public Class<? extends FloodgateConfig> floodgateConfigClass() {
return ProxyFloodgateConfig.class;
}
@Provides
@Singleton
public ProxyFloodgateApi proxyFloodgateApi() {
return new ProxyFloodgateApi();
}
@Provides
@Singleton
public FloodgateLogger floodgateLogger() {
return new JavaDefaultFloodgateLogger(plugin.getLogger());
}
/*
Commands / Listeners
*/
@Provides
@Singleton
public CommandRegistration commandRegistration() {
return new BungeeCommandRegistration(plugin);
}
@Provides
@Singleton
public CommandUtil commandUtil(FloodgateLogger logger) {
return new BungeeCommandUtil(logger);
}
@Provides
@Singleton
public ListenerRegistration<Listener> listenerRegistration() {
return new BungeeListenerRegistration(plugin);
}
/*
DebugAddon / PlatformInjector
*/
@Provides
@Singleton
public CommonPlatformInjector platformInjector(FloodgateLogger logger) {
return new BungeeInjector(logger);
}
@Provides
@Named("packetEncoder")
public String packetEncoder() {
return "packet-encoder";
}
@Provides
@Named("packetDecoder")
public String packetDecoder() {
return "packet-decoder";
}
@Provides
@Named("packetHandler")
public String packetHandler() {
return "inbound-boss";
}
@Provides
@Named("implementationName")
public String implementationName() {
return "Bungeecord";
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.util;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandMessage;
import org.geysermc.floodgate.platform.command.util.CommandResponseCache;
import org.geysermc.floodgate.platform.command.util.CommandUtil;
@RequiredArgsConstructor
public final class BungeeCommandUtil extends CommandResponseCache<BaseComponent[]> implements CommandUtil {
private final FloodgateLogger logger;
@Override
public void sendMessage(Object player, CommandMessage message, Object... args) {
cast(player).sendMessage(getOrAddCachedMessage(message, args));
}
@Override
public void kickPlayer(Object player, CommandMessage message, Object... args) {
cast(player).disconnect(getOrAddCachedMessage(message, args));
}
@Override
protected BaseComponent[] transformMessage(String message) {
return TextComponent.fromLegacyText(message);
}
protected ProxiedPlayer cast(Object player) {
try {
return (ProxiedPlayer) player;
} catch (ClassCastException exception) {
logger.error("Failed to cast {} to ProxiedPlayer", player.getClass().getName());
throw exception;
}
}
}

View File

@@ -1,23 +0,0 @@
package org.geysermc.floodgate.util;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.geysermc.floodgate.command.CommandMessage;
public class CommandUtil extends AbstractCommandResponseCache<BaseComponent[]> implements ICommandUtil<ProxiedPlayer> {
@Override
public void sendMessage(ProxiedPlayer player, CommandMessage message, Object... args) {
player.sendMessage(getOrAddCachedMessage(message, args));
}
@Override
public void kickPlayer(ProxiedPlayer player, CommandMessage message, Object... args) {
player.disconnect(getOrAddCachedMessage(message, args));
}
@Override
protected BaseComponent[] transformMessage(String message) {
return TextComponent.fromLegacyText(message);
}
}

View File

@@ -1,21 +1,5 @@
name: ${project.name}
name: ${outputName}
description: ${project.description}
version: ${project.version}
author: ${project.organization.name}
main: org.geysermc.floodgate.BungeePlugin
# Configuration for player linking
player-link:
# Whether to enable the linking system. Turning this off will prevent
# players from using the linking feature even if they are already linked.
enable: false
# The type of storage system you want to use
# Currently implemented: SQLite
type: sqlite
# Whether to allow the use of /linkaccount and /unlinkaccount
# You can also use allow specific people to use the commands using the
# permissions floodgate.linkaccount and floodgate.unlinkaccount.
# This is only for linking, already connected people will stay connected
allow-linking: true
# The amount of time until a link code expires in seconds
link-code-timeout: 300
main: org.geysermc.floodgate.BungeePlugin

View File

@@ -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">
<parent>
<artifactId>floodgate-parent</artifactId>
<groupId>org.geysermc</groupId>
<artifactId>parent</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>floodgate-common</artifactId>
<artifactId>common</artifactId>
<repositories>
<repository>
@@ -36,28 +36,31 @@
<dependencies>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>common</artifactId>
<version>${geyser-version}</version>
<scope>compile</scope>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10.4</version>
<scope>compile</scope>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>4.1.49.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>4.1.49.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.30.1</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@@ -1,94 +0,0 @@
package org.geysermc.floodgate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
abstract class AbstractFloodgateAPI {
static final Map<UUID, FloodgatePlayer> players = new HashMap<>();
/**
* Get info about the given Bedrock player
* @param uuid the uuid of the <b>online</b> 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 <b>online</b> player is a bedrock player
* @param uuid The uuid of the <b>online</b> player
* @return true if the given <b>online</b> 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;
}
}

View File

@@ -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<P, C extends ICommandUtil<P>> {
private final Map<String, LinkRequest> 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 <gamertag>"),
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 <gamertag>"),
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);
}
}
}

View File

@@ -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<P, C extends ICommandUtil<P>> {
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);
}
}
}

View File

@@ -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 extends FloodgateConfig> T load(Logger logger, Path configPath, Class<T> 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;
}
}

View File

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

View File

@@ -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<br>
* 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.<br>
* 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.<br>
* Please note that the LinkedPlayer will be loaded (sync) when used for the first time.<br>
* 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<LinkedPlayer> fetchLinkedPlayerAsync() {
return PlayerLink.isEnabledAndAllowed() ?
PlayerLink.getInstance().getLinkedPlayer(javaUniqueId) :
CompletableFuture.completedFuture(null);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<LinkedPlayer> getLinkedPlayer(UUID bedrockId);
public abstract CompletableFuture<Boolean> isLinkedPlayer(UUID bedrockId);
public abstract CompletableFuture<Void> linkPlayer(UUID bedrockId, UUID uuid, String username);
public abstract CompletableFuture<Void> 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 <U> CompletableFuture<U> failedFuture(Throwable exception) {
CompletableFuture<U> future = new CompletableFuture<>();
future.completeExceptionally(exception);
return future;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.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<UUID, String> 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();
}
}
}

View File

@@ -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<UUID, FloodgatePlayer> 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;
}
}

View File

@@ -1,6 +0,0 @@
package org.geysermc.floodgate.command;
public interface CommandMessage {
char COLOR_CHAR = '\u00A7';
String getMessage();
}

View File

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

View File

@@ -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<String, LinkRequest> 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 <gamertag>"),
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 <gamertag>"),
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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, Object> currentVersion,
Map<String, String> renames, Path defaultConfigLocation) throws IOException {
List<String> notFound = new ArrayList<>();
List<String> 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());
}
}
}

View File

@@ -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<String, Object> config = new Yaml().load(configReader);
// currently unused, but can be used when a config name has been changed
Map<String, String> 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");
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.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<Channel> injectedClients = new HashSet<>();
private final Map<Class<?>, 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 extends InjectorAddon> T removeAddon(Class<T> 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);
}
}
}
}

View File

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

View File

@@ -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<LinkedPlayer> getLinkedPlayer(UUID bedrockId) {
return null;
}
@Override
public CompletableFuture<Boolean> isLinkedPlayer(UUID bedrockId) {
return null;
}
@Override
public CompletableFuture<Void> linkPlayer(UUID bedrockId, UUID javaId, String username) {
return null;
}
@Override
public CompletableFuture<Void> 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() {
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.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));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <T> 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<T> {
private final Map<CommandMessage, T> 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();
}
}

View File

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

View File

@@ -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 <T> the platform-specific listener class
*/
public interface ListenerRegistration<T> {
/**
* This method will register the specified listener.
*
* @param listener the listener to register
*/
void register(T listener);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.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<InjectorAddon> addons) {
for (InjectorAddon addon : addons) {
guice.injectMembers(addon);
injector.addAddon(addon);
}
}
}

View File

@@ -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<Command> foundCommands) {
for (Command command : foundCommands) {
guice.injectMembers(command);
registration.register(command);
}
}
}

View File

@@ -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<T> {
private final ListenerRegistration<T> registration;
@Inject
public void registerListeners(Set<T> foundListeners) {
for (T listener : foundListeners) {
registration.register(listener);
}
}
}

View File

@@ -1,46 +0,0 @@
package org.geysermc.floodgate.util;
import org.geysermc.floodgate.command.CommandMessage;
import java.util.HashMap;
import java.util.Map;
/**
* @param <T> Message Type stored. For example BaseComponent[] for Bungeecord
*/
public abstract class AbstractCommandResponseCache<T> {
private final Map<CommandMessage, T> 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:<br>
* If cached: return cached message.<br>
* If not cached: transform it, add the message to cache and return the message.<br>
* 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;
}
}

View File

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

View File

@@ -1,26 +0,0 @@
package org.geysermc.floodgate.util;
import org.geysermc.floodgate.command.CommandMessage;
public interface ICommandUtil<P> {
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);
}

View File

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

View File

@@ -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<br>
* Example net.minecraft.server.v1_8R3.PacketHandhakingInSetProtocol will become:<br>
* 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:<br>
* <code>{@link #getValue(Object, Field) getValue}(instance,
* {@link #getField(Class, String) getField}(instance.getClass(), fieldName))</code>
*/
public static Object getValue(Object instance, String fieldName) {
return getValue(instance, getField(instance.getClass(), fieldName));
}
@SuppressWarnings("unchecked")
public static <T> T getCastedValue(Object instance, Field field, Class<T> returnType) {
public static <T> T getCastedValue(Object instance, Field field) {
return (T) getValue(instance, field);
}
@SuppressWarnings("unchecked")
public static <T> T getCastedValue(Object instance, String fieldName, Class<T> returnType) {
return (T) getValue(instance, getField(instance.getClass(), fieldName));
/**
* This method is equal to running:<br>
* <code>{@link #getCastedValue(Object, Field) getCastedValue}(instance,
* {@link #getField(Class, String) getField}(instance.getClass(), fieldName))</code>
*/
public static <T> 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> T invokeCasted(Object instance, Method method, Class<T> cast) {
return (T) invoke(instance, method);
public static <T> T castedInvoke(Object instance, Method method, Object... args) {
try {
return (T) invoke(instance, method, args);
} catch (NullPointerException exception) {
return null;
}
}
public static <T> 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 extends AccessibleObject> T makeAccessible(T accessibleObject) {
if (!accessibleObject.isAccessible()) accessibleObject.setAccessible(true);
if (!accessibleObject.isAccessible()) {
accessibleObject.setAccessible(true);
}
return accessibleObject;
}
}

View File

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

View File

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

52
database/pom.xml Normal file
View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<parent>
<artifactId>parent</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.geysermc.floodgate.database</groupId>
<artifactId>parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>database</name>
<modules>
<module>sqlite</module>
</modules>
<properties>
<outputName>floodgate-${project.name}-database</outputName>
</properties>
</project>

88
database/sqlite/pom.xml Normal file
View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
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">
<parent>
<artifactId>parent</artifactId>
<groupId>org.geysermc.floodgate.database</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sqlite</artifactId>
<dependencies>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.30.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<finalName>${outputName}</finalName>
<shadedArtifactAttached>true</shadedArtifactAttached>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

@@ -0,0 +1,3 @@
{
"mainClass": "org.geysermc.floodgate.database.SqliteDatabase"
}

14
pom.xml
View File

@@ -4,19 +4,21 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-parent</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>bungee</module>
<module>api</module>
<module>common</module>
<module>bukkit</module>
<module>spigot</module>
<module>bungee</module>
<module>velocity</module>
<module>database</module>
</modules>
<packaging>pom</packaging>
<name>floodgate</name>
<description>Allows Bedrock players to join Java edition servers while keeping online mode</description>
<url>https://github.com/GeyserMC/Floodgate/</url>
<url>https://github.com/GeyserMC/Floodgate</url>
<properties>
<geyser-version>1.0-SNAPSHOT</geyser-version>
@@ -24,7 +26,7 @@
<bungee-version>1.15-SNAPSHOT</bungee-version>
<velocity-version>1.1.0-SNAPSHOT</velocity-version>
<outputName>${project.name}</outputName>
<outputName>floodgate-${project.name}</outputName>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>

View File

@@ -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">
<parent>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-parent</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>floodgate-bukkit</artifactId>
<artifactId>spigot</artifactId>
<repositories>
<repository>
@@ -38,10 +38,9 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-common</artifactId>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>

Some files were not shown because too many files have changed in this diff Show More