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

Developers can now change some Floodgate related data during handshake

This commit is contained in:
Tim203
2021-01-12 20:49:24 +01:00
parent dd4a12a5e5
commit 9b0cbd5cdd
21 changed files with 612 additions and 124 deletions

View File

@@ -27,6 +27,7 @@ package org.geysermc.floodgate.api;
import java.util.UUID;
import lombok.Getter;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
@@ -34,10 +35,16 @@ public final class InstanceHolder {
@Getter private static FloodgateApi instance;
@Getter private static PlayerLink playerLink;
@Getter private static PlatformInjector injector;
@Getter private static HandshakeHandlers handshakeHandlers;
private static UUID key;
public static boolean setInstance(FloodgateApi floodgateApi, PlayerLink link,
PlatformInjector platformInjector, UUID key) {
public static boolean setInstance(
FloodgateApi floodgateApi,
PlayerLink link,
PlatformInjector platformInjector,
HandshakeHandlers handshakeHandlers,
UUID key
) {
if (instance == null) {
InstanceHolder.key = key;
} else if (!InstanceHolder.key.equals(key)) {
@@ -46,6 +53,7 @@ public final class InstanceHolder {
instance = floodgateApi;
playerLink = link;
injector = platformInjector;
InstanceHolder.handshakeHandlers = handshakeHandlers;
return true;
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2019-2021 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.handshake;
import io.netty.channel.Channel;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
/**
* For advanced users only! You shouldn't play with this unless you know what you're doing.<br>
* <br>
* This class allows you change specific things of a Bedrock player before it is applied to the
* server. Note that at the time I'm writing this that the HandshakeData is created after requesting
* the player link. So the link is present here, if applicable.
*/
public interface HandshakeData {
/**
* Returns the Channel holding the connection between the client and the server.
*/
Channel getChannel();
/**
* Returns true if the given player is a Floodgate player, false otherwise.
*/
boolean isFloodgatePlayer();
/**
* Returns the decrypted BedrockData sent by Geyser or null if the player isn't a Floodgate
* player.
*/
BedrockData getBedrockData();
/**
* Returns the linked account associated with the client or null if the player isn't linked or
* not a Floodgate player.
*/
LinkedPlayer getLinkedPlayer();
/**
* Set the LinkedPlayer. This will be ignored if the player isn't a Floodgate player
*
* @param player the player to use as link
*/
void setLinkedPlayer(LinkedPlayer player);
/**
* Returns the skin of the client. Can be null even though the player is a Floodgate player.
*/
RawSkin getRawSkin();
/**
* Manually set the skin of the client.
*
* @param rawSkin the skin of the client
*/
void setRawSkin(RawSkin rawSkin);
/**
* Returns the hostname used in the handshake packet. This is the hostname after Floodgate
* removed the data.
*/
String getHostname();
/**
* Set the hostname of the handshake packet. Changing it here will also change it in the
* handshake packet.
*
* @param hostname the new hostname
*/
void setHostname(String hostname);
/**
* Returns the reason to disconnect the current player.
*/
String getDisconnectReason();
/**
* Set the reason to disconnect the current player.
*
* @param reason the reason to disconnect
*/
void setDisconnectReason(String reason);
/**
* Returns if the player should be disconnected
*/
default boolean shouldDisconnect() {
return getDisconnectReason() != null;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2019-2021 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.handshake;
/**
* This class allows you to change and/or get specific data of the Bedrock client before Floodgate
* does something with this data. This means that Floodgate decrypts the data, then calls the
* handshake handlers and then applies the data to the connection.<br>
* <br>
* /!\ Note that this class will be called for both Java and Bedrock connections, but {@link
* HandshakeData#isFloodgatePlayer()} will be false and Floodgate related methods will return null
* for Java players
*/
@FunctionalInterface
public interface HandshakeHandler {
/**
* Method that will be called during the time that Floodgate handles the handshake.
*
* @param data the data usable during the handshake
*/
void handle(HandshakeData data);
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2019-2021 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.handshake;
public interface HandshakeHandlers {
/**
* Register a custom handshake handler. This can be used to check and edit the player during the
* handshake handling.
*
* @param handshakeHandler the handshake handler to register
* @return a random (unique) int to identify this handshake handler or -1 if null
*/
int addHandshakeHandler(HandshakeHandler handshakeHandler);
/**
* Removes a custom handshake handler by id.
*
* @param handshakeHandlerId the id of the handshake handler to remove
*/
void removeHandshakeHandler(int handshakeHandlerId);
/**
* Remove a custom handshake handler by instance.
*
* @param handshakeHandler the instance to remove
*/
void removeHandshakeHandler(Class<? extends HandshakeHandler> handshakeHandler);
}

View File

@@ -73,7 +73,7 @@ public interface PlatformInjector {
*
* @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
* @return the removed addon instance
*/
<T extends InjectorAddon> T removeAddon(Class<T> addon);
}

View File

@@ -89,4 +89,9 @@ public interface FloodgateLogger {
* this method, but they will be hidden from the console.
*/
void disableDebug();
/**
* Returns if debugging is enabled
*/
boolean isDebug();
}

View File

@@ -29,18 +29,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static org.geysermc.floodgate.player.HandshakeHandler.ResultType;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.packet.Handshake;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
@@ -81,10 +81,6 @@ public final class BungeeDataHandler {
checkNotNull(CACHED_HANDSHAKE_PACKET, "Cached handshake packet field cannot be null");
}
@Inject
@Named("playerAttribute")
private AttributeKey<FloodgatePlayer> playerAttribute;
@Inject private Plugin plugin;
@Inject private ProxyFloodgateConfig config;
@Inject private ProxyFloodgateApi api;
@@ -94,16 +90,22 @@ public final class BungeeDataHandler {
public void handlePreLogin(PreLoginEvent event) {
event.registerIntent(plugin);
plugin.getProxy().getScheduler().runAsync(plugin, () -> {
String extraData = ReflectionUtils.getCastedValue(
event.getConnection(), EXTRA_HANDSHAKE_DATA
);
PendingConnection connection = event.getConnection();
Object channelWrapper =
ReflectionUtils.getValue(event.getConnection(), PLAYER_CHANNEL_WRAPPER);
String extraData = ReflectionUtils.getCastedValue(connection, EXTRA_HANDSHAKE_DATA);
Object channelWrapper = ReflectionUtils.getValue(connection, PLAYER_CHANNEL_WRAPPER);
Channel channel = ReflectionUtils.getCastedValue(channelWrapper, PLAYER_CHANNEL);
HandshakeResult result = handler.handle(channel, extraData);
HandshakeData handshakeData = result.getHandshakeData();
if (handshakeData.getDisconnectReason() != null) {
//noinspection ConstantConditions
channel.close(); // todo disconnect with message
return;
}
switch (result.getResultType()) {
case EXCEPTION:
event.setCancelReason(config.getDisconnect().getInvalidKey());
@@ -124,12 +126,10 @@ public final class BungeeDataHandler {
FloodgatePlayer player = result.getFloodgatePlayer();
event.getConnection().setOnlineMode(false);
event.getConnection().setUniqueId(player.getCorrectUniqueId());
connection.setOnlineMode(false);
connection.setUniqueId(player.getCorrectUniqueId());
ReflectionUtils.setValue(
event.getConnection(), PLAYER_NAME, player.getCorrectUsername()
);
ReflectionUtils.setValue(connection, PLAYER_NAME, player.getCorrectUsername());
SocketAddress remoteAddress =
ReflectionUtils.getCastedValue(channelWrapper, PLAYER_REMOTE_ADDRESS);

View File

@@ -34,6 +34,7 @@ import java.nio.file.Path;
import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
@@ -64,8 +65,12 @@ public class FloodgatePlatform {
}
@Inject
public void init(@Named("dataDirectory") Path dataDirectory, ConfigLoader configLoader,
FloodgateConfigHolder configHolder) {
public void init(
@Named("dataDirectory") Path dataDirectory,
ConfigLoader configLoader,
FloodgateConfigHolder configHolder,
HandshakeHandlers handshakeHandlers
) {
if (!Files.isDirectory(dataDirectory)) {
try {
@@ -85,7 +90,7 @@ public class FloodgatePlatform {
guice = guice.createChildInjector(new ConfigLoadedModule(config));
PlayerLink link = guice.getInstance(PlayerLinkLoader.class).load();
InstanceHolder.setInstance(api, link, this.injector, KEY);
InstanceHolder.setInstance(api, link, this.injector, handshakeHandlers, KEY);
}
public boolean enable(Module... postInitializeModules) {

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.addon.data;
import io.netty.channel.Channel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
@Getter
@RequiredArgsConstructor
@AllArgsConstructor
public class HandshakeDataImpl implements HandshakeData {
private final Channel channel;
private final boolean floodgatePlayer;
private final BedrockData bedrockData;
@Setter private LinkedPlayer linkedPlayer;
@Setter private RawSkin rawSkin;
@Setter private String hostname;
@Setter private String disconnectReason;
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.addon.data;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import java.util.Random;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.handshake.HandshakeHandler;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
public class HandshakeHandlersImpl implements HandshakeHandlers {
private final Random random = new Random();
private final IntObjectMap<HandshakeHandler> handshakeHandlers = new IntObjectHashMap<>();
@Override
public int addHandshakeHandler(HandshakeHandler handshakeHandler) {
if (handshakeHandler == null) {
return -1;
}
int key;
do {
key = random.nextInt(Integer.MAX_VALUE);
} while (handshakeHandlers.putIfAbsent(key, handshakeHandler) != null);
return key;
}
@Override
public void removeHandshakeHandler(int handshakeHandlerId) {
// key is always positive
if (handshakeHandlerId <= 0) {
return;
}
handshakeHandlers.remove(handshakeHandlerId);
}
@Override
public void removeHandshakeHandler(Class<? extends HandshakeHandler> handshakeHandler) {
if (HandshakeHandler.class == handshakeHandler) {
return;
}
handshakeHandlers.values().removeIf(handler -> handler.getClass() == handshakeHandler);
}
public void callHandshakeHandlers(HandshakeData handshakeData) {
for (HandshakeHandler handshakeHandler : handshakeHandlers.values()) {
handshakeHandler.handle(handshakeData);
}
}
}

View File

@@ -35,6 +35,7 @@ import org.geysermc.cumulus.util.FormBuilder;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor
public class SimpleFloodgateApi implements FloodgateApi {
@@ -66,7 +67,7 @@ public class SimpleFloodgateApi implements FloodgateApi {
@Override
public UUID createJavaPlayerId(long xuid) {
return new UUID(0, xuid);
return Utils.getJavaUuid(xuid);
}
@Override

View File

@@ -26,22 +26,20 @@
package org.geysermc.floodgate.link;
import com.google.gson.JsonObject;
import com.google.inject.Inject;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.link.LinkRequestResult;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.Utils;
public class GlobalPlayerLinking extends CommonPlayerLink {
private static final String GET_BEDROCK_LINK = "http://localhost:4000/api/link/bedrock?xuid=";
@Inject private FloodgateApi api;
@Override
public void load() {}
public void load() {
}
@Override
public CompletableFuture<LinkedPlayer> getLinkedPlayer(UUID bedrockId) {
@@ -69,7 +67,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
return LinkedPlayer.of(
data.get("javaName").getAsString(),
UUID.fromString(data.get("javaId").getAsString()),
api.createJavaPlayerId(data.get("bedrockId").getAsLong()));
Utils.getJavaUuid(data.get("bedrockId").getAsLong()));
},
getExecutorService());
}
@@ -117,8 +115,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
public CompletableFuture<?> createLinkRequest(
UUID javaId,
String javaUsername,
String bedrockUsername
) {
String bedrockUsername) {
return null;
}
@@ -127,8 +124,7 @@ public class GlobalPlayerLinking extends CommonPlayerLink {
UUID bedrockId,
String javaUsername,
String bedrockUsername,
String code
) {
String code) {
return null;
}
}

View File

@@ -86,4 +86,9 @@ public final class JavaUtilFloodgateLogger implements FloodgateLogger {
logger.setLevel(originLevel);
}
}
@Override
public boolean isDebug() {
return logger.getLevel() == Level.ALL;
}
}

View File

@@ -32,8 +32,10 @@ import com.google.inject.name.Named;
import io.netty.util.AttributeKey;
import java.nio.file.Path;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeHandlers;
import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -60,6 +62,7 @@ public class CommonModule extends AbstractModule {
protected void configure() {
bind(FloodgateApi.class).to(SimpleFloodgateApi.class);
bind(PlatformInjector.class).to(CommonPlatformInjector.class);
bind(HandshakeHandlers.class).to(HandshakeHandlersImpl.class);
}
@Provides
@@ -120,15 +123,30 @@ public class CommonModule extends AbstractModule {
return new LanguageManager(configHolder, logger);
}
@Provides
@Singleton
public HandshakeHandlersImpl handshakeHandlers() {
return new HandshakeHandlersImpl();
}
@Provides
@Singleton
public HandshakeHandler handshakeHandler(
HandshakeHandlersImpl handshakeHandlers,
SimpleFloodgateApi api,
FloodgateCipher cipher,
FloodgateConfigHolder configHolder,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger
) {
return new HandshakeHandler(api, cipher, configHolder, playerAttribute);
return new HandshakeHandler(
handshakeHandlers,
api,
cipher,
configHolder,
playerAttribute,
logger
);
}
@Provides

View File

@@ -37,6 +37,7 @@ 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.handshake.HandshakeData;
import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
@@ -49,6 +50,7 @@ import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile;
import org.geysermc.floodgate.util.Utils;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@@ -79,8 +81,11 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
*/
@Setter private boolean login = true;
protected static FloodgatePlayerImpl from(BedrockData data, RawSkin skin,
protected static FloodgatePlayerImpl from(
BedrockData data,
HandshakeData handshakeData,
FloodgateConfigHolder configHolder) {
FloodgateApi api = FloodgateApi.getInstance();
FloodgateConfig config = configHolder.get();
@@ -91,22 +96,14 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
javaUsername = javaUsername.replaceAll(" ", "_");
}
UUID javaUniqueId = api.createJavaPlayerId(Long.parseLong(data.getXuid()));
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());
DeviceOs deviceOs = DeviceOs.getById(data.getDeviceOs());
UiProfile uiProfile = UiProfile.getById(data.getUiProfile());
InputMode inputMode = InputMode.getById(data.getInputMode());
LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
if (data.hasPlayerLink()) {
linkedPlayer = data.getLinkedPlayer();
} else {
// every implementation (Bukkit, Bungee and Velocity) run this constructor async,
// so we should be fine doing this synchronised.
linkedPlayer = fetchLinkedPlayer(api.getPlayerLink(), javaUniqueId);
}
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
RawSkin skin = handshakeData.getRawSkin();
FloodgatePlayerImpl player = new FloodgatePlayerImpl(
data.getVersion(), data.getUsername(), javaUsername, javaUniqueId, data.getXuid(),
@@ -149,7 +146,8 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
* isn't enabled
* @see #fetchLinkedPlayer(PlayerLink, UUID) for the sync version
*/
public static CompletableFuture<LinkedPlayer> fetchLinkedPlayerAsync(PlayerLink link,
public static CompletableFuture<LinkedPlayer> fetchLinkedPlayerAsync(
PlayerLink link,
UUID javaUniqueId) {
return link.isEnabled() ?
link.getLinkedPlayer(javaUniqueId) :

View File

@@ -31,12 +31,18 @@ import com.google.common.base.Charsets;
import io.netty.channel.Channel;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.addon.data.HandshakeDataImpl;
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
@@ -45,29 +51,39 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.Base64Utils;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.InvalidFormatException;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor
public final class HandshakeHandler {
private final HandshakeHandlersImpl handshakeHandlers;
private final SimpleFloodgateApi api;
private final FloodgateCipher cipher;
private final FloodgateConfigHolder configHolder;
private final AttributeKey<FloodgatePlayer> playerAttribute;
private final FloodgateLogger logger;
public HandshakeResult handle(Channel channel, @NonNull String handshakeData) {
public HandshakeResult handle(Channel channel, @NonNull String hostname) {
try {
String[] dataArray = handshakeData.split("\0");
String[] split = hostname.split("\0");
String data = null;
for (String value : dataArray) {
if (FloodgateCipher.hasHeader(value)) {
StringBuilder hostnameBuilder = new StringBuilder();
for (String value : split) {
if (data == null && FloodgateCipher.hasHeader(value)) {
data = value;
break;
continue;
}
hostnameBuilder.append(value);
}
// hostname now doesn't have Floodgate data anymore if it had
hostname = hostnameBuilder.toString();
if (data == null) {
return ResultType.NOT_FLOODGATE_DATA.getCachedResult();
return callHandlerAndReturnResult(
ResultType.NOT_FLOODGATE_DATA,
channel, null, hostname);
}
// header + base64 iv - 0x21 - encrypted data - 0x21 - RawSkin
@@ -91,7 +107,9 @@ public final class HandshakeHandler {
BedrockData bedrockData = BedrockData.fromString(decrypted);
if (bedrockData.getDataLength() != EXPECTED_LENGTH) {
return ResultType.INVALID_DATA_LENGTH.getCachedResult();
return callHandlerAndReturnResult(
ResultType.INVALID_DATA_LENGTH,
channel, bedrockData, hostname);
}
RawSkin rawSkin = null;
@@ -102,7 +120,29 @@ public final class HandshakeHandler {
rawSkin = RawSkin.decode(rawSkinData);
}
FloodgatePlayer player = FloodgatePlayerImpl.from(bedrockData, rawSkin, configHolder);
LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
if (bedrockData.hasPlayerLink()) {
linkedPlayer = bedrockData.getLinkedPlayer();
} else {
// every implementation (Bukkit, Bungee and Velocity) run this constructor async,
// so we should be fine doing this synchronised.
linkedPlayer = fetchLinkedPlayer(Utils.getJavaUuid(bedrockData.getXuid()));
}
HandshakeData handshakeData = new HandshakeDataImpl(channel, true, bedrockData.clone(),
linkedPlayer != null ? linkedPlayer.clone() : null, rawSkin, hostname, null);
handshakeHandlers.callHandshakeHandlers(handshakeData);
UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid());
handshakeData.setHostname(correctHostname(
handshakeData.getHostname(), bedrockData, javaUuid
));
FloodgatePlayer player =
FloodgatePlayerImpl.from(bedrockData, handshakeData, configHolder);
api.addPlayer(player.getJavaUniqueId(), player);
channel.attr(playerAttribute).set(player);
@@ -111,19 +151,76 @@ public final class HandshakeHandler {
InetSocketAddress socketAddress = new InetSocketAddress(bedrockData.getIp(), port);
player.addProperty(PropertyKey.SOCKET_ADDRESS, socketAddress);
return new HandshakeResult(ResultType.SUCCESS, dataArray, bedrockData, player);
return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player);
} catch (InvalidFormatException formatException) {
// only header exceptions should return 'not floodgate data',
// all the other format exceptions are because of invalid/tempered Floodgate data
if (formatException.isHeader()) {
return ResultType.NOT_FLOODGATE_DATA.getCachedResult();
return callHandlerAndReturnResult(
ResultType.NOT_FLOODGATE_DATA,
channel, null, hostname);
}
formatException.printStackTrace();
return ResultType.EXCEPTION.getCachedResult();
return callHandlerAndReturnResult(
ResultType.EXCEPTION,
channel, null, hostname);
} catch (Exception exception) {
exception.printStackTrace();
return ResultType.EXCEPTION.getCachedResult();
return callHandlerAndReturnResult(
ResultType.EXCEPTION,
channel, null, hostname);
}
}
private HandshakeResult callHandlerAndReturnResult(
ResultType resultType,
Channel channel,
BedrockData bedrockData,
String hostname
) {
HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null,
bedrockData, null, null, hostname, null);
handshakeHandlers.callHandshakeHandlers(handshakeData);
if (bedrockData != null) {
UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid());
handshakeData.setHostname(correctHostname(
handshakeData.getHostname(), bedrockData, javaUuid
));
}
return new HandshakeResult(resultType, handshakeData, bedrockData, null);
}
private String correctHostname(String hostname, BedrockData data, UUID correctUuid) {
// replace the ip and uuid with the Bedrock client IP and an uuid based of the xuid
String[] split = hostname.split("\0");
if (split.length >= 3) {
if (logger.isDebug()) {
logger.info("Replacing hostname arg1 '{}' with '{}' and arg2 '{}' with '{}'",
split[1], data.getIp(), split[2], correctUuid.toString());
}
split[1] = data.getIp();
split[2] = correctUuid.toString();
}
return String.join("\0", split);
}
private LinkedPlayer fetchLinkedPlayer(UUID javaUniqueId) {
if (!api.getPlayerLink().isEnabled()) {
return null;
}
try {
return api.getPlayerLink().getLinkedPlayer(javaUniqueId).get();
} catch (InterruptedException | ExecutionException exception) {
exception.printStackTrace();
return null;
}
}
@@ -131,21 +228,14 @@ public final class HandshakeHandler {
EXCEPTION,
NOT_FLOODGATE_DATA,
INVALID_DATA_LENGTH,
SUCCESS;
SUCCESS
}
@Getter
private final HandshakeResult cachedResult;
ResultType() {
cachedResult = new HandshakeResult(this, null, null, null);
}
}
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public static class HandshakeResult {
private final ResultType resultType;
private final String[] handshakeData;
private final HandshakeData handshakeData;
private final BedrockData bedrockData;
private final FloodgatePlayer floodgatePlayer;
}

View File

@@ -38,6 +38,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
public class Utils {
/**
@@ -62,8 +63,9 @@ public class Utils {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
if (line == null) {
break;
}
result.add(line);
}
return result;
@@ -79,4 +81,12 @@ public class Utils {
public static String getLocale(Locale locale) {
return locale.getLanguage() + "_" + locale.getCountry();
}
public static UUID getJavaUuid(long xuid) {
return new UUID(0, xuid);
}
public static UUID getJavaUuid(String xuid) {
return getJavaUuid(Long.parseLong(xuid));
}
}

View File

@@ -43,6 +43,7 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
@@ -159,7 +160,7 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
private final HandshakeHandler handshakeHandler;
private final FloodgateLogger logger;
private Object networkManager;
private FloodgatePlayer fPlayer;
private FloodgatePlayer player;
private boolean bungeeData;
private boolean done;
@@ -180,6 +181,13 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
String handshakeValue = getCastedValue(packet, HANDSHAKE_HOST);
HandshakeResult result = handshakeHandler.handle(ctx.channel(), handshakeValue);
HandshakeData handshakeData = result.getHandshakeData();
if (handshakeData.getDisconnectReason() != null) {
ctx.close(); // todo disconnect with message
return;
}
switch (result.getResultType()) {
case SUCCESS:
break;
@@ -195,24 +203,18 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
return;
}
fPlayer = result.getFloodgatePlayer();
BedrockData bedrockData = result.getBedrockData();
String[] data = result.getHandshakeData();
player = result.getFloodgatePlayer();
bungeeData = isBungeeData();
InetSocketAddress correctAddress = fPlayer.getProperty(PropertyKey.SOCKET_ADDRESS);
setValue(packet, HANDSHAKE_HOST, handshakeData.getHostname());
if (bungeeData) {
setValue(packet, HANDSHAKE_HOST, data[0] + '\0' +
bedrockData.getIp() + '\0' +
fPlayer.getCorrectUniqueId() +
(data.length == 5 ? '\0' + data[4] : ""));
} else {
if (!bungeeData) {
// Use a spoofedUUID for initUUID (just like Bungeecord)
setValue(networkManager, "spoofedUUID", fPlayer.getCorrectUniqueId());
setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId());
// Use the player his IP for stuff instead of Geyser his IP
setValue(networkManager, SOCKET_ADDRESS, correctAddress);
InetSocketAddress address = player.getProperty(PropertyKey.SOCKET_ADDRESS);
setValue(networkManager, SOCKET_ADDRESS, address);
}
} else if (isLogin) {
if (!bungeeData) {
@@ -227,7 +229,7 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
// set the player his GameProfile, we can't change the username without this
Object gameProfile = GAME_PROFILE_CONSTRUCTOR.newInstance(
fPlayer.getCorrectUniqueId(), fPlayer.getCorrectUsername()
player.getCorrectUniqueId(), player.getCorrectUsername()
);
setValue(loginListener, LOGIN_PROFILE, gameProfile);
@@ -251,7 +253,7 @@ public final class SpigotDataHandler extends SimpleChannelInboundHandler<Object>
ctx.fireChannelRead(packet);
}
if (isHandshake && bungeeData || isLogin && !bungeeData || fPlayer == null) {
if (isHandshake && bungeeData || isLogin && !bungeeData || player == null) {
// we're done, we'll just wait for the loginSuccessCall
done = true;
}

View File

@@ -32,7 +32,6 @@ import io.netty.util.AttributeKey;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.inject.InjectorAddon;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.HandshakeHandler;
import org.geysermc.floodgate.util.Utils;
@@ -51,10 +50,6 @@ public final class VelocityDataAddon implements InjectorAddon {
@Named("packetEncoder")
private String packetEncoder;
@Inject
@Named("playerAttribute")
private AttributeKey<FloodgatePlayer> playerAttribute;
@Inject
@Named("kickMessageAttribute")
private AttributeKey<String> kickMessageAttribute;
@@ -71,10 +66,7 @@ public final class VelocityDataAddon implements InjectorAddon {
// The handler is already added so we should add our handler before it
channel.pipeline().addBefore(
packetHandler, "floodgate_data_handler",
new VelocityProxyDataHandler(
config, api, handshakeHandler, playerAttribute,
kickMessageAttribute, logger
)
new VelocityProxyDataHandler(config, handshakeHandler, kickMessageAttribute, logger)
);
}

View File

@@ -29,6 +29,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue;
import static org.geysermc.floodgate.util.ReflectionUtils.getField;
import static org.geysermc.floodgate.util.ReflectionUtils.getPrefixedClass;
import static org.geysermc.floodgate.util.ReflectionUtils.setValue;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
@@ -36,9 +37,10 @@ import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import java.lang.reflect.Field;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.HandshakeHandler;
import org.geysermc.floodgate.player.HandshakeHandler.HandshakeResult;
@@ -48,6 +50,7 @@ public final class VelocityProxyDataHandler extends SimpleChannelInboundHandler<
private static final Field HANDSHAKE;
private static final Class<?> HANDSHAKE_PACKET;
private static final Field HANDSHAKE_SERVER_ADDRESS;
private static final Field REMOTE_ADDRESS;
static {
Class<?> iic = getPrefixedClass("connection.client.InitialInboundConnection");
@@ -61,12 +64,13 @@ public final class VelocityProxyDataHandler extends SimpleChannelInboundHandler<
HANDSHAKE_SERVER_ADDRESS = getField(HANDSHAKE_PACKET, "serverAddress");
checkNotNull(HANDSHAKE_SERVER_ADDRESS, "Address in the Handshake packet cannot be null");
Class<?> minecraftConnection = getPrefixedClass("connection.MinecraftConnection");
REMOTE_ADDRESS = getField(minecraftConnection, "remoteAddress");
}
private final ProxyFloodgateConfig config;
private final ProxyFloodgateApi api;
private final HandshakeHandler handshakeHandler;
private final AttributeKey<FloodgatePlayer> playerAttribute;
private final AttributeKey<String> kickMessageAttribute;
private final FloodgateLogger logger;
private boolean done;
@@ -90,6 +94,13 @@ public final class VelocityProxyDataHandler extends SimpleChannelInboundHandler<
String address = getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS);
HandshakeResult result = handshakeHandler.handle(ctx.channel(), address);
HandshakeData handshakeData = result.getHandshakeData();
if (handshakeData.getDisconnectReason() != null) {
ctx.channel().attr(kickMessageAttribute).set(handshakeData.getDisconnectReason());
return;
}
switch (result.getResultType()) {
case SUCCESS:
break;
@@ -99,12 +110,17 @@ public final class VelocityProxyDataHandler extends SimpleChannelInboundHandler<
ctx.channel().attr(kickMessageAttribute)
.set(config.getDisconnect().getInvalidArgumentsLength());
return;
default:
default: // only continue when SUCCESS
return;
}
FloodgatePlayer player = result.getFloodgatePlayer();
setValue(packet, HANDSHAKE_SERVER_ADDRESS, handshakeData.getHostname());
Object connection = ctx.pipeline().get("handler");
setValue(connection, REMOTE_ADDRESS, player.getProperty(PropertyKey.SOCKET_ADDRESS));
logger.info("Floodgate player who is logged in as {} {} joined",
player.getCorrectUsername(), player.getCorrectUniqueId());
}

View File

@@ -87,4 +87,9 @@ public final class Slf4jFloodgateLogger implements FloodgateLogger {
Configurator.setLevel(logger.getName(), Level.INFO);
}
}
@Override
public boolean isDebug() {
return logger.isDebugEnabled();
}
}