From 1c49036e3a5920f075fa4885f54c1f9efee62ef4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 6 Nov 2022 21:32:55 -0500 Subject: [PATCH] Proof-of-concept for Geyser-Floodgate merge --- api/base/build.gradle.kts | 7 - .../main/java/org/geysermc/api/Geyser.java | 95 ------------- .../java/org/geysermc/api/GeyserApiBase.java | 130 ------------------ .../geysermc/api/connection/Connection.java | 121 ---------------- .../java/org/geysermc/api/util/InputMode.java | 49 ------- api/geyser/build.gradle.kts | 2 +- bootstrap/bungeecord/build.gradle.kts | 2 + .../bungeecord/BungeeHybridListener.java | 122 ++++++++++++++++ .../bungeecord/GeyserBungeeConfiguration.java | 1 + .../bungeecord/GeyserBungeePlugin.java | 35 +++-- bootstrap/spigot/build.gradle.kts | 3 + .../spigot/GeyserSpigotConfiguration.java | 1 + .../platform/spigot/GeyserSpigotInjector.java | 11 +- .../platform/spigot/GeyserSpigotPlugin.java | 64 +++------ .../spigot/SpigotHybridChannelHandler.java | 88 ++++++++++++ .../standalone/gui/GeyserStandaloneGUI.java | 2 +- build.gradle.kts | 6 +- .../org/geysermc/floodgate/util/DeviceOs.java | 73 ---------- .../geysermc/floodgate/util/InputMode.java | 46 ------- core/build.gradle.kts | 4 + .../org/geysermc/geyser/GeyserBootstrap.java | 17 +++ .../java/org/geysermc/geyser/GeyserImpl.java | 25 ++-- .../configuration/GeyserConfiguration.java | 9 ++ .../GeyserJacksonConfiguration.java | 12 ++ .../org/geysermc/geyser/dump/DumpInfo.java | 6 +- .../geyser/entity/type/FireworkEntity.java | 8 -- .../hybrid/FloodgateHybridProvider.java | 37 +++-- .../geyser/hybrid/HybridProvider.java | 90 ++++++++++++ .../hybrid/IntegratedHybridProvider.java | 37 +++-- .../geyser/hybrid/ProxyHybridProvider.java | 65 ++++----- .../netty/LocalServerChannelWrapper.java | 14 +- .../geyser/network/netty/LocalSession.java | 15 +- .../geyser/session/GeyserSession.java | 22 +-- .../session/auth/BedrockClientData.java | 12 +- ...Uploader.java => BedrockSkinUploader.java} | 19 +-- .../BedrockNetworkStackLatencyTranslator.java | 4 +- core/src/main/resources/config.yml | 6 - gradle.properties | 4 +- gradle/libs.versions.toml | 3 + settings.gradle.kts | 4 +- 40 files changed, 545 insertions(+), 726 deletions(-) delete mode 100644 api/base/build.gradle.kts delete mode 100644 api/base/src/main/java/org/geysermc/api/Geyser.java delete mode 100644 api/base/src/main/java/org/geysermc/api/GeyserApiBase.java delete mode 100644 api/base/src/main/java/org/geysermc/api/connection/Connection.java delete mode 100644 api/base/src/main/java/org/geysermc/api/util/InputMode.java create mode 100644 bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeHybridListener.java create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotHybridChannelHandler.java delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/InputMode.java rename api/base/src/main/java/org/geysermc/api/util/UiProfile.java => core/src/main/java/org/geysermc/geyser/hybrid/FloodgateHybridProvider.java (56%) create mode 100644 core/src/main/java/org/geysermc/geyser/hybrid/HybridProvider.java rename common/src/main/java/org/geysermc/floodgate/util/UiProfile.java => core/src/main/java/org/geysermc/geyser/hybrid/IntegratedHybridProvider.java (55%) rename api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java => core/src/main/java/org/geysermc/geyser/hybrid/ProxyHybridProvider.java (53%) rename core/src/main/java/org/geysermc/geyser/skin/{FloodgateSkinUploader.java => BedrockSkinUploader.java} (91%) diff --git a/api/base/build.gradle.kts b/api/base/build.gradle.kts deleted file mode 100644 index 6b6fb8f46..000000000 --- a/api/base/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -dependencies { - api(libs.cumulus) - api(libs.events) { - exclude(group = "com.google.guava", module = "guava") - exclude(group = "org.lanternpowered", module = "lmbda") - } -} \ No newline at end of file diff --git a/api/base/src/main/java/org/geysermc/api/Geyser.java b/api/base/src/main/java/org/geysermc/api/Geyser.java deleted file mode 100644 index 7543d1661..000000000 --- a/api/base/src/main/java/org/geysermc/api/Geyser.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2019-2022 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/Geyser - */ - -package org.geysermc.api; - -import org.checkerframework.checker.nullness.qual.NonNull; - -/** - * General API class for Geyser. - */ -@NonNull -public class Geyser { - private static GeyserApiBase api; - - /** - * Returns the base api. - * - * @return the base api - */ - @NonNull - public static GeyserApiBase api() { - if (api == null) { - throw new RuntimeException("Api has not been registered yet!"); - } - - return api; - } - - /** - * Returns the api of the given type. - * - * @param apiClass the api class - * @param the type - * @return the api of the given type - */ - @SuppressWarnings("unchecked") - public static T api(@NonNull Class apiClass) { - if (apiClass.isInstance(api)) { - return (T) api; - } - - if (api == null) { - throw new RuntimeException("Api has not been registered yet!"); - } else { - throw new RuntimeException("Api was not an instance of " + apiClass + "! Was " + api.getClass().getCanonicalName()); - } - } - - /** - * Registers the given api type. The api cannot be - * registered if {@link #isRegistered()} is true as - * an api has already been specified. - * - * @param api the api - */ - public static void set(@NonNull GeyserApiBase api) { - if (Geyser.api != null) { - throw new RuntimeException("Cannot redefine already registered api!"); - } - - Geyser.api = api; - } - - /** - * Gets if the api has been registered and - * is ready for usage. - * - * @return if the api has been registered - */ - public static boolean isRegistered() { - return api != null; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java b/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java deleted file mode 100644 index a845e37fd..000000000 --- a/api/base/src/main/java/org/geysermc/api/GeyserApiBase.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2019-2022 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/Geyser - */ - -package org.geysermc.api; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.value.qual.IntRange; -import org.geysermc.api.connection.Connection; -import org.geysermc.cumulus.form.Form; -import org.geysermc.cumulus.form.util.FormBuilder; - -import java.util.List; -import java.util.UUID; - -/** - * The base API class. - */ -public interface GeyserApiBase { - /** - * Gets the connection from the given UUID, if applicable. The player must be logged in to the Java server - * for this to return a non-null value. - * - * @param uuid the UUID of the connection - * @return the connection from the given UUID, if applicable - */ - @Nullable - Connection connectionByUuid(@NonNull UUID uuid); - - /** - * Gets the connection from the given XUID, if applicable. This method only works for online connections. - * - * @param xuid the XUID of the session - * @return the connection from the given UUID, if applicable - */ - @Nullable - Connection connectionByXuid(@NonNull String xuid); - - /** - * Method to determine if the given online player is a Bedrock player. - * - * @param uuid the uuid of the online player - * @return true if the given online player is a Bedrock player - */ - boolean isBedrockPlayer(@NonNull UUID uuid); - - /** - * Sends a form to the given connection and opens it. - * - * @param uuid the uuid of the connection to open it on - * @param form the form to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull UUID uuid, @NonNull Form form); - - /** - * Sends a form to the given connection and opens it. - * - * @param uuid the uuid of the connection to open it on - * @param formBuilder the formBuilder to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull UUID uuid, @NonNull FormBuilder formBuilder); - - /** - * Transfer the given connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. - * - * @param uuid the uuid of the connection - * @param address the address of the server - * @param port the port of the server - * @return true if the transfer was a success - */ - boolean transfer(@NonNull UUID uuid, @NonNull String address, @IntRange(from = 0, to = 65535) int port); - - - /** - * Returns all the online connections. - */ - @NonNull - List onlineConnections(); - - /** - * Returns the amount of online connections. - */ - int onlineConnectionsCount(); - - /** - * Returns the prefix used by Floodgate. Will be null when the auth-type isn't Floodgate. - */ - @MonotonicNonNull - String usernamePrefix(); - - /** - * Returns the major API version. Bumped whenever a significant breaking change or feature addition is added. - */ - default int majorApiVersion() { - return 1; - } - - /** - * Returns the minor API version. May be bumped for new API additions. - */ - default int minorApiVersion() { - return 0; - } -} diff --git a/api/base/src/main/java/org/geysermc/api/connection/Connection.java b/api/base/src/main/java/org/geysermc/api/connection/Connection.java deleted file mode 100644 index 1cd7a9d13..000000000 --- a/api/base/src/main/java/org/geysermc/api/connection/Connection.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2022 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/Geyser - */ - -package org.geysermc.api.connection; - -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.common.value.qual.IntRange; -import org.geysermc.api.util.BedrockPlatform; -import org.geysermc.api.util.InputMode; -import org.geysermc.api.util.UiProfile; -import org.geysermc.cumulus.form.Form; -import org.geysermc.cumulus.form.util.FormBuilder; - -import java.util.UUID; - -/** - * Represents a player connection. - */ -public interface Connection { - /** - * Returns the bedrock name of the connection. - */ - @NonNull String bedrockUsername(); - - /** - * Returns the java name of the connection. - */ - @MonotonicNonNull - String javaUsername(); - - /** - * Returns the UUID of the connection. - */ - @MonotonicNonNull - UUID javaUuid(); - - /** - * Returns the XUID of the connection. - */ - @NonNull String xuid(); - - /** - * Returns the version of the Bedrock client. - */ - @NonNull String version(); - - /** - * Returns the platform that the connection is playing on. - */ - @NonNull BedrockPlatform platform(); - - /** - * Returns the language code of the connection. - */ - @NonNull String languageCode(); - - /** - * Returns the User Interface Profile of the connection. - */ - @NonNull UiProfile uiProfile(); - - /** - * Returns the Input Mode of the Bedrock client. - */ - @NonNull InputMode inputMode(); - - /** - * Returns whether the connection is linked. - * This will always return false when the auth-type isn't Floodgate. - */ - boolean isLinked(); - - /** - * Sends a form to the connection and opens it. - * - * @param form the form to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull Form form); - - /** - * Sends a form to the connection and opens it. - * - * @param formBuilder the formBuilder to send - * @return whether the form was successfully sent - */ - boolean sendForm(@NonNull FormBuilder formBuilder); - - /** - * Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are - * currently playing on. - * - * @param address the address of the server - * @param port the port of the server - * @return true if the transfer was a success - */ - boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port); -} diff --git a/api/base/src/main/java/org/geysermc/api/util/InputMode.java b/api/base/src/main/java/org/geysermc/api/util/InputMode.java deleted file mode 100644 index 70346ffa5..000000000 --- a/api/base/src/main/java/org/geysermc/api/util/InputMode.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2019-2022 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/Geyser - */ - -package org.geysermc.api.util; - -import org.checkerframework.checker.nullness.qual.NonNull; - -public enum InputMode { - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, - CONTROLLER, - VR; - - private static final InputMode[] VALUES = values(); - - /** - * Get the InputMode from the identifier. - * - * @param id the InputMode identifier - * @return The InputMode or {@link #UNKNOWN} if the mode wasn't found - */ - @NonNull - public static InputMode fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; - } -} \ No newline at end of file diff --git a/api/geyser/build.gradle.kts b/api/geyser/build.gradle.kts index dcde85337..6babe9874 100644 --- a/api/geyser/build.gradle.kts +++ b/api/geyser/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } dependencies { - api(projects.api) + api(libs.baseApi) } publishing { diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 3e0e9c147..e95ddd618 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -2,6 +2,8 @@ dependencies { api(projects.core) implementation(libs.adventure.text.serializer.bungeecord) + + implementation("org.geysermc.floodgate", "bungee", "2.2.0-SNAPSHOT") } platformRelocate("net.md_5.bungee.jni") diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeHybridListener.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeHybridListener.java new file mode 100644 index 000000000..66fa0a211 --- /dev/null +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/BungeeHybridListener.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019-2022 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/Geyser + */ + +package org.geysermc.geyser.platform.bungeecord; + +import io.netty.channel.Channel; +import net.md_5.bungee.api.connection.PendingConnection; +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.connection.InitialHandler; +import net.md_5.bungee.event.EventHandler; +import net.md_5.bungee.event.EventPriority; +import net.md_5.bungee.netty.ChannelWrapper; +import net.md_5.bungee.protocol.packet.Handshake; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.connection.Connection; +import org.geysermc.floodgate.player.FloodgatePlayerImpl; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.floodgate.util.ReflectionUtils; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.hybrid.IntegratedHybridProvider; +import org.geysermc.geyser.hybrid.ProxyHybridProvider; + +import java.lang.reflect.Field; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class BungeeHybridListener implements Listener { + // TODO consolidate with Floodgate + private static final Field CHANNEL_WRAPPER; + private static final Field PLAYER_NAME; + + static { + CHANNEL_WRAPPER = + ReflectionUtils.getFieldOfType(InitialHandler.class, ChannelWrapper.class); + checkNotNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null"); + + PLAYER_NAME = ReflectionUtils.getField(InitialHandler.class, "name"); + checkNotNull(PLAYER_NAME, "Initial name field cannot be null"); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPreLogin(PreLoginEvent event) { + // well, no reason to check if the player will be kicked anyway + if (event.isCancelled()) { + return; + } + + PendingConnection connection = event.getConnection(); + Connection player = getPlayer(connection); + if (player != null) { + connection.setOnlineMode(false); + connection.setUniqueId(player.javaUuid()); + ReflectionUtils.setValue(connection, PLAYER_NAME, player.javaUsername()); + } + } + + @EventHandler(priority = EventPriority.LOW) + public void onServerConnect(ServerConnectEvent event) { + boolean sendFloodgateData = false; // TODO + if (!sendFloodgateData) { + return; // TODO just don't register event? + } + + PendingConnection connection = event.getPlayer().getPendingConnection(); + Connection player = getPlayer(connection); + if (player != null) { + Handshake handshake = ReflectionUtils.getCastedValue(connection, "handshake"); + BedrockData data = ((FloodgatePlayerImpl) player).toBedrockData(); // FIXME + String encryptedData = ((ProxyHybridProvider) GeyserImpl.getInstance().getHybridProvider()) + .createEncryptedDataString(data); + + String address = handshake.getHost(); + + // our data goes before all the other data + int addressFinished = address.indexOf('\0'); + String originalAddress; + String remaining; + if (addressFinished != -1) { + originalAddress = address.substring(0, addressFinished); + remaining = address.substring(addressFinished); + } else { + originalAddress = address; + remaining = ""; + } + + handshake.setHost(originalAddress + '\0' + encryptedData + remaining); + // Bungeecord will add its data after our data + } + } + + @Nullable + private Connection getPlayer(PendingConnection connection) { + ChannelWrapper wrapper = ReflectionUtils.getCastedValue(connection, CHANNEL_WRAPPER); + Channel channel = wrapper.getHandle(); + + return channel.attr(IntegratedHybridProvider.SESSION_KEY).get(); // TODO re-use Floodgate's attribute key here? + } +} diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java index bc084a34e..3b43f2cb7 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeeConfiguration.java @@ -40,6 +40,7 @@ public final class GeyserBungeeConfiguration extends GeyserJacksonConfiguration @JsonIgnore private Path floodgateKeyPath; + // TODO remove public void loadFloodgate(GeyserBungeePlugin plugin) { Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate"); Path geyserDataFolder = plugin.getDataFolder().toPath(); diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java index 1c460f4de..d4fff600e 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/geyser/platform/bungeecord/GeyserBungeePlugin.java @@ -32,6 +32,9 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.protocol.ProtocolConstants; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.common.PlatformType; +import org.geysermc.floodgate.BungeePlatform; +import org.geysermc.floodgate.pluginmessage.BungeeSkinApplier; +import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; @@ -40,6 +43,8 @@ import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.hybrid.HybridProvider; +import org.geysermc.geyser.hybrid.ProxyHybridProvider; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; import org.geysermc.geyser.ping.IGeyserPingPassthrough; import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor; @@ -110,12 +115,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { @Override public void onEnable() { - // Remove this in like a year - if (getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/")); - return; - } - if (getProxy().getConfig().getListeners().size() == 1) { ListenerInfo listener = getProxy().getConfig().getListeners().toArray(new ListenerInfo[0])[0]; @@ -154,16 +153,13 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { } } - if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && getProxy().getPluginManager().getPlugin("floodgate") == null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); - return; - } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate") != null) { - // Floodgate installed means that the user wants Floodgate authentication - geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + if (getProxy().getPluginManager().getPlugin("floodgate") != null) { + geyserLogger.warning("WHY DO YOU HAVE FLOODGATE INSTALLED???1/"); } - geyserConfig.loadFloodgate(this); + if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE) { + getProxy().getPluginManager().registerListener(this, new BungeeHybridListener()); + } // Big hack - Bungee does not provide us an event to listen to, so schedule a repeating // task that waits for a field to be filled which is set after the plugin enable @@ -274,4 +270,15 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { public SocketAddress getSocketAddress() { return this.geyserInjector.getServerSocketAddress(); } + + @Override + public HybridProvider createHybridProvider(GeyserImpl geyser) { + return new ProxyHybridProvider(geyser); + } + + @Override + public SkinApplier createSkinApplier() { + new BungeePlatform(this); // TODO hack to ensure ReflectionUtils prefix is applied and I don't forget about dealing with it + return new BungeeSkinApplier(null); // Also TODO + } } diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index b5ef4e69e..0a30dd1b7 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17) } } + + implementation("org.geysermc.floodgate", "spigot", "2.2.0-SNAPSHOT") } platformRelocate("it.unimi.dsi.fastutil") @@ -30,6 +32,7 @@ platformRelocate("io.netty.channel.kqueue") // These dependencies are already present on the platform provided(libs.viaversion) +provided("com.mojang", "authlib", "1.5.21") application { mainClass.set("org.geysermc.geyser.platform.spigot.GeyserSpigotMain") diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java index 3320ffa65..a4db21371 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotConfiguration.java @@ -41,6 +41,7 @@ public final class GeyserSpigotConfiguration extends GeyserJacksonConfiguration @JsonIgnore private Path floodgateKeyPath; + // TODO REMOVE public void loadFloodgate(GeyserSpigotPlugin plugin) { Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); Path geyserDataFolder = plugin.getDataFolder().toPath(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java index c1d3b6871..77dfaf4d4 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotInjector.java @@ -33,6 +33,8 @@ import io.netty.channel.local.LocalAddress; import io.netty.util.concurrent.DefaultThreadFactory; import org.bukkit.Bukkit; import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.network.netty.GeyserInjector; import org.geysermc.geyser.network.netty.LocalServerChannelWrapper; import org.geysermc.geyser.network.netty.LocalSession; @@ -119,6 +121,13 @@ public class GeyserSpigotInjector extends GeyserInjector { @Override protected void initChannel(Channel ch) throws Exception { initChannel.invoke(childHandler, ch); + if (GeyserImpl.getInstance().getConfig().getRemote().authType() == AuthType.FLOODGATE) { + // we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks + ch.pipeline().addBefore( + "packet_handler", "geyser_data_handler", + new SpigotHybridChannelHandler() + ); + } } }) // Set to MAX_PRIORITY as MultithreadEventLoopGroup#newDefaultThreadFactory which DefaultEventLoopGroup implements does by default @@ -170,7 +179,7 @@ public class GeyserSpigotInjector extends GeyserInjector { */ private void workAroundWeirdBug(GeyserBootstrap bootstrap) { MinecraftProtocol protocol = new MinecraftProtocol(); - LocalSession session = new LocalSession(bootstrap.getGeyserConfig().getRemote().address(), + LocalSession session = new LocalSession(null, bootstrap.getGeyserConfig().getRemote().address(), bootstrap.getGeyserConfig().getRemote().port(), this.serverSocketAddress, InetAddress.getLoopbackAddress().getHostAddress(), protocol, protocol.createHelper()); session.connect(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java index 5f0061382..64a360a35 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/GeyserSpigotPlugin.java @@ -43,16 +43,20 @@ import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.geysermc.common.PlatformType; +import org.geysermc.floodgate.pluginmessage.SpigotSkinApplier; +import org.geysermc.floodgate.skin.SkinApplier; +import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.extension.Extension; -import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.hybrid.HybridProvider; +import org.geysermc.geyser.hybrid.IntegratedHybridProvider; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough; @@ -62,7 +66,9 @@ import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener; import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener; -import org.geysermc.geyser.platform.spigot.world.manager.*; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotLegacyNativeWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorldManager; +import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.util.FileUtils; @@ -163,13 +169,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return; } - // Remove this in like a year - if (Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.outdated", Constants.FLOODGATE_DOWNLOAD_LOCATION)); - this.getPluginLoader().disablePlugin(this); - return; - } - // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); @@ -184,17 +183,10 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserConfig.getBedrock().setPort(Bukkit.getPort()); } - if (geyserConfig.getRemote().authType() == AuthType.FLOODGATE && Bukkit.getPluginManager().getPlugin("floodgate") == null) { - geyserLogger.severe(GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); - this.getPluginLoader().disablePlugin(this); - } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate") != null) { - // Floodgate installed means that the user wants Floodgate authentication - geyserLogger.debug("Auto-setting to Floodgate authentication."); - geyserConfig.getRemote().setAuthType(AuthType.FLOODGATE); + if (Bukkit.getPluginManager().getPlugin("floodgate") != null) { + geyserLogger.severe("WHY DO YOU HAVE FLOODGATE INSTALLED!!!!!!! REMOVE IT!!!!"); } - geyserConfig.loadFloodgate(this); - if (!INITIALIZED) { // Needs to be an anonymous inner class otherwise Bukkit complains about missing classes Bukkit.getPluginManager().registerEvents(new Listener() { @@ -431,38 +423,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return this.geyserInjector.getServerSocketAddress(); } - public boolean isCompatible(String version, String whichVersion) { - int[] currentVersion = parseVersion(version); - int[] otherVersion = parseVersion(whichVersion); - int length = Math.max(currentVersion.length, otherVersion.length); - for (int index = 0; index < length; index = index + 1) { - int self = (index < currentVersion.length) ? currentVersion[index] : 0; - int other = (index < otherVersion.length) ? otherVersion[index] : 0; - - if (self != other) { - return (self - other) > 0; - } - } - return true; + @Override + public HybridProvider createHybridProvider(GeyserImpl geyser) { + return new IntegratedHybridProvider(geyser); } - private int[] parseVersion(String versionParam) { - versionParam = (versionParam == null) ? "" : versionParam; - if (versionParam.contains("(MC: ")) { - versionParam = versionParam.split("\\(MC: ")[1]; - versionParam = versionParam.split("\\)")[0]; - } - String[] stringArray = versionParam.split("[_.-]"); - int[] temp = new int[stringArray.length]; - for (int index = 0; index <= (stringArray.length - 1); index = index + 1) { - String t = stringArray[index].replaceAll("\\D", ""); - try { - temp[index] = Integer.parseInt(t); - } catch (NumberFormatException ex) { - temp[index] = 0; - } - } - return temp; + @Override + public SkinApplier createSkinApplier() { + return new SpigotSkinApplier(new SpigotVersionSpecificMethods(this), this); } /** diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotHybridChannelHandler.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotHybridChannelHandler.java new file mode 100644 index 000000000..acfd30f40 --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/SpigotHybridChannelHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019-2022 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/Geyser + */ + +package org.geysermc.geyser.platform.spigot; + +import com.mojang.authlib.GameProfile; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.geysermc.floodgate.util.ClassNames; +import org.geysermc.geyser.hybrid.IntegratedHybridProvider; +import org.geysermc.geyser.session.GeyserSession; + +import javax.annotation.Nonnull; + +import static org.geysermc.floodgate.util.ReflectionUtils.setValue; + +@ChannelHandler.Sharable +public final class SpigotHybridChannelHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(@Nonnull ChannelHandlerContext ctx, @Nonnull Object packet) throws Exception { + GeyserSession session = ctx.channel().attr(IntegratedHybridProvider.SESSION_KEY).get(); + // TODO generify this code within Floodgate + if (ClassNames.LOGIN_START_PACKET.isInstance(packet)) { + Object networkManager = ctx.channel().pipeline().get("packet_handler"); + Object packetListener = ClassNames.PACKET_LISTENER.get(networkManager); + + setValue(networkManager, "spoofedUUID", session.javaUuid()); + + // check if the server is actually in the Login state + if (!ClassNames.LOGIN_LISTENER.isInstance(packetListener)) { + // player is not in the login state, abort + + // I would've liked to close the channel for security reasons, but our big friend + // ProtocolSupport, who likes to break things, doesn't work otherwise + ctx.pipeline().remove(this); + return; + } + + // set the player his GameProfile, we can't change the username without this + GameProfile gameProfile = new GameProfile( + // TODO testing only + session.javaUuid(), session.javaUsername() + ); + setValue(packetListener, ClassNames.LOGIN_PROFILE, gameProfile); + + // we have to fake the offline player (login) cycle + // just like on Spigot: + + // LoginListener#initUUID + // new LoginHandler().fireEvents(); + + // and the tick of LoginListener will do the rest + + ClassNames.INIT_UUID.invoke(packetListener); + + Object loginHandler = ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(packetListener); + ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler); + + ctx.pipeline().remove(this); + return; + } + ctx.fireChannelRead(packet); + } +} diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java index 41cbafb25..fed2f5a08 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/gui/GeyserStandaloneGUI.java @@ -310,7 +310,7 @@ public class GeyserStandaloneGUI { for (GeyserSession player : GeyserImpl.getInstance().getSessionManager().getSessions().values()) { Vector row = new Vector<>(); - row.add(player.getSocketAddress().getHostName()); + row.add(player.socketAddress().getHostName()); row.add(player.getPlayerEntity().getUsername()); playerTableModel.addRow(row); diff --git a/build.gradle.kts b/build.gradle.kts index 06c2e987b..626f34224 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { allprojects { group = "org.geysermc" - version = "2.1.0-SNAPSHOT" + version = "3.0.0-SNAPSHOT" description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers." tasks.withType { @@ -23,8 +23,6 @@ val platforms = setOf( projects.velocity ).map { it.dependencyProject } -val api: Project = projects.api.dependencyProject - subprojects { apply { plugin("java-library") @@ -40,7 +38,7 @@ subprojects { group = rootProject.group as String + ".geyser" when (this) { in platforms -> plugins.apply("geyser.platform-conventions") - api -> plugins.apply("geyser.publish-conventions") + //api -> plugins.apply("geyser.publish-conventions") FIXME else -> plugins.apply("geyser.base-conventions") } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java deleted file mode 100644 index 406204759..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019-2022 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/Geyser - */ - -package org.geysermc.floodgate.util; - -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; - -/** - * The Operation Systems where Bedrock players can connect with - */ -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public enum DeviceOs { - UNKNOWN("Unknown"), - GOOGLE("Android"), - IOS("iOS"), - OSX("macOS"), - AMAZON("Amazon"), - GEARVR("Gear VR"), - HOLOLENS("Hololens"), - UWP("Windows"), - WIN32("Windows x86"), - DEDICATED("Dedicated"), - TVOS("Apple TV"), - PS4("PS4"), - NX("Switch"), - XBOX("Xbox One"), - WINDOWS_PHONE("Windows Phone"); - - private static final DeviceOs[] VALUES = values(); - - private final String displayName; - - /** - * Get the DeviceOs instance from the identifier. - * - * @param id the DeviceOs identifier - * @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found - */ - public static DeviceOs fromId(int id) { - return id < VALUES.length ? VALUES[id] : VALUES[0]; - } - - /** - * @return friendly display name of platform. - */ - @Override - public String toString() { - return displayName; - } -} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java deleted file mode 100644 index 2bcfb88fb..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2022 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/Geyser - */ - -package org.geysermc.floodgate.util; - -public enum InputMode { - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, - CONTROLLER, - VR; - - private static final InputMode[] VALUES = values(); - - /** - * Get the InputMode instance from the identifier. - * - * @param id the InputMode identifier - * @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found - */ - public static InputMode fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; - } -} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 994325ea0..2a17de51a 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -60,6 +60,10 @@ dependencies { compileOnly(projects.ap) annotationProcessor(projects.ap) + + implementation("org.geysermc.floodgate", "core", "2.2.0-SNAPSHOT") { + exclude("org.geysermc", "api") + } } configurations.api { diff --git a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java index 261c7416b..2d0674db0 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserBootstrap.java @@ -25,9 +25,12 @@ package org.geysermc.geyser; +import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.geyser.command.GeyserCommandManager; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.dump.BootstrapDumpInfo; +import org.geysermc.geyser.hybrid.FloodgateHybridProvider; +import org.geysermc.geyser.hybrid.HybridProvider; import org.geysermc.geyser.level.GeyserWorldManager; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.ping.IGeyserPingPassthrough; @@ -134,6 +137,20 @@ public interface GeyserBootstrap { return Paths.get("logs/latest.log"); } + /** + * Creates the hybrid provider for this platform. The provider will differ based on server access. + */ + default HybridProvider createHybridProvider(GeyserImpl geyser) { + return new FloodgateHybridProvider(geyser); + } + + /** + * Returns the skin applier for this platform, if the hybrid provider is integrated with the system. + */ + default SkinApplier createSkinApplier() { + throw new IllegalStateException(); + } + /** * Get an InputStream for the given resource path. * Overridden on platforms that have different class loader properties. diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index a10e54f90..8edae903e 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -50,10 +50,8 @@ import org.geysermc.api.Geyser; import org.geysermc.common.PlatformType; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; -import org.geysermc.floodgate.crypto.AesCipher; -import org.geysermc.floodgate.crypto.AesKeyProducer; -import org.geysermc.floodgate.crypto.Base64Topping; -import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.api.InstanceHolder; +import org.geysermc.floodgate.api.impl.FloodgateApiWrapper; import org.geysermc.floodgate.news.NewsItemAction; import org.geysermc.geyser.api.GeyserApi; import org.geysermc.geyser.api.event.EventBus; @@ -69,6 +67,7 @@ import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.event.GeyserEventBus; import org.geysermc.geyser.extension.GeyserExtensionManager; +import org.geysermc.geyser.hybrid.HybridProvider; import org.geysermc.geyser.level.WorldManager; import org.geysermc.geyser.network.ConnectorServerEventHandler; import org.geysermc.geyser.pack.ResourcePack; @@ -78,7 +77,7 @@ import org.geysermc.geyser.scoreboard.ScoreboardUpdater; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.PendingMicrosoftAuthentication; import org.geysermc.geyser.session.SessionManager; -import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.skin.BedrockSkinUploader; import org.geysermc.geyser.skin.SkinProvider; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; @@ -92,7 +91,6 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; -import java.security.Key; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -134,8 +132,8 @@ public class GeyserImpl implements GeyserApi { @Setter private static boolean shouldStartListener = true; - private FloodgateCipher cipher; - private FloodgateSkinUploader skinUploader; + private HybridProvider hybridProvider; + private BedrockSkinUploader skinUploader; private NewsHandler newsHandler; private volatile boolean shuttingDown = false; @@ -161,6 +159,7 @@ public class GeyserImpl implements GeyserApi { instance = this; Geyser.set(this); + InstanceHolder.set(new FloodgateApiWrapper(this), null, null, null, null); // TODO this.platformType = platformType; this.bootstrap = bootstrap; @@ -325,16 +324,14 @@ public class GeyserImpl implements GeyserApi { } if (config.getRemote().authType() == AuthType.FLOODGATE) { + hybridProvider = bootstrap.createHybridProvider(this); try { - Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); - cipher = new AesCipher(new Base64Topping()); - cipher.init(key); - logger.debug(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); // Note: this is positioned after the bind so the skin uploader doesn't try to run if Geyser fails // to load successfully. Spigot complains about class loader if the plugin is disabled. - skinUploader = new FloodgateSkinUploader(this).start(); + // TODO not Floodgate exclusive? + skinUploader = new BedrockSkinUploader(this).start(); } catch (Exception exception) { - logger.severe(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); + logger.severe("Could not start the skin uploader!", exception); } } diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index 109ad3211..2288a8a3d 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -46,6 +46,8 @@ public interface GeyserConfiguration { IRemoteConfiguration getRemote(); + HybridInfo getHybridInfo(); + List getSavedUserLogins(); @Deprecated @@ -152,6 +154,13 @@ public interface GeyserConfiguration { } } + // TODO this is definitely temporary + interface HybridInfo { + String usernamePrefix(); + + boolean replaceSpaces(); + } + interface IUserAuthenticationInfo { String getEmail(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 73e208963..af1d952fe 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -62,6 +62,18 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private BedrockConfiguration bedrock = new BedrockConfiguration(); private RemoteConfiguration remote = new RemoteConfiguration(); + private HybridInfo hybridInfo = new HybridInfo() { + @Override + public String usernamePrefix() { + return "."; + } + + @Override + public boolean replaceSpaces() { + return true; + } + }; + @JsonProperty("saved-user-logins") private List savedUserLogins = Collections.emptyList(); diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 5197f2107..f017d9195 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -36,7 +36,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.AllArgsConstructor; import lombok.Getter; -import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.api.util.BedrockPlatform; import org.geysermc.floodgate.util.FloodgateInfoHolder; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.GeyserApi; @@ -73,7 +73,7 @@ public class DumpInfo { private final GitInfo gitInfo; private final GeyserConfiguration config; private final Floodgate floodgate; - private final Object2IntMap userPlatforms; + private final Object2IntMap userPlatforms; private final HashInfo hashInfo; private final RamInfo ramInfo; private LogsInfo logsInfo; @@ -121,7 +121,7 @@ public class DumpInfo { this.userPlatforms = new Object2IntOpenHashMap<>(); for (GeyserSession session : GeyserImpl.getInstance().getSessionManager().getAllSessions()) { - DeviceOs device = session.getClientData().getDeviceOs(); + BedrockPlatform device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java index 12498f752..b9443bee7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireworkEntity.java @@ -36,7 +36,6 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; -import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.level.FireworkColor; @@ -65,13 +64,6 @@ public class FireworkEntity extends Entity { return; } - // TODO: Remove once Mojang fixes bugs with fireworks crashing clients on these specific devices. - // https://bugs.mojang.com/browse/MCPE-89115 - if (session.getClientData().getDeviceOs() == DeviceOs.XBOX - || session.getClientData().getDeviceOs() == DeviceOs.PS4) { - return; - } - CompoundTag fireworks = tag.get("Fireworks"); if (fireworks == null) { // Thank you Mineplex very cool diff --git a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java b/core/src/main/java/org/geysermc/geyser/hybrid/FloodgateHybridProvider.java similarity index 56% rename from api/base/src/main/java/org/geysermc/api/util/UiProfile.java rename to core/src/main/java/org/geysermc/geyser/hybrid/FloodgateHybridProvider.java index cddb97260..4bf653b77 100644 --- a/api/base/src/main/java/org/geysermc/api/util/UiProfile.java +++ b/core/src/main/java/org/geysermc/geyser/hybrid/FloodgateHybridProvider.java @@ -23,23 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.api.util; +package org.geysermc.geyser.hybrid; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.PluginMessageUtils; -public enum UiProfile { - CLASSIC, POCKET; +import java.nio.charset.StandardCharsets; - private static final UiProfile[] VALUES = values(); +public final class FloodgateHybridProvider implements HybridProvider { + private final FloodgateCipher cipher; - /** - * Get the UiProfile from the identifier. - * - * @param id the UiProfile identifier - * @return The UiProfile or {@link #CLASSIC} if the profile wasn't found - */ - @NonNull - public static UiProfile fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; + public FloodgateHybridProvider(GeyserImpl geyser) { + cipher = HybridProvider.getOrCreateKey(geyser); + } + + @Override + public void onSkinUpload(GeyserSession session, String value, String signature) { + byte[] bytes = (value + '\0' + signature) + .getBytes(StandardCharsets.UTF_8); + PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes); + } + + @Override + public FloodgateCipher getCipher() { + return cipher; } } diff --git a/core/src/main/java/org/geysermc/geyser/hybrid/HybridProvider.java b/core/src/main/java/org/geysermc/geyser/hybrid/HybridProvider.java new file mode 100644 index 000000000..493f1e5e4 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/hybrid/HybridProvider.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019-2022 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/Geyser + */ + +package org.geysermc.geyser.hybrid; + +import org.geysermc.floodgate.crypto.AesCipher; +import org.geysermc.floodgate.crypto.AesKeyProducer; +import org.geysermc.floodgate.crypto.Base64Topping; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.configuration.GeyserConfiguration; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.GeyserLocale; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; + +public interface HybridProvider { + void onSkinUpload(GeyserSession session, String value, String signature); + + FloodgateCipher getCipher(); + + static FloodgateCipher getOrCreateKey(GeyserImpl geyser) { + GeyserLogger logger = geyser.getLogger(); + GeyserConfiguration config = geyser.getConfig(); + try { + // TODO make this common code with Floodgate. Like, make sure Geyser's core and Floodgate's core points to the same thing + FloodgateCipher cipher = new AesCipher(new Base64Topping()); + + Path keyPath = config.getFloodgateKeyPath(); + if (!Files.exists(keyPath)) { + generateFloodgateKey(cipher, keyPath); // Should also init the cipher for us. + // TODO good? + logger.info("We just created a Floodgate key at " + keyPath + ". You will need to copy this file into " + + "your Floodgate config folder(s)."); + } else { + Key key = new AesKeyProducer().produceFrom(keyPath); + cipher.init(key); + } + logger.debug(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + return cipher; + } catch (Exception exception) { + logger.severe(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); + return null; + } + } + + static void generateFloodgateKey(FloodgateCipher cipher, Path keyPath) throws Exception { + Key key = new AesKeyProducer().produce(); + cipher.init(key); + + String test = "abcdefghijklmnopqrstuvwxyz0123456789"; + byte[] encrypted = cipher.encryptFromString(test); + String decrypted = cipher.decryptToString(encrypted); + + if (!test.equals(decrypted)) { + throw new RuntimeException("Failed to decrypt test message.\n" + + "Original message: " + test + "." + + "Decrypted message: " + decrypted + ".\n" + + "The encrypted message itself: " + new String(encrypted) + ); + } + + Files.write(keyPath, key.getEncoded()); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/core/src/main/java/org/geysermc/geyser/hybrid/IntegratedHybridProvider.java similarity index 55% rename from common/src/main/java/org/geysermc/floodgate/util/UiProfile.java rename to core/src/main/java/org/geysermc/geyser/hybrid/IntegratedHybridProvider.java index d93042277..b31bfd944 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java +++ b/core/src/main/java/org/geysermc/geyser/hybrid/IntegratedHybridProvider.java @@ -23,21 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.floodgate.util; +package org.geysermc.geyser.hybrid; -public enum UiProfile { - CLASSIC, - POCKET; +import io.netty.util.AttributeKey; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.skin.SkinApplier; +import org.geysermc.floodgate.skin.SkinData; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.GeyserSession; - private static final UiProfile[] VALUES = values(); +public class IntegratedHybridProvider implements HybridProvider { + // TODO This will probably end up as its own class. + public static final AttributeKey SESSION_KEY = AttributeKey.valueOf("geyser-session"); - /** - * Get the UiProfile instance from the identifier. - * - * @param id the UiProfile identifier - * @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found - */ - public static UiProfile fromId(int id) { - return VALUES.length > id ? VALUES[id] : VALUES[0]; + private final SkinApplier skinApplier; + + public IntegratedHybridProvider(GeyserImpl geyser) { + skinApplier = geyser.getBootstrap().createSkinApplier(); + } + + @Override + public void onSkinUpload(GeyserSession session, String value, String signature) { + skinApplier.applySkin(session, new SkinData(value, signature)); + } + + @Override + public FloodgateCipher getCipher() { + throw new UnsupportedOperationException(); } } diff --git a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java b/core/src/main/java/org/geysermc/geyser/hybrid/ProxyHybridProvider.java similarity index 53% rename from api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java rename to core/src/main/java/org/geysermc/geyser/hybrid/ProxyHybridProvider.java index 15d0da027..101ac8834 100644 --- a/api/base/src/main/java/org/geysermc/api/util/BedrockPlatform.java +++ b/core/src/main/java/org/geysermc/geyser/hybrid/ProxyHybridProvider.java @@ -23,51 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.api.util; +package org.geysermc.geyser.hybrid; -import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.floodgate.crypto.FloodgateCipher; +import org.geysermc.floodgate.util.BedrockData; +import org.geysermc.geyser.GeyserImpl; -public enum BedrockPlatform { - UNKNOWN("Unknown"), - GOOGLE("Android"), - IOS("iOS"), - OSX("macOS"), - AMAZON("Amazon"), - GEARVR("Gear VR"), - HOLOLENS("Hololens"), - UWP("Windows"), - WIN32("Windows x86"), - DEDICATED("Dedicated"), - TVOS("Apple TV"), - PS4("PS4"), - NX("Switch"), - XBOX("Xbox One"), - WINDOWS_PHONE("Windows Phone"); +import java.nio.charset.StandardCharsets; - private static final BedrockPlatform[] VALUES = values(); +public final class ProxyHybridProvider extends IntegratedHybridProvider { + private final FloodgateCipher cipher; - private final String displayName; - - BedrockPlatform(String displayName) { - this.displayName = displayName; + public ProxyHybridProvider(GeyserImpl geyser) { + super(geyser); + this.cipher = HybridProvider.getOrCreateKey(geyser); } - /** - * Get the BedrockPlatform from the identifier. - * - * @param id the BedrockPlatform identifier - * @return The BedrockPlatform or {@link #UNKNOWN} if the platform wasn't found - */ - @NonNull - public static BedrockPlatform fromId(int id) { - return id < VALUES.length ? VALUES[id] : VALUES[0]; - } - - /** - * @return friendly display name of platform. - */ @Override - public String toString() { - return displayName; + public FloodgateCipher getCipher() { + return cipher; + } + + // TODO copied from ProxyFloodgateApi + public byte[] createEncryptedData(BedrockData bedrockData) { + try { + return cipher.encryptFromString(bedrockData.toString()); + } catch (Exception exception) { + throw new IllegalStateException("We failed to create the encrypted data, " + + "but creating encrypted data is mandatory!", exception); + } + } + + public String createEncryptedDataString(BedrockData bedrockData) { + return new String(createEncryptedData(bedrockData), StandardCharsets.UTF_8); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java index bb8ca754f..b91653611 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalServerChannelWrapper.java @@ -27,6 +27,10 @@ package org.geysermc.geyser.network.netty; import io.netty.channel.local.LocalChannel; import io.netty.channel.local.LocalServerChannel; +import io.netty.util.Attribute; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.hybrid.IntegratedHybridProvider; +import org.geysermc.geyser.session.GeyserSession; /** * If the incoming channel if an instance of LocalChannelWithRemoteAddress, this server creates a LocalChannelWrapper @@ -36,9 +40,17 @@ public class LocalServerChannelWrapper extends LocalServerChannel { @Override protected LocalChannel newLocalChannel(LocalChannel peer) { // LocalChannel here should be an instance of LocalChannelWithRemoteAddress, which we can use to set the "remote address" on the other end - if (peer instanceof LocalChannelWithRemoteAddress) { + if (peer instanceof LocalChannelWithRemoteAddress) { // TODO also use attribute for this LocalChannelWrapper channel = new LocalChannelWrapper(this, peer); channel.wrapper().remoteAddress(((LocalChannelWithRemoteAddress) peer).spoofedRemoteAddress()); + + if (GeyserImpl.getInstance().getHybridProvider() instanceof IntegratedHybridProvider) { + Attribute attribute = peer.attr(IntegratedHybridProvider.SESSION_KEY); + GeyserSession session = attribute.get(); + // Garbage collect since it's no longer relevant for the PacketLib side. + attribute.set(null); + channel.attr(IntegratedHybridProvider.SESSION_KEY).set(session); + } return channel; } return super.newLocalChannel(peer); diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java index 370604db9..13f30a200 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/LocalSession.java @@ -36,7 +36,12 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.*; import io.netty.channel.unix.PreferredDirectByteBufAllocator; import io.netty.handler.codec.haproxy.*; +import io.netty.util.Attribute; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.hybrid.IntegratedHybridProvider; +import org.geysermc.geyser.session.GeyserSession; +import javax.annotation.Nullable; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -52,11 +57,14 @@ public final class LocalSession extends TcpSession { private final String clientIp; private final PacketCodecHelper codecHelper; - public LocalSession(String host, int port, SocketAddress targetAddress, String clientIp, PacketProtocol protocol, PacketCodecHelper codecHelper) { + private final GeyserSession session; + + public LocalSession(@Nullable GeyserSession session, String host, int port, SocketAddress targetAddress, String clientIp, PacketProtocol protocol, PacketCodecHelper codecHelper) { super(host, port, protocol); this.targetAddress = targetAddress; this.clientIp = clientIp; this.codecHelper = codecHelper; + this.session = session; } @Override @@ -88,6 +96,11 @@ public final class LocalSession extends TcpSession { pipeline.addLast("manager", LocalSession.this); addHAProxySupport(pipeline); + + if (GeyserImpl.getInstance().getHybridProvider() instanceof IntegratedHybridProvider) { + Attribute attribute = channel.attr(IntegratedHybridProvider.SESSION_KEY); + attribute.set(session); + } } }).group(DEFAULT_EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout() * 1000); diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 67aedec15..4a8ce0561 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -112,6 +112,8 @@ import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.Tickable; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; +import org.geysermc.geyser.hybrid.FloodgateHybridProvider; +import org.geysermc.geyser.hybrid.HybridProvider; import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.recipe.GeyserRecipe; @@ -128,7 +130,7 @@ import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.auth.AuthData; import org.geysermc.geyser.session.auth.BedrockClientData; import org.geysermc.geyser.session.cache.*; -import org.geysermc.geyser.skin.FloodgateSkinUploader; +import org.geysermc.geyser.skin.BedrockSkinUploader; import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.MinecraftLocale; import org.geysermc.geyser.text.TextDecoration; @@ -857,7 +859,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (geyser.getBootstrap().getSocketAddress() != null) { // We're going to connect through the JVM and not through TCP - downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(), + downstream = new LocalSession(this, this.remoteServer.address(), this.remoteServer.port(), geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(), this.protocol, this.protocol.createHelper()); } else { @@ -879,12 +881,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { //todo move this somewhere else if (event.getPacket() instanceof ClientIntentionPacket) { String addressSuffix; - if (floodgate) { + HybridProvider provider; + if (floodgate && (provider = geyser.getHybridProvider()) instanceof FloodgateHybridProvider) { byte[] encryptedData; try { - FloodgateSkinUploader skinUploader = geyser.getSkinUploader(); - FloodgateCipher cipher = geyser.getCipher(); + BedrockSkinUploader skinUploader = geyser.getSkinUploader(); + FloodgateCipher cipher = provider.getCipher(); String bedrockAddress = upstream.getAddress().getAddress().getHostAddress(); // both BungeeCord and Velocity remove the IPv6 scope (if there is one) for Spigot @@ -1377,7 +1380,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { upstream.sendPacket(chunkRadiusUpdatedPacket); } - public InetSocketAddress getSocketAddress() { + @Override + public InetSocketAddress socketAddress() { return this.upstream.getAddress(); } @@ -1857,7 +1861,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public @NonNull BedrockPlatform platform() { - return BedrockPlatform.values()[clientData.getDeviceOs().ordinal()]; //todo + return clientData.getDeviceOs(); } @Override @@ -1867,12 +1871,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public @NonNull UiProfile uiProfile() { - return UiProfile.values()[clientData.getUiProfile().ordinal()]; //todo + return clientData.getUiProfile(); } @Override public @NonNull InputMode inputMode() { - return InputMode.values()[clientData.getCurrentInputMode().ordinal()]; //todo + return clientData.getCurrentInputMode(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java index 07dd38491..4ce8d555e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java +++ b/core/src/main/java/org/geysermc/geyser/session/auth/BedrockClientData.java @@ -30,9 +30,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; -import org.geysermc.floodgate.util.DeviceOs; -import org.geysermc.floodgate.util.InputMode; -import org.geysermc.floodgate.util.UiProfile; +import org.geysermc.api.util.BedrockPlatform; +import org.geysermc.api.util.InputMode; +import org.geysermc.api.util.UiProfile; import java.util.UUID; @@ -80,7 +80,7 @@ public final class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOs deviceOs; + private BedrockPlatform deviceOs; @JsonProperty(value = "UIProfile") private UiProfile uiProfile; @JsonProperty(value = "GuiScale") @@ -113,8 +113,8 @@ public final class BedrockClientData { @Setter private String originalString = null; - public DeviceOs getDeviceOs() { - return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; + public BedrockPlatform getDeviceOs() { + return deviceOs != null ? deviceOs : BedrockPlatform.UNKNOWN; } public InputMode getCurrentInputMode() { diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/BedrockSkinUploader.java similarity index 91% rename from core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java rename to core/src/main/java/org/geysermc/geyser/skin/BedrockSkinUploader.java index 7b6dacd16..ac853d82d 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/BedrockSkinUploader.java @@ -30,26 +30,23 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Getter; -import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.floodgate.util.WebsocketEventType; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.util.PluginMessageUtils; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import javax.net.ssl.SSLException; import java.net.ConnectException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -public final class FloodgateSkinUploader { +public final class BedrockSkinUploader { private final ObjectMapper JACKSON = new ObjectMapper(); private final List skinQueue = new ArrayList<>(); @@ -59,9 +56,8 @@ public final class FloodgateSkinUploader { @Getter private int id; @Getter private String verifyCode; - @Getter private int subscribersCount; - public FloodgateSkinUploader(GeyserImpl geyser) { + public BedrockSkinUploader(GeyserImpl geyser) { this.logger = geyser.getLogger(); this.client = new WebSocketClient(Constants.GLOBAL_API_WS_URI) { @Override @@ -100,14 +96,11 @@ public final class FloodgateSkinUploader { verifyCode = node.get("verify_code").asText(); break; case SUBSCRIBER_COUNT: - subscribersCount = node.get("subscribers_count").asInt(); + logger.debug("Ignoring subscribers count message."); break; case SKIN_UPLOADED: // if Geyser is the only subscriber we have send it to the server manually // otherwise it's handled by the Floodgate plugin subscribers - if (subscribersCount != 1) { - break; - } String xuid = node.get("xuid").asText(); GeyserSession session = geyser.connectionByXuid(xuid); @@ -123,9 +116,7 @@ public final class FloodgateSkinUploader { String value = data.get("value").asText(); String signature = data.get("signature").asText(); - byte[] bytes = (value + '\0' + signature) - .getBytes(StandardCharsets.UTF_8); - PluginMessageUtils.sendMessage(session, PluginMessageChannels.SKIN, bytes); + geyser.getHybridProvider().onSkinUpload(session, value, signature); } break; case LOG_MESSAGE: @@ -222,7 +213,7 @@ public final class FloodgateSkinUploader { .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); } - public FloodgateSkinUploader start() { + public BedrockSkinUploader start() { client.connect(); return this; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java index 876395114..4f0fb35fc 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundKe import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; -import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.api.util.BedrockPlatform; import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -50,7 +50,7 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator