mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Proof-of-concept for Geyser-Floodgate merge
This commit is contained in:
parent
592b48dbf5
commit
1c49036e3a
40 changed files with 545 additions and 726 deletions
|
@ -1,7 +0,0 @@
|
|||
dependencies {
|
||||
api(libs.cumulus)
|
||||
api(libs.events) {
|
||||
exclude(group = "com.google.guava", module = "guava")
|
||||
exclude(group = "org.lanternpowered", module = "lmbda")
|
||||
}
|
||||
}
|
|
@ -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 <T> the type
|
||||
* @return the api of the given type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends GeyserApiBase> T api(@NonNull Class<T> 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;
|
||||
}
|
||||
}
|
|
@ -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 <b>online</b> 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<? extends Connection> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ plugins {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.api)
|
||||
api(libs.baseApi)
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -310,7 +310,7 @@ public class GeyserStandaloneGUI {
|
|||
|
||||
for (GeyserSession player : GeyserImpl.getInstance().getSessionManager().getSessions().values()) {
|
||||
Vector<String> row = new Vector<>();
|
||||
row.add(player.getSocketAddress().getHostName());
|
||||
row.add(player.socketAddress().getHostName());
|
||||
row.add(player.getPlayerEntity().getUsername());
|
||||
|
||||
playerTableModel.addRow(row);
|
||||
|
|
|
@ -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<JavaCompile> {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ public interface GeyserConfiguration {
|
|||
|
||||
IRemoteConfiguration getRemote();
|
||||
|
||||
HybridInfo getHybridInfo();
|
||||
|
||||
List<String> getSavedUserLogins();
|
||||
|
||||
@Deprecated
|
||||
|
@ -152,6 +154,13 @@ public interface GeyserConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO this is definitely temporary
|
||||
interface HybridInfo {
|
||||
String usernamePrefix();
|
||||
|
||||
boolean replaceSpaces();
|
||||
}
|
||||
|
||||
interface IUserAuthenticationInfo {
|
||||
String getEmail();
|
||||
|
||||
|
|
|
@ -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<String> savedUserLogins = Collections.emptyList();
|
||||
|
||||
|
|
|
@ -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<DeviceOs> userPlatforms;
|
||||
private final Object2IntMap<BedrockPlatform> 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<GeyserSession> 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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<GeyserSession> 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);
|
||||
|
|
|
@ -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<GeyserSession> attribute = channel.attr(IntegratedHybridProvider.SESSION_KEY);
|
||||
attribute.set(session);
|
||||
}
|
||||
}
|
||||
}).group(DEFAULT_EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout() * 1000);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<String> 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;
|
||||
}
|
|
@ -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<Netwo
|
|||
// so apparently, as of 1.16.200
|
||||
// PS4 divides the network stack latency timestamp FOR US!!!
|
||||
// WTF
|
||||
if (session.getClientData().getDeviceOs().equals(DeviceOs.PS4)) {
|
||||
if (session.getClientData().getDeviceOs() == BedrockPlatform.PS4) {
|
||||
pingId = packet.getTimestamp();
|
||||
} else {
|
||||
pingId = packet.getTimestamp() / 1000;
|
||||
|
|
|
@ -209,10 +209,4 @@ enable-proxy-connections: false
|
|||
# 1400 is the default.
|
||||
mtu: 1400
|
||||
|
||||
# Whether to connect directly into the Java server without creating a TCP connection.
|
||||
# This should only be disabled if a plugin that interfaces with packets or the network does not work correctly with Geyser.
|
||||
# If enabled on plugin versions, the remote address and port sections are ignored
|
||||
# If disabled on plugin versions, expect performance decrease and latency increase
|
||||
use-direct-connection: true
|
||||
|
||||
config-version: 4
|
||||
|
|
|
@ -5,8 +5,6 @@ org.gradle.caching=true
|
|||
org.gradle.parallel=true
|
||||
|
||||
group=org.geysermc
|
||||
version=2.1.0-SNAPSHOT
|
||||
version=3.0.0-SNAPSHOT
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.vfs.watch=false
|
|
@ -1,4 +1,5 @@
|
|||
[versions]
|
||||
geyser-base-api = "3.0.0-SNAPSHOT"
|
||||
jackson = "2.13.4"
|
||||
fastutil = "8.5.2"
|
||||
netty = "4.1.80.Final"
|
||||
|
@ -31,6 +32,8 @@ fabric-loader = "0.14.8"
|
|||
fabric-api = "0.58.5+1.19.1"
|
||||
|
||||
[libraries]
|
||||
baseApi = { group = "org.geysermc", name = "api", version.ref = "geyser-base-api" }
|
||||
|
||||
jackson-annotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jackson" }
|
||||
jackson-core = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" }
|
||||
jackson-dataformat-yaml = { group = "com.fasterxml.jackson.dataformat", name = "jackson-dataformat-yaml", version.ref = "jackson" }
|
||||
|
|
|
@ -3,6 +3,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
|||
dependencyResolutionManagement {
|
||||
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenLocal()
|
||||
// Floodgate, Cumulus etc.
|
||||
maven("https://repo.opencollab.dev/main")
|
||||
|
||||
|
@ -24,7 +25,6 @@ dependencyResolutionManagement {
|
|||
mavenContent { releasesOnly() }
|
||||
}
|
||||
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
|
||||
// ViaVersion
|
||||
|
@ -61,7 +61,6 @@ pluginManagement {
|
|||
rootProject.name = "geyser-parent"
|
||||
|
||||
include(":ap")
|
||||
include(":api")
|
||||
include(":geyser-api")
|
||||
include(":bungeecord")
|
||||
include(":fabric")
|
||||
|
@ -73,7 +72,6 @@ include(":common")
|
|||
include(":core")
|
||||
|
||||
// Specify project dirs
|
||||
project(":api").projectDir = file("api/base")
|
||||
project(":geyser-api").projectDir = file("api/geyser")
|
||||
project(":bungeecord").projectDir = file("bootstrap/bungeecord")
|
||||
project(":fabric").projectDir = file("bootstrap/fabric")
|
||||
|
|
Loading…
Reference in a new issue