From 35d8edd15e665878627915c4c37c80710b0fc9cf Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 29 Jul 2020 12:53:00 +0200 Subject: [PATCH 01/51] Floodgate 2.0 update --- .../geysermc/floodgate/util/BedrockData.java | 67 +++++++++++----- .../util/{DeviceOS.java => DeviceOs.java} | 22 ++++-- .../floodgate/util/EncryptionUtil.java | 6 +- .../geysermc/floodgate/util/InputMode.java | 49 ++++++++++++ .../geysermc/floodgate/util/LinkedPlayer.java | 76 +++++++++++++++++++ .../geysermc/floodgate/util/UiProfile.java | 46 +++++++++++ .../network/session/GeyserSession.java | 1 + .../session/auth/BedrockClientData.java | 26 ++----- 8 files changed, 244 insertions(+), 49 deletions(-) rename common/src/main/java/org/geysermc/floodgate/util/{DeviceOS.java => DeviceOs.java} (79%) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/InputMode.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/UiProfile.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index dc895a79d..44544291d 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -1,36 +1,53 @@ package org.geysermc.floodgate.util; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; -import java.util.UUID; - -@AllArgsConstructor +/** + * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. + * This class is only used internally, and you should look at FloodgatePlayer instead + * (FloodgatePlayer is present in the common module in the Floodgate repo) + */ +@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter -public class BedrockData { - public static final int EXPECTED_LENGTH = 7; +public final class BedrockData { + public static final int EXPECTED_LENGTH = 9; public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; - private String version; - private String username; - private String xuid; - private int deviceId; - private String languageCode; - private int inputMode; - private String ip; - private int dataLength; + private final String version; + private final String username; + private final String xuid; + private final int deviceOs; + private final String languageCode; + private final int uiProfile; + private final int inputMode; + private final String ip; + private final LinkedPlayer linkedPlayer; + private final int dataLength; - public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode, String ip) { - this(version, username, xuid, deviceId, languageCode, inputMode, ip, EXPECTED_LENGTH); + public BedrockData(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer) { + this(version, username, xuid, deviceOs, languageCode, + inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); + } + + public BedrockData(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip) { + this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); } public static BedrockData fromString(String data) { String[] split = data.split("\0"); - if (split.length != EXPECTED_LENGTH) return null; + if (split.length != EXPECTED_LENGTH) return emptyData(split.length); + LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); + // The format is the same as the order of the fields in this class return new BedrockData( - split[0], split[1], split[2], Integer.parseInt(split[3]), - split[4], Integer.parseInt(split[5]), split[6], split.length + split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], + linkedPlayer, split.length ); } @@ -40,7 +57,17 @@ public class BedrockData { @Override public String toString() { - return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ - inputMode +'\0'+ ip; + // The format is the same as the order of the fields in this class + return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + + (linkedPlayer != null ? linkedPlayer.toString() : "null"); + } + + public boolean hasPlayerLink() { + return linkedPlayer != null; + } + + private static BedrockData emptyData(int dataLength) { + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java similarity index 79% rename from common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java rename to common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index 93d3c121e..da783982c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOS.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -26,9 +26,14 @@ package org.geysermc.floodgate.util; import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; -public enum DeviceOS { - +/** + * The Operation Systems where Bedrock players can connect with + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum DeviceOs { @JsonEnumDefaultValue UNKNOWN("Unknown"), ANDROID("Android"), @@ -46,15 +51,16 @@ public enum DeviceOS { XBOX_ONE("Xbox One"), WIN_PHONE("Windows Phone"); - private static final DeviceOS[] VALUES = values(); + private static final DeviceOs[] VALUES = values(); private final String displayName; - DeviceOS(final String displayName) { - this.displayName = displayName; - } - - public static DeviceOS getById(int id) { + /** + * 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 getById(int id) { return id < VALUES.length ? VALUES[id] : VALUES[0]; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java index 881d01ba9..619c7011a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java @@ -12,7 +12,11 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; -public class EncryptionUtil { +/** + * The class which contains all the encryption and decryption method used in Geyser and Floodgate + * (for Floodgate data). This is only used internally and doesn't serve a purpose for anything else + */ +public final class EncryptionUtil { public static String encrypt(Key key, String data) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { KeyGenerator generator = KeyGenerator.getInstance("AES"); diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java new file mode 100644 index 000000000..4dcaa8ab3 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum InputMode { + @JsonEnumDefaultValue + UNKNOWN, + KEYBOARD_MOUSE, + TOUCH, // I guess 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 getById(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java new file mode 100644 index 000000000..a930b013a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +public final class LinkedPlayer { + /** + * The Java username of the linked player + */ + private final String javaUsername; + /** + * The Java UUID of the linked player + */ + private final UUID javaUniqueId; + /** + * The UUID of the Bedrock player + */ + private final UUID bedrockId; + /** + * If the LinkedPlayer is send from a different platform. + * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. + */ + @Setter(AccessLevel.PRIVATE) + private boolean fromDifferentPlatform = false; + + public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { + this.javaUsername = javaUsername; + this.javaUniqueId = javaUniqueId; + this.bedrockId = bedrockId; + } + + static LinkedPlayer fromString(String data) { + if (data.length() == 4) return null; + String[] split = data.split(";"); + LinkedPlayer player = new LinkedPlayer( + split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) + ); + player.setFromDifferentPlatform(true); + return player; + } + + @Override + public String toString() { + return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java new file mode 100644 index 000000000..441e9202a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; + +public enum UiProfile { + @JsonEnumDefaultValue + CLASSIC, + POCKET; + + private static final UiProfile[] VALUES = values(); + + /** + * 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 getById(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index b861f64c4..623bf16a9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -327,6 +327,7 @@ public class GeyserSession implements CommandSender { authData.getXboxUUID(), clientData.getDeviceOS().ordinal(), clientData.getLanguageCode(), + clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), upstream.getSession().getAddress().getAddress().getHostAddress() )); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 6aeebbaa3..fe8c769e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -1,16 +1,17 @@ package org.geysermc.connector.network.session.auth; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.UiProfile; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter -public class BedrockClientData { +public final class BedrockClientData { @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -52,9 +53,9 @@ public class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOS deviceOS; + private DeviceOs deviceOS; @JsonProperty(value = "UIProfile") - private UIProfile uiProfile; + private UiProfile uiProfile; @JsonProperty(value = "GuiScale") private int guiScale; @JsonProperty(value = "CurrentInputMode") @@ -78,19 +79,4 @@ public class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - - public enum UIProfile { - @JsonEnumDefaultValue - CLASSIC, - POCKET - } - - public enum InputMode { - @JsonEnumDefaultValue - UNKNOWN, - KEYBOARD_MOUSE, - TOUCH, // I guess Touch? - CONTROLLER, - VR - } } From d37113388ba649ecb0b9941c040fa94bb1ce6e1f Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 18 Aug 2020 11:39:29 -0400 Subject: [PATCH 02/51] Update to 1.16.2 --- .../src/main/java/org/geysermc/connector/dump/DumpInfo.java | 6 +++--- .../java/org/geysermc/connector/entity/FireworkEntity.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index 9d91cde6b..5aed562ec 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -36,7 +36,7 @@ import org.geysermc.connector.network.BedrockProtocol; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; import java.io.IOException; import java.net.InetAddress; @@ -54,7 +54,7 @@ public class DumpInfo { private final DumpInfo.VersionInfo versionInfo; private Properties gitInfo; private final GeyserConfiguration config; - private Object2IntMap userPlatforms; + private Object2IntMap userPlatforms; private RamInfo ramInfo; private final BootstrapDumpInfo bootstrapInfo; @@ -72,7 +72,7 @@ public class DumpInfo { this.userPlatforms = new Object2IntOpenHashMap(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - DeviceOS device = session.getClientData().getDeviceOS(); + DeviceOs device = session.getClientData().getDeviceOS(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index b940b87b9..3ca170436 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -40,7 +40,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.FireworkColor; import org.geysermc.connector.utils.MathUtils; -import org.geysermc.floodgate.util.DeviceOS; +import org.geysermc.floodgate.util.DeviceOs; import java.util.ArrayList; import java.util.List; @@ -67,7 +67,7 @@ public class FireworkEntity extends Entity { // 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_ONE || session.getClientData().getDeviceOS() == DeviceOS.ORBIS) { + if (session.getClientData().getDeviceOS() == DeviceOs.XBOX_ONE || session.getClientData().getDeviceOS() == DeviceOs.ORBIS) { return; } From ec87344a772254e04e381cc313789f6b142d6594 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 19 Aug 2020 23:13:05 -0400 Subject: [PATCH 03/51] Initial skin support --- .../spigot/GeyserSpigotConfiguration.java | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../geysermc/floodgate/util/BedrockData.java | 20 ++++--- .../floodgate/util/EncryptionUtil.java | 11 +++- .../org/geysermc/floodgate/util/RawSkin.java | 56 +++++++++++++++++++ .../network/session/GeyserSession.java | 3 +- .../session/auth/BedrockClientData.java | 47 ++++++++++++++++ .../connector/utils/LoginEncryptionUtils.java | 4 +- 8 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/RawSkin.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 380f70376..8667a692b 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -48,7 +48,7 @@ public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { private Path floodgateKey; public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-bukkit"); + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-spigot"); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 496681d33..b0cc5e6c3 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -93,7 +93,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") == null) { + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-spigot") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 3d80c0f9b..1f1b3bc88 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -51,19 +51,21 @@ public final class BedrockData { private final LinkedPlayer linkedPlayer; private final int dataLength; + private RawSkin skin; + public BedrockData(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer) { + LinkedPlayer linkedPlayer, RawSkin skin) { this(version, username, xuid, deviceOs, languageCode, - inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); + inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH, skin); } public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); + String languageCode, int uiProfile, int inputMode, String ip, RawSkin skin) { + this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin); } - public static BedrockData fromString(String data) { + public static BedrockData fromString(String data, String skin) { String[] split = data.split("\0"); if (split.length != EXPECTED_LENGTH) return emptyData(split.length); @@ -72,12 +74,12 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, split.length + linkedPlayer, split.length, RawSkin.parse(skin) ); } - public static BedrockData fromRawData(byte[] data) { - return fromString(new String(data)); + public static BedrockData fromRawData(byte[] data, String skin) { + return fromString(new String(data), skin); } @Override @@ -93,6 +95,6 @@ public final class BedrockData { } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength, null); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java index ccad8d1c2..2f3048bd9 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java @@ -58,9 +58,14 @@ public final class EncryptionUtil { Base64.getEncoder().encodeToString(encryptedText); } + public static String encryptBedrockData(Key key, BedrockData data, boolean includeSkin) throws IllegalBlockSizeException, + InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { + return encrypt(key, data.toString()) + (includeSkin ? data.getSkin() : ""); + } + public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return encrypt(key, data.toString()); + return encryptBedrockData(key, data, true); } public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, @@ -80,9 +85,9 @@ public final class EncryptionUtil { return cipher.doFinal(Base64.getDecoder().decode(split[1])); } - public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, + public static BedrockData decryptBedrockData(Key key, String encryptedData, String skin) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return BedrockData.fromRawData(decrypt(key, encryptedData)); + return BedrockData.fromRawData(decrypt(key, encryptedData), skin); } @SuppressWarnings("unchecked") diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java new file mode 100644 index 000000000..ba22b632a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +import lombok.AllArgsConstructor; + +import java.nio.charset.StandardCharsets; + +@AllArgsConstructor +public class RawSkin { + public int width; + public int height; + public byte[] data; + + private RawSkin() {} + + public static RawSkin parse(String data) { + if (data == null) return null; + String[] split = data.split(":"); + if (split.length != 3) return null; + + RawSkin skin = new RawSkin(); + skin.width = Integer.parseInt(split[0]); + skin.height = Integer.parseInt(split[1]); + skin.data = split[2].getBytes(StandardCharsets.UTF_8); + return skin; + } + + @Override + public String toString() { + return Integer.toString(width) + ':' + height + ':' + new String(data); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 06c669c9d..e53dd03aa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -371,7 +371,8 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getSession().getAddress().getAddress().getHostAddress(), + clientData.getImage("Skin") )); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index ad1fb2fb1..2a3e174ed 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -25,18 +25,25 @@ package org.geysermc.connector.network.session.auth; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; +import net.minidev.json.JSONObject; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; +import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; +import java.util.Base64; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter public final class BedrockClientData { + @JsonIgnore + private JSONObject jsonData; + @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -104,4 +111,44 @@ public final class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; + + public void setJsonData(JSONObject data) { + if (this.jsonData != null && data != null) { + this.jsonData = data; + } + } + + /** + * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
+ * Internally only used for Skins, but can be used for Capes too + */ + public RawSkin getImage(String name) { + if (jsonData == null || !jsonData.containsKey(name + "Data")) return null; + byte[] image = Base64.getDecoder().decode(jsonData.getAsString(name + "Data")); + if (jsonData.containsKey(name + "ImageWidth") && jsonData.containsKey(name + "ImageHeight")) { + return new RawSkin( + (int) jsonData.getAsNumber(name + "ImageWidth"), + (int) jsonData.get(name + "ImageHeight"), + image + ); + } + return getLegacyImage(image); + } + + private static RawSkin getLegacyImage(byte[] imageData) { + if (imageData == null) return null; + // width * height * 4 (rgba) + switch (imageData.length) { + case 8192: + return new RawSkin(64, 32, imageData); + case 16384: + return new RawSkin(64, 64, imageData); + case 32768: + return new RawSkin(64, 128, imageData); + case 65536: + return new RawSkin(128, 128, imageData); + default: + throw new IllegalArgumentException("Unknown legacy skin size"); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 4bc997bdf..7e4989d6e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -131,7 +131,9 @@ public class LoginEncryptionUtils { JWSObject clientJwt = JWSObject.parse(clientData); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); - session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class)); + BedrockClientData data = JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class); + data.setJsonData(clientJwt.getPayload().toJSONObject()); + session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); From 29977605213682fd672449def0829e9246ebcb04 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 12 Sep 2020 15:29:18 +0200 Subject: [PATCH 04/51] Switch Floodgate encryption from RSA to AES --- .../geysermc/floodgate/crypto/AesCipher.java | 95 ++++++++++ .../floodgate/crypto/AesKeyProducer.java | 56 ++++++ .../floodgate/crypto/FloodgateCipher.java | 165 ++++++++++++++++++ .../floodgate/crypto/KeyProducer.java | 41 +++++ .../geysermc/floodgate/util/BedrockData.java | 19 +- .../floodgate/util/EncryptionUtil.java | 80 --------- .../util/InvalidHeaderException.java | 41 +++++ .../geysermc/floodgate/util/LinkedPlayer.java | 10 +- .../geysermc/connector/GeyserConnector.java | 19 +- .../network/session/GeyserSession.java | 43 ++--- 10 files changed, 445 insertions(+), 124 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java new file mode 100644 index 000000000..3e6fef01a --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import org.geysermc.floodgate.util.InvalidHeaderException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import java.nio.ByteBuffer; +import java.security.Key; +import java.security.SecureRandom; + +public final class AesCipher implements FloodgateCipher { + private static final int IV_LENGTH = 12; + private static final int TAG_BIT_LENGTH = 128; + private static final String CIPHER_NAME = "AES/GCM/NoPadding"; + + private final SecureRandom secureRandom = new SecureRandom(); + private SecretKey secretKey; + + public void init(Key key) { + if (!"AES".equals(key.getAlgorithm())) { + throw new RuntimeException( + "Algorithm was expected to be AES, but got " + key.getAlgorithm() + ); + } + secretKey = (SecretKey) key; + } + + public byte[] encrypt(byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + byte[] iv = new byte[IV_LENGTH]; + secureRandom.nextBytes(iv); + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); + byte[] cipherText = cipher.doFinal(data); + + return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH) + .put(IDENTIFIER).put(VERSION) // header + .put(iv) + .put(cipherText) + .array(); + } + + public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { + HeaderResult pair = checkHeader(cipherTextWithIv); + if (pair.getVersion() != VERSION) { + throw new InvalidHeaderException( + "Expected version " + VERSION + ", got " + pair.getVersion() + ); + } + + Cipher cipher = Cipher.getInstance(CIPHER_NAME); + + int bufferLength = cipherTextWithIv.length - HEADER_LENGTH; + ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength); + + byte[] iv = new byte[IV_LENGTH]; + buffer.get(iv); + + byte[] cipherText = new byte[buffer.remaining()]; + buffer.get(cipherText); + + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); + return cipher.doFinal(cipherText); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java new file mode 100644 index 000000000..5217b4cf7 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; + +public final class AesKeyProducer implements KeyProducer { + public static int KEY_SIZE = 128; + + @Override + public SecretKey produce() { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(KEY_SIZE, SecureRandom.getInstanceStrong()); + return keyGenerator.generateKey(); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + @Override + public SecretKey produceFrom(byte[] keyFileData) { + try { + return new SecretKeySpec(keyFileData, "AES"); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java new file mode 100644 index 000000000..23e57fb64 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.geysermc.floodgate.util.InvalidHeaderException; + +import java.nio.charset.StandardCharsets; +import java.security.Key; + +/** + * Responsible for both encrypting and decrypting data + */ +public interface FloodgateCipher { + byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); + byte VERSION = 2; + + int HEADER_LENGTH = IDENTIFIER.length + 1; // one byte for version + + /** + * Initializes the instance by giving it the key it needs to encrypt or decrypt data + * + * @param key the key used to encrypt and decrypt data + */ + void init(Key key); + + /** + * Encrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + byte[] encrypt(byte[] data) throws Exception; + + /** + * Encrypts data from a String.
+ * This method internally calls {@link #encrypt(byte[])} + * + * @param data the data to encrypt + * @return the encrypted data + * @throws Exception when the encryption failed + */ + default byte[] encryptFromString(String data) throws Exception { + return encrypt(data.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decrypts the given data using the Key provided in {@link #init(Key)} + * + * @param data the data to decrypt + * @return the decrypted data + * @throws Exception when the decrypting failed + */ + byte[] decrypt(byte[] data) throws Exception; + + /** + * Decrypts a byte[] and turn it into a String.
+ * This method internally calls {@link #decrypt(byte[])} + * and converts the returned byte[] into a String. + * + * @param data the data to encrypt + * @return the decrypted data in a UTF-8 String + * @throws Exception when the decrypting failed + */ + default String decryptToString(byte[] data) throws Exception { + byte[] decrypted = decrypt(data); + if (decrypted == null) { + return null; + } + return new String(decrypted, StandardCharsets.UTF_8); + } + + /** + * Decrypts a String.
+ * This method internally calls {@link #decrypt(byte[])} + * by converting the UTF-8 String into a byte[] + * + * @param data the data to decrypt + * @return the decrypted data in a byte[] + * @throws Exception when the decrypting failed + */ + default byte[] decryptFromString(String data) throws Exception { + return decrypt(data.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Checks if the header is valid and return a IntPair containing the header version + * and the index to start reading the actual encrypted data from. + * + * @param data the data to check + * @return IntPair. x = version number, y = the index to start reading from. + * @throws InvalidHeaderException when the header is invalid + */ + default HeaderResult checkHeader(byte[] data) throws InvalidHeaderException { + final int identifierLength = IDENTIFIER.length; + + if (data.length <= HEADER_LENGTH) { + throw new InvalidHeaderException("Data length is smaller then header." + + "Needed " + HEADER_LENGTH + ", got " + data.length); + } + + for (int i = 0; i < identifierLength; i++) { + if (IDENTIFIER[i] != data[i]) { + StringBuilder receivedIdentifier = new StringBuilder(); + for (byte b : IDENTIFIER) { + receivedIdentifier.append(b); + } + + throw new InvalidHeaderException(String.format( + "Expected identifier %s, got %s", + new String(IDENTIFIER, StandardCharsets.UTF_8), + receivedIdentifier.toString() + )); + } + } + + return new HeaderResult(data[identifierLength], HEADER_LENGTH); + } + + static boolean hasHeader(String data) { + if (data.length() < IDENTIFIER.length) { + return false; + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data.charAt(i)) { + return false; + } + } + return true; + } + + @Data + @AllArgsConstructor + class HeaderResult { + private int version; + private int startIndex; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java new file mode 100644 index 000000000..fc2ac512d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Floodgate + * + */ + +package org.geysermc.floodgate.crypto; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; + +public interface KeyProducer { + Key produce(); + Key produceFrom(byte[] keyFileData); + + default Key produceFrom(Path keyFileLocation) throws IOException { + return produceFrom(Files.readAllBytes(keyFileLocation)); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 44544291d..f730d75bd 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -9,11 +9,10 @@ import lombok.Getter; * This class is only used internally, and you should look at FloodgatePlayer instead * (FloodgatePlayer is present in the common module in the Floodgate repo) */ -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter public final class BedrockData { public static final int EXPECTED_LENGTH = 9; - public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; private final String version; private final String username; @@ -38,9 +37,15 @@ public final class BedrockData { this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); } + public boolean hasPlayerLink() { + return linkedPlayer != null; + } + public static BedrockData fromString(String data) { String[] split = data.split("\0"); - if (split.length != EXPECTED_LENGTH) return emptyData(split.length); + if (split.length != BedrockData.EXPECTED_LENGTH) { + return emptyData(split.length); + } LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); // The format is the same as the order of the fields in this class @@ -51,10 +56,6 @@ public final class BedrockData { ); } - public static BedrockData fromRawData(byte[] data) { - return fromString(new String(data)); - } - @Override public String toString() { // The format is the same as the order of the fields in this class @@ -63,10 +64,6 @@ public final class BedrockData { (linkedPlayer != null ? linkedPlayer.toString() : "null"); } - public boolean hasPlayerLink() { - return linkedPlayer != null; - } - private static BedrockData emptyData(int dataLength) { return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); } diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java deleted file mode 100644 index 619c7011a..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.geysermc.floodgate.util; - -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.*; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -/** - * The class which contains all the encryption and decryption method used in Geyser and Floodgate - * (for Floodgate data). This is only used internally and doesn't serve a purpose for anything else - */ -public final class EncryptionUtil { - public static String encrypt(Key key, String data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(128); - SecretKey secretKey = generator.generateKey(); - - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptedText = cipher.doFinal(data.getBytes()); - - cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - return Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded())) + '\0' + - Base64.getEncoder().encodeToString(encryptedText); - } - - public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return encrypt(key, data.toString()); - } - - public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - String[] split = encryptedData.split("\0"); - if (split.length != 2) { - throw new IllegalArgumentException("Expected two arguments, got " + split.length); - } - - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); - byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0])); - - SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); - cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - return cipher.doFinal(Base64.getDecoder().decode(split[1])); - } - - public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, - InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { - return BedrockData.fromRawData(decrypt(key, encryptedData)); - } - - @SuppressWarnings("unchecked") - public static T getKeyFromFile(Path fileLocation, Class keyType) throws - IOException, InvalidKeySpecException, NoSuchAlgorithmException { - boolean isPublicKey = keyType == PublicKey.class; - if (!isPublicKey && keyType != PrivateKey.class) { - throw new RuntimeException("I can only read public and private keys!"); - } - - byte[] key = Files.readAllBytes(fileLocation); - - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key); - return (T) (isPublicKey ? - keyFactory.generatePublic(keySpec) : - keyFactory.generatePrivate(keySpec) - ); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java new file mode 100644 index 000000000..30dbf0726 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.floodgate.util; + +public class InvalidHeaderException extends Exception { + public InvalidHeaderException() { + super(); + } + + public InvalidHeaderException(String message) { + super(message); + } + + public InvalidHeaderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index a930b013a..53a167f9b 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -26,9 +26,7 @@ package org.geysermc.floodgate.util; -import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; import java.util.UUID; @@ -50,7 +48,6 @@ public final class LinkedPlayer { * If the LinkedPlayer is send from a different platform. * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. */ - @Setter(AccessLevel.PRIVATE) private boolean fromDifferentPlatform = false; public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { @@ -60,12 +57,15 @@ public final class LinkedPlayer { } static LinkedPlayer fromString(String data) { - if (data.length() == 4) return null; + if (data.length() != 3) { + return null; + } + String[] split = data.split(";"); LinkedPlayer player = new LinkedPlayer( split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) ); - player.setFromDifferentPlatform(true); + player.fromDifferentPlatform = true; return player; } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index ee687dbbd..622add139 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -57,10 +57,14 @@ import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.floodgate.crypto.AesCipher; +import org.geysermc.floodgate.crypto.AesKeyProducer; +import org.geysermc.floodgate.crypto.FloodgateCipher; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; import java.net.InetSocketAddress; +import java.security.Key; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; @@ -77,7 +81,7 @@ public class GeyserConnector { public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v407.V407_CODEC; public static final String NAME = "Geyser"; - public static final String VERSION = "DEV"; // A fallback for running in IDEs + public static final String VERSION = "1.0.0 (git-master-35d8edd)"; // A fallback for running in IDEs private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"; @@ -89,6 +93,8 @@ public class GeyserConnector { @Setter private AuthType authType; + private FloodgateCipher cipher; + private boolean shuttingDown = false; private final ScheduledExecutorService generalThreadPool; @@ -165,6 +171,17 @@ public class GeyserConnector { remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); authType = AuthType.getByName(config.getRemote().getAuthType()); + if (authType == AuthType.FLOODGATE) { + try { + Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); + cipher = new AesCipher(); + cipher.init(key); + logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + } catch (Exception exception) { + logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); + } + } + if (config.isAboveBedrockNetherBuilding()) DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 623bf16a9..a02a356fb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -71,15 +71,13 @@ import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; +import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; -import org.geysermc.floodgate.util.EncryptionUtil; -import java.io.IOException; import java.net.InetSocketAddress; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -294,24 +292,6 @@ public class GeyserSession implements CommandSender { } boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; - final PublicKey publicKey; - - if (floodgate) { - PublicKey key = null; - try { - key = EncryptionUtil.getKeyFromFile( - connector.getConfig().getFloodgateKeyFile(), - PublicKey.class - ); - } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { - connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e); - } - publicKey = key; - } else publicKey = null; - - if (publicKey != null) { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); - } downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); downstream.getSession().addListener(new SessionAdapter() { @@ -319,9 +299,11 @@ public class GeyserSession implements CommandSender { public void packetSending(PacketSendingEvent event) { //todo move this somewhere else if (event.getPacket() instanceof HandshakePacket && floodgate) { - String encrypted = ""; + byte[] encryptedData; + try { - encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( + FloodgateCipher cipher = connector.getCipher(); + encryptedData = cipher.encryptFromString(new BedrockData( clientData.getGameVersion(), authData.getName(), authData.getXboxUUID(), @@ -330,15 +312,22 @@ public class GeyserSession implements CommandSender { clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), upstream.getSession().getAddress().getAddress().getHostAddress() - )); + ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); + disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode())); + return; } + String encrypted = new String( + Base64.getEncoder().encode(encryptedData), + StandardCharsets.UTF_8 + ); + HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, + handshakePacket.getHostname() + '\0' + encrypted, handshakePacket.getPort(), handshakePacket.getIntent() )); From 7fbc401dfa6b7841307be48eca457d699745e90e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 19 Sep 2020 14:21:54 +0200 Subject: [PATCH 05/51] Added RawSkins, Toppings and renamed the Floodgate plugin name --- .../bungeecord/GeyserBungeeConfiguration.java | 2 +- .../bungeecord/GeyserBungeePlugin.java | 2 +- .../spigot/GeyserSpigotConfiguration.java | 2 +- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../geysermc/floodgate/crypto/AesCipher.java | 49 ++++++++++--- .../floodgate/crypto/Base64Topping.java | 40 +++++++++++ .../floodgate/crypto/FloodgateCipher.java | 35 +++++---- .../geysermc/floodgate/crypto/Topping.java | 31 ++++++++ .../geysermc/floodgate/util/BedrockData.java | 20 ++---- ...ption.java => InvalidFormatException.java} | 18 +++-- .../org/geysermc/floodgate/util/RawSkin.java | 55 ++++++++++---- .../geysermc/connector/GeyserConnector.java | 3 +- .../network/session/GeyserSession.java | 27 +++---- .../session/auth/BedrockClientData.java | 72 +++++++++++++------ .../connector/utils/LoginEncryptionUtils.java | 5 +- .../connector/utils/SkinProvider.java | 2 +- 16 files changed, 261 insertions(+), 104 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java create mode 100644 common/src/main/java/org/geysermc/floodgate/crypto/Topping.java rename common/src/main/java/org/geysermc/floodgate/util/{InvalidHeaderException.java => InvalidFormatException.java} (77%) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java index d9b86a2e8..500f342a6 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java @@ -44,7 +44,7 @@ public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration { private Path floodgateKey; public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) { - Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); + Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate"); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); } diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index 059e1dfd8..a600ebcd3 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -94,7 +94,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java index 8667a692b..de4e58c3b 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotConfiguration.java @@ -48,7 +48,7 @@ public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration { private Path floodgateKey; public void loadFloodgate(GeyserSpigotPlugin plugin) { - Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-spigot"); + Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate"); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index b0cc5e6c3..dab63c4ea 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -93,7 +93,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); - if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-spigot") == null) { + if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java index 3e6fef01a..2627584f6 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -26,7 +26,7 @@ package org.geysermc.floodgate.crypto; -import org.geysermc.floodgate.util.InvalidHeaderException; +import lombok.RequiredArgsConstructor; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -35,12 +35,14 @@ import java.nio.ByteBuffer; import java.security.Key; import java.security.SecureRandom; +@RequiredArgsConstructor public final class AesCipher implements FloodgateCipher { - private static final int IV_LENGTH = 12; + public static final int IV_LENGTH = 12; private static final int TAG_BIT_LENGTH = 128; private static final String CIPHER_NAME = "AES/GCM/NoPadding"; private final SecureRandom secureRandom = new SecureRandom(); + private final Topping topping; private SecretKey secretKey; public void init(Key key) { @@ -62,32 +64,57 @@ public final class AesCipher implements FloodgateCipher { cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); byte[] cipherText = cipher.doFinal(data); - return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH) - .put(IDENTIFIER).put(VERSION) // header + if (topping != null) { + iv = topping.encode(iv); + cipherText = topping.encode(cipherText); + } + + return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH + 1) + .put(IDENTIFIER) // header .put(iv) + .put((byte) 0x21) .put(cipherText) .array(); } public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { - HeaderResult pair = checkHeader(cipherTextWithIv); - if (pair.getVersion() != VERSION) { - throw new InvalidHeaderException( - "Expected version " + VERSION + ", got " + pair.getVersion() - ); - } + checkHeader(cipherTextWithIv); Cipher cipher = Cipher.getInstance(CIPHER_NAME); int bufferLength = cipherTextWithIv.length - HEADER_LENGTH; ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength); - byte[] iv = new byte[IV_LENGTH]; + int ivLength = IV_LENGTH; + + if (topping != null) { + int mark = buffer.position(); + + // we need the first index, the second is for the optional RawSkin + boolean found = false; + while (buffer.hasRemaining() && !found) { + if (buffer.get() == 0x21) { + found = true; + } + } + + ivLength = buffer.position() - mark - 1; // don't include the splitter itself + buffer.position(mark); // reset to the pre-while index + } + + byte[] iv = new byte[ivLength]; buffer.get(iv); + buffer.position(buffer.position() + 1); // skip splitter + byte[] cipherText = new byte[buffer.remaining()]; buffer.get(cipherText); + if (topping != null) { + iv = topping.decode(iv); + cipherText = topping.decode(cipherText); + } + GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); return cipher.doFinal(cipherText); diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java new file mode 100644 index 000000000..fbec78a1d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Base64Topping.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.crypto; + +import java.util.Base64; + +public final class Base64Topping implements Topping { + @Override + public byte[] encode(byte[] data) { + return Base64.getEncoder().encode(data); + } + + @Override + public byte[] decode(byte[] data) { + return Base64.getDecoder().decode(data); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java index 23e57fb64..4869531e2 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -28,7 +28,7 @@ package org.geysermc.floodgate.crypto; import lombok.AllArgsConstructor; import lombok.Data; -import org.geysermc.floodgate.util.InvalidHeaderException; +import org.geysermc.floodgate.util.InvalidFormatException; import java.nio.charset.StandardCharsets; import java.security.Key; @@ -38,9 +38,7 @@ import java.security.Key; */ public interface FloodgateCipher { byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); - byte VERSION = 2; - - int HEADER_LENGTH = IDENTIFIER.length + 1; // one byte for version + int HEADER_LENGTH = IDENTIFIER.length; /** * Initializes the instance by giving it the key it needs to encrypt or decrypt data @@ -110,19 +108,20 @@ public interface FloodgateCipher { } /** - * Checks if the header is valid and return a IntPair containing the header version - * and the index to start reading the actual encrypted data from. + * Checks if the header is valid. + * This method will throw an InvalidFormatException when the header is invalid. * * @param data the data to check - * @return IntPair. x = version number, y = the index to start reading from. - * @throws InvalidHeaderException when the header is invalid + * @throws InvalidFormatException when the header is invalid */ - default HeaderResult checkHeader(byte[] data) throws InvalidHeaderException { + default void checkHeader(byte[] data) throws InvalidFormatException { final int identifierLength = IDENTIFIER.length; if (data.length <= HEADER_LENGTH) { - throw new InvalidHeaderException("Data length is smaller then header." + - "Needed " + HEADER_LENGTH + ", got " + data.length); + throw new InvalidFormatException("Data length is smaller then header." + + "Needed " + HEADER_LENGTH + ", got " + data.length, + true + ); } for (int i = 0; i < identifierLength; i++) { @@ -132,15 +131,15 @@ public interface FloodgateCipher { receivedIdentifier.append(b); } - throw new InvalidHeaderException(String.format( - "Expected identifier %s, got %s", - new String(IDENTIFIER, StandardCharsets.UTF_8), - receivedIdentifier.toString() - )); + throw new InvalidFormatException( + String.format("Expected identifier %s, got %s", + new String(IDENTIFIER, StandardCharsets.UTF_8), + receivedIdentifier.toString() + ), + true + ); } } - - return new HeaderResult(data[identifierLength], HEADER_LENGTH); } static boolean hasHeader(String data) { diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java new file mode 100644 index 000000000..f3dc0d4b8 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/crypto/Topping.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.crypto; + +public interface Topping { + byte[] encode(byte[] data); + byte[] decode(byte[] data); +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index dcc412f50..89eaf5395 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -50,25 +50,23 @@ public final class BedrockData { private final LinkedPlayer linkedPlayer; private final int dataLength; - private RawSkin skin; - public BedrockData(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, RawSkin skin) { + LinkedPlayer linkedPlayer) { this(version, username, xuid, deviceOs, languageCode, - inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH, skin); + inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); } public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, RawSkin skin) { - this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin); + String languageCode, int uiProfile, int inputMode, String ip) { + this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); } public boolean hasPlayerLink() { return linkedPlayer != null; } - public static BedrockData fromString(String data, String skin) { + public static BedrockData fromString(String data) { String[] split = data.split("\0"); if (split.length != EXPECTED_LENGTH) { return emptyData(split.length); @@ -79,14 +77,10 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, split.length, RawSkin.parse(skin) + linkedPlayer, split.length ); } - public static BedrockData fromRawData(byte[] data, String skin) { - return fromString(new String(data), skin); - } - @Override public String toString() { // The format is the same as the order of the fields in this class @@ -96,6 +90,6 @@ public final class BedrockData { } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength, null); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java similarity index 77% rename from common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java rename to common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java index 30dbf0726..9ec5b1710 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidFormatException.java @@ -26,16 +26,26 @@ package org.geysermc.floodgate.util; -public class InvalidHeaderException extends Exception { - public InvalidHeaderException() { +import lombok.Getter; + +@Getter +public class InvalidFormatException extends Exception { + private boolean header = false; + + public InvalidFormatException() { super(); } - public InvalidHeaderException(String message) { + public InvalidFormatException(String message) { super(message); } - public InvalidHeaderException(String message, Throwable cause) { + public InvalidFormatException(String message, boolean header) { + super(message); + this.header = header; + } + + public InvalidFormatException(String message, Throwable cause) { super(message, cause); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index ba22b632a..2ff0e3fb2 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -26,31 +26,62 @@ package org.geysermc.floodgate.util; import lombok.AllArgsConstructor; +import lombok.ToString; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; +import java.util.Base64; @AllArgsConstructor -public class RawSkin { +@ToString +public final class RawSkin { public int width; public int height; public byte[] data; private RawSkin() {} - public static RawSkin parse(String data) { - if (data == null) return null; - String[] split = data.split(":"); - if (split.length != 3) return null; + public static RawSkin decode(byte[] data) throws InvalidFormatException { + if (data == null) { + return null; + } + + int maxEncodedLength = 4 * (((64 * 64 * 4 + 8) + 2) / 3); + // if the RawSkin is longer then the max Java Edition skin length + if (data.length > maxEncodedLength) { + throw new InvalidFormatException( + "Encoded data cannot be longer then " + maxEncodedLength + " bytes!" + ); + } + + // if the encoded data doesn't even contain the width and height (8 bytes, 2 ints) + if (data.length < 4 * ((8 + 2) / 3)) { + throw new InvalidFormatException("Encoded data must be at least 12 bytes long!"); + } + + data = Base64.getDecoder().decode(data); + + ByteBuffer buffer = ByteBuffer.wrap(data); RawSkin skin = new RawSkin(); - skin.width = Integer.parseInt(split[0]); - skin.height = Integer.parseInt(split[1]); - skin.data = split[2].getBytes(StandardCharsets.UTF_8); + skin.width = buffer.getInt(); + skin.height = buffer.getInt(); + if (buffer.remaining() != (skin.width * skin.height * 4)) { + throw new InvalidFormatException(String.format( + "Expected skin length to be %s, got %s", + (skin.width * skin.height * 4), buffer.remaining() + )); + } + skin.data = new byte[buffer.remaining()]; + buffer.get(skin.data); return skin; } - @Override - public String toString() { - return Integer.toString(width) + ':' + height + ':' + new String(data); + public byte[] encode() { + // 2 x int = 8 bytes + ByteBuffer buffer = ByteBuffer.allocate(8 + data.length); + buffer.putInt(width); + buffer.putInt(height); + buffer.put(data); + return Base64.getEncoder().encode(buffer.array()); } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 21fb5564f..9b6045be2 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -57,6 +57,7 @@ import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; 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 javax.naming.directory.Attribute; @@ -180,7 +181,7 @@ public class GeyserConnector { if (authType == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); - cipher = new AesCipher(); + cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); } catch (Exception exception) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 3e4314fd4..9a2ebe56d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -77,15 +77,8 @@ import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; import java.net.InetSocketAddress; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.*; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @Getter @@ -359,8 +352,7 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress(), - clientData.getImage("Skin") + upstream.getSession().getAddress().getAddress().getHostAddress() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); @@ -368,15 +360,18 @@ public class GeyserSession implements CommandSender { return; } - String encrypted = new String( - Base64.getEncoder().encode(encryptedData), - StandardCharsets.UTF_8 - ); + byte[] rawSkin = clientData.getAndTransformImage("Skin").encode(); + byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1]; + System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length); + finalData[encryptedData.length] = 0x21; // splitter + System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length); + + String finalDataString = new String(finalData, StandardCharsets.UTF_8); HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + encrypted, + handshakePacket.getHostname() + '\0' + finalDataString, handshakePacket.getPort(), handshakePacket.getIntent() )); @@ -626,7 +621,7 @@ public class GeyserSession implements CommandSender { /** * Send a packet immediately to the player. - * + * * @param packet the bedrock packet from the NukkitX protocol lib */ public void sendUpstreamPacketImmediately(BedrockPacket packet) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 2a3e174ed..ab0fad845 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -28,13 +28,15 @@ package org.geysermc.connector.network.session.auth; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; -import net.minidev.json.JSONObject; +import org.geysermc.connector.utils.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; +import java.awt.image.BufferedImage; import java.util.Base64; import java.util.UUID; @@ -42,7 +44,7 @@ import java.util.UUID; @Getter public final class BedrockClientData { @JsonIgnore - private JSONObject jsonData; + private JsonNode jsonData; @JsonProperty(value = "GameVersion") private String gameVersion; @@ -112,31 +114,17 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - public void setJsonData(JSONObject data) { - if (this.jsonData != null && data != null) { + public void setJsonData(JsonNode data) { + if (this.jsonData == null && data != null) { this.jsonData = data; } } - /** - * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
- * Internally only used for Skins, but can be used for Capes too - */ - public RawSkin getImage(String name) { - if (jsonData == null || !jsonData.containsKey(name + "Data")) return null; - byte[] image = Base64.getDecoder().decode(jsonData.getAsString(name + "Data")); - if (jsonData.containsKey(name + "ImageWidth") && jsonData.containsKey(name + "ImageHeight")) { - return new RawSkin( - (int) jsonData.getAsNumber(name + "ImageWidth"), - (int) jsonData.get(name + "ImageHeight"), - image - ); - } - return getLegacyImage(image); - } - private static RawSkin getLegacyImage(byte[] imageData) { - if (imageData == null) return null; + if (imageData == null) { + return null; + } + // width * height * 4 (rgba) switch (imageData.length) { case 8192: @@ -151,4 +139,44 @@ public final class BedrockClientData { throw new IllegalArgumentException("Unknown legacy skin size"); } } + + /** + * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
+ * Internally only used for Skins, but can be used for Capes too + */ + public RawSkin getImage(String name) { + System.out.println(jsonData.toString()); + if (jsonData == null || !jsonData.has(name + "Data")) { + return null; + } + + byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); + if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { + return new RawSkin( + jsonData.get(name + "ImageWidth").asInt(), + jsonData.get(name + "ImageHeight").asInt(), + image + ); + } + return getLegacyImage(image); + } + + public RawSkin getAndTransformImage(String name) { + RawSkin skin = getImage(name); + if (skin != null && (skin.width > 64 || skin.height > 64)) { + BufferedImage scaledImage = SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); + + int max = Math.max(skin.width, skin.height); + while (max > 64) { + max /= 2; + scaledImage = SkinProvider.scale(scaledImage); + } + + byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage); + skin.width = scaledImage.getWidth(); + skin.height = scaledImage.getHeight(); + skin.data = skinData; + } + return skin; + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 7e4989d6e..62d70f612 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -131,8 +131,9 @@ public class LoginEncryptionUtils { JWSObject clientJwt = JWSObject.parse(clientData); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); - BedrockClientData data = JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class); - data.setJsonData(clientJwt.getPayload().toJSONObject()); + JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()); + BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); + data.setJsonData(clientDataJson); session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index 5551230b9..9b3d737e8 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -460,7 +460,7 @@ public class SkinProvider { return null; } - private static BufferedImage scale(BufferedImage bufferedImage) { + public static BufferedImage scale(BufferedImage bufferedImage) { BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); From f8939ca5de6382b3f087aee9bc64e01bdbd76f27 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 19 Sep 2020 14:45:36 +0200 Subject: [PATCH 06/51] Updated to latest Geyser --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index de4d2c2bf..6cd6aa521 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -186,7 +186,7 @@ public class GeyserConnector { if (authType == AuthType.FLOODGATE) { try { - Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); + Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); From d75169392b0e35237a79f574df1e97c6d97a0759 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sun, 27 Sep 2020 21:08:31 -0400 Subject: [PATCH 07/51] Small consistency fixes --- .../org/geysermc/platform/bungeecord/GeyserBungeePlugin.java | 2 +- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- .../main/java/org/geysermc/connector/FloodgateKeyLoader.java | 2 +- connector/src/main/resources/config.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java index 9d8e05ac7..e06640896 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeePlugin.java @@ -97,7 +97,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap { if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); return; - } else if (geyserConfig.isAutoconfiguredRemote() && getProxy().getPluginManager().getPlugin("floodgate-bungee") != null) { + } 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("floodgate"); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index a8ae50d69..12794d1c8 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -101,7 +101,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); this.getPluginLoader().disablePlugin(this); return; - } else if (geyserConfig.isAutoconfiguredRemote() && Bukkit.getPluginManager().getPlugin("floodgate-bukkit") != null) { + } 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("floodgate"); diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index ec5dd349a..7bf0ac67f 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -37,7 +37,7 @@ public class FloodgateKeyLoader { if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { if (floodgate != null) { - Path autoKey = floodgateDataFolder.resolve("public-key.pem"); + Path autoKey = floodgateDataFolder.resolve("key.pem"); if (Files.exists(autoKey)) { logger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.auto_loaded")); floodgateKey = autoKey; diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index 0602bb546..b9e6700fc 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -34,9 +34,9 @@ remote: auth-type: online # Floodgate uses encryption to ensure use from authorised sources. -# This should point to the public key generated by Floodgate (Bungee or CraftBukkit) +# This should point to the public key generated by Floodgate (BungeeCord, Spigot or Velocity) # You can ignore this when not using Floodgate. -floodgate-key-file: public-key.pem +floodgate-key-file: key.pem ## the Xbox/MCPE username is the key for the Java server auth-info ## this allows automatic configuration/login to the remote Java server From c9102348ded01f7fa058dcbbe983413a2b8dcd49 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 30 Oct 2020 00:58:17 +0100 Subject: [PATCH 08/51] First version of the Forms rewrite The next version will split the implementation from the api --- common/pom.xml | 2 +- .../org/geysermc/common/form/CustomForm.java | 177 ++++++++++++++++ .../java/org/geysermc/common/form/Form.java | 137 +++++++++++++ .../org/geysermc/common/form/ModalForm.java | 103 ++++++++++ .../org/geysermc/common/form/SimpleForm.java | 117 +++++++++++ .../component/ButtonComponent.java} | 37 ++-- .../common/form/component/Component.java | 77 +++++++ .../form/component/DropdownComponent.java | 102 +++++++++ .../component/InputComponent.java | 40 ++-- .../component/LabelComponent.java | 19 +- .../form/component/SliderComponent.java | 73 +++++++ .../form/component/StepSliderComponent.java | 116 +++++++++++ .../component/ToggleComponent.java | 35 ++-- .../form/response/CustomFormResponse.java | 193 ++++++++++++++++++ .../response/FormResponse.java | 8 +- .../response/ModalFormResponse.java} | 47 ++--- .../response/SimpleFormResponse.java} | 52 ++--- .../common/form/util/FormAdaptor.java | 126 ++++++++++++ .../button => form/util}/FormImage.java | 36 ++-- .../common/window/CustomFormBuilder.java | 71 ------- .../common/window/CustomFormWindow.java | 165 --------------- .../common/window/ModalFormWindow.java | 82 -------- .../common/window/SimpleFormWindow.java | 94 --------- .../window/component/DropdownComponent.java | 56 ----- .../window/component/FormComponent.java | 38 ---- .../window/component/SliderComponent.java | 65 ------ .../window/response/CustomFormResponse.java | 46 ----- .../window/response/FormResponseData.java | 37 ---- .../window/response/ModalFormResponse.java | 38 ---- .../window/response/SimpleFormResponse.java | 39 ---- .../network/UpstreamPacketHandler.java | 10 +- .../network/session/GeyserSession.java | 28 ++- .../{WindowCache.java => FormCache.java} | 70 +++---- ...edrockServerSettingsRequestTranslator.java | 8 +- .../java/JavaPluginMessageTranslator.java | 72 +++++-- .../connector/utils/LoginEncryptionUtils.java | 114 ++++------- .../connector/utils/SettingsUtils.java | 141 +++++-------- 37 files changed, 1559 insertions(+), 1112 deletions(-) create mode 100644 common/src/main/java/org/geysermc/common/form/CustomForm.java create mode 100644 common/src/main/java/org/geysermc/common/form/Form.java create mode 100644 common/src/main/java/org/geysermc/common/form/ModalForm.java create mode 100644 common/src/main/java/org/geysermc/common/form/SimpleForm.java rename common/src/main/java/org/geysermc/common/{window/button/FormButton.java => form/component/ButtonComponent.java} (65%) create mode 100644 common/src/main/java/org/geysermc/common/form/component/Component.java create mode 100644 common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java rename common/src/main/java/org/geysermc/common/{window => form}/component/InputComponent.java (62%) rename common/src/main/java/org/geysermc/common/{window => form}/component/LabelComponent.java (81%) create mode 100644 common/src/main/java/org/geysermc/common/form/component/SliderComponent.java create mode 100644 common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java rename common/src/main/java/org/geysermc/common/{window => form}/component/ToggleComponent.java (70%) create mode 100644 common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java rename common/src/main/java/org/geysermc/common/{window => form}/response/FormResponse.java (87%) rename common/src/main/java/org/geysermc/common/{window/FormWindow.java => form/response/ModalFormResponse.java} (55%) rename common/src/main/java/org/geysermc/common/{window/component/StepSliderComponent.java => form/response/SimpleFormResponse.java} (56%) create mode 100644 common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java rename common/src/main/java/org/geysermc/common/{window/button => form/util}/FormImage.java (72%) delete mode 100644 common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java delete mode 100644 common/src/main/java/org/geysermc/common/window/CustomFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/ModalFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/window/component/FormComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/window/component/SliderComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/FormResponseData.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java rename connector/src/main/java/org/geysermc/connector/network/session/cache/{WindowCache.java => FormCache.java} (56%) diff --git a/common/pom.xml b/common/pom.xml index 85dde12c6..30c8afc06 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -15,7 +15,7 @@ com.google.code.gson gson - 2.8.2 + 2.8.5 compile diff --git a/common/src/main/java/org/geysermc/common/form/CustomForm.java b/common/src/main/java/org/geysermc/common/form/CustomForm.java new file mode 100644 index 000000000..5e8d93252 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/CustomForm.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.component.*; +import org.geysermc.common.form.response.CustomFormResponse; +import org.geysermc.common.form.util.FormAdaptor; +import org.geysermc.common.form.util.FormImage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class CustomForm extends Form { + private final String title; + private final FormImage icon; + private final List content; + + private CustomForm(String title, FormImage icon, List content) { + super(Form.Type.CUSTOM_FORM); + + this.title = title; + this.icon = icon; + this.content = Collections.unmodifiableList(content); + } + + public static Builder builder() { + return new Builder(); + } + + public static CustomForm of(String title, FormImage icon, List content) { + return new CustomForm(title, icon, content); + } + + public CustomFormResponse parseResponse(String data) { + if (isClosed(data)) { + return CustomFormResponse.closed(); + } + return CustomFormResponse.of(this, data); + } + + public static final class Builder extends Form.Builder { + private final List components = new ArrayList<>(); + private FormImage icon; + + public Builder icon(FormImage.Type type, String data) { + icon = FormImage.of(type, data); + return this; + } + + public Builder iconPath(String path) { + return icon(FormImage.Type.PATH, path); + } + + public Builder iconUrl(String url) { + return icon(FormImage.Type.URL, url); + } + + public Builder component(Component component) { + components.add(component); + return this; + } + + public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { + return component(dropdownBuilder.translateAndBuild(this::translate)); + } + + public Builder dropdown(String text, int defaultOption, String... options) { + List optionsList = new ArrayList<>(); + for (String option : options) { + optionsList.add(translate(option)); + } + return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); + } + + public Builder dropdown(String text, String... options) { + return dropdown(text, -1, options); + } + + public Builder input(String text, String placeholder, String defaultText) { + return component(InputComponent.of( + translate(text), translate(placeholder), translate(defaultText) + )); + } + + public Builder input(String text, String placeholder) { + return component(InputComponent.of(translate(text), translate(placeholder))); + } + + public Builder input(String text) { + return component(InputComponent.of(translate(text))); + } + + public Builder label(String text) { + return component(LabelComponent.of(translate(text))); + } + + public Builder slider(String text, float min, float max, int step, float defaultValue) { + return component(SliderComponent.of(text, min, max, step, defaultValue)); + } + + public Builder slider(String text, float min, float max, int step) { + return slider(text, min, max, step, -1); + } + + public Builder slider(String text, float min, float max, float defaultValue) { + return slider(text, min, max, -1, defaultValue); + } + + public Builder slider(String text, float min, float max) { + return slider(text, min, max, -1, -1); + } + + public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { + return component(stepSliderBuilder.translateAndBuild(this::translate)); + } + + public Builder stepSlider(String text, int defaultStep, String... steps) { + List stepsList = new ArrayList<>(); + for (String option : steps) { + stepsList.add(translate(option)); + } + return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); + } + + public Builder stepSlider(String text, String... steps) { + return stepSlider(text, -1, steps); + } + + public Builder toggle(String text, boolean defaultValue) { + return component(ToggleComponent.of(translate(text), defaultValue)); + } + + public Builder toggle(String text) { + return component(ToggleComponent.of(translate(text))); + } + + @Override + public CustomForm build() { + CustomForm form = of(title, icon, components); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java new file mode 100644 index 000000000..5b3437572 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/Form.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.geysermc.common.form.response.FormResponse; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +@Getter +public abstract class Form { + protected static final Gson GSON = new Gson(); + private final Type type; + + @Getter(AccessLevel.NONE) + protected String hardcodedJsonData = null; + + @Setter protected Consumer responseHandler; + + public Form(Type type) { + this.type = type; + } + + public String getJsonData() { + if (hardcodedJsonData != null) { + return hardcodedJsonData; + } + return GSON.toJson(this); + } + + public abstract FormResponse parseResponse(String response); + + @SuppressWarnings("unchecked") + public T parseResponseAs(String response) { + return (T) parseResponse(response); + } + + public boolean isClosed(String response) { + return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); + } + + @Getter + @RequiredArgsConstructor + public enum Type { + @SerializedName("form") + SIMPLE_FORM(SimpleForm.class), + @SerializedName("modal") + MODAL_FORM(ModalForm.class), + @SerializedName("custom_form") + CUSTOM_FORM(CustomForm.class); + + private static final Type[] VALUES = values(); + private final Class typeClass; + + public static Type getByOrdinal(int ordinal) { + return ordinal < VALUES.length ? VALUES[ordinal] : null; + } + } + + public static abstract class Builder, F extends Form> { + protected String title = ""; + + protected BiFunction translationHandler = null; + protected BiConsumer biResponseHandler; + protected Consumer responseHandler; + protected String locale; + + public T title(String title) { + this.title = translate(title); + return self(); + } + + public T translator(BiFunction translator, String locale) { + this.translationHandler = translator; + this.locale = locale; + return title(title); + } + + public T translator(BiFunction translator) { + return translator(translator, locale); + } + + public T responseHandler(BiConsumer responseHandler) { + biResponseHandler = responseHandler; + return self(); + } + + public T responseHandler(Consumer responseHandler) { + this.responseHandler = responseHandler; + return self(); + } + + public abstract F build(); + + protected String translate(String text) { + if (translationHandler != null && text != null && !text.isEmpty()) { + return translationHandler.apply(text, locale); + } + return text; + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java new file mode 100644 index 000000000..10309f9c2 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/ModalForm.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.response.ModalFormResponse; +import org.geysermc.common.form.util.FormAdaptor; + +@Getter +@JsonAdapter(FormAdaptor.class) +public class ModalForm extends Form { + private final String title; + private final String content; + private final String button1; + private final String button2; + + private ModalForm(String title, String content, String button1, String button2) { + super(Type.MODAL_FORM); + + this.title = title; + this.content = content; + this.button1 = button1; + this.button2 = button2; + } + + public static Builder builder() { + return new Builder(); + } + + public static ModalForm of(String title, String content, String button1, String button2) { + return new ModalForm(title, content, button1, button2); + } + + public ModalFormResponse parseResponse(String data) { + if (isClosed(data)) { + return ModalFormResponse.closed(); + } + + if ("true".equals(data)) { + return ModalFormResponse.of(0, button1); + } else if ("false".equals(data)) { + return ModalFormResponse.of(1, button2); + } + return ModalFormResponse.invalid(); + } + + public static final class Builder extends Form.Builder { + private String content; + private String button1; + private String button2; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button1(String button1) { + this.button1 = translate(button1); + return this; + } + + public Builder button2(String button2) { + this.button2 = translate(button2); + return this; + } + + @Override + public ModalForm build() { + ModalForm form = of(title, content, button1, button2); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java new file mode 100644 index 000000000..1a1ce616b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/SimpleForm.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.response.SimpleFormResponse; +import org.geysermc.common.form.util.FormAdaptor; +import org.geysermc.common.form.util.FormImage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class SimpleForm extends Form { + private final String title; + private final String content; + private final List buttons; + + private SimpleForm(String title, String content, List buttons) { + super(Type.SIMPLE_FORM); + + this.title = title; + this.content = content; + this.buttons = Collections.unmodifiableList(buttons); + } + + public static Builder builder() { + return new Builder(); + } + + public static SimpleForm of(String title, String content, List buttons) { + return new SimpleForm(title, content, buttons); + } + + public SimpleFormResponse parseResponse(String data) { + if (isClosed(data)) { + return SimpleFormResponse.closed(); + } + + int buttonId; + try { + buttonId = Integer.parseInt(data); + } catch (Exception exception) { + return SimpleFormResponse.invalid(); + } + + if (buttonId >= buttons.size()) { + return SimpleFormResponse.invalid(); + } + + return SimpleFormResponse.of(buttonId, buttons.get(buttonId)); + } + + public static final class Builder extends Form.Builder { + private final List buttons = new ArrayList<>(); + private String content; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button(String text, FormImage.Type type, String data) { + buttons.add(ButtonComponent.of(translate(text), type, data)); + return this; + } + + public Builder button(String text, FormImage image) { + buttons.add(ButtonComponent.of(translate(text), image)); + return this; + } + + public Builder button(String text) { + buttons.add(ButtonComponent.of(translate(text))); + return this; + } + + @Override + public SimpleForm build() { + SimpleForm form = of(title, content, buttons); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/button/FormButton.java b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java similarity index 65% rename from common/src/main/java/org/geysermc/common/window/button/FormButton.java rename to common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java index 6daa2feae..de241e3dd 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormButton.java +++ b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java @@ -23,35 +23,28 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.common.form.component; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.util.FormImage; -public class FormButton { +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ButtonComponent { + private final String text; + private final FormImage image; - @Getter - @Setter - private String text; - - @Getter - private FormImage image; - - public FormButton(String text) { - this.text = text; + public static ButtonComponent of(String text, FormImage image) { + return new ButtonComponent(text, image); } - public FormButton(String text, FormImage image) { - this.text = text; - - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } + public static ButtonComponent of(String text, FormImage.Type type, String data) { + return of(text, FormImage.of(type, data)); } - public void setImage(FormImage image) { - if (image.getData() != null && !image.getData().isEmpty()) { - this.image = image; - } + public static ButtonComponent of(String text) { + return of(text, null); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/Component.java b/common/src/main/java/org/geysermc/common/form/component/Component.java new file mode 100644 index 000000000..8f44a2a0a --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/Component.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +@Getter +public abstract class Component { + private final Type type; + private final String text; + + Component(Type type, String text) { + Objects.requireNonNull(type, "Type cannot be null"); + Objects.requireNonNull(text, "Text cannot be null"); + + this.type = type; + this.text = text; + } + + @Getter + @RequiredArgsConstructor + public enum Type { + @SerializedName("dropdown") + DROPDOWN(DropdownComponent.class), + @SerializedName("input") + INPUT(InputComponent.class), + @SerializedName("label") + LABEL(LabelComponent.class), + @SerializedName("slider") + SLIDER(SliderComponent.class), + @SerializedName("step_slider") + STEP_SLIDER(StepSliderComponent.class), + @SerializedName("toggle") + TOGGLE(ToggleComponent.class); + + private static final Type[] VALUES = values(); + + private final String name = name().toLowerCase(); + private final Class componentClass; + + public static Type getByName(String name) { + for (Type type : VALUES) { + if (type.name.equals(name)) { + return type; + } + } + return null; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java new file mode 100644 index 000000000..6bf3f5e1a --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public class DropdownComponent extends Component { + private final List options; + @SerializedName("default") + private final int defaultOption; + + private DropdownComponent(String text, List options, int defaultOption) { + super(Type.DROPDOWN, text); + this.options = Collections.unmodifiableList(options); + this.defaultOption = defaultOption; + } + + public static DropdownComponent of(String text, List options, int defaultOption) { + if (defaultOption == -1 || defaultOption >= options.size()) { + defaultOption = 0; + } + return new DropdownComponent(text, options, defaultOption); + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String text) { + return builder().text(text); + } + + public static class Builder { + private final List options = new ArrayList<>(); + private String text; + private int defaultOption = 0; + + public Builder text(String text) { + this.text = text; + return this; + } + + public Builder option(String option, boolean isDefault) { + options.add(option); + if (isDefault) { + defaultOption = options.size() - 1; + } + return this; + } + + public Builder option(String option) { + return option(option, false); + } + + public Builder defaultOption(int defaultOption) { + this.defaultOption = defaultOption; + return this; + } + + public DropdownComponent build() { + return of(text, options, defaultOption); + } + + public DropdownComponent translateAndBuild(Function translator) { + for (int i = 0; i < options.size(); i++) { + options.set(i, translator.apply(options.get(i))); + } + + return of(translator.apply(text), options, defaultOption); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java similarity index 62% rename from common/src/main/java/org/geysermc/common/window/component/InputComponent.java rename to common/src/main/java/org/geysermc/common/form/component/InputComponent.java index fad6a0fed..b5e07535f 100644 --- a/common/src/main/java/org/geysermc/common/window/component/InputComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java @@ -23,30 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.component; +import com.google.gson.annotations.SerializedName; import lombok.Getter; -import lombok.Setter; -public class InputComponent extends FormComponent { +@Getter +public class InputComponent extends Component { + private final String placeholder; + @SerializedName("default") + private final String defaultText; - @Getter - @Setter - private String text; - - @Getter - @Setter - private String placeholder; - - @Getter - @Setter - private String defaultText; - - public InputComponent(String text, String placeholder, String defaultText) { - super("input"); - - this.text = text; + private InputComponent(String text, String placeholder, String defaultText) { + super(Type.INPUT, text); this.placeholder = placeholder; this.defaultText = defaultText; } + + public static InputComponent of(String text, String placeholder, String defaultText) { + return new InputComponent(text, placeholder, defaultText); + } + + public static InputComponent of(String text, String placeholder) { + return new InputComponent(text, placeholder, ""); + } + + public static InputComponent of(String text) { + return new InputComponent(text, "", ""); + } } diff --git a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java similarity index 81% rename from common/src/main/java/org/geysermc/common/window/component/LabelComponent.java rename to common/src/main/java/org/geysermc/common/form/component/LabelComponent.java index a76b313fa..410f14a25 100644 --- a/common/src/main/java/org/geysermc/common/window/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java @@ -23,20 +23,17 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.component; import lombok.Getter; -import lombok.Setter; -public class LabelComponent extends FormComponent { +@Getter +public class LabelComponent extends Component { + private LabelComponent(String text) { + super(Type.LABEL, text); + } - @Getter - @Setter - private String text; - - public LabelComponent(String text) { - super("label"); - - this.text = text; + public static LabelComponent of(String text) { + return new LabelComponent(text); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java new file mode 100644 index 000000000..0eb019e3a --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +@Getter +public final class SliderComponent extends Component { + private final float min; + private final float max; + private final int step; + @SerializedName("default") + private final float defaultValue; + + private SliderComponent(String text, float min, float max, int step, float defaultValue) { + super(Type.SLIDER, text); + this.min = min; + this.max = max; + this.step = step; + this.defaultValue = defaultValue; + } + + public static SliderComponent of(String text, float min, float max, int step, float defaultValue) { + min = Math.max(min, 0f); + max = Math.max(max, min); + + if (step < 1) { + step = 1; + } + + if (defaultValue == -1f) { + defaultValue = (int) Math.floor(min + max / 2D); + } + + return new SliderComponent(text, min, max, step, defaultValue); + } + + public static SliderComponent of(String text, float min, float max, int step) { + return of(text, min, max, step, -1); + } + + public static SliderComponent of(String text, float min, float max, float defaultValue) { + return of(text, min, max, -1, defaultValue); + } + + public static SliderComponent of(String text, float min, float max) { + return of(text, min, max, -1, -1); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java new file mode 100644 index 000000000..4d6d8fcb2 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public final class StepSliderComponent extends Component { + private final List steps; + @SerializedName("default") + private final int defaultStep; + + private StepSliderComponent(String text, List steps, int defaultStep) { + super(Type.STEP_SLIDER, text); + this.steps = Collections.unmodifiableList(steps); + this.defaultStep = defaultStep; + } + + public static StepSliderComponent of(String text, List steps, int defaultStep) { + if (text == null) { + text = ""; + } + + if (defaultStep >= steps.size() || defaultStep == -1) { + defaultStep = 0; + } + + return new StepSliderComponent(text, steps, defaultStep); + } + + public static StepSliderComponent of(String text, int defaultStep, String... steps) { + return of(text, Arrays.asList(steps), defaultStep); + } + + public static StepSliderComponent of(String text, String... steps) { + return of(text, 0, steps); + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String text) { + return builder().text(text); + } + + public static final class Builder { + private final List steps = new ArrayList<>(); + private String text; + private int defaultStep; + + public Builder text(String text) { + this.text = text; + return this; + } + + public Builder step(String step, boolean defaultStep) { + steps.add(step); + if (defaultStep) { + this.defaultStep = steps.size() - 1; + } + return this; + } + + public Builder step(String step) { + return step(step, false); + } + + public Builder defaultStep(int defaultStep) { + this.defaultStep = defaultStep; + return this; + } + + public StepSliderComponent build() { + return of(text, steps, defaultStep); + } + + public StepSliderComponent translateAndBuild(Function translator) { + for (int i = 0; i < steps.size(); i++) { + steps.set(i, translator.apply(steps.get(i))); + } + + return of(translator.apply(text), steps, defaultStep); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java similarity index 70% rename from common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java rename to common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java index f972d5906..08341aaca 100644 --- a/common/src/main/java/org/geysermc/common/window/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java @@ -23,29 +23,26 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.component; +import com.google.gson.annotations.SerializedName; import lombok.Getter; -import lombok.Setter; -public class ToggleComponent extends FormComponent { +@Getter +public class ToggleComponent extends Component { + @SerializedName("default") + private final boolean defaultValue; - @Getter - @Setter - private String text; - - @Getter - @Setter - private boolean defaultValue; - - public ToggleComponent(String text) { - this(text, false); - } - - public ToggleComponent(String text, boolean defaultValue) { - super("toggle"); - - this.text = text; + private ToggleComponent(String text, boolean defaultValue) { + super(Type.TOGGLE, text); this.defaultValue = defaultValue; } + + public static ToggleComponent of(String text, boolean defaultValue) { + return new ToggleComponent(text, defaultValue); + } + + public static ToggleComponent of(String text) { + return of(text, false); + } } diff --git a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java new file mode 100644 index 000000000..8ef50c7fb --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.response; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.component.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +public class CustomFormResponse implements FormResponse { + private static final Gson GSON = new Gson(); + private static final CustomFormResponse CLOSED = + new CustomFormResponse(true, false, null, null); + private static final CustomFormResponse INVALID = + new CustomFormResponse(false, true, null, null); + private final boolean closed; + private final boolean invalid; + + private final JsonArray responses; + private final List componentTypes; + + private int index = -1; + + public static CustomFormResponse closed() { + return CLOSED; + } + + public static CustomFormResponse invalid() { + return INVALID; + } + + public static CustomFormResponse of(CustomForm form, String responseData) { + JsonArray responses = GSON.fromJson(responseData, JsonArray.class); + List types = new ArrayList<>(); + for (Component component : form.getContent()) { + types.add(component.getType()); + } + return of(types, responses); + } + + public static CustomFormResponse of(List componentTypes, + JsonArray responses) { + if (componentTypes.size() != responses.size()) { + return invalid(); + } + + return new CustomFormResponse(false, false, responses, + Collections.unmodifiableList(componentTypes)); + } + + @SuppressWarnings("unchecked") + public T next(boolean includeLabels) { + if (!hasNext()) { + return null; + } + + while (++index < responses.size()) { + Component.Type type = componentTypes.get(index); + if (type == Component.Type.LABEL && !includeLabels) { + continue; + } + return (T) getDataFromType(type, index); + } + return null; // we don't have anything to check anymore + } + + public T next() { + return next(false); + } + + public void skip(int amount) { + index += amount; + } + + public void skip() { + skip(1); + } + + public void index(int index) { + this.index = index; + } + + public boolean hasNext() { + return responses.size() > index + 1; + } + + public JsonPrimitive get(int index) { + try { + return responses.get(index).getAsJsonPrimitive(); + } catch (IllegalStateException exception) { + wrongType(index, "a primitive"); + return null; + } + } + + public int getDropdown(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "dropdown"); + } + return primitive.getAsInt(); + } + + public String getInput(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isString()) { + wrongType(index, "input"); + } + return primitive.getAsString(); + } + + public float getSlider(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "slider"); + } + return primitive.getAsFloat(); + } + + public int getStepSlide(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "step slider"); + } + return primitive.getAsInt(); + } + + public boolean getToggle(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isBoolean()) { + wrongType(index, "toggle"); + } + return primitive.getAsBoolean(); + } + + private Object getDataFromType(Component.Type type, int index) { + switch (type) { + case DROPDOWN: + return getDropdown(index); + case INPUT: + return getInput(index); + case SLIDER: + return getSlider(index); + case STEP_SLIDER: + return getStepSlide(index); + case TOGGLE: + return getToggle(index); + default: + return null; // label e.g. is always null + } + } + + private void wrongType(int index, String expected) { + throw new IllegalStateException(String.format( + "Expected %s on %s, got %s", + expected, index, responses.get(index).toString())); + } +} diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java b/common/src/main/java/org/geysermc/common/form/response/FormResponse.java similarity index 87% rename from common/src/main/java/org/geysermc/common/window/response/FormResponse.java rename to common/src/main/java/org/geysermc/common/form/response/FormResponse.java index 2be646837..7c62236f6 100644 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/FormResponse.java @@ -23,7 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.response; +package org.geysermc.common.form.response; public interface FormResponse { + boolean isClosed(); + boolean isInvalid(); + + default boolean isCorrect() { + return !isClosed() && !isInvalid(); + } } diff --git a/common/src/main/java/org/geysermc/common/window/FormWindow.java b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java similarity index 55% rename from common/src/main/java/org/geysermc/common/window/FormWindow.java rename to common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java index c3cc4258b..b0e4de883 100644 --- a/common/src/main/java/org/geysermc/common/window/FormWindow.java +++ b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java @@ -23,37 +23,38 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window; +package org.geysermc.common.form.response; -import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.FormResponse; +import lombok.RequiredArgsConstructor; -public abstract class FormWindow { +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ModalFormResponse implements FormResponse { + private static final ModalFormResponse CLOSED = + new ModalFormResponse(true, false, -1, null); + private static final ModalFormResponse INVALID = + new ModalFormResponse(false, true, -1, null); + private final boolean closed; + private final boolean invalid; - @Getter - private final String type; + private final int clickedButtonId; + private final String clickedButtonText; - @Getter - protected FormResponse response; - - @Getter - @Setter - protected boolean closed; - - public FormWindow(String type) { - this.type = type; + public static ModalFormResponse closed() { + return CLOSED; } - // Lombok won't work here, so we need to make our own method - public void setResponse(FormResponse response) { - this.response = response; + public static ModalFormResponse invalid() { + return INVALID; } - @JsonIgnore - public abstract String getJSONData(); - - public abstract void setResponse(String response); + public static ModalFormResponse of(int clickedButtonId, String clickedButtonText) { + return new ModalFormResponse(false, false, clickedButtonId, clickedButtonText); + } + public boolean getResult() { + return clickedButtonId == 0; + } } diff --git a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java similarity index 56% rename from common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java rename to common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java index 8f128d1a4..6ee819f37 100644 --- a/common/src/main/java/org/geysermc/common/window/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java @@ -23,47 +23,33 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.component; +package org.geysermc.common.form.response; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.component.ButtonComponent; -import java.util.ArrayList; -import java.util.List; +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class SimpleFormResponse implements FormResponse { + private static final SimpleFormResponse CLOSED = new SimpleFormResponse(true, false, -1, null); + private static final SimpleFormResponse INVALID = new SimpleFormResponse(false, true, -1, null); + private final boolean closed; + private final boolean invalid; -public class StepSliderComponent extends FormComponent { + private final int clickedButtonId; + private final ButtonComponent clickedButton; - @Getter - @Setter - private String text; - - @Getter - private List steps; - - @Getter - @Setter - private int defaultStepIndex; - - public StepSliderComponent(String text) { - this(text, new ArrayList()); + public static SimpleFormResponse closed() { + return CLOSED; } - public StepSliderComponent(String text, List steps) { - this(text, steps, 0); + public static SimpleFormResponse invalid() { + return INVALID; } - public StepSliderComponent(String text, List steps, int defaultStepIndex) { - super("step_slider"); - - this.text = text; - this.steps = steps; - this.defaultStepIndex = defaultStepIndex; - } - - public void addStep(String step, boolean isDefault) { - steps.add(step); - - if (isDefault) - defaultStepIndex = steps.size() - 1; + public static SimpleFormResponse of(int clickedButtonId, ButtonComponent clickedButton) { + return new SimpleFormResponse(false, false, clickedButtonId, clickedButton); } } diff --git a/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java b/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java new file mode 100644 index 000000000..8c30e7e8f --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.util; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.Form; +import org.geysermc.common.form.ModalForm; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.component.Component; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class FormAdaptor implements JsonDeserializer
, JsonSerializer { + private static final Type LIST_BUTTON_TYPE = + new TypeToken>() {}.getType(); + + @Override + public Form deserialize(JsonElement jsonElement, Type typeOfT, + JsonDeserializationContext context) + throws JsonParseException { + + if (!jsonElement.isJsonObject()) { + throw new JsonParseException("Form has to be a JsonObject"); + } + JsonObject json = jsonElement.getAsJsonObject(); + + if (typeOfT == SimpleForm.class) { + String title = json.get("title").getAsString(); + String content = json.get("content").getAsString(); + List buttons = context + .deserialize(json.get("buttons"), LIST_BUTTON_TYPE); + return SimpleForm.of(title, content, buttons); + } + + if (typeOfT == ModalForm.class) { + String title = json.get("title").getAsString(); + String content = json.get("content").getAsString(); + String button1 = json.get("button1").getAsString(); + String button2 = json.get("button2").getAsString(); + return ModalForm.of(title, content, button1, button2); + } + + if (typeOfT == CustomForm.class) { + String title = json.get("title").getAsString(); + FormImage icon = context.deserialize(json.get("icon"), FormImage.class); + List content = new ArrayList<>(); + + JsonArray contentArray = json.getAsJsonArray("content"); + for (JsonElement contentElement : contentArray) { + String typeName = contentElement.getAsJsonObject().get("type").getAsString(); + + Component.Type type = Component.Type.getByName(typeName); + if (type == null) { + throw new JsonParseException("Failed to find Component type " + typeName); + } + + content.add(context.deserialize(contentElement, type.getComponentClass())); + } + return CustomForm.of(title, icon, content); + } + return null; + } + + @Override + public JsonElement serialize(Form src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.add("type", context.serialize(src.getType())); + + if (typeOfSrc == SimpleForm.class) { + SimpleForm form = (SimpleForm) src; + + result.addProperty("title", form.getTitle()); + result.addProperty("content", form.getContent()); + result.add("buttons", context.serialize(form.getButtons(), LIST_BUTTON_TYPE)); + return result; + } + + if (typeOfSrc == ModalForm.class) { + ModalForm form = (ModalForm) src; + + result.addProperty("title", form.getTitle()); + result.addProperty("content", form.getContent()); + result.addProperty("button1", form.getButton1()); + result.addProperty("button2", form.getButton2()); + return result; + } + + if (typeOfSrc == CustomForm.class) { + CustomForm form = (CustomForm) src; + + result.addProperty("title", form.getTitle()); + result.add("icon", context.serialize(form.getIcon())); + result.add("content", context.serialize(form.getContent())); + return result; + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/common/window/button/FormImage.java b/common/src/main/java/org/geysermc/common/form/util/FormImage.java similarity index 72% rename from common/src/main/java/org/geysermc/common/window/button/FormImage.java rename to common/src/main/java/org/geysermc/common/form/util/FormImage.java index 72579f7ac..876e662d0 100644 --- a/common/src/main/java/org/geysermc/common/window/button/FormImage.java +++ b/common/src/main/java/org/geysermc/common/form/util/FormImage.java @@ -23,36 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.window.button; +package org.geysermc.common.form.util; +import lombok.AccessLevel; import lombok.Getter; -import lombok.Setter; +import lombok.RequiredArgsConstructor; +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class FormImage { + private final String type; + private final String data; - @Getter - @Setter - private String type; - - @Getter - @Setter - private String data; - - public FormImage(FormImageType type, String data) { - this.type = type.getName(); - this.data = data; + public static FormImage of(String type, String data) { + return new FormImage(type, data); } - public enum FormImageType { + public static FormImage of(Type type, String data) { + return of(type.getName(), data); + } + + @RequiredArgsConstructor + public enum Type { PATH("path"), URL("url"); - @Getter - private String name; - - FormImageType(String name) { - this.name = name; - } + @Getter private final String name; @Override public String toString() { diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java b/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java deleted file mode 100644 index 004b00a96..000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormBuilder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import lombok.Getter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.FormComponent; -import org.geysermc.common.window.response.CustomFormResponse; - -public class CustomFormBuilder { - - @Getter - private CustomFormWindow form; - - public CustomFormBuilder(String title) { - form = new CustomFormWindow(title); - } - - public CustomFormBuilder setTitle(String title) { - form.setTitle(title); - return this; - } - - public CustomFormBuilder setIcon(FormImage icon) { - form.setIcon(icon); - return this; - } - - public CustomFormBuilder setResponse(String data) { - form.setResponse(data); - return this; - } - - public CustomFormBuilder setResponse(CustomFormResponse response) { - form.setResponse(response); - return this; - } - - public CustomFormBuilder addComponent(FormComponent component) { - form.addComponent(component); - return this; - } - - public CustomFormWindow build() { - return form; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java deleted file mode 100644 index efc71ae8d..000000000 --- a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.*; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class CustomFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private FormImage icon; - - @Getter - private List content; - - public CustomFormWindow(String title) { - this(title, new ArrayList<>()); - } - - public CustomFormWindow(String title, List content) { - this(title, content, (FormImage) null); - } - - public CustomFormWindow(String title, List content, String icon) { - this(title, content, new FormImage(FormImage.FormImageType.URL, icon)); - } - - public CustomFormWindow(String title, List content, FormImage icon) { - super("custom_form"); - - this.title = title; - this.content = content; - this.icon = icon; - } - - public void addComponent(FormComponent component) { - content.add(component); - } - - public String getJSONData() { - String toModify = ""; - try { - toModify = new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { } - - //We need to replace this due to Java not supporting declaring class field 'default' - return toModify.replace("defaultOptionIndex", "default") - .replace("defaultText", "default") - .replace("defaultValue", "default") - .replace("defaultStepIndex", "default"); - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null") || data.isEmpty()) { - closed = true; - return; - } - - int i = 0; - Map dropdownResponses = new HashMap(); - Map inputResponses = new HashMap(); - Map sliderResponses = new HashMap(); - Map stepSliderResponses = new HashMap(); - Map toggleResponses = new HashMap(); - Map responses = new HashMap(); - Map labelResponses = new HashMap(); - - List componentResponses = new ArrayList<>(); - try { - componentResponses = new ObjectMapper().readValue(data, new TypeReference>(){}); - } catch (IOException e) { } - - for (String response : componentResponses) { - if (i >= content.size()) { - break; - } - - FormComponent component = content.get(i); - if (component == null) - return; - - if (component instanceof LabelComponent) { - LabelComponent labelComponent = (LabelComponent) component; - labelResponses.put(i, labelComponent.getText()); - } - - if (component instanceof DropdownComponent) { - DropdownComponent dropdownComponent = (DropdownComponent) component; - String option = dropdownComponent.getOptions().get(Integer.parseInt(response)); - - dropdownResponses.put(i, new FormResponseData(Integer.parseInt(response), option)); - responses.put(i, option); - } - - if (component instanceof InputComponent) { - inputResponses.put(i, response); - responses.put(i, response); - } - - if (component instanceof SliderComponent) { - float value = Float.parseFloat(response); - sliderResponses.put(i, value); - responses.put(i, value); - } - - if (component instanceof StepSliderComponent) { - StepSliderComponent stepSliderComponent = (StepSliderComponent) component; - String step = stepSliderComponent.getSteps().get(Integer.parseInt(response)); - stepSliderResponses.put(i, new FormResponseData(Integer.parseInt(response), step)); - responses.put(i, step); - } - - if (component instanceof ToggleComponent) { - boolean answer = Boolean.parseBoolean(response); - toggleResponses.put(i, answer); - responses.put(i, answer); - } - i++; - } - - this.response = new CustomFormResponse(responses, dropdownResponses, inputResponses, - sliderResponses, stepSliderResponses, toggleResponses, labelResponses); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java b/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java deleted file mode 100644 index bfeafa1b0..000000000 --- a/common/src/main/java/org/geysermc/common/window/ModalFormWindow.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.response.ModalFormResponse; - -public class ModalFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private String button1; - - @Getter - @Setter - private String button2; - - public ModalFormWindow(String title, String content, String button1, String button2) { - super("modal"); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { - closed = true; - return; - } - - if (Boolean.parseBoolean(data)) { - response = new ModalFormResponse(0, button1); - } else { - response = new ModalFormResponse(1, button2); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java deleted file mode 100644 index 7c1acc26f..000000000 --- a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; - -import java.util.ArrayList; -import java.util.List; - - -public class SimpleFormWindow extends FormWindow { - - @Getter - @Setter - private String title; - - @Getter - @Setter - private String content; - - @Getter - @Setter - private List buttons; - - public SimpleFormWindow(String title, String content) { - this(title, content, new ArrayList()); - } - - public SimpleFormWindow(String title, String content, List buttons) { - super("form"); - - this.title = title; - this.content = content; - this.buttons = buttons; - } - - @Override - public String getJSONData() { - try { - return new ObjectMapper().writeValueAsString(this); - } catch (JsonProcessingException e) { - return ""; - } - } - - public void setResponse(String data) { - if (data == null || data.equalsIgnoreCase("null")) { - closed = true; - return; - } - - int buttonID; - try { - buttonID = Integer.parseInt(data); - } catch (Exception ex) { - return; - } - - if (buttonID >= buttons.size()) { - response = new SimpleFormResponse(buttonID, null); - return; - } - - response = new SimpleFormResponse(buttonID, buttons.get(buttonID)); - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java deleted file mode 100644 index 4dac6b043..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/DropdownComponent.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -import java.util.List; - -public class DropdownComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private List options; - - @Getter - @Setter - private int defaultOptionIndex; - - public DropdownComponent() { - super("dropdown"); - } - - public void addOption(String option, boolean isDefault) { - options.add(option); - if (isDefault) - defaultOptionIndex = options.size() - 1; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java b/common/src/main/java/org/geysermc/common/window/component/FormComponent.java deleted file mode 100644 index 5a56ae0cc..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/FormComponent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; - -public abstract class FormComponent { - - @Getter - private final String type; - - public FormComponent(String type) { - this.type = type; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java deleted file mode 100644 index a7a78362e..000000000 --- a/common/src/main/java/org/geysermc/common/window/component/SliderComponent.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.component; - -import lombok.Getter; -import lombok.Setter; - -public class SliderComponent extends FormComponent { - - @Getter - @Setter - private String text; - - @Getter - @Setter - private float min; - - @Getter - @Setter - private float max; - - @Getter - @Setter - private int step; - - @Getter - @Setter - private float defaultValue; - - public SliderComponent(String text, float min, float max, int step, float defaultValue) { - super("slider"); - - this.text = text; - this.min = Math.max(min, 0f); - this.max = max > this.min ? max : this.min; - if (step != -1f && step > 0) - this.step = step; - - if (defaultValue != -1f) - this.defaultValue = defaultValue; - } -} diff --git a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java deleted file mode 100644 index 6cdd70978..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/CustomFormResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; -import org.geysermc.common.window.response.FormResponseData; - -import java.util.Map; - -@Getter -@AllArgsConstructor -public class CustomFormResponse implements FormResponse { - - private Map responses; - private Map dropdownResponses; - private Map inputResponses; - private Map sliderResponses; - private Map stepSliderResponses; - private Map toggleResponses; - private Map labelResponses; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java b/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java deleted file mode 100644 index fd40be0fb..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/FormResponseData.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public class FormResponseData { - - private int elementID; - private String elementContent; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java deleted file mode 100644 index e1a14039d..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/ModalFormResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.response.FormResponse; - -@Getter -@AllArgsConstructor -public class ModalFormResponse implements FormResponse { - - private int clickedButtonId; - private String clickedButtonText; -} diff --git a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java deleted file mode 100644 index e80d58e78..000000000 --- a/common/src/main/java/org/geysermc/common/window/response/SimpleFormResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.window.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.FormResponse; - -@Getter -@AllArgsConstructor -public class SimpleFormResponse implements FormResponse { - - private int clickedButtonId; - private FormButton clickedButton; -} diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 7e97d4298..9f6a88e18 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -39,7 +39,6 @@ import org.geysermc.connector.utils.LoginEncryptionUtils; import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.ResourcePack; import org.geysermc.connector.utils.ResourcePackManifest; -import org.geysermc.connector.utils.SettingsUtils; import java.io.FileInputStream; import java.io.InputStream; @@ -139,11 +138,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { - if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) { - return SettingsUtils.handleSettingsForm(session, packet.getFormData()); - } - - return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData()); + session.getFormCache().handleResponse(packet); + return true; } private boolean couldLoginUserByName(String bedrockUsername) { @@ -170,7 +166,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthData().getName())) { - LoginEncryptionUtils.showLoginWindow(session); + LoginEncryptionUtils.buildAndShowLoginWindow(session); } // else we were able to log the user in } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 42c2f2437..ec8c7f0ee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -55,8 +55,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; +import org.geysermc.common.form.Form; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; @@ -101,7 +100,7 @@ public class GeyserSession implements CommandSender { private EntityCache entityCache; private InventoryCache inventoryCache; private WorldCache worldCache; - private WindowCache windowCache; + private FormCache formCache; @Setter private TeleportCache teleportCache; @@ -192,9 +191,6 @@ public class GeyserSession implements CommandSender { private boolean reducedDebugInfo = false; - @Setter - private CustomFormWindow settingsForm; - /** * The op permission level set by the server */ @@ -239,7 +235,7 @@ public class GeyserSession implements CommandSender { /** * Stores the last text inputted into a sign. - * + *

* Bedrock sends packets every time you update the sign, Java only wants the final packet. * Until we determine that the user has finished editing, we save the sign's current status. */ @@ -256,7 +252,7 @@ public class GeyserSession implements CommandSender { this.entityCache = new EntityCache(this); this.inventoryCache = new InventoryCache(this); this.worldCache = new WorldCache(this); - this.windowCache = new WindowCache(this); + this.formCache = new FormCache(this); this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); this.inventory = new PlayerInventory(); @@ -497,7 +493,7 @@ public class GeyserSession implements CommandSender { this.entityCache = null; this.worldCache = null; this.inventoryCache = null; - this.windowCache = null; + this.formCache = null; closed = true; } @@ -533,10 +529,6 @@ public class GeyserSession implements CommandSender { return false; } - public void sendForm(FormWindow window, int id) { - windowCache.showWindow(window, id); - } - public void setRenderDistance(int renderDistance) { renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle if (renderDistance > 32) renderDistance = 32; // <3 u ViaVersion but I don't like crashing clients x) @@ -551,8 +543,12 @@ public class GeyserSession implements CommandSender { return this.upstream.getAddress(); } - public void sendForm(FormWindow window) { - windowCache.showWindow(window); + public void sendForm(Form form) { + formCache.showForm(form); + } + + public void sendForm(Form.Builder formBuilder) { + formCache.showForm(formBuilder.build()); } private void startGame() { @@ -678,7 +674,7 @@ public class GeyserSession implements CommandSender { * Send a gamerule value to the client * * @param gameRule The gamerule to send - * @param value The value of the gamerule + * @param value The value of the gamerule */ public void sendGameRule(String gameRule, Object value) { GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java similarity index 56% rename from connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java rename to connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 15b9a7705..20ade2869 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -26,56 +26,54 @@ package org.geysermc.connector.network.session.cache; import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; - +import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; - -import lombok.Getter; - -import org.geysermc.common.window.FormWindow; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.Form; import org.geysermc.connector.network.session.GeyserSession; -public class WindowCache { +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; - private GeyserSession session; +@RequiredArgsConstructor +public class FormCache { + private final AtomicInteger formId = new AtomicInteger(0); + private final Int2ObjectMap forms = new Int2ObjectOpenHashMap<>(); + private final GeyserSession session; - @Getter - private Int2ObjectMap windows = new Int2ObjectOpenHashMap<>(); - - public WindowCache(GeyserSession session) { - this.session = session; + public int addForm(Form form) { + int windowId = formId.getAndIncrement(); + forms.put(windowId, form); + return windowId; } - public void addWindow(FormWindow window) { - windows.put(windows.size() + 1, window); + public int showForm(Form form) { + int windowId = addForm(form); + + ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); + formRequestPacket.setFormId(windowId); + formRequestPacket.setFormData(form.getJsonData()); + + session.sendUpstreamPacket(formRequestPacket); + return windowId; } - public void addWindow(FormWindow window, int id) { - windows.put(id, window); - } - - public void showWindow(FormWindow window) { - showWindow(window, windows.size() + 1); - } - - public void showWindow(int id) { - if (!windows.containsKey(id)) + public void handleResponse(ModalFormResponsePacket response) { + Form form = forms.get(response.getFormId()); + if (form == null) { return; + } - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(windows.get(id).getJSONData()); + Consumer responseConsumer = form.getResponseHandler(); + if (responseConsumer != null) { + responseConsumer.accept(response.getFormData().trim()); + } - session.sendUpstreamPacket(formRequestPacket); + removeWindow(response.getFormId()); } - public void showWindow(FormWindow window, int id) { - ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); - formRequestPacket.setFormId(id); - formRequestPacket.setFormData(window.getJSONData()); - - session.sendUpstreamPacket(formRequestPacket); - - addWindow(window, id); + public boolean removeWindow(int id) { + return forms.remove(id) != null; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index a8591cd7f..01bf07b9c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket; import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket; +import org.geysermc.common.form.CustomForm; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -37,11 +38,12 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { - - private static byte[] brandData; + private static final byte[] brandData; static { byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); @@ -48,21 +54,11 @@ public class JavaPluginMessageTranslator extends PacketTranslator>>= 7; if (value != 0) { temp |= 0b10000000; @@ -85,4 +81,48 @@ public class JavaPluginMessageTranslator extends PacketTranslator { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + byte[] finalData = new byte[raw.length + 2]; + + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, finalData)); + }); + session.sendForm(form); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 62d70f612..8109ef219 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -34,20 +34,16 @@ import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.FormWindow; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.response.CustomFormResponse; -import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.ModalForm; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.response.CustomFormResponse; +import org.geysermc.common.form.response.ModalFormResponse; +import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; -import org.geysermc.connector.network.session.cache.WindowCache; import javax.crypto.SecretKey; import java.io.IOException; @@ -72,7 +68,7 @@ public class LoginEncryptionUtils { } if (lastKey != null) { - if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; + if (!EncryptionUtils.verifyJwt(jwt, lastKey)) return false; } JsonNode payloadNode = JSON_MAPPER.readTree(jwt.getPayload().toString()); @@ -159,69 +155,49 @@ public class LoginEncryptionUtils { session.sendUpstreamPacketImmediately(packet); } - private static int AUTH_FORM_ID = 1336; - private static int AUTH_DETAILS_FORM_ID = 1337; - - public static void showLoginWindow(GeyserSession session) { + public static void buildAndShowLoginWindow(GeyserSession session) { String userLanguage = session.getClientData().getLanguageCode(); - SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage)); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage))); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage))); - session.sendForm(window, AUTH_FORM_ID); - } + session.sendForm( + SimpleForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .title("geyser.auth.login.form.notice.title") + .content("geyser.auth.login.form.notice.desc") + .button("geyser.auth.login.form.notice.btn_login") // id = 0 + .button("geyser.auth.login.form.notice.btn_disconnect") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData.trim()); + if (!response.isCorrect()) { + buildAndShowLoginWindow(session); + return; + } - public static void showLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); - CustomFormWindow window = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.title", userLanguage)) - .addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.desc", userLanguage))) - .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.email", userLanguage), "account@geysermc.org", "")) - .addComponent(new InputComponent(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.details.pass", userLanguage), "123456", "")) - .build(); + if (response.getClickedButtonId() == 0) { + buildAndShowLoginDetailsWindow(session); + return; + } - session.sendForm(window, AUTH_DETAILS_FORM_ID); - } - - public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) { - WindowCache windowCache = session.getWindowCache(); - if (!windowCache.getWindows().containsKey(formId)) - return false; - - if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) { - FormWindow window = windowCache.getWindows().remove(formId); - window.setResponse(formData.trim()); - - if (!session.isLoggedIn()) { - if (formId == AUTH_DETAILS_FORM_ID && window instanceof CustomFormWindow) { - CustomFormWindow customFormWindow = (CustomFormWindow) window; - - CustomFormResponse response = (CustomFormResponse) customFormWindow.getResponse(); - if (response != null) { - String email = response.getInputResponses().get(1); - String password = response.getInputResponses().get(2); - - session.authenticate(email, password); - } else { - showLoginDetailsWindow(session); - } - - // Clear windows so authentication data isn't accidentally cached - windowCache.getWindows().clear(); - } else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) { - SimpleFormResponse response = (SimpleFormResponse) window.getResponse(); - if (response != null) { - if (response.getClickedButtonId() == 0) { - showLoginDetailsWindow(session); - } else if(response.getClickedButtonId() == 1) { session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode())); - } - } else { - showLoginWindow(session); - } - } - } - } - return true; + })); } + public static void buildAndShowLoginDetailsWindow(GeyserSession session) { + String userLanguage = session.getClientData().getLanguageCode(); + session.sendForm( + CustomForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .title("geyser.auth.login.form.details.title") + .label("geyser.auth.login.form.details.desc") + .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") + .input("geyser.auth.login.form.details.pass", "123456", "") + .responseHandler((form, responseData) -> { + CustomFormResponse response = form.parseResponse(responseData.trim()); + if (!response.isCorrect()) { + buildAndShowLoginDetailsWindow(session); + return; + } + + session.authenticate(response.next(), response.next()); + })); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 89e9fe67b..a2f6aeba4 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -27,62 +27,53 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import org.geysermc.common.window.CustomFormBuilder; -import org.geysermc.common.window.CustomFormWindow; -import org.geysermc.common.window.button.FormImage; -import org.geysermc.common.window.component.DropdownComponent; -import org.geysermc.common.window.component.InputComponent; -import org.geysermc.common.window.component.LabelComponent; -import org.geysermc.common.window.component.ToggleComponent; -import org.geysermc.common.window.response.CustomFormResponse; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.component.DropdownComponent; +import org.geysermc.common.form.response.CustomFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; - -import java.util.ArrayList; +import org.geysermc.connector.network.translators.world.WorldManager; public class SettingsUtils { - // Used in UpstreamPacketHandler.java - public static final int SETTINGS_FORM_ID = 1338; - /** * Build a settings form for the given session and store it for later * * @param session The session to build the form for */ - public static void buildForm(GeyserSession session) { + public static CustomForm buildForm(GeyserSession session) { // Cache the language for cleaner access String language = session.getClientData().getLanguageCode(); - CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language)); - builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png")); - - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language))); - builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates()))); + CustomForm.Builder builder = CustomForm.builder() + .translator(LanguageUtils::getPlayerLocaleString, language) + .title("geyser.settings.title.main") + .iconPath("textures/ui/settings_glyph_color_2x.png") + .label("geyser.settings.title.client") + .toggle("geyser.settings.option.coordinates"); if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language))); + builder.label("geyser.settings.title.server"); - DropdownComponent gamemodeDropdown = new DropdownComponent(); - gamemodeDropdown.setText("%createWorldScreen.gameMode.personal"); - gamemodeDropdown.setOptions(new ArrayList<>()); + DropdownComponent.Builder gamemodeDropdown = DropdownComponent.builder("%createWorldScreen.gameMode.personal"); for (GameMode gamemode : GameMode.values()) { - gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode); + gamemodeDropdown.option("selectWorld.gameMode." + gamemode.name().toLowerCase(), session.getGameMode() == gamemode); } - builder.addComponent(gamemodeDropdown); + builder.dropdown(gamemodeDropdown); - DropdownComponent difficultyDropdown = new DropdownComponent(); - difficultyDropdown.setText("%options.difficulty"); - difficultyDropdown.setOptions(new ArrayList<>()); + DropdownComponent.Builder difficultyDropdown = DropdownComponent.builder("%options.difficulty"); for (Difficulty difficulty : Difficulty.values()) { - difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); + difficultyDropdown.option("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); } - builder.addComponent(difficultyDropdown); + builder.dropdown(difficultyDropdown); } if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language))); + builder.label("geyser.settings.title.game_rules") + .translator(LocaleUtils::getLocaleString); // we need translate gamerules next + + WorldManager worldManager = GeyserConnector.getInstance().getWorldManager(); for (GameRule gamerule : GameRule.values()) { if (gamerule.equals(GameRule.UNKNOWN)) { continue; @@ -90,74 +81,54 @@ public class SettingsUtils { // Add the relevant form item based on the gamerule type if (Boolean.class.equals(gamerule.getType())) { - builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule))); + builder.toggle("gamerule." + gamerule.getJavaID(), worldManager.getGameRuleBool(session, gamerule)); } else if (Integer.class.equals(gamerule.getType())) { - builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule)))); + builder.input("gamerule." + gamerule.getJavaID(), "", String.valueOf(worldManager.getGameRuleInt(session, gamerule))); } } } - session.setSettingsForm(builder.build()); - } - - /** - * Handle the settings form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleSettingsForm(GeyserSession session, String response) { - CustomFormWindow settingsForm = session.getSettingsForm(); - settingsForm.setResponse(response); - - CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse(); - int offset = 0; - - offset++; // Client settings title - - session.getWorldCache().setShowCoordinates(settingsResponse.getToggleResponses().get(offset)); - offset++; - - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { - offset++; // Server settings title - - GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (gameMode != null && gameMode != session.getGameMode()) { - session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); + builder.responseHandler((form, responseData) -> { + CustomFormResponse response = form.parseResponseAs(responseData); + if (response.isClosed() || response.isInvalid()) { + return; } - offset++; - Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()]; - if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { - session.getConnector().getWorldManager().setDifficulty(session, difficulty); - } - offset++; - } + session.getWorldCache().setShowCoordinates(response.next()); - if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { - offset++; // Game rule title - - for (GameRule gamerule : GameRule.values()) { - if (gamerule.equals(GameRule.UNKNOWN)) { - continue; + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { + GameMode gameMode = GameMode.values()[(int) response.next()]; + if (gameMode != null && gameMode != session.getGameMode()) { + session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode); } - if (Boolean.class.equals(gamerule.getType())) { - Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue(); - if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + Difficulty difficulty = Difficulty.values()[(int) response.next()]; + if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { + session.getConnector().getWorldManager().setDifficulty(session, difficulty); + } + } + + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) { + for (GameRule gamerule : GameRule.values()) { + if (gamerule.equals(GameRule.UNKNOWN)) { + continue; } - } else if (Integer.class.equals(gamerule.getType())) { - int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset)); - if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { - session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + + if (Boolean.class.equals(gamerule.getType())) { + boolean value = response.next(); + if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { + session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + } + } else if (Integer.class.equals(gamerule.getType())) { + int value = Integer.parseInt(response.next()); + if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) { + session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); + } } } - offset++; } - } + }); - return true; + return builder.build(); } } From dc8fb4642818012118b61411c83aa32e24283dc7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 30 Oct 2020 01:14:34 +0100 Subject: [PATCH 09/51] Manually solve some merge issues --- .../java/org/geysermc/common/form/Form.java | 12 ++- .../java/JavaPluginMessageTranslator.java | 78 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java index 5b3437572..5924e6896 100644 --- a/common/src/main/java/org/geysermc/common/form/Form.java +++ b/common/src/main/java/org/geysermc/common/form/Form.java @@ -26,12 +26,14 @@ package org.geysermc.common.form; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import org.geysermc.common.form.response.FormResponse; +import org.geysermc.common.form.util.FormAdaptor; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -39,7 +41,11 @@ import java.util.function.Consumer; @Getter public abstract class Form { - protected static final Gson GSON = new Gson(); + protected static final Gson GSON = + new GsonBuilder() + .registerTypeAdapter(ModalForm.class, new FormAdaptor()) + .create(); + private final Type type; @Getter(AccessLevel.NONE) @@ -51,6 +57,10 @@ public abstract class Form { this.type = type; } + public static T fromJson(String json, Class formClass) { + return GSON.fromJson(json, formClass); + } + public String getJsonData() { if (hardcodedJsonData != null) { return hardcodedJsonData; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java new file mode 100644 index 000000000..2a791d5a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java; + +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import com.google.common.base.Charsets; +import org.geysermc.common.form.Form; +import org.geysermc.connector.common.AuthType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +import java.nio.charset.StandardCharsets; + +@Translator(packet = ServerPluginMessagePacket.class) +public class JavaPluginMessageTranslator extends PacketTranslator { + @Override + public void translate(ServerPluginMessagePacket packet, GeyserSession session) { + // The only plugin messages to listen for are Floodgate plugin messages + if (session.getConnector().getAuthType() != AuthType.FLOODGATE) { + return; + } + + String channel = packet.getChannel(); + + if (channel.equals("floodgate:form")) { + byte[] data = packet.getData(); + + // receive: first byte is form type, second and third are the id, remaining is the form data + // respond: first and second byte id, remaining is form response data + + Form.Type type = Form.Type.getByOrdinal(data[0]); + if (type == null) { + throw new NullPointerException( + "Got type " + data[0] + " which isn't a valid form type!"); + } + + String dataString = new String(data, 3, data.length - 3, Charsets.UTF_8); + + Form form = Form.fromJson(dataString, type.getTypeClass()); + form.setResponseHandler(response -> { + byte[] raw = response.getBytes(StandardCharsets.UTF_8); + byte[] finalData = new byte[raw.length + 2]; + + finalData[0] = data[1]; + finalData[1] = data[2]; + System.arraycopy(raw, 0, finalData, 2, raw.length); + + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, finalData)); + }); + session.sendForm(form); + } + } +} From 819ff09ee6f390f112950212913908006b4b37bb Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 30 Oct 2020 01:31:22 +0100 Subject: [PATCH 10/51] Manually resolved merge conflicts This specific commit isn't compilable, since StatisticsUtils hasn't been updated to match the new Form style. --- .../org/geysermc/connector/network/UpstreamPacketHandler.java | 2 +- .../main/java/org/geysermc/connector/utils/StatisticsUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 72574b591..20d2bb9e7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -139,7 +139,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public boolean handle(ModalFormResponsePacket packet) { session.getFormCache().handleResponse(packet); - return true; //todo change the Statistics Form to match the new style + return true; } private boolean couldLoginUserByName(String bedrockUsername) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 3c42182d7..732217c24 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -37,7 +37,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.Map; -public class StatisticsUtils { +public class StatisticsUtils { //todo make Geyser compilable // Used in UpstreamPacketHandler.java public static final int STATISTICS_MENU_FORM_ID = 1339; From 7e3a736f20c9810f86c328f034e907399a338fb7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 18 Nov 2020 19:38:49 +0100 Subject: [PATCH 11/51] Register Floodgate payload, updated Statistics, smaller jar, fixed bugs Quite a lot of changes, but I was too lazy to split them in different commits (and they'll be squashed later anyway): * Floodgate plugin message channels are now registered (because Spigot requires that, and I guess it's better practice) * Updated the Statistics form to match the new Forms API * The common jar is now much smaller, because Jackson isn't needed anymore in the common module * Fixed some bugs in Forms where empty fields would lead to excluding them in the serialization (making Bedrock complain) And a few other things, like a new boolean in RawSkin saying if the Skin is an Alex or Steve model. --- common/pom.xml | 6 - .../org/geysermc/common/form/ModalForm.java | 6 +- .../org/geysermc/common/form/SimpleForm.java | 2 +- .../common/window/CustomFormWindow.java | 0 .../common/window/SimpleFormWindow.java | 0 .../geysermc/floodgate/util/BedrockData.java | 49 +-- .../org/geysermc/floodgate/util/DeviceOs.java | 3 +- .../geysermc/floodgate/util/InputMode.java | 4 +- .../geysermc/floodgate/util/LinkedPlayer.java | 15 +- .../org/geysermc/floodgate/util/RawSkin.java | 33 +- .../geysermc/floodgate/util/UiProfile.java | 7 +- .../geysermc/connector/GeyserConnector.java | 2 +- .../org/geysermc/connector/dump/DumpInfo.java | 2 +- .../connector/entity/FireworkEntity.java | 2 +- .../network/session/GeyserSession.java | 4 +- .../session/auth/BedrockClientData.java | 69 +++- .../java/JavaJoinGameTranslator.java | 7 +- .../java/JavaStatisticsTranslator.java | 3 +- .../connector/utils/LanguageUtils.java | 13 +- .../connector/utils/LoginEncryptionUtils.java | 2 - .../connector/utils/PluginMessageUtils.java | 36 +- .../connector/utils/SkinProvider.java | 3 +- .../connector/utils/StatisticsUtils.java | 317 +++++++++--------- 23 files changed, 324 insertions(+), 261 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/common/window/CustomFormWindow.java delete mode 100644 common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java diff --git a/common/pom.xml b/common/pom.xml index 30c8afc06..9223331be 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -18,11 +18,5 @@ 2.8.5 compile - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.8 - compile - \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java index 10309f9c2..ea58a466e 100644 --- a/common/src/main/java/org/geysermc/common/form/ModalForm.java +++ b/common/src/main/java/org/geysermc/common/form/ModalForm.java @@ -69,9 +69,9 @@ public class ModalForm extends Form { } public static final class Builder extends Form.Builder { - private String content; - private String button1; - private String button2; + private String content = ""; + private String button1 = ""; + private String button2 = ""; public Builder content(String content) { this.content = translate(content); diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java index 1a1ce616b..275fb8a86 100644 --- a/common/src/main/java/org/geysermc/common/form/SimpleForm.java +++ b/common/src/main/java/org/geysermc/common/form/SimpleForm.java @@ -80,7 +80,7 @@ public final class SimpleForm extends Form { public static final class Builder extends Form.Builder { private final List buttons = new ArrayList<>(); - private String content; + private String content = ""; public Builder content(String content) { this.content = translate(content); diff --git a/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java b/common/src/main/java/org/geysermc/common/window/CustomFormWindow.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java b/common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 89eaf5395..08c1e28d3 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -30,14 +30,14 @@ import lombok.AllArgsConstructor; import lombok.Getter; /** - * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. - * This class is only used internally, and you should look at FloodgatePlayer instead - * (FloodgatePlayer is present in the common module in the Floodgate repo) + * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This + * class is only used internally, and you should look at FloodgatePlayer instead (FloodgatePlayer is + * present in the API module of the Floodgate repo) */ -@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData { - public static final int EXPECTED_LENGTH = 9; + public static final int EXPECTED_LENGTH = 10; private final String version; private final String username; @@ -48,22 +48,21 @@ public final class BedrockData { private final int inputMode; private final String ip; private final LinkedPlayer linkedPlayer; + private final boolean fromProxy; + private final int dataLength; - public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer) { - this(version, username, xuid, deviceOs, languageCode, - inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH); + public static BedrockData of(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer, boolean fromProxy) { + return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, + uiProfile, ip, linkedPlayer, fromProxy, EXPECTED_LENGTH); } - public BedrockData(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null); - } - - public boolean hasPlayerLink() { - return linkedPlayer != null; + public static BedrockData of(String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip) { + return of(version, username, xuid, deviceOs, languageCode, + uiProfile, inputMode, ip, null, false); } public static BedrockData fromString(String data) { @@ -77,19 +76,23 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, split.length + linkedPlayer, Boolean.parseBoolean(split[9]), split.length ); } + private static BedrockData emptyData(int dataLength) { + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, dataLength); + } + + public boolean hasPlayerLink() { + return linkedPlayer != null; + } + @Override public String toString() { // The format is the same as the order of the fields in this class return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + - (linkedPlayer != null ? linkedPlayer.toString() : "null"); - } - - private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength); + fromProxy + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null"); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index da783982c..b3245ef81 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -25,7 +25,6 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -34,7 +33,6 @@ import lombok.RequiredArgsConstructor; */ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum DeviceOs { - @JsonEnumDefaultValue UNKNOWN("Unknown"), ANDROID("Android"), IOS("iOS"), @@ -57,6 +55,7 @@ public enum DeviceOs { /** * Get the DeviceOs instance from the identifier. + * * @param id the DeviceOs identifier * @return The DeviceOs or {@link #UNKNOWN} if the DeviceOs wasn't found */ diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java index 4dcaa8ab3..9664e18ae 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -26,10 +26,7 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; - public enum InputMode { - @JsonEnumDefaultValue UNKNOWN, KEYBOARD_MOUSE, TOUCH, // I guess Touch? @@ -40,6 +37,7 @@ public enum InputMode { /** * Get the InputMode instance from the identifier. + * * @param id the InputMode identifier * @return The InputMode or {@link #UNKNOWN} if the DeviceOs wasn't found */ diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 53a167f9b..c29d461d1 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -26,11 +26,14 @@ package org.geysermc.floodgate.util; +import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.util.UUID; @Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class LinkedPlayer { /** * The Java username of the linked player @@ -45,19 +48,17 @@ public final class LinkedPlayer { */ private final UUID bedrockId; /** - * If the LinkedPlayer is send from a different platform. - * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. + * If the LinkedPlayer is send from a different platform. For example the LinkedPlayer is from + * Bungee but the data has been sent to the Bukkit server. */ private boolean fromDifferentPlatform = false; - public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { - this.javaUsername = javaUsername; - this.javaUniqueId = javaUniqueId; - this.bedrockId = bedrockId; + public static LinkedPlayer of(String javaUsername, UUID javaUniqueId, UUID bedrockId) { + return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); } static LinkedPlayer fromString(String data) { - if (data.length() != 3) { + if (data.length() == 4) { return null; } diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index 2ff0e3fb2..152cbec5a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -31,31 +31,36 @@ import lombok.ToString; import java.nio.ByteBuffer; import java.util.Base64; +import static java.lang.String.format; + @AllArgsConstructor @ToString public final class RawSkin { public int width; public int height; public byte[] data; + public boolean alex; - private RawSkin() {} + private RawSkin() { + } public static RawSkin decode(byte[] data) throws InvalidFormatException { if (data == null) { return null; } - int maxEncodedLength = 4 * (((64 * 64 * 4 + 8) + 2) / 3); + int maxEncodedLength = 4 * (((64 * 64 * 4 + 9) + 2) / 3); // if the RawSkin is longer then the max Java Edition skin length if (data.length > maxEncodedLength) { - throw new InvalidFormatException( - "Encoded data cannot be longer then " + maxEncodedLength + " bytes!" - ); + throw new InvalidFormatException(format( + "Encoded data cannot be longer then %s bytes! Got %s", + maxEncodedLength, data.length + )); } - // if the encoded data doesn't even contain the width and height (8 bytes, 2 ints) - if (data.length < 4 * ((8 + 2) / 3)) { - throw new InvalidFormatException("Encoded data must be at least 12 bytes long!"); + // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex + if (data.length < 4 * ((9 + 2) / 3)) { + throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); } data = Base64.getDecoder().decode(data); @@ -65,23 +70,25 @@ public final class RawSkin { RawSkin skin = new RawSkin(); skin.width = buffer.getInt(); skin.height = buffer.getInt(); - if (buffer.remaining() != (skin.width * skin.height * 4)) { - throw new InvalidFormatException(String.format( + if (buffer.remaining() - 1 != (skin.width * skin.height * 4)) { + throw new InvalidFormatException(format( "Expected skin length to be %s, got %s", (skin.width * skin.height * 4), buffer.remaining() )); } - skin.data = new byte[buffer.remaining()]; + skin.data = new byte[buffer.remaining() - 1]; buffer.get(skin.data); + skin.alex = buffer.get() == 1; return skin; } public byte[] encode() { - // 2 x int = 8 bytes - ByteBuffer buffer = ByteBuffer.allocate(8 + data.length); + // 2 x int + 1 = 9 bytes + ByteBuffer buffer = ByteBuffer.allocate(9 + data.length); buffer.putInt(width); buffer.putInt(height); buffer.put(data); + buffer.put((byte) (alex ? 1 : 0)); return Base64.getEncoder().encode(buffer.array()); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java index 441e9202a..58d615eaa 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -26,17 +26,14 @@ package org.geysermc.floodgate.util; -import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; - public enum UiProfile { - @JsonEnumDefaultValue - CLASSIC, - POCKET; + CLASSIC, POCKET; private static final UiProfile[] VALUES = values(); /** * Get the UiProfile instance from the identifier. + * * @param id the UiProfile identifier * @return The UiProfile or {@link #CLASSIC} if the UiProfile wasn't found */ diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 1c35b23de..c44f8f434 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -229,7 +229,7 @@ public class GeyserConnector { for (GeyserSession session : players) { if (session == null) continue; if (session.getClientData() == null) continue; - String os = session.getClientData().getDeviceOS().toString(); + String os = session.getClientData().getDeviceOs().toString(); if (!valueMap.containsKey(os)) { valueMap.put(os, 1); } else { diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index b229e1670..d41ad64a3 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -73,7 +73,7 @@ public class DumpInfo { this.userPlatforms = new Object2IntOpenHashMap(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - DeviceOs device = session.getClientData().getDeviceOS(); + DeviceOs device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index ede3b78bb..630a5edd9 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -67,7 +67,7 @@ public class FireworkEntity extends Entity { // 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_ONE || session.getClientData().getDeviceOS() == DeviceOs.ORBIS) { + if (session.getClientData().getDeviceOs() == DeviceOs.XBOX_ONE || session.getClientData().getDeviceOs() == DeviceOs.ORBIS) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index bea234b50..99aada2a5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -402,11 +402,11 @@ public class GeyserSession implements CommandSender { try { FloodgateCipher cipher = connector.getCipher(); - encryptedData = cipher.encryptFromString(new BedrockData( + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.getName(), authData.getXboxUUID(), - clientData.getDeviceOS().ordinal(), + clientData.getDeviceOs().ordinal(), clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index ab0fad845..905245ee4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Charsets; import lombok.Getter; import org.geysermc.connector.utils.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; @@ -87,7 +88,7 @@ public final class BedrockClientData { @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") - private DeviceOs deviceOS; + private DeviceOs deviceOs; @JsonProperty(value = "UIProfile") private UiProfile uiProfile; @JsonProperty(value = "GuiScale") @@ -114,13 +115,7 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - public void setJsonData(JsonNode data) { - if (this.jsonData == null && data != null) { - this.jsonData = data; - } - } - - private static RawSkin getLegacyImage(byte[] imageData) { + private static RawSkin getLegacyImage(byte[] imageData, boolean alex) { if (imageData == null) { return null; } @@ -128,43 +123,54 @@ public final class BedrockClientData { // width * height * 4 (rgba) switch (imageData.length) { case 8192: - return new RawSkin(64, 32, imageData); + return new RawSkin(64, 32, imageData, alex); case 16384: - return new RawSkin(64, 64, imageData); + return new RawSkin(64, 64, imageData, alex); case 32768: - return new RawSkin(64, 128, imageData); + return new RawSkin(64, 128, imageData, alex); case 65536: - return new RawSkin(128, 128, imageData); + return new RawSkin(128, 128, imageData, alex); default: throw new IllegalArgumentException("Unknown legacy skin size"); } } + public void setJsonData(JsonNode data) { + if (this.jsonData == null && data != null) { + this.jsonData = data; + } + } + /** * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
* Internally only used for Skins, but can be used for Capes too */ public RawSkin getImage(String name) { - System.out.println(jsonData.toString()); if (jsonData == null || !jsonData.has(name + "Data")) { return null; } + boolean alex = false; + if (name.equals("Skin")) { + alex = isAlex(); + } + byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { return new RawSkin( jsonData.get(name + "ImageWidth").asInt(), jsonData.get(name + "ImageHeight").asInt(), - image + image, alex ); } - return getLegacyImage(image); + return getLegacyImage(image, alex); } public RawSkin getAndTransformImage(String name) { RawSkin skin = getImage(name); if (skin != null && (skin.width > 64 || skin.height > 64)) { - BufferedImage scaledImage = SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); + BufferedImage scaledImage = + SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); int max = Math.max(skin.width, skin.height); while (max > 64) { @@ -179,4 +185,35 @@ public final class BedrockClientData { } return skin; } + + public boolean isAlex() { + try { + byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); + String geometryName = + SkinProvider.OBJECT_MAPPER + .readTree(bytes) + .get("geometry").get("default") + .asText(); + return "geometry.humanoid.customSlim".equals(geometryName); + } catch (Exception exception) { + exception.printStackTrace(); + return false; + } + } + + public DeviceOs getDeviceOs() { + return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; + } + + public InputMode getCurrentInputMode() { + return currentInputMode != null ? currentInputMode : InputMode.UNKNOWN; + } + + public InputMode getDefaultInputMode() { + return defaultInputMode != null ? defaultInputMode : InputMode.UNKNOWN; + } + + public UiProfile getUiProfile() { + return uiProfile != null ? uiProfile : UiProfile.CLASSIC; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index a86c1a971..c0207841d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -34,6 +34,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePack import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -46,7 +47,6 @@ import java.util.List; @Translator(packet = ServerJoinGamePacket.class) public class JavaJoinGameTranslator extends PacketTranslator { - @Override public void translate(ServerJoinGamePacket packet, GeyserSession session) { PlayerEntity entity = session.getPlayerEntity(); @@ -96,6 +96,11 @@ public class JavaJoinGameTranslator extends PacketTranslator { - @Override public void translate(ServerStatisticsPacket packet, GeyserSession session) { session.updateStatistics(packet.getStatistics()); if (session.isWaitingForStatistics()) { session.setWaitingForStatistics(false); - session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID); + StatisticsUtils.buildAndSendStatisticsMenu(session); } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index 06d2936ef..fd25ce0a0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -60,7 +60,9 @@ public class LanguageUtils { public static void loadGeyserLocale(String locale) { locale = formatLocale(locale); // Don't load the locale if it's already loaded. - if (LOCALE_MAPPINGS.containsKey(locale)) return; + if (LOCALE_MAPPINGS.containsKey(locale)) { + return; + } InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties"); @@ -109,7 +111,7 @@ public class LanguageUtils { // Try and get the key from the default locale if (formatString == null) { - properties = LOCALE_MAPPINGS.get(formatLocale(getDefaultLocale())); + properties = LOCALE_MAPPINGS.get(getDefaultLocale()); formatString = properties.getProperty(key); } @@ -121,7 +123,7 @@ public class LanguageUtils { // Final fallback if (formatString == null) { - formatString = key; + return key; } return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values); @@ -147,7 +149,10 @@ public class LanguageUtils { * @return the current default locale */ public static String getDefaultLocale() { - if (CACHED_LOCALE != null) return CACHED_LOCALE; // We definitely know the locale the user is using + if (CACHED_LOCALE != null) { + return CACHED_LOCALE; // We definitely know the locale the user is using + } + String locale; boolean isValid = true; if (GeyserConnector.getInstance() != null && diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 391a02849..84997deb6 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -35,10 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.ModalForm; import org.geysermc.common.form.SimpleForm; import org.geysermc.common.form.response.CustomFormResponse; -import org.geysermc.common.form.response.ModalFormResponse; import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index eff9ee57b..4c7547d3a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,27 +25,47 @@ package org.geysermc.connector.utils; +import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; -import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; public class PluginMessageUtils { - private static final byte[] BRAND_DATA; + private static final byte[] GEYSER_BRAND_DATA; + private static final byte[] FLOODGATE_REGISTER_DATA; static { - byte[] data = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8); - byte[] varInt = writeVarInt(data.length); - BRAND_DATA = new byte[varInt.length + data.length]; - System.arraycopy(varInt, 0, BRAND_DATA, 0, varInt.length); - System.arraycopy(data, 0, BRAND_DATA, varInt.length, data.length); + byte[] data = GeyserConnector.NAME.getBytes(Charsets.UTF_8); + GEYSER_BRAND_DATA = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); + + data = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); + FLOODGATE_REGISTER_DATA = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); } /** * Get the prebuilt brand as a byte array + * * @return the brand information of the Geyser client */ public static byte[] getGeyserBrandData() { - return BRAND_DATA; + return GEYSER_BRAND_DATA; + } + + /** + * Get the prebuilt register data as a byte array + * + * @return the register data of the Floodgate channels + */ + public static byte[] getFloodgateRegisterData() { + return FLOODGATE_REGISTER_DATA; } private static byte[] writeVarInt(int value) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index b74ecefad..530c551ab 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -77,7 +77,7 @@ public class SkinProvider { public static String EARS_GEOMETRY; public static String EARS_GEOMETRY_SLIM; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { /* Load in the normal ears geometry */ @@ -525,7 +525,6 @@ public class SkinProvider { outputStream.write((rgba >> 24) & 0xFF); } } - return outputStream.toByteArray(); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 732217c24..41084504a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -28,196 +28,170 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.MagicValues; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.statistic.*; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -public class StatisticsUtils { //todo make Geyser compilable - - // Used in UpstreamPacketHandler.java - public static final int STATISTICS_MENU_FORM_ID = 1339; - public static final int STATISTICS_LIST_FORM_ID = 1340; +public class StatisticsUtils { + private static final Pattern CONTENT_PATTERN = Pattern.compile("^\\S+:", Pattern.MULTILINE); /** * Build a form for the given session with all statistic categories * * @param session The session to build the form for */ - public static SimpleFormWindow buildMenuForm(GeyserSession session) { + public static void buildAndSendStatisticsMenu(GeyserSession session) { // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + String language = session.getLocale(); - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.stats", language), ""); + session.sendForm( + SimpleForm.builder() + .translator(StatisticsUtils::translate, language) + .title("gui.stats") + .button("stat.generalButton") + .button("stat.itemsButton - stat_type.minecraft.mined") + .button("stat.itemsButton - stat_type.minecraft.broken") + .button("stat.itemsButton - stat_type.minecraft.crafted") + .button("stat.itemsButton - stat_type.minecraft.used") + .button("stat.itemsButton - stat_type.minecraft.picked_up") + .button("stat.itemsButton - stat_type.minecraft.dropped") + .button("stat.mobsButton - geyser.statistics.killed") + .button("stat.mobsButton - geyser.statistics.killed_by") + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.generalButton", language))); + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(StatisticsUtils::translate, language); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language))); + StringBuilder content = new StringBuilder(); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language))); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language))); + switch (response.getClickedButtonId()) { + case 0: + builder.title("stat.generalButton"); - return window; - } + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof GenericStatistic) { + String statName = ((GenericStatistic) entry.getKey()).name().toLowerCase(); + content.append("stat.minecraft.").append(statName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 1: + builder.title("stat.itemsButton - stat_type.minecraft.mined"); - /** - * Handle the menu form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleMenuForm(GeyserSession session, String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_MENU_FORM_ID); - menuForm.setResponse(response); - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakBlockStatistic) { + String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); + block = block.replace("minecraft:", "block.minecraft."); + content.append(block).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 2: + builder.title("stat.itemsButton - stat_type.minecraft.broken"); - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof BreakItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 3: + builder.title("stat.itemsButton - stat_type.minecraft.crafted"); - if (formResponse != null && formResponse.getClickedButton() != null) { - String title; - StringBuilder content = new StringBuilder(); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof CraftItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 4: + builder.title("stat.itemsButton - stat_type.minecraft.used"); - switch (formResponse.getClickedButtonId()) { - case 0: - title = LocaleUtils.getLocaleString("stat.generalButton", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof UseItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 5: + builder.title("stat.itemsButton - stat_type.minecraft.picked_up"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof GenericStatistic) { - content.append(LocaleUtils.getLocaleString("stat.minecraft." + ((GenericStatistic) entry.getKey()).name().toLowerCase(), language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 1: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.mined", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof PickupItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 6: + builder.title("stat.itemsButton - stat_type.minecraft.dropped"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakBlockStatistic) { - String block = BlockTranslator.JAVA_ID_TO_JAVA_IDENTIFIER_MAP.get(((BreakBlockStatistic) entry.getKey()).getId()); - block = block.replace("minecraft:", "block.minecraft."); - block = LocaleUtils.getLocaleString(block, language); - content.append(block + ": " + entry.getValue() + "\n"); - } - } - break; - case 2: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.broken", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof DropItemStatistic) { + String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); + content.append(getItemTranslateKey(item, language)).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 7: + builder.title("stat.mobsButton - geyser.statistics.killed"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof BreakItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((BreakItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 3: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.crafted", language); + for (Map.Entry entry : session.getStatistics().entrySet()) { + if (entry.getKey() instanceof KillEntityStatistic) { + String entityName = MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); + content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + case 8: + builder.title("stat.mobsButton - geyser.statistics.killed_by"); - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof CraftItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((CraftItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 4: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.used", language); + for (Map.Entry entry : session + .getStatistics().entrySet()) { + if (entry.getKey() instanceof KilledByEntityStatistic) { + String entityName = MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(); + content.append("entity.minecraft.").append(entityName).append(": ").append(entry.getValue()).append("\n"); + } + } + break; + default: + return; + } - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof UseItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((UseItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 5: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.picked_up", language); + if (content.length() == 0) { + content = new StringBuilder("geyser.statistics.none"); + } - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof PickupItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((PickupItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 6: - title = LocaleUtils.getLocaleString("stat.itemsButton", language) + " - " + LocaleUtils.getLocaleString("stat_type.minecraft.dropped", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof DropItemStatistic) { - String item = ItemRegistry.ITEM_ENTRIES.get(((DropItemStatistic) entry.getKey()).getId()).getJavaIdentifier(); - content.append(getItemTranslateKey(item, language) + ": " + entry.getValue() + "\n"); - } - } - break; - case 7: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KillEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KillEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - case 8: - title = LocaleUtils.getLocaleString("stat.mobsButton", language) + " - " + LanguageUtils.getPlayerLocaleString("geyser.statistics.killed_by", language); - - for (Map.Entry entry : session.getStatistics().entrySet()) { - if (entry.getKey() instanceof KilledByEntityStatistic) { - String mob = LocaleUtils.getLocaleString("entity.minecraft." + MagicValues.key(EntityType.class, ((KilledByEntityStatistic) entry.getKey()).getId()).name().toLowerCase(), language); - content.append(mob + ": " + entry.getValue() + "\n"); - } - } - break; - default: - return false; - } - - if (content.length() == 0) { - content = new StringBuilder(LanguageUtils.getPlayerLocaleString("geyser.statistics.none", language)); - } - - SimpleFormWindow window = new SimpleFormWindow(title, content.toString()); - window.getButtons().add(new FormButton(LocaleUtils.getLocaleString("gui.back", language))); - session.sendForm(window, STATISTICS_LIST_FORM_ID); - } - - return true; - } - - /** - * Handle the list form response - * - * @param session The session that sent the response - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public static boolean handleListForm(GeyserSession session, String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(STATISTICS_LIST_FORM_ID); - listForm.setResponse(response); - - if (!listForm.isClosed()) { - session.sendForm(buildMenuForm(session), STATISTICS_MENU_FORM_ID); - } - - return true; + session.sendForm( + builder.content(content.toString()) + .button("gui.back") + .responseHandler((form1, responseData1) -> { + SimpleFormResponse response1 = form.parseResponse(responseData1); + if (response1.isCorrect()) { + buildAndSendStatisticsMenu(session); + } + })); + })); } /** * Finds the item translation key from the Java locale. - * - * @param item the namespaced item to search for. + * + * @param item the namespaced item to search for. * @param language the language to search in * @return the full name of the item */ @@ -230,4 +204,31 @@ public class StatisticsUtils { //todo make Geyser compilable } return translatedItem; } + + private static String translate(String keys, String locale) { + Matcher matcher = CONTENT_PATTERN.matcher(keys); + + StringBuffer buffer = new StringBuffer(); + while (matcher.find()) { + String group = matcher.group(); + matcher.appendReplacement(buffer, translateEntry(group.substring(0, group.length() - 1), locale) + ":"); + } + + if (buffer.length() != 0) { + return matcher.appendTail(buffer).toString(); + } + + String[] keySplitted = keys.split(" - "); + for (int i = 0; i < keySplitted.length; i++) { + keySplitted[i] = translateEntry(keySplitted[i], locale); + } + return String.join(" - ", keySplitted); + } + + private static String translateEntry(String key, String locale) { + if (key.startsWith("geyser.")) { + return LanguageUtils.getPlayerLocaleString(key, locale); + } + return LocaleUtils.getLocaleString(key, locale); + } } From 8b811b43fb1315b0c43cedd044897a300beee486 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 21 Nov 2020 02:48:15 +0100 Subject: [PATCH 12/51] Fixed mistake in LinkedPlayer --- .../main/java/org/geysermc/floodgate/util/LinkedPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index c29d461d1..1e8d67c27 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -58,11 +58,11 @@ public final class LinkedPlayer { } static LinkedPlayer fromString(String data) { - if (data.length() == 4) { + String[] split = data.split(";"); + if (split.length != 3) { return null; } - String[] split = data.split(";"); LinkedPlayer player = new LinkedPlayer( split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) ); From e583abffdf6cf692a51a579fd6051321af5408db Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 26 Nov 2020 23:00:43 +0100 Subject: [PATCH 13/51] Split Forms into an Api and an implementation --- .../org/geysermc/common/PlatformType.java | 1 - .../org/geysermc/common/form/CustomForm.java | 151 +++---------- .../java/org/geysermc/common/form/Form.java | 156 ++++--------- .../org/geysermc/common/form/FormBuilder.java | 44 ++++ .../org/geysermc/common/form/FormImage.java | 62 ++++++ .../org/geysermc/common/form/FormType.java | 51 +++++ .../java/org/geysermc/common/form/Forms.java | 46 ++++ .../org/geysermc/common/form/ModalForm.java | 76 +------ .../org/geysermc/common/form/SimpleForm.java | 90 ++------ .../form/component/ButtonComponent.java | 27 +-- .../common/form/component/Component.java | 51 +---- .../common/form/component/ComponentType.java | 61 ++++++ .../form/component/DropdownComponent.java | 71 ++---- .../common/form/component/InputComponent.java | 30 +-- .../common/form/component/LabelComponent.java | 13 +- .../form/component/SliderComponent.java | 53 ++--- .../form/component/StepSliderComponent.java | 85 ++----- .../form/component/ToggleComponent.java | 22 +- .../common/form/impl/CustomFormImpl.java | 179 +++++++++++++++ .../geysermc/common/form/impl/FormImpl.java | 121 ++++++++++ .../common/form/impl/ModalFormImpl.java | 105 +++++++++ .../common/form/impl/SimpleFormImpl.java | 120 ++++++++++ .../impl/component/ButtonComponentImpl.java | 52 +++++ .../common/form/impl/component/Component.java | 45 ++++ .../impl/component/DropdownComponentImpl.java | 102 +++++++++ .../impl/component/InputComponentImpl.java | 56 +++++ .../impl/component/LabelComponentImpl.java | 41 ++++ .../impl/component/SliderComponentImpl.java | 76 +++++++ .../component/StepSliderComponentImpl.java | 118 ++++++++++ .../impl/component/ToggleComponentImpl.java | 52 +++++ .../impl/response/CustomFormResponseImpl.java | 207 ++++++++++++++++++ .../impl/response/ModalFormResponseImpl.java | 62 ++++++ .../impl/response/SimpleFormResponseImpl.java | 62 ++++++ .../form/{ => impl}/util/FormAdaptor.java | 51 +++-- .../util/FormImageImpl.java} | 28 +-- .../form/response/CustomFormResponse.java | 163 ++------------ .../form/response/ModalFormResponse.java | 34 +-- .../form/response/SimpleFormResponse.java | 27 +-- .../network/session/GeyserSession.java | 7 +- .../network/session/cache/FormCache.java | 20 +- .../BedrockNetworkStackLatencyTranslator.java | 31 ++- .../java/JavaPluginMessageTranslator.java | 6 +- .../connector/utils/SettingsUtils.java | 2 +- 43 files changed, 1965 insertions(+), 892 deletions(-) create mode 100644 common/src/main/java/org/geysermc/common/form/FormBuilder.java create mode 100644 common/src/main/java/org/geysermc/common/form/FormImage.java create mode 100644 common/src/main/java/org/geysermc/common/form/FormType.java create mode 100644 common/src/main/java/org/geysermc/common/form/Forms.java create mode 100644 common/src/main/java/org/geysermc/common/form/component/ComponentType.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/FormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/Component.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java create mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java rename common/src/main/java/org/geysermc/common/form/{ => impl}/util/FormAdaptor.java (71%) rename common/src/main/java/org/geysermc/common/form/{util/FormImage.java => impl/util/FormImageImpl.java} (72%) diff --git a/common/src/main/java/org/geysermc/common/PlatformType.java b/common/src/main/java/org/geysermc/common/PlatformType.java index 883490208..e3770f299 100644 --- a/common/src/main/java/org/geysermc/common/PlatformType.java +++ b/common/src/main/java/org/geysermc/common/PlatformType.java @@ -31,7 +31,6 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum PlatformType { - ANDROID("Android"), BUNGEECORD("BungeeCord"), FABRIC("Fabric"), diff --git a/common/src/main/java/org/geysermc/common/form/CustomForm.java b/common/src/main/java/org/geysermc/common/form/CustomForm.java index 5e8d93252..41097b60e 100644 --- a/common/src/main/java/org/geysermc/common/form/CustomForm.java +++ b/common/src/main/java/org/geysermc/common/form/CustomForm.java @@ -25,153 +25,62 @@ package org.geysermc.common.form; -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.component.*; +import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.DropdownComponent; +import org.geysermc.common.form.component.StepSliderComponent; +import org.geysermc.common.form.impl.CustomFormImpl; import org.geysermc.common.form.response.CustomFormResponse; -import org.geysermc.common.form.util.FormAdaptor; -import org.geysermc.common.form.util.FormImage; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -@Getter -@JsonAdapter(FormAdaptor.class) -public final class CustomForm extends Form { - private final String title; - private final FormImage icon; - private final List content; - - private CustomForm(String title, FormImage icon, List content) { - super(Form.Type.CUSTOM_FORM); - - this.title = title; - this.icon = icon; - this.content = Collections.unmodifiableList(content); +public interface CustomForm extends Form { + static CustomForm.Builder builder() { + return new CustomFormImpl.Builder(); } - public static Builder builder() { - return new Builder(); + static CustomForm of(String title, FormImage icon, List content) { + return CustomFormImpl.of(title, icon, content); } - public static CustomForm of(String title, FormImage icon, List content) { - return new CustomForm(title, icon, content); - } + interface Builder extends FormBuilder { + Builder icon(FormImage.Type type, String data); - public CustomFormResponse parseResponse(String data) { - if (isClosed(data)) { - return CustomFormResponse.closed(); - } - return CustomFormResponse.of(this, data); - } + Builder iconPath(String path); - public static final class Builder extends Form.Builder { - private final List components = new ArrayList<>(); - private FormImage icon; + Builder iconUrl(String url); - public Builder icon(FormImage.Type type, String data) { - icon = FormImage.of(type, data); - return this; - } + Builder component(Component component); - public Builder iconPath(String path) { - return icon(FormImage.Type.PATH, path); - } + Builder dropdown(DropdownComponent.Builder dropdownBuilder); - public Builder iconUrl(String url) { - return icon(FormImage.Type.URL, url); - } + Builder dropdown(String text, int defaultOption, String... options); - public Builder component(Component component) { - components.add(component); - return this; - } + Builder dropdown(String text, String... options); - public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { - return component(dropdownBuilder.translateAndBuild(this::translate)); - } + Builder input(String text, String placeholder, String defaultText); - public Builder dropdown(String text, int defaultOption, String... options) { - List optionsList = new ArrayList<>(); - for (String option : options) { - optionsList.add(translate(option)); - } - return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); - } + Builder input(String text, String placeholder); - public Builder dropdown(String text, String... options) { - return dropdown(text, -1, options); - } + Builder input(String text); - public Builder input(String text, String placeholder, String defaultText) { - return component(InputComponent.of( - translate(text), translate(placeholder), translate(defaultText) - )); - } + Builder label(String text); - public Builder input(String text, String placeholder) { - return component(InputComponent.of(translate(text), translate(placeholder))); - } + Builder slider(String text, float min, float max, int step, float defaultValue); - public Builder input(String text) { - return component(InputComponent.of(translate(text))); - } + Builder slider(String text, float min, float max, int step); - public Builder label(String text) { - return component(LabelComponent.of(translate(text))); - } + Builder slider(String text, float min, float max, float defaultValue); - public Builder slider(String text, float min, float max, int step, float defaultValue) { - return component(SliderComponent.of(text, min, max, step, defaultValue)); - } + Builder slider(String text, float min, float max); - public Builder slider(String text, float min, float max, int step) { - return slider(text, min, max, step, -1); - } + Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder); - public Builder slider(String text, float min, float max, float defaultValue) { - return slider(text, min, max, -1, defaultValue); - } + Builder stepSlider(String text, int defaultStep, String... steps); - public Builder slider(String text, float min, float max) { - return slider(text, min, max, -1, -1); - } + Builder stepSlider(String text, String... steps); - public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { - return component(stepSliderBuilder.translateAndBuild(this::translate)); - } + Builder toggle(String text, boolean defaultValue); - public Builder stepSlider(String text, int defaultStep, String... steps) { - List stepsList = new ArrayList<>(); - for (String option : steps) { - stepsList.add(translate(option)); - } - return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); - } - - public Builder stepSlider(String text, String... steps) { - return stepSlider(text, -1, steps); - } - - public Builder toggle(String text, boolean defaultValue) { - return component(ToggleComponent.of(translate(text), defaultValue)); - } - - public Builder toggle(String text) { - return component(ToggleComponent.of(translate(text))); - } - - @Override - public CustomForm build() { - CustomForm form = of(title, icon, components); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } + Builder toggle(String text); } } diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java index 5924e6896..6fd1cbc2d 100644 --- a/common/src/main/java/org/geysermc/common/form/Form.java +++ b/common/src/main/java/org/geysermc/common/form/Form.java @@ -25,123 +25,59 @@ package org.geysermc.common.form; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.annotations.SerializedName; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; import org.geysermc.common.form.response.FormResponse; -import org.geysermc.common.form.util.FormAdaptor; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Consumer; -@Getter -public abstract class Form { - protected static final Gson GSON = - new GsonBuilder() - .registerTypeAdapter(ModalForm.class, new FormAdaptor()) - .create(); +/** + * Base class of all Forms. While it can be used it doesn't contain every data you could get when + * using the specific class of the form type. + * + * @param class provided by the specific form type. It understands the response data and makes + * the data easily accessible + */ +public interface Form { + /** + * Returns the form type of this specific instance. The valid form types can be found {@link + * FormType in the FormType class} + */ + FormType getType(); - private final Type type; + /** + * Returns the data that will be sent by Geyser to the Bedrock client + */ + String getJsonData(); - @Getter(AccessLevel.NONE) - protected String hardcodedJsonData = null; + /** + * Returns the handler that will be invoked once the form got a response from the Bedrock + * client + */ + Consumer getResponseHandler(); - @Setter protected Consumer responseHandler; + /** + * Sets the handler that will be invoked once the form got a response from the Bedrock client. + * This handler contains the raw data sent by the Bedrock client. See {@link + * #parseResponse(String)} if you want to turn the given data into something that's easier to + * handle. + * + * @param responseHandler the response handler + */ + void setResponseHandler(Consumer responseHandler); - public Form(Type type) { - this.type = type; - } + /** + * Parses the method into something provided by the form implementation, which will make the + * data given by the Bedrock client easier to handle. + * + * @param response the raw data given by the Bedrock client + * @return the data in an easy-to-handle class + */ + T parseResponse(String response); - public static T fromJson(String json, Class formClass) { - return GSON.fromJson(json, formClass); - } - - public String getJsonData() { - if (hardcodedJsonData != null) { - return hardcodedJsonData; - } - return GSON.toJson(this); - } - - public abstract FormResponse parseResponse(String response); - - @SuppressWarnings("unchecked") - public T parseResponseAs(String response) { - return (T) parseResponse(response); - } - - public boolean isClosed(String response) { - return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); - } - - @Getter - @RequiredArgsConstructor - public enum Type { - @SerializedName("form") - SIMPLE_FORM(SimpleForm.class), - @SerializedName("modal") - MODAL_FORM(ModalForm.class), - @SerializedName("custom_form") - CUSTOM_FORM(CustomForm.class); - - private static final Type[] VALUES = values(); - private final Class typeClass; - - public static Type getByOrdinal(int ordinal) { - return ordinal < VALUES.length ? VALUES[ordinal] : null; - } - } - - public static abstract class Builder, F extends Form> { - protected String title = ""; - - protected BiFunction translationHandler = null; - protected BiConsumer biResponseHandler; - protected Consumer responseHandler; - protected String locale; - - public T title(String title) { - this.title = translate(title); - return self(); - } - - public T translator(BiFunction translator, String locale) { - this.translationHandler = translator; - this.locale = locale; - return title(title); - } - - public T translator(BiFunction translator) { - return translator(translator, locale); - } - - public T responseHandler(BiConsumer responseHandler) { - biResponseHandler = responseHandler; - return self(); - } - - public T responseHandler(Consumer responseHandler) { - this.responseHandler = responseHandler; - return self(); - } - - public abstract F build(); - - protected String translate(String text) { - if (translationHandler != null && text != null && !text.isEmpty()) { - return translationHandler.apply(text, locale); - } - return text; - } - - @SuppressWarnings("unchecked") - protected T self() { - return (T) this; - } - } + /** + * Checks if the given data by the Bedrock client is saying that the client closed the form. + * + * @param response the raw data given by the Bedrock client + * @return true if the raw data implies that the Bedrock client closed the form + */ + boolean isClosed(String response); } diff --git a/common/src/main/java/org/geysermc/common/form/FormBuilder.java b/common/src/main/java/org/geysermc/common/form/FormBuilder.java new file mode 100644 index 000000000..f5a1ce76d --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/FormBuilder.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +public interface FormBuilder, F extends Form> { + T title(String title); + + T translator(BiFunction translator, String locale); + + T translator(BiFunction translator); + + T responseHandler(BiConsumer responseHandler); + + T responseHandler(Consumer responseHandler); + + F build(); +} diff --git a/common/src/main/java/org/geysermc/common/form/FormImage.java b/common/src/main/java/org/geysermc/common/form/FormImage.java new file mode 100644 index 000000000..f5672923f --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/FormImage.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.SerializedName; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.impl.util.FormImageImpl; + +public interface FormImage { + static FormImage of(Type type, String data) { + return FormImageImpl.of(type, data); + } + + static FormImage of(String type, String data) { + return FormImageImpl.of(type, data); + } + + Type getType(); + + String getData(); + + @RequiredArgsConstructor + enum Type { + @SerializedName("path") PATH, + @SerializedName("url") URL; + + private static final Type[] VALUES = values(); + + public static Type getByName(String name) { + String upper = name.toUpperCase(); + for (Type value : VALUES) { + if (value.name().equals(upper)) { + return value; + } + } + return null; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/FormType.java b/common/src/main/java/org/geysermc/common/form/FormType.java new file mode 100644 index 000000000..064dec4b3 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/FormType.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.impl.CustomFormImpl; +import org.geysermc.common.form.impl.ModalFormImpl; +import org.geysermc.common.form.impl.SimpleFormImpl; + +@Getter +@RequiredArgsConstructor +public enum FormType { + @SerializedName("form") + SIMPLE_FORM(SimpleFormImpl.class), + @SerializedName("modal") + MODAL_FORM(ModalFormImpl.class), + @SerializedName("custom_form") + CUSTOM_FORM(CustomFormImpl.class); + + private static final FormType[] VALUES = values(); + private final Class typeClass; + + public static FormType getByOrdinal(int ordinal) { + return ordinal < VALUES.length ? VALUES[ordinal] : null; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/Forms.java b/common/src/main/java/org/geysermc/common/form/Forms.java new file mode 100644 index 000000000..1a1c0a4cb --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/Forms.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.geysermc.common.form.impl.FormImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; + +public final class Forms { + private static final Gson GSON = + new GsonBuilder() + .registerTypeAdapter(FormImpl.class, new FormAdaptor()) + .create(); + + public static Gson getGson() { + return GSON; + } + + public static > T fromJson(String json, Class formClass) { + return GSON.fromJson(json, formClass); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java index ea58a466e..8e35bc8b0 100644 --- a/common/src/main/java/org/geysermc/common/form/ModalForm.java +++ b/common/src/main/java/org/geysermc/common/form/ModalForm.java @@ -25,79 +25,23 @@ package org.geysermc.common.form; -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; +import org.geysermc.common.form.impl.ModalFormImpl; import org.geysermc.common.form.response.ModalFormResponse; -import org.geysermc.common.form.util.FormAdaptor; -@Getter -@JsonAdapter(FormAdaptor.class) -public class ModalForm extends Form { - private final String title; - private final String content; - private final String button1; - private final String button2; - - private ModalForm(String title, String content, String button1, String button2) { - super(Type.MODAL_FORM); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; +public interface ModalForm extends Form { + static Builder builder() { + return new ModalFormImpl.Builder(); } - public static Builder builder() { - return new Builder(); + static ModalForm of(String title, String content, String button1, String button2) { + return ModalFormImpl.of(title, content, button1, button2); } - public static ModalForm of(String title, String content, String button1, String button2) { - return new ModalForm(title, content, button1, button2); - } + interface Builder extends FormBuilder { + Builder content(String content); - public ModalFormResponse parseResponse(String data) { - if (isClosed(data)) { - return ModalFormResponse.closed(); - } + Builder button1(String button1); - if ("true".equals(data)) { - return ModalFormResponse.of(0, button1); - } else if ("false".equals(data)) { - return ModalFormResponse.of(1, button2); - } - return ModalFormResponse.invalid(); - } - - public static final class Builder extends Form.Builder { - private String content = ""; - private String button1 = ""; - private String button2 = ""; - - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button1(String button1) { - this.button1 = translate(button1); - return this; - } - - public Builder button2(String button2) { - this.button2 = translate(button2); - return this; - } - - @Override - public ModalForm build() { - ModalForm form = of(title, content, button1, button2); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } + Builder button2(String button2); } } diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java index 275fb8a86..dd11544b9 100644 --- a/common/src/main/java/org/geysermc/common/form/SimpleForm.java +++ b/common/src/main/java/org/geysermc/common/form/SimpleForm.java @@ -25,93 +25,37 @@ package org.geysermc.common.form; -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.impl.SimpleFormImpl; import org.geysermc.common.form.response.SimpleFormResponse; -import org.geysermc.common.form.util.FormAdaptor; -import org.geysermc.common.form.util.FormImage; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -@Getter -@JsonAdapter(FormAdaptor.class) -public final class SimpleForm extends Form { - private final String title; - private final String content; - private final List buttons; - - private SimpleForm(String title, String content, List buttons) { - super(Type.SIMPLE_FORM); - - this.title = title; - this.content = content; - this.buttons = Collections.unmodifiableList(buttons); +/** + * + */ +public interface SimpleForm extends Form { + static Builder builder() { + return new SimpleFormImpl.Builder(); } - public static Builder builder() { - return new Builder(); + static SimpleForm of(String title, String content, List buttons) { + return SimpleFormImpl.of(title, content, buttons); } - public static SimpleForm of(String title, String content, List buttons) { - return new SimpleForm(title, content, buttons); - } + String getTitle(); - public SimpleFormResponse parseResponse(String data) { - if (isClosed(data)) { - return SimpleFormResponse.closed(); - } + String getContent(); - int buttonId; - try { - buttonId = Integer.parseInt(data); - } catch (Exception exception) { - return SimpleFormResponse.invalid(); - } + List getButtons(); - if (buttonId >= buttons.size()) { - return SimpleFormResponse.invalid(); - } + interface Builder extends FormBuilder { + Builder content(String content); - return SimpleFormResponse.of(buttonId, buttons.get(buttonId)); - } + Builder button(String text, FormImage.Type type, String data); - public static final class Builder extends Form.Builder { - private final List buttons = new ArrayList<>(); - private String content = ""; + Builder button(String text, FormImage image); - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button(String text, FormImage.Type type, String data) { - buttons.add(ButtonComponent.of(translate(text), type, data)); - return this; - } - - public Builder button(String text, FormImage image) { - buttons.add(ButtonComponent.of(translate(text), image)); - return this; - } - - public Builder button(String text) { - buttons.add(ButtonComponent.of(translate(text))); - return this; - } - - @Override - public SimpleForm build() { - SimpleForm form = of(title, content, buttons); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } + Builder button(String text); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java index de241e3dd..3e8f5d438 100644 --- a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java @@ -25,26 +25,23 @@ package org.geysermc.common.form.component; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.util.FormImage; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.impl.component.ButtonComponentImpl; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ButtonComponent { - private final String text; - private final FormImage image; - - public static ButtonComponent of(String text, FormImage image) { - return new ButtonComponent(text, image); +public interface ButtonComponent { + static ButtonComponent of(String text, FormImage image) { + return ButtonComponentImpl.of(text, image); } - public static ButtonComponent of(String text, FormImage.Type type, String data) { - return of(text, FormImage.of(type, data)); + static ButtonComponent of(String text, FormImage.Type type, String data) { + return ButtonComponentImpl.of(text, type, data); } - public static ButtonComponent of(String text) { + static ButtonComponent of(String text) { return of(text, null); } + + String getText(); + + FormImage getImage(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/Component.java b/common/src/main/java/org/geysermc/common/form/component/Component.java index 8f44a2a0a..524d2a1a9 100644 --- a/common/src/main/java/org/geysermc/common/form/component/Component.java +++ b/common/src/main/java/org/geysermc/common/form/component/Component.java @@ -25,53 +25,8 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +public interface Component { + ComponentType getType(); -import java.util.Objects; - -@Getter -public abstract class Component { - private final Type type; - private final String text; - - Component(Type type, String text) { - Objects.requireNonNull(type, "Type cannot be null"); - Objects.requireNonNull(text, "Text cannot be null"); - - this.type = type; - this.text = text; - } - - @Getter - @RequiredArgsConstructor - public enum Type { - @SerializedName("dropdown") - DROPDOWN(DropdownComponent.class), - @SerializedName("input") - INPUT(InputComponent.class), - @SerializedName("label") - LABEL(LabelComponent.class), - @SerializedName("slider") - SLIDER(SliderComponent.class), - @SerializedName("step_slider") - STEP_SLIDER(StepSliderComponent.class), - @SerializedName("toggle") - TOGGLE(ToggleComponent.class); - - private static final Type[] VALUES = values(); - - private final String name = name().toLowerCase(); - private final Class componentClass; - - public static Type getByName(String name) { - for (Type type : VALUES) { - if (type.name.equals(name)) { - return type; - } - } - return null; - } - } + String getText(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/ComponentType.java b/common/src/main/java/org/geysermc/common/form/component/ComponentType.java new file mode 100644 index 000000000..898e10d07 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/component/ComponentType.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ComponentType { + @SerializedName("dropdown") + DROPDOWN(DropdownComponent.class), + @SerializedName("input") + INPUT(InputComponent.class), + @SerializedName("label") + LABEL(LabelComponent.class), + @SerializedName("slider") + SLIDER(SliderComponent.class), + @SerializedName("step_slider") + STEP_SLIDER(StepSliderComponent.class), + @SerializedName("toggle") + TOGGLE(ToggleComponent.class); + + private static final ComponentType[] VALUES = values(); + + private final String name = name().toLowerCase(); + private final Class componentClass; + + public static ComponentType getByName(String name) { + for (ComponentType type : VALUES) { + if (type.name.equals(name)) { + return type; + } + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java index 6bf3f5e1a..a68b3b1e2 100644 --- a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java @@ -25,78 +25,39 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.DropdownComponentImpl; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Function; -@Getter -public class DropdownComponent extends Component { - private final List options; - @SerializedName("default") - private final int defaultOption; - - private DropdownComponent(String text, List options, int defaultOption) { - super(Type.DROPDOWN, text); - this.options = Collections.unmodifiableList(options); - this.defaultOption = defaultOption; +public interface DropdownComponent extends Component { + static DropdownComponent of(String text, List options, int defaultOption) { + return DropdownComponentImpl.of(text, options, defaultOption); } - public static DropdownComponent of(String text, List options, int defaultOption) { - if (defaultOption == -1 || defaultOption >= options.size()) { - defaultOption = 0; - } - return new DropdownComponent(text, options, defaultOption); + static Builder builder() { + return new DropdownComponentImpl.Builder(); } - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(String text) { + static Builder builder(String text) { return builder().text(text); } - public static class Builder { - private final List options = new ArrayList<>(); - private String text; - private int defaultOption = 0; + List getOptions(); - public Builder text(String text) { - this.text = text; - return this; - } + int getDefaultOption(); - public Builder option(String option, boolean isDefault) { - options.add(option); - if (isDefault) { - defaultOption = options.size() - 1; - } - return this; - } + interface Builder { + Builder text(String text); - public Builder option(String option) { - return option(option, false); - } + Builder option(String option, boolean isDefault); - public Builder defaultOption(int defaultOption) { - this.defaultOption = defaultOption; - return this; - } + Builder option(String option); - public DropdownComponent build() { - return of(text, options, defaultOption); - } + Builder defaultOption(int defaultOption); - public DropdownComponent translateAndBuild(Function translator) { - for (int i = 0; i < options.size(); i++) { - options.set(i, translator.apply(options.get(i))); - } + DropdownComponent build(); - return of(translator.apply(text), options, defaultOption); - } + DropdownComponent translateAndBuild(Function translator); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java index b5e07535f..4f054957c 100644 --- a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java @@ -25,30 +25,22 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.InputComponentImpl; -@Getter -public class InputComponent extends Component { - private final String placeholder; - @SerializedName("default") - private final String defaultText; - - private InputComponent(String text, String placeholder, String defaultText) { - super(Type.INPUT, text); - this.placeholder = placeholder; - this.defaultText = defaultText; +public interface InputComponent extends Component { + static InputComponent of(String text, String placeholder, String defaultText) { + return InputComponentImpl.of(text, placeholder, defaultText); } - public static InputComponent of(String text, String placeholder, String defaultText) { - return new InputComponent(text, placeholder, defaultText); + static InputComponent of(String text, String placeholder) { + return InputComponentImpl.of(text, placeholder, ""); } - public static InputComponent of(String text, String placeholder) { - return new InputComponent(text, placeholder, ""); + static InputComponent of(String text) { + return InputComponentImpl.of(text, "", ""); } - public static InputComponent of(String text) { - return new InputComponent(text, "", ""); - } + String getPlaceholder(); + + String getDefaultText(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java index 410f14a25..ed6f47029 100644 --- a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java @@ -25,15 +25,10 @@ package org.geysermc.common.form.component; -import lombok.Getter; +import org.geysermc.common.form.impl.component.LabelComponentImpl; -@Getter -public class LabelComponent extends Component { - private LabelComponent(String text) { - super(Type.LABEL, text); - } - - public static LabelComponent of(String text) { - return new LabelComponent(text); +public interface LabelComponent extends Component { + static LabelComponent of(String text) { + return LabelComponentImpl.of(text); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java index 0eb019e3a..9a0e3343e 100644 --- a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java @@ -25,49 +25,30 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.SliderComponentImpl; -@Getter -public final class SliderComponent extends Component { - private final float min; - private final float max; - private final int step; - @SerializedName("default") - private final float defaultValue; - - private SliderComponent(String text, float min, float max, int step, float defaultValue) { - super(Type.SLIDER, text); - this.min = min; - this.max = max; - this.step = step; - this.defaultValue = defaultValue; +public interface SliderComponent extends Component { + static SliderComponent of(String text, float min, float max, int step, float defaultValue) { + return SliderComponentImpl.of(text, min, max, step, defaultValue); } - public static SliderComponent of(String text, float min, float max, int step, float defaultValue) { - min = Math.max(min, 0f); - max = Math.max(max, min); - - if (step < 1) { - step = 1; - } - - if (defaultValue == -1f) { - defaultValue = (int) Math.floor(min + max / 2D); - } - - return new SliderComponent(text, min, max, step, defaultValue); + static SliderComponent of(String text, float min, float max, int step) { + return SliderComponentImpl.of(text, min, max, step); } - public static SliderComponent of(String text, float min, float max, int step) { - return of(text, min, max, step, -1); + static SliderComponent of(String text, float min, float max, float defaultValue) { + return SliderComponentImpl.of(text, min, max, defaultValue); } - public static SliderComponent of(String text, float min, float max, float defaultValue) { - return of(text, min, max, -1, defaultValue); + static SliderComponent of(String text, float min, float max) { + return SliderComponentImpl.of(text, min, max); } - public static SliderComponent of(String text, float min, float max) { - return of(text, min, max, -1, -1); - } + float getMin(); + + float getMax(); + + int getStep(); + + float getDefaultValue(); } diff --git a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java index 4d6d8fcb2..cda4e7acb 100644 --- a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java @@ -25,92 +25,47 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.StepSliderComponentImpl; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.function.Function; -@Getter -public final class StepSliderComponent extends Component { - private final List steps; - @SerializedName("default") - private final int defaultStep; - - private StepSliderComponent(String text, List steps, int defaultStep) { - super(Type.STEP_SLIDER, text); - this.steps = Collections.unmodifiableList(steps); - this.defaultStep = defaultStep; +public interface StepSliderComponent extends Component { + static StepSliderComponent of(String text, List steps, int defaultStep) { + return StepSliderComponentImpl.of(text, steps, defaultStep); } - public static StepSliderComponent of(String text, List steps, int defaultStep) { - if (text == null) { - text = ""; - } - - if (defaultStep >= steps.size() || defaultStep == -1) { - defaultStep = 0; - } - - return new StepSliderComponent(text, steps, defaultStep); + static StepSliderComponent of(String text, int defaultStep, String... steps) { + return StepSliderComponentImpl.of(text, defaultStep, steps); } - public static StepSliderComponent of(String text, int defaultStep, String... steps) { - return of(text, Arrays.asList(steps), defaultStep); + static StepSliderComponent of(String text, String... steps) { + return StepSliderComponentImpl.of(text, steps); } - public static StepSliderComponent of(String text, String... steps) { - return of(text, 0, steps); + static Builder builder() { + return new StepSliderComponentImpl.Builder(); } - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(String text) { + static Builder builder(String text) { return builder().text(text); } - public static final class Builder { - private final List steps = new ArrayList<>(); - private String text; - private int defaultStep; + List getSteps(); - public Builder text(String text) { - this.text = text; - return this; - } + int getDefaultStep(); - public Builder step(String step, boolean defaultStep) { - steps.add(step); - if (defaultStep) { - this.defaultStep = steps.size() - 1; - } - return this; - } + interface Builder { + Builder text(String text); - public Builder step(String step) { - return step(step, false); - } + Builder step(String step, boolean defaultStep); - public Builder defaultStep(int defaultStep) { - this.defaultStep = defaultStep; - return this; - } + Builder step(String step); - public StepSliderComponent build() { - return of(text, steps, defaultStep); - } + Builder defaultStep(int defaultStep); - public StepSliderComponent translateAndBuild(Function translator) { - for (int i = 0; i < steps.size(); i++) { - steps.set(i, translator.apply(steps.get(i))); - } + StepSliderComponent build(); - return of(translator.apply(text), steps, defaultStep); - } + StepSliderComponent translateAndBuild(Function translator); } } diff --git a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java index 08341aaca..e505e995b 100644 --- a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java +++ b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java @@ -25,24 +25,16 @@ package org.geysermc.common.form.component; -import com.google.gson.annotations.SerializedName; -import lombok.Getter; +import org.geysermc.common.form.impl.component.ToggleComponentImpl; -@Getter -public class ToggleComponent extends Component { - @SerializedName("default") - private final boolean defaultValue; - - private ToggleComponent(String text, boolean defaultValue) { - super(Type.TOGGLE, text); - this.defaultValue = defaultValue; +public interface ToggleComponent extends Component { + static ToggleComponent of(String text, boolean defaultValue) { + return ToggleComponentImpl.of(text, defaultValue); } - public static ToggleComponent of(String text, boolean defaultValue) { - return new ToggleComponent(text, defaultValue); + static ToggleComponent of(String text) { + return ToggleComponentImpl.of(text); } - public static ToggleComponent of(String text) { - return of(text, false); - } + boolean getDefaultValue(); } diff --git a/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java new file mode 100644 index 000000000..4d1f80713 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.CustomForm; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.component.*; +import org.geysermc.common.form.impl.response.CustomFormResponseImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; +import org.geysermc.common.form.impl.util.FormImageImpl; +import org.geysermc.common.form.response.CustomFormResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class CustomFormImpl extends FormImpl implements CustomForm { + private final String title; + private final FormImage icon; + private final List content; + + private CustomFormImpl(String title, FormImage icon, List content) { + super(FormType.CUSTOM_FORM); + + this.title = title; + this.icon = icon; + this.content = Collections.unmodifiableList(content); + } + + public static CustomFormImpl of(String title, FormImage icon, List content) { + return new CustomFormImpl(title, icon, content); + } + + public CustomFormResponseImpl parseResponse(String data) { + if (isClosed(data)) { + return CustomFormResponseImpl.closed(); + } + return CustomFormResponseImpl.of(this, data); + } + + public static final class Builder extends FormImpl.Builder + implements CustomForm.Builder { + + private final List components = new ArrayList<>(); + private FormImage icon; + + public Builder icon(FormImage.Type type, String data) { + icon = FormImageImpl.of(type, data); + return this; + } + + public Builder iconPath(String path) { + return icon(FormImage.Type.PATH, path); + } + + public Builder iconUrl(String url) { + return icon(FormImage.Type.URL, url); + } + + public Builder component(Component component) { + components.add(component); + return this; + } + + public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { + return component(dropdownBuilder.translateAndBuild(this::translate)); + } + + public Builder dropdown(String text, int defaultOption, String... options) { + List optionsList = new ArrayList<>(); + for (String option : options) { + optionsList.add(translate(option)); + } + return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); + } + + public Builder dropdown(String text, String... options) { + return dropdown(text, -1, options); + } + + public Builder input(String text, String placeholder, String defaultText) { + return component(InputComponent.of( + translate(text), translate(placeholder), translate(defaultText) + )); + } + + public Builder input(String text, String placeholder) { + return component(InputComponent.of(translate(text), translate(placeholder))); + } + + public Builder input(String text) { + return component(InputComponent.of(translate(text))); + } + + public Builder label(String text) { + return component(LabelComponent.of(translate(text))); + } + + public Builder slider(String text, float min, float max, int step, float defaultValue) { + return component(SliderComponent.of(text, min, max, step, defaultValue)); + } + + public Builder slider(String text, float min, float max, int step) { + return slider(text, min, max, step, -1); + } + + public Builder slider(String text, float min, float max, float defaultValue) { + return slider(text, min, max, -1, defaultValue); + } + + public Builder slider(String text, float min, float max) { + return slider(text, min, max, -1, -1); + } + + public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { + return component(stepSliderBuilder.translateAndBuild(this::translate)); + } + + public Builder stepSlider(String text, int defaultStep, String... steps) { + List stepsList = new ArrayList<>(); + for (String option : steps) { + stepsList.add(translate(option)); + } + return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); + } + + public Builder stepSlider(String text, String... steps) { + return stepSlider(text, -1, steps); + } + + public Builder toggle(String text, boolean defaultValue) { + return component(ToggleComponent.of(translate(text), defaultValue)); + } + + public Builder toggle(String text) { + return component(ToggleComponent.of(translate(text))); + } + + @Override + public CustomFormImpl build() { + CustomFormImpl form = of(title, icon, components); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java new file mode 100644 index 000000000..e9d648b8b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl; + +import com.google.gson.Gson; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.common.form.Form; +import org.geysermc.common.form.FormBuilder; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.Forms; +import org.geysermc.common.form.response.FormResponse; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +@Getter +public abstract class FormImpl implements Form { + protected static final Gson GSON = Forms.getGson(); + + private final FormType type; + protected String hardcodedJsonData = null; + @Setter protected Consumer responseHandler; + + public FormImpl(FormType type) { + this.type = type; + } + + @Override + public String getJsonData() { + if (hardcodedJsonData != null) { + return hardcodedJsonData; + } + return GSON.toJson(this); + } + + @Override + public boolean isClosed(String response) { + return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); + } + + public static abstract class Builder, F extends Form> + implements FormBuilder { + + protected String title = ""; + + protected BiFunction translationHandler = null; + protected BiConsumer biResponseHandler; + protected Consumer responseHandler; + protected String locale; + + @Override + public T title(String title) { + this.title = translate(title); + return self(); + } + + @Override + public T translator(BiFunction translator, String locale) { + this.translationHandler = translator; + this.locale = locale; + return title(title); + } + + @Override + public T translator(BiFunction translator) { + return translator(translator, locale); + } + + @Override + public T responseHandler(BiConsumer responseHandler) { + biResponseHandler = responseHandler; + return self(); + } + + @Override + public T responseHandler(Consumer responseHandler) { + this.responseHandler = responseHandler; + return self(); + } + + @Override + public abstract F build(); + + protected String translate(String text) { + if (translationHandler != null && text != null && !text.isEmpty()) { + return translationHandler.apply(text, locale); + } + return text; + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java new file mode 100644 index 000000000..b047dc59d --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.ModalForm; +import org.geysermc.common.form.impl.response.ModalFormResponseImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; +import org.geysermc.common.form.response.ModalFormResponse; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class ModalFormImpl extends FormImpl implements ModalForm { + private final String title; + private final String content; + private final String button1; + private final String button2; + + private ModalFormImpl(String title, String content, String button1, String button2) { + super(FormType.MODAL_FORM); + + this.title = title; + this.content = content; + this.button1 = button1; + this.button2 = button2; + } + + public static ModalFormImpl of(String title, String content, String button1, String button2) { + return new ModalFormImpl(title, content, button1, button2); + } + + public ModalFormResponse parseResponse(String data) { + if (isClosed(data)) { + return ModalFormResponseImpl.closed(); + } + data = data.trim(); + + if ("true".equals(data)) { + return ModalFormResponseImpl.of(0, button1); + } else if ("false".equals(data)) { + return ModalFormResponseImpl.of(1, button2); + } + return ModalFormResponseImpl.invalid(); + } + + public static final class Builder extends FormImpl.Builder + implements ModalForm.Builder { + + private String content = ""; + private String button1 = ""; + private String button2 = ""; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button1(String button1) { + this.button1 = translate(button1); + return this; + } + + public Builder button2(String button2) { + this.button2 = translate(button2); + return this; + } + + @Override + public ModalForm build() { + ModalFormImpl form = of(title, content, button1, button2); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java new file mode 100644 index 000000000..d5633ec05 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl; + +import com.google.gson.annotations.JsonAdapter; +import lombok.Getter; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.impl.component.ButtonComponentImpl; +import org.geysermc.common.form.impl.response.SimpleFormResponseImpl; +import org.geysermc.common.form.impl.util.FormAdaptor; +import org.geysermc.common.form.response.SimpleFormResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@JsonAdapter(FormAdaptor.class) +public final class SimpleFormImpl extends FormImpl implements SimpleForm { + private final String title; + private final String content; + private final List buttons; + + private SimpleFormImpl(String title, String content, List buttons) { + super(FormType.SIMPLE_FORM); + + this.title = title; + this.content = content; + this.buttons = Collections.unmodifiableList(buttons); + } + + public static SimpleFormImpl of(String title, String content, List buttons) { + return new SimpleFormImpl(title, content, buttons); + } + + public SimpleFormResponse parseResponse(String data) { + if (isClosed(data)) { + return SimpleFormResponseImpl.closed(); + } + data = data.trim(); + + int buttonId; + try { + buttonId = Integer.parseInt(data); + } catch (Exception exception) { + return SimpleFormResponseImpl.invalid(); + } + + if (buttonId >= buttons.size()) { + return SimpleFormResponseImpl.invalid(); + } + + return SimpleFormResponseImpl.of(buttonId, buttons.get(buttonId)); + } + + public static final class Builder extends FormImpl.Builder + implements SimpleForm.Builder { + + private final List buttons = new ArrayList<>(); + private String content = ""; + + public Builder content(String content) { + this.content = translate(content); + return this; + } + + public Builder button(String text, FormImage.Type type, String data) { + buttons.add(ButtonComponentImpl.of(translate(text), type, data)); + return this; + } + + public Builder button(String text, FormImage image) { + buttons.add(ButtonComponentImpl.of(translate(text), image)); + return this; + } + + public Builder button(String text) { + buttons.add(ButtonComponentImpl.of(translate(text))); + return this; + } + + @Override + public SimpleForm build() { + SimpleFormImpl form = of(title, content, buttons); + if (biResponseHandler != null) { + form.setResponseHandler(response -> biResponseHandler.accept(form, response)); + return form; + } + + form.setResponseHandler(responseHandler); + return form; + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java new file mode 100644 index 000000000..0efac1a6d --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.FormImage; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.impl.util.FormImageImpl; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ButtonComponentImpl implements ButtonComponent { + private final String text; + private final FormImageImpl image; + + public static ButtonComponentImpl of(String text, FormImage image) { + return new ButtonComponentImpl(text, (FormImageImpl) image); + } + + public static ButtonComponentImpl of(String text, FormImage.Type type, String data) { + return of(text, FormImageImpl.of(type, data)); + } + + public static ButtonComponentImpl of(String text) { + return of(text, null); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/Component.java b/common/src/main/java/org/geysermc/common/form/impl/component/Component.java new file mode 100644 index 000000000..ff2f29f87 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/Component.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; + +import java.util.Objects; + +@Getter +public abstract class Component { + private final ComponentType type; + private final String text; + + Component(ComponentType type, String text) { + Objects.requireNonNull(type, "Type cannot be null"); + Objects.requireNonNull(text, "Text cannot be null"); + + this.type = type; + this.text = text; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java new file mode 100644 index 000000000..ae175ab74 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.DropdownComponent; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public final class DropdownComponentImpl extends Component implements DropdownComponent { + private final List options; + @SerializedName("default") + private final int defaultOption; + + private DropdownComponentImpl(String text, List options, int defaultOption) { + super(ComponentType.DROPDOWN, text); + this.options = Collections.unmodifiableList(options); + this.defaultOption = defaultOption; + } + + public static DropdownComponentImpl of(String text, List options, int defaultOption) { + if (defaultOption == -1 || defaultOption >= options.size()) { + defaultOption = 0; + } + return new DropdownComponentImpl(text, options, defaultOption); + } + + public static class Builder implements DropdownComponent.Builder { + private final List options = new ArrayList<>(); + private String text; + private int defaultOption = 0; + + @Override + public Builder text(String text) { + this.text = text; + return this; + } + + @Override + public Builder option(String option, boolean isDefault) { + options.add(option); + if (isDefault) { + defaultOption = options.size() - 1; + } + return this; + } + + @Override + public Builder option(String option) { + return option(option, false); + } + + @Override + public Builder defaultOption(int defaultOption) { + this.defaultOption = defaultOption; + return this; + } + + @Override + public DropdownComponentImpl build() { + return of(text, options, defaultOption); + } + + @Override + public DropdownComponentImpl translateAndBuild(Function translator) { + for (int i = 0; i < options.size(); i++) { + options.set(i, translator.apply(options.get(i))); + } + + return of(translator.apply(text), options, defaultOption); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java new file mode 100644 index 000000000..ba741304b --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.InputComponent; + +@Getter +public final class InputComponentImpl extends Component implements InputComponent { + private final String placeholder; + @SerializedName("default") + private final String defaultText; + + private InputComponentImpl(String text, String placeholder, String defaultText) { + super(ComponentType.INPUT, text); + this.placeholder = placeholder; + this.defaultText = defaultText; + } + + public static InputComponentImpl of(String text, String placeholder, String defaultText) { + return new InputComponentImpl(text, placeholder, defaultText); + } + + public static InputComponentImpl of(String text, String placeholder) { + return new InputComponentImpl(text, placeholder, ""); + } + + public static InputComponentImpl of(String text) { + return new InputComponentImpl(text, "", ""); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java new file mode 100644 index 000000000..4dacb8406 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.LabelComponent; + +@Getter +public final class LabelComponentImpl extends Component implements LabelComponent { + private LabelComponentImpl(String text) { + super(ComponentType.LABEL, text); + } + + public static LabelComponentImpl of(String text) { + return new LabelComponentImpl(text); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java new file mode 100644 index 000000000..29fc18dc8 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.SliderComponent; + +@Getter +public final class SliderComponentImpl extends Component implements SliderComponent { + private final float min; + private final float max; + private final int step; + @SerializedName("default") + private final float defaultValue; + + private SliderComponentImpl(String text, float min, float max, int step, float defaultValue) { + super(ComponentType.SLIDER, text); + this.min = min; + this.max = max; + this.step = step; + this.defaultValue = defaultValue; + } + + public static SliderComponentImpl of(String text, float min, float max, int step, + float defaultValue) { + min = Math.max(min, 0f); + max = Math.max(max, min); + + if (step < 1) { + step = 1; + } + + if (defaultValue == -1f) { + defaultValue = (int) Math.floor(min + max / 2D); + } + + return new SliderComponentImpl(text, min, max, step, defaultValue); + } + + public static SliderComponentImpl of(String text, float min, float max, int step) { + return of(text, min, max, step, -1); + } + + public static SliderComponentImpl of(String text, float min, float max, float defaultValue) { + return of(text, min, max, -1, defaultValue); + } + + public static SliderComponentImpl of(String text, float min, float max) { + return of(text, min, max, -1, -1); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java new file mode 100644 index 000000000..dc87c54a5 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.StepSliderComponent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +@Getter +public final class StepSliderComponentImpl extends Component implements StepSliderComponent { + private final List steps; + @SerializedName("default") + private final int defaultStep; + + private StepSliderComponentImpl(String text, List steps, int defaultStep) { + super(ComponentType.STEP_SLIDER, text); + this.steps = Collections.unmodifiableList(steps); + this.defaultStep = defaultStep; + } + + public static StepSliderComponentImpl of(String text, List steps, int defaultStep) { + if (text == null) { + text = ""; + } + + if (defaultStep >= steps.size() || defaultStep == -1) { + defaultStep = 0; + } + + return new StepSliderComponentImpl(text, steps, defaultStep); + } + + public static StepSliderComponentImpl of(String text, int defaultStep, String... steps) { + return of(text, Arrays.asList(steps), defaultStep); + } + + public static StepSliderComponentImpl of(String text, String... steps) { + return of(text, 0, steps); + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String text) { + return builder().text(text); + } + + public static final class Builder implements StepSliderComponent.Builder { + private final List steps = new ArrayList<>(); + private String text; + private int defaultStep; + + public Builder text(String text) { + this.text = text; + return this; + } + + public Builder step(String step, boolean defaultStep) { + steps.add(step); + if (defaultStep) { + this.defaultStep = steps.size() - 1; + } + return this; + } + + public Builder step(String step) { + return step(step, false); + } + + public Builder defaultStep(int defaultStep) { + this.defaultStep = defaultStep; + return this; + } + + public StepSliderComponentImpl build() { + return of(text, steps, defaultStep); + } + + public StepSliderComponentImpl translateAndBuild(Function translator) { + for (int i = 0; i < steps.size(); i++) { + steps.set(i, translator.apply(steps.get(i))); + } + + return of(translator.apply(text), steps, defaultStep); + } + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java new file mode 100644 index 000000000..0e29462b3 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.component; + +import com.google.gson.annotations.SerializedName; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.component.ToggleComponent; + +public final class ToggleComponentImpl extends Component implements ToggleComponent { + @SerializedName("default") + private final boolean defaultValue; + + private ToggleComponentImpl(String text, boolean defaultValue) { + super(ComponentType.TOGGLE, text); + this.defaultValue = defaultValue; + } + + public static ToggleComponentImpl of(String text, boolean defaultValue) { + return new ToggleComponentImpl(text, defaultValue); + } + + public static ToggleComponentImpl of(String text) { + return of(text, false); + } + + public boolean getDefaultValue() { + return defaultValue; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java new file mode 100644 index 000000000..cc0ce64b7 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.response; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonPrimitive; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.impl.CustomFormImpl; +import org.geysermc.common.form.response.CustomFormResponse; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@ToString +public final class CustomFormResponseImpl implements CustomFormResponse { + private static final Gson GSON = new Gson(); + private static final CustomFormResponseImpl CLOSED = + new CustomFormResponseImpl(true, false, null, null); + private static final CustomFormResponseImpl INVALID = + new CustomFormResponseImpl(false, true, null, null); + private final boolean closed; + private final boolean invalid; + + private final JsonArray responses; + private final List componentTypes; + + private int index = -1; + + public static CustomFormResponseImpl closed() { + return CLOSED; + } + + public static CustomFormResponseImpl invalid() { + return INVALID; + } + + public static CustomFormResponseImpl of(CustomFormImpl form, String responseData) { + JsonArray responses = GSON.fromJson(responseData, JsonArray.class); + List types = new ArrayList<>(); + for (Component component : form.getContent()) { + types.add(component.getType()); + } + return of(types, responses); + } + + public static CustomFormResponseImpl of(List componentTypes, + JsonArray responses) { + if (componentTypes.size() != responses.size()) { + return invalid(); + } + + return new CustomFormResponseImpl(false, false, responses, + Collections.unmodifiableList(componentTypes)); + } + + @Override + @SuppressWarnings("unchecked") + public T next(boolean includeLabels) { + if (!hasNext()) { + return null; + } + + while (++index < responses.size()) { + ComponentType type = componentTypes.get(index); + if (type == ComponentType.LABEL && !includeLabels) { + continue; + } + return (T) getDataFromType(type, index); + } + return null; // we don't have anything to check anymore + } + + @Override + public T next() { + return next(false); + } + + @Override + public void skip(int amount) { + index += amount; + } + + @Override + public void skip() { + skip(1); + } + + @Override + public void index(int index) { + this.index = index; + } + + @Override + public boolean hasNext() { + return responses.size() > index + 1; + } + + @Override + public JsonPrimitive get(int index) { + try { + return responses.get(index).getAsJsonPrimitive(); + } catch (IllegalStateException exception) { + wrongType(index, "a primitive"); + return null; + } + } + + @Override + public int getDropdown(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "dropdown"); + } + return primitive.getAsInt(); + } + + @Override + public String getInput(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isString()) { + wrongType(index, "input"); + } + return primitive.getAsString(); + } + + @Override + public float getSlider(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "slider"); + } + return primitive.getAsFloat(); + } + + @Override + public int getStepSlide(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isNumber()) { + wrongType(index, "step slider"); + } + return primitive.getAsInt(); + } + + @Override + public boolean getToggle(int index) { + JsonPrimitive primitive = get(index); + if (!primitive.isBoolean()) { + wrongType(index, "toggle"); + } + return primitive.getAsBoolean(); + } + + private Object getDataFromType(ComponentType type, int index) { + switch (type) { + case DROPDOWN: + return getDropdown(index); + case INPUT: + return getInput(index); + case SLIDER: + return getSlider(index); + case STEP_SLIDER: + return getStepSlide(index); + case TOGGLE: + return getToggle(index); + default: + return null; // label e.g. is always null + } + } + + private void wrongType(int index, String expected) { + throw new IllegalStateException(String.format( + "Expected %s on %s, got %s", + expected, index, responses.get(index).toString())); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java new file mode 100644 index 000000000..6d20431cd --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.response.FormResponse; +import org.geysermc.common.form.response.ModalFormResponse; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class ModalFormResponseImpl implements ModalFormResponse { + private static final ModalFormResponseImpl CLOSED = + new ModalFormResponseImpl(true, false, -1, null); + private static final ModalFormResponseImpl INVALID = + new ModalFormResponseImpl(false, true, -1, null); + private final boolean closed; + private final boolean invalid; + + private final int clickedButtonId; + private final String clickedButtonText; + + public static ModalFormResponseImpl closed() { + return CLOSED; + } + + public static ModalFormResponseImpl invalid() { + return INVALID; + } + + public static ModalFormResponseImpl of(int clickedButtonId, String clickedButtonText) { + return new ModalFormResponseImpl(false, false, clickedButtonId, clickedButtonText); + } + + public boolean getResult() { + return clickedButtonId == 0; + } +} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java new file mode 100644 index 000000000..24bc39366 --- /dev/null +++ b/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.common.form.impl.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.component.ButtonComponent; +import org.geysermc.common.form.response.SimpleFormResponse; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public final class SimpleFormResponseImpl implements SimpleFormResponse { + private static final SimpleFormResponseImpl CLOSED = + new SimpleFormResponseImpl(true, false, -1, null); + private static final SimpleFormResponseImpl INVALID = + new SimpleFormResponseImpl(false, true, -1, null); + private final boolean closed; + private final boolean invalid; + + private final int clickedButtonId; + private final ButtonComponent clickedButton; + + public static SimpleFormResponseImpl closed() { + return CLOSED; + } + + public static SimpleFormResponseImpl invalid() { + return INVALID; + } + + public static SimpleFormResponseImpl of(int clickedButtonId, ButtonComponent clickedButton) { + return new SimpleFormResponseImpl(false, false, clickedButtonId, clickedButton); + } + + public String getClickedButtonText() { + return clickedButton.getText(); + } +} diff --git a/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java similarity index 71% rename from common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java rename to common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java index 8c30e7e8f..11d7eefe4 100644 --- a/common/src/main/java/org/geysermc/common/form/util/FormAdaptor.java +++ b/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java @@ -23,28 +23,31 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.form.util; +package org.geysermc.common.form.impl.util; import com.google.gson.*; import com.google.gson.reflect.TypeToken; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.ModalForm; -import org.geysermc.common.form.SimpleForm; +import org.geysermc.common.form.FormImage; import org.geysermc.common.form.component.ButtonComponent; import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.ComponentType; +import org.geysermc.common.form.impl.CustomFormImpl; +import org.geysermc.common.form.impl.FormImpl; +import org.geysermc.common.form.impl.ModalFormImpl; +import org.geysermc.common.form.impl.SimpleFormImpl; +import org.geysermc.common.form.impl.component.ButtonComponentImpl; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; -public class FormAdaptor implements JsonDeserializer, JsonSerializer { +public final class FormAdaptor implements JsonDeserializer>, JsonSerializer> { private static final Type LIST_BUTTON_TYPE = - new TypeToken>() {}.getType(); + new TypeToken>() {}.getType(); @Override - public Form deserialize(JsonElement jsonElement, Type typeOfT, - JsonDeserializationContext context) + public FormImpl deserialize(JsonElement jsonElement, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { if (!jsonElement.isJsonObject()) { @@ -52,50 +55,50 @@ public class FormAdaptor implements JsonDeserializer, JsonSerializer } JsonObject json = jsonElement.getAsJsonObject(); - if (typeOfT == SimpleForm.class) { + if (typeOfT == SimpleFormImpl.class) { String title = json.get("title").getAsString(); String content = json.get("content").getAsString(); List buttons = context .deserialize(json.get("buttons"), LIST_BUTTON_TYPE); - return SimpleForm.of(title, content, buttons); + return SimpleFormImpl.of(title, content, buttons); } - if (typeOfT == ModalForm.class) { + if (typeOfT == ModalFormImpl.class) { String title = json.get("title").getAsString(); String content = json.get("content").getAsString(); String button1 = json.get("button1").getAsString(); String button2 = json.get("button2").getAsString(); - return ModalForm.of(title, content, button1, button2); + return ModalFormImpl.of(title, content, button1, button2); } - if (typeOfT == CustomForm.class) { + if (typeOfT == CustomFormImpl.class) { String title = json.get("title").getAsString(); - FormImage icon = context.deserialize(json.get("icon"), FormImage.class); + FormImage icon = context.deserialize(json.get("icon"), FormImageImpl.class); List content = new ArrayList<>(); JsonArray contentArray = json.getAsJsonArray("content"); for (JsonElement contentElement : contentArray) { String typeName = contentElement.getAsJsonObject().get("type").getAsString(); - Component.Type type = Component.Type.getByName(typeName); + ComponentType type = ComponentType.getByName(typeName); if (type == null) { throw new JsonParseException("Failed to find Component type " + typeName); } content.add(context.deserialize(contentElement, type.getComponentClass())); } - return CustomForm.of(title, icon, content); + return CustomFormImpl.of(title, icon, content); } return null; } @Override - public JsonElement serialize(Form src, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(FormImpl src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); result.add("type", context.serialize(src.getType())); - if (typeOfSrc == SimpleForm.class) { - SimpleForm form = (SimpleForm) src; + if (typeOfSrc == SimpleFormImpl.class) { + SimpleFormImpl form = (SimpleFormImpl) src; result.addProperty("title", form.getTitle()); result.addProperty("content", form.getContent()); @@ -103,8 +106,8 @@ public class FormAdaptor implements JsonDeserializer, JsonSerializer return result; } - if (typeOfSrc == ModalForm.class) { - ModalForm form = (ModalForm) src; + if (typeOfSrc == ModalFormImpl.class) { + ModalFormImpl form = (ModalFormImpl) src; result.addProperty("title", form.getTitle()); result.addProperty("content", form.getContent()); @@ -113,8 +116,8 @@ public class FormAdaptor implements JsonDeserializer, JsonSerializer return result; } - if (typeOfSrc == CustomForm.class) { - CustomForm form = (CustomForm) src; + if (typeOfSrc == CustomFormImpl.class) { + CustomFormImpl form = (CustomFormImpl) src; result.addProperty("title", form.getTitle()); result.add("icon", context.serialize(form.getIcon())); diff --git a/common/src/main/java/org/geysermc/common/form/util/FormImage.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java similarity index 72% rename from common/src/main/java/org/geysermc/common/form/util/FormImage.java rename to common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java index 876e662d0..4b8248e94 100644 --- a/common/src/main/java/org/geysermc/common/form/util/FormImage.java +++ b/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java @@ -23,36 +23,24 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.common.form.util; +package org.geysermc.common.form.impl.util; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.common.form.FormImage; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public class FormImage { - private final String type; +public final class FormImageImpl implements FormImage { + private final Type type; private final String data; - public static FormImage of(String type, String data) { - return new FormImage(type, data); + public static FormImageImpl of(Type type, String data) { + return new FormImageImpl(type, data); } - public static FormImage of(Type type, String data) { - return of(type.getName(), data); - } - - @RequiredArgsConstructor - public enum Type { - PATH("path"), - URL("url"); - - @Getter private final String name; - - @Override - public String toString() { - return name; - } + public static FormImageImpl of(String type, String data) { + return of(Type.getByName(type), data); } } diff --git a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java index 8ef50c7fb..d4d0fd99e 100644 --- a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java @@ -25,169 +25,38 @@ package org.geysermc.common.form.response; -import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonPrimitive; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.component.Component; +import org.geysermc.common.form.component.ComponentType; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -public class CustomFormResponse implements FormResponse { - private static final Gson GSON = new Gson(); - private static final CustomFormResponse CLOSED = - new CustomFormResponse(true, false, null, null); - private static final CustomFormResponse INVALID = - new CustomFormResponse(false, true, null, null); - private final boolean closed; - private final boolean invalid; +public interface CustomFormResponse extends FormResponse { + JsonArray getResponses(); - private final JsonArray responses; - private final List componentTypes; + List getComponentTypes(); - private int index = -1; + T next(boolean includeLabels); - public static CustomFormResponse closed() { - return CLOSED; - } + T next(); - public static CustomFormResponse invalid() { - return INVALID; - } + void skip(int amount); - public static CustomFormResponse of(CustomForm form, String responseData) { - JsonArray responses = GSON.fromJson(responseData, JsonArray.class); - List types = new ArrayList<>(); - for (Component component : form.getContent()) { - types.add(component.getType()); - } - return of(types, responses); - } + void skip(); - public static CustomFormResponse of(List componentTypes, - JsonArray responses) { - if (componentTypes.size() != responses.size()) { - return invalid(); - } + void index(int index); - return new CustomFormResponse(false, false, responses, - Collections.unmodifiableList(componentTypes)); - } + boolean hasNext(); - @SuppressWarnings("unchecked") - public T next(boolean includeLabels) { - if (!hasNext()) { - return null; - } + JsonPrimitive get(int index); - while (++index < responses.size()) { - Component.Type type = componentTypes.get(index); - if (type == Component.Type.LABEL && !includeLabels) { - continue; - } - return (T) getDataFromType(type, index); - } - return null; // we don't have anything to check anymore - } + int getDropdown(int index); - public T next() { - return next(false); - } + String getInput(int index); - public void skip(int amount) { - index += amount; - } + float getSlider(int index); - public void skip() { - skip(1); - } + int getStepSlide(int index); - public void index(int index) { - this.index = index; - } - - public boolean hasNext() { - return responses.size() > index + 1; - } - - public JsonPrimitive get(int index) { - try { - return responses.get(index).getAsJsonPrimitive(); - } catch (IllegalStateException exception) { - wrongType(index, "a primitive"); - return null; - } - } - - public int getDropdown(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "dropdown"); - } - return primitive.getAsInt(); - } - - public String getInput(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isString()) { - wrongType(index, "input"); - } - return primitive.getAsString(); - } - - public float getSlider(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "slider"); - } - return primitive.getAsFloat(); - } - - public int getStepSlide(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "step slider"); - } - return primitive.getAsInt(); - } - - public boolean getToggle(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isBoolean()) { - wrongType(index, "toggle"); - } - return primitive.getAsBoolean(); - } - - private Object getDataFromType(Component.Type type, int index) { - switch (type) { - case DROPDOWN: - return getDropdown(index); - case INPUT: - return getInput(index); - case SLIDER: - return getSlider(index); - case STEP_SLIDER: - return getStepSlide(index); - case TOGGLE: - return getToggle(index); - default: - return null; // label e.g. is always null - } - } - - private void wrongType(int index, String expected) { - throw new IllegalStateException(String.format( - "Expected %s on %s, got %s", - expected, index, responses.get(index).toString())); - } + boolean getToggle(int index); } diff --git a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java index b0e4de883..6e594e0ee 100644 --- a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java @@ -25,36 +25,10 @@ package org.geysermc.common.form.response; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +public interface ModalFormResponse extends FormResponse { + int getClickedButtonId(); -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ModalFormResponse implements FormResponse { - private static final ModalFormResponse CLOSED = - new ModalFormResponse(true, false, -1, null); - private static final ModalFormResponse INVALID = - new ModalFormResponse(false, true, -1, null); - private final boolean closed; - private final boolean invalid; + String getClickedButtonText(); - private final int clickedButtonId; - private final String clickedButtonText; - - public static ModalFormResponse closed() { - return CLOSED; - } - - public static ModalFormResponse invalid() { - return INVALID; - } - - public static ModalFormResponse of(int clickedButtonId, String clickedButtonText) { - return new ModalFormResponse(false, false, clickedButtonId, clickedButtonText); - } - - public boolean getResult() { - return clickedButtonId == 0; - } + boolean getResult(); } diff --git a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java index 6ee819f37..528a79ed1 100644 --- a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java +++ b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java @@ -25,31 +25,10 @@ package org.geysermc.common.form.response; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.geysermc.common.form.component.ButtonComponent; -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class SimpleFormResponse implements FormResponse { - private static final SimpleFormResponse CLOSED = new SimpleFormResponse(true, false, -1, null); - private static final SimpleFormResponse INVALID = new SimpleFormResponse(false, true, -1, null); - private final boolean closed; - private final boolean invalid; +public interface SimpleFormResponse extends FormResponse { + int getClickedButtonId(); - private final int clickedButtonId; - private final ButtonComponent clickedButton; - - public static SimpleFormResponse closed() { - return CLOSED; - } - - public static SimpleFormResponse invalid() { - return INVALID; - } - - public static SimpleFormResponse of(int clickedButtonId, ButtonComponent clickedButton) { - return new SimpleFormResponse(false, false, clickedButtonId, clickedButton); - } + ButtonComponent getClickedButton(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index d3bb2436f..5f0a844b0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -58,13 +58,13 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; import org.geysermc.common.form.Form; +import org.geysermc.common.form.FormBuilder; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -72,6 +72,7 @@ import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.*; @@ -606,11 +607,11 @@ public class GeyserSession implements CommandSender { return this.upstream.getAddress(); } - public void sendForm(Form form) { + public void sendForm(Form form) { formCache.showForm(form); } - public void sendForm(Form.Builder formBuilder) { + public void sendForm(FormBuilder formBuilder) { formCache.showForm(formBuilder.build()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 20ade2869..1f112e9ce 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -27,40 +27,50 @@ package org.geysermc.connector.network.session.cache; import com.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket; import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket; +import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.geysermc.common.form.Form; import org.geysermc.connector.network.session.GeyserSession; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @RequiredArgsConstructor public class FormCache { private final AtomicInteger formId = new AtomicInteger(0); - private final Int2ObjectMap forms = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap> forms = new Int2ObjectOpenHashMap<>(); private final GeyserSession session; - public int addForm(Form form) { + public int addForm(Form form) { int windowId = formId.getAndIncrement(); forms.put(windowId, form); return windowId; } - public int showForm(Form form) { + public int showForm(Form form) { int windowId = addForm(form); ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); formRequestPacket.setFormId(windowId); formRequestPacket.setFormData(form.getJsonData()); - session.sendUpstreamPacket(formRequestPacket); + + // Hack to fix the url image loading bug + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(-System.currentTimeMillis()); + session.getConnector().getGeneralThreadPool().schedule(() -> + session.sendUpstreamPacket(latencyPacket), + 500, TimeUnit.MILLISECONDS); + return windowId; } public void handleResponse(ModalFormResponsePacket response) { - Form form = forms.get(response.getFormId()); + Form form = forms.get(response.getFormId()); if (form == null) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index d480b526f..c4ddfb4c2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -27,9 +27,16 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket; import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.Attribute; +import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; /** * Used to send the keep alive packet back to the server @@ -39,8 +46,26 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator 0) { + // The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number + ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp()); + session.sendDownstreamPacket(keepAlivePacket); + return; + } + + // Hack to fix the url image loading bug + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId()); + + Attribute attribute = session.getPlayerEntity().getAttributes().get(AttributeType.EXPERIENCE_LEVEL); + if (attribute != null) { + attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(attribute))); + } else { + attributesPacket.setAttributes(Collections.singletonList(AttributeUtils.getBedrockAttribute(AttributeType.EXPERIENCE_LEVEL.getAttribute(0)))); + } + + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(attributesPacket), + 500, TimeUnit.MILLISECONDS); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java index 2a791d5a2..57d64ac75 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -29,6 +29,8 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessag import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.common.form.Form; +import org.geysermc.common.form.FormType; +import org.geysermc.common.form.Forms; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -53,7 +55,7 @@ public class JavaPluginMessageTranslator extends PacketTranslator form = Forms.fromJson(dataString, type.getTypeClass()); form.setResponseHandler(response -> { byte[] raw = response.getBytes(StandardCharsets.UTF_8); byte[] finalData = new byte[raw.length + 2]; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 68ca24831..d3d1c703b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -89,7 +89,7 @@ public class SettingsUtils { } builder.responseHandler((form, responseData) -> { - CustomFormResponse response = form.parseResponseAs(responseData); + CustomFormResponse response = form.parseResponse(responseData); if (response.isClosed() || response.isInvalid()) { return; } From deae3d566d550a0420ddf27457ecf6639001935c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 1 Dec 2020 19:54:51 +0100 Subject: [PATCH 14/51] Updated DeviceOs --- .../org/geysermc/floodgate/util/DeviceOs.java | 16 ++++++++-------- .../connector/entity/FireworkEntity.java | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java index b3245ef81..bbc5451cf 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java +++ b/common/src/main/java/org/geysermc/floodgate/util/DeviceOs.java @@ -34,20 +34,20 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum DeviceOs { UNKNOWN("Unknown"), - ANDROID("Android"), + GOOGLE("Android"), IOS("iOS"), OSX("macOS"), - FIREOS("FireOS"), + AMAZON("Amazon"), GEARVR("Gear VR"), HOLOLENS("Hololens"), - WIN10("Windows 10"), - WIN32("Windows"), + UWP("Windows 10"), + WIN32("Windows x86"), DEDICATED("Dedicated"), - ORBIS("PS4"), + TVOS("Apple TV"), + PS4("PS4"), NX("Switch"), - SWITCH("Switch"), - XBOX_ONE("Xbox One"), - WIN_PHONE("Windows Phone"); + XBOX("Xbox One"), + WINDOWS_PHONE("Windows Phone"); private static final DeviceOs[] VALUES = values(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java index 630a5edd9..7c53de8ea 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FireworkEntity.java @@ -67,7 +67,8 @@ public class FireworkEntity extends Entity { // 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_ONE || session.getClientData().getDeviceOs() == DeviceOs.ORBIS) { + if (session.getClientData().getDeviceOs() == DeviceOs.XBOX + || session.getClientData().getDeviceOs() == DeviceOs.PS4) { return; } From f7d23788450416ffe9134b2782958345932e0fc9 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 1 Dec 2020 23:17:54 +0100 Subject: [PATCH 15/51] Added Floodgate to GeyserDump --- .../floodgate/util/FloodgateConfigHolder.java | 35 +++++++++++++++++++ .../connector/dump/BootstrapDumpInfo.java | 9 ++--- .../org/geysermc/connector/dump/DumpInfo.java | 30 ++++++++-------- 3 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java new file mode 100644 index 000000000..4dd175d33 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +import lombok.Getter; +import lombok.Setter; + +public class FloodgateConfigHolder { + @Getter + @Setter + private static Object config; +} diff --git a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java index 585565533..1f2339c9a 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/BootstrapDumpInfo.java @@ -34,8 +34,7 @@ import java.util.List; @Getter public class BootstrapDumpInfo { - - private PlatformType platform; + private final PlatformType platform; public BootstrapDumpInfo() { this.platform = GeyserConnector.getInstance().getPlatformType(); @@ -43,8 +42,7 @@ public class BootstrapDumpInfo { @Getter @AllArgsConstructor - public class PluginInfo { - + public static class PluginInfo { public boolean enabled; public String name; public String version; @@ -54,8 +52,7 @@ public class BootstrapDumpInfo { @Getter @AllArgsConstructor - public class ListenerInfo { - + public static class ListenerInfo { public String ip; public int port; } diff --git a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java index d41ad64a3..fdd8ea73e 100644 --- a/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java +++ b/connector/src/main/java/org/geysermc/connector/dump/DumpInfo.java @@ -38,6 +38,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.DockerCheck; import org.geysermc.connector.utils.FileUtils; import org.geysermc.floodgate.util.DeviceOs; +import org.geysermc.floodgate.util.FloodgateConfigHolder; import java.io.IOException; import java.net.InetAddress; @@ -48,30 +49,31 @@ import java.util.Properties; @Getter public class DumpInfo { - @JsonIgnore private static final long MEGABYTE = 1024L * 1024L; private final DumpInfo.VersionInfo versionInfo; private Properties gitInfo; private final GeyserConfiguration config; - private Object2IntMap userPlatforms; - private RamInfo ramInfo; + private final Object floodgateConfig; + private final Object2IntMap userPlatforms; + private final RamInfo ramInfo; private final BootstrapDumpInfo bootstrapInfo; public DumpInfo() { - this.versionInfo = new DumpInfo.VersionInfo(); + this.versionInfo = new VersionInfo(); try { this.gitInfo = new Properties(); this.gitInfo.load(FileUtils.getResource("git.properties")); - } catch (IOException ignored) { } + } catch (IOException ignored) { + } this.config = GeyserConnector.getInstance().getConfig(); - + this.floodgateConfig = FloodgateConfigHolder.getConfig(); this.ramInfo = new DumpInfo.RamInfo(); - this.userPlatforms = new Object2IntOpenHashMap(); + this.userPlatforms = new Object2IntOpenHashMap<>(); for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { DeviceOs device = session.getClientData().getDeviceOs(); userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); @@ -81,8 +83,7 @@ public class DumpInfo { } @Getter - public class VersionInfo { - + public static class VersionInfo { private final String name; private final String version; private final String javaVersion; @@ -97,7 +98,8 @@ public class DumpInfo { this.name = GeyserConnector.NAME; this.version = GeyserConnector.VERSION; this.javaVersion = System.getProperty("java.version"); - this.architecture = System.getProperty("os.arch"); // Usually gives Java architecture but still may be helpful. + // Usually gives Java architecture but still may be helpful. + this.architecture = System.getProperty("os.arch"); this.operatingSystem = System.getProperty("os.name"); this.operatingSystemVersion = System.getProperty("os.version"); @@ -108,9 +110,8 @@ public class DumpInfo { @Getter public static class NetworkInfo { - - private String internalIP; private final boolean dockerCheck; + private String internalIP; NetworkInfo() { if (AsteriskSerializer.showSensitive) { @@ -123,7 +124,8 @@ public class DumpInfo { try { // Fallback to the normal way of getting the local IP this.internalIP = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException ignored) { } + } catch (UnknownHostException ignored) { + } } } else { // Sometimes the internal IP is the external IP... @@ -136,7 +138,6 @@ public class DumpInfo { @Getter public static class MCInfo { - private final String bedrockVersion; private final int bedrockProtocol; private final String javaVersion; @@ -152,7 +153,6 @@ public class DumpInfo { @Getter public static class RamInfo { - private final long free; private final long total; private final long max; From 45596a87a92c9f31de3f61ffa28e1f356957d5e7 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 10 Dec 2020 22:57:48 +0100 Subject: [PATCH 16/51] Use Cumulus as form library --- common/pom.xml | 8 +- .../org/geysermc/common/form/CustomForm.java | 86 -------- .../java/org/geysermc/common/form/Form.java | 83 ------- .../org/geysermc/common/form/FormBuilder.java | 44 ---- .../org/geysermc/common/form/FormImage.java | 62 ------ .../org/geysermc/common/form/FormType.java | 51 ----- .../java/org/geysermc/common/form/Forms.java | 46 ---- .../org/geysermc/common/form/ModalForm.java | 47 ---- .../org/geysermc/common/form/SimpleForm.java | 61 ------ .../form/component/ButtonComponent.java | 47 ---- .../common/form/component/Component.java | 32 --- .../common/form/component/ComponentType.java | 61 ------ .../form/component/DropdownComponent.java | 63 ------ .../common/form/component/InputComponent.java | 46 ---- .../common/form/component/LabelComponent.java | 34 --- .../form/component/SliderComponent.java | 54 ----- .../form/component/StepSliderComponent.java | 71 ------ .../form/component/ToggleComponent.java | 40 ---- .../common/form/impl/CustomFormImpl.java | 179 --------------- .../geysermc/common/form/impl/FormImpl.java | 121 ---------- .../common/form/impl/ModalFormImpl.java | 105 --------- .../common/form/impl/SimpleFormImpl.java | 120 ---------- .../impl/component/ButtonComponentImpl.java | 52 ----- .../common/form/impl/component/Component.java | 45 ---- .../impl/component/DropdownComponentImpl.java | 102 --------- .../impl/component/InputComponentImpl.java | 56 ----- .../impl/component/LabelComponentImpl.java | 41 ---- .../impl/component/SliderComponentImpl.java | 76 ------- .../component/StepSliderComponentImpl.java | 118 ---------- .../impl/component/ToggleComponentImpl.java | 52 ----- .../impl/response/CustomFormResponseImpl.java | 207 ------------------ .../impl/response/ModalFormResponseImpl.java | 62 ------ .../impl/response/SimpleFormResponseImpl.java | 62 ------ .../common/form/impl/util/FormAdaptor.java | 129 ----------- .../common/form/impl/util/FormImageImpl.java | 46 ---- .../form/response/CustomFormResponse.java | 62 ------ .../common/form/response/FormResponse.java | 35 --- .../form/response/ModalFormResponse.java | 34 --- .../form/response/SimpleFormResponse.java | 34 --- connector/pom.xml | 1 - .../network/session/GeyserSession.java | 6 +- .../network/session/cache/FormCache.java | 14 +- ...edrockServerSettingsRequestTranslator.java | 3 +- .../java/JavaPluginMessageTranslator.java | 8 +- .../connector/utils/LoginEncryptionUtils.java | 8 +- .../connector/utils/SettingsUtils.java | 6 +- .../connector/utils/StatisticsUtils.java | 4 +- 47 files changed, 30 insertions(+), 2694 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/common/form/CustomForm.java delete mode 100644 common/src/main/java/org/geysermc/common/form/Form.java delete mode 100644 common/src/main/java/org/geysermc/common/form/FormBuilder.java delete mode 100644 common/src/main/java/org/geysermc/common/form/FormImage.java delete mode 100644 common/src/main/java/org/geysermc/common/form/FormType.java delete mode 100644 common/src/main/java/org/geysermc/common/form/Forms.java delete mode 100644 common/src/main/java/org/geysermc/common/form/ModalForm.java delete mode 100644 common/src/main/java/org/geysermc/common/form/SimpleForm.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/Component.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/ComponentType.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/InputComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/LabelComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/SliderComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/FormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/Component.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java delete mode 100644 common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/FormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java delete mode 100644 common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java diff --git a/common/pom.xml b/common/pom.xml index af5664ca1..0f4d5421b 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -11,11 +11,15 @@ common + + org.geysermc.cumulus + cumulus + 1.0-SNAPSHOT + com.google.code.gson gson - 2.8.5 - compile + 2.8.6 \ No newline at end of file diff --git a/common/src/main/java/org/geysermc/common/form/CustomForm.java b/common/src/main/java/org/geysermc/common/form/CustomForm.java deleted file mode 100644 index 41097b60e..000000000 --- a/common/src/main/java/org/geysermc/common/form/CustomForm.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.component.Component; -import org.geysermc.common.form.component.DropdownComponent; -import org.geysermc.common.form.component.StepSliderComponent; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.response.CustomFormResponse; - -import java.util.List; - -public interface CustomForm extends Form { - static CustomForm.Builder builder() { - return new CustomFormImpl.Builder(); - } - - static CustomForm of(String title, FormImage icon, List content) { - return CustomFormImpl.of(title, icon, content); - } - - interface Builder extends FormBuilder { - Builder icon(FormImage.Type type, String data); - - Builder iconPath(String path); - - Builder iconUrl(String url); - - Builder component(Component component); - - Builder dropdown(DropdownComponent.Builder dropdownBuilder); - - Builder dropdown(String text, int defaultOption, String... options); - - Builder dropdown(String text, String... options); - - Builder input(String text, String placeholder, String defaultText); - - Builder input(String text, String placeholder); - - Builder input(String text); - - Builder label(String text); - - Builder slider(String text, float min, float max, int step, float defaultValue); - - Builder slider(String text, float min, float max, int step); - - Builder slider(String text, float min, float max, float defaultValue); - - Builder slider(String text, float min, float max); - - Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder); - - Builder stepSlider(String text, int defaultStep, String... steps); - - Builder stepSlider(String text, String... steps); - - Builder toggle(String text, boolean defaultValue); - - Builder toggle(String text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/Form.java b/common/src/main/java/org/geysermc/common/form/Form.java deleted file mode 100644 index 6fd1cbc2d..000000000 --- a/common/src/main/java/org/geysermc/common/form/Form.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.response.FormResponse; - -import java.util.function.Consumer; - -/** - * Base class of all Forms. While it can be used it doesn't contain every data you could get when - * using the specific class of the form type. - * - * @param class provided by the specific form type. It understands the response data and makes - * the data easily accessible - */ -public interface Form { - /** - * Returns the form type of this specific instance. The valid form types can be found {@link - * FormType in the FormType class} - */ - FormType getType(); - - /** - * Returns the data that will be sent by Geyser to the Bedrock client - */ - String getJsonData(); - - /** - * Returns the handler that will be invoked once the form got a response from the Bedrock - * client - */ - Consumer getResponseHandler(); - - /** - * Sets the handler that will be invoked once the form got a response from the Bedrock client. - * This handler contains the raw data sent by the Bedrock client. See {@link - * #parseResponse(String)} if you want to turn the given data into something that's easier to - * handle. - * - * @param responseHandler the response handler - */ - void setResponseHandler(Consumer responseHandler); - - /** - * Parses the method into something provided by the form implementation, which will make the - * data given by the Bedrock client easier to handle. - * - * @param response the raw data given by the Bedrock client - * @return the data in an easy-to-handle class - */ - T parseResponse(String response); - - /** - * Checks if the given data by the Bedrock client is saying that the client closed the form. - * - * @param response the raw data given by the Bedrock client - * @return true if the raw data implies that the Bedrock client closed the form - */ - boolean isClosed(String response); -} diff --git a/common/src/main/java/org/geysermc/common/form/FormBuilder.java b/common/src/main/java/org/geysermc/common/form/FormBuilder.java deleted file mode 100644 index f5a1ce76d..000000000 --- a/common/src/main/java/org/geysermc/common/form/FormBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -public interface FormBuilder, F extends Form> { - T title(String title); - - T translator(BiFunction translator, String locale); - - T translator(BiFunction translator); - - T responseHandler(BiConsumer responseHandler); - - T responseHandler(Consumer responseHandler); - - F build(); -} diff --git a/common/src/main/java/org/geysermc/common/form/FormImage.java b/common/src/main/java/org/geysermc/common/form/FormImage.java deleted file mode 100644 index f5672923f..000000000 --- a/common/src/main/java/org/geysermc/common/form/FormImage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import com.google.gson.annotations.SerializedName; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.impl.util.FormImageImpl; - -public interface FormImage { - static FormImage of(Type type, String data) { - return FormImageImpl.of(type, data); - } - - static FormImage of(String type, String data) { - return FormImageImpl.of(type, data); - } - - Type getType(); - - String getData(); - - @RequiredArgsConstructor - enum Type { - @SerializedName("path") PATH, - @SerializedName("url") URL; - - private static final Type[] VALUES = values(); - - public static Type getByName(String name) { - String upper = name.toUpperCase(); - for (Type value : VALUES) { - if (value.name().equals(upper)) { - return value; - } - } - return null; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/FormType.java b/common/src/main/java/org/geysermc/common/form/FormType.java deleted file mode 100644 index 064dec4b3..000000000 --- a/common/src/main/java/org/geysermc/common/form/FormType.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.impl.ModalFormImpl; -import org.geysermc.common.form.impl.SimpleFormImpl; - -@Getter -@RequiredArgsConstructor -public enum FormType { - @SerializedName("form") - SIMPLE_FORM(SimpleFormImpl.class), - @SerializedName("modal") - MODAL_FORM(ModalFormImpl.class), - @SerializedName("custom_form") - CUSTOM_FORM(CustomFormImpl.class); - - private static final FormType[] VALUES = values(); - private final Class typeClass; - - public static FormType getByOrdinal(int ordinal) { - return ordinal < VALUES.length ? VALUES[ordinal] : null; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/Forms.java b/common/src/main/java/org/geysermc/common/form/Forms.java deleted file mode 100644 index 1a1c0a4cb..000000000 --- a/common/src/main/java/org/geysermc/common/form/Forms.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import org.geysermc.common.form.impl.FormImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; - -public final class Forms { - private static final Gson GSON = - new GsonBuilder() - .registerTypeAdapter(FormImpl.class, new FormAdaptor()) - .create(); - - public static Gson getGson() { - return GSON; - } - - public static > T fromJson(String json, Class formClass) { - return GSON.fromJson(json, formClass); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/ModalForm.java b/common/src/main/java/org/geysermc/common/form/ModalForm.java deleted file mode 100644 index 8e35bc8b0..000000000 --- a/common/src/main/java/org/geysermc/common/form/ModalForm.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.impl.ModalFormImpl; -import org.geysermc.common.form.response.ModalFormResponse; - -public interface ModalForm extends Form { - static Builder builder() { - return new ModalFormImpl.Builder(); - } - - static ModalForm of(String title, String content, String button1, String button2) { - return ModalFormImpl.of(title, content, button1, button2); - } - - interface Builder extends FormBuilder { - Builder content(String content); - - Builder button1(String button1); - - Builder button2(String button2); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/SimpleForm.java b/common/src/main/java/org/geysermc/common/form/SimpleForm.java deleted file mode 100644 index dd11544b9..000000000 --- a/common/src/main/java/org/geysermc/common/form/SimpleForm.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form; - -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.impl.SimpleFormImpl; -import org.geysermc.common.form.response.SimpleFormResponse; - -import java.util.List; - -/** - * - */ -public interface SimpleForm extends Form { - static Builder builder() { - return new SimpleFormImpl.Builder(); - } - - static SimpleForm of(String title, String content, List buttons) { - return SimpleFormImpl.of(title, content, buttons); - } - - String getTitle(); - - String getContent(); - - List getButtons(); - - interface Builder extends FormBuilder { - Builder content(String content); - - Builder button(String text, FormImage.Type type, String data); - - Builder button(String text, FormImage image); - - Builder button(String text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java b/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java deleted file mode 100644 index 3e8f5d438..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/ButtonComponent.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.impl.component.ButtonComponentImpl; - -public interface ButtonComponent { - static ButtonComponent of(String text, FormImage image) { - return ButtonComponentImpl.of(text, image); - } - - static ButtonComponent of(String text, FormImage.Type type, String data) { - return ButtonComponentImpl.of(text, type, data); - } - - static ButtonComponent of(String text) { - return of(text, null); - } - - String getText(); - - FormImage getImage(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/Component.java b/common/src/main/java/org/geysermc/common/form/component/Component.java deleted file mode 100644 index 524d2a1a9..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/Component.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -public interface Component { - ComponentType getType(); - - String getText(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/ComponentType.java b/common/src/main/java/org/geysermc/common/form/component/ComponentType.java deleted file mode 100644 index 898e10d07..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/ComponentType.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ComponentType { - @SerializedName("dropdown") - DROPDOWN(DropdownComponent.class), - @SerializedName("input") - INPUT(InputComponent.class), - @SerializedName("label") - LABEL(LabelComponent.class), - @SerializedName("slider") - SLIDER(SliderComponent.class), - @SerializedName("step_slider") - STEP_SLIDER(StepSliderComponent.class), - @SerializedName("toggle") - TOGGLE(ToggleComponent.class); - - private static final ComponentType[] VALUES = values(); - - private final String name = name().toLowerCase(); - private final Class componentClass; - - public static ComponentType getByName(String name) { - for (ComponentType type : VALUES) { - if (type.name.equals(name)) { - return type; - } - } - return null; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java b/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java deleted file mode 100644 index a68b3b1e2..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/DropdownComponent.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.DropdownComponentImpl; - -import java.util.List; -import java.util.function.Function; - -public interface DropdownComponent extends Component { - static DropdownComponent of(String text, List options, int defaultOption) { - return DropdownComponentImpl.of(text, options, defaultOption); - } - - static Builder builder() { - return new DropdownComponentImpl.Builder(); - } - - static Builder builder(String text) { - return builder().text(text); - } - - List getOptions(); - - int getDefaultOption(); - - interface Builder { - Builder text(String text); - - Builder option(String option, boolean isDefault); - - Builder option(String option); - - Builder defaultOption(int defaultOption); - - DropdownComponent build(); - - DropdownComponent translateAndBuild(Function translator); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java b/common/src/main/java/org/geysermc/common/form/component/InputComponent.java deleted file mode 100644 index 4f054957c..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/InputComponent.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.InputComponentImpl; - -public interface InputComponent extends Component { - static InputComponent of(String text, String placeholder, String defaultText) { - return InputComponentImpl.of(text, placeholder, defaultText); - } - - static InputComponent of(String text, String placeholder) { - return InputComponentImpl.of(text, placeholder, ""); - } - - static InputComponent of(String text) { - return InputComponentImpl.of(text, "", ""); - } - - String getPlaceholder(); - - String getDefaultText(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java b/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java deleted file mode 100644 index ed6f47029..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/LabelComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.LabelComponentImpl; - -public interface LabelComponent extends Component { - static LabelComponent of(String text) { - return LabelComponentImpl.of(text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java deleted file mode 100644 index 9a0e3343e..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/SliderComponent.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.SliderComponentImpl; - -public interface SliderComponent extends Component { - static SliderComponent of(String text, float min, float max, int step, float defaultValue) { - return SliderComponentImpl.of(text, min, max, step, defaultValue); - } - - static SliderComponent of(String text, float min, float max, int step) { - return SliderComponentImpl.of(text, min, max, step); - } - - static SliderComponent of(String text, float min, float max, float defaultValue) { - return SliderComponentImpl.of(text, min, max, defaultValue); - } - - static SliderComponent of(String text, float min, float max) { - return SliderComponentImpl.of(text, min, max); - } - - float getMin(); - - float getMax(); - - int getStep(); - - float getDefaultValue(); -} diff --git a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java b/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java deleted file mode 100644 index cda4e7acb..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/StepSliderComponent.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.StepSliderComponentImpl; - -import java.util.List; -import java.util.function.Function; - -public interface StepSliderComponent extends Component { - static StepSliderComponent of(String text, List steps, int defaultStep) { - return StepSliderComponentImpl.of(text, steps, defaultStep); - } - - static StepSliderComponent of(String text, int defaultStep, String... steps) { - return StepSliderComponentImpl.of(text, defaultStep, steps); - } - - static StepSliderComponent of(String text, String... steps) { - return StepSliderComponentImpl.of(text, steps); - } - - static Builder builder() { - return new StepSliderComponentImpl.Builder(); - } - - static Builder builder(String text) { - return builder().text(text); - } - - List getSteps(); - - int getDefaultStep(); - - interface Builder { - Builder text(String text); - - Builder step(String step, boolean defaultStep); - - Builder step(String step); - - Builder defaultStep(int defaultStep); - - StepSliderComponent build(); - - StepSliderComponent translateAndBuild(Function translator); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java b/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java deleted file mode 100644 index e505e995b..000000000 --- a/common/src/main/java/org/geysermc/common/form/component/ToggleComponent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.component; - -import org.geysermc.common.form.impl.component.ToggleComponentImpl; - -public interface ToggleComponent extends Component { - static ToggleComponent of(String text, boolean defaultValue) { - return ToggleComponentImpl.of(text, defaultValue); - } - - static ToggleComponent of(String text) { - return ToggleComponentImpl.of(text); - } - - boolean getDefaultValue(); -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java deleted file mode 100644 index 4d1f80713..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/CustomFormImpl.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.component.*; -import org.geysermc.common.form.impl.response.CustomFormResponseImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; -import org.geysermc.common.form.impl.util.FormImageImpl; -import org.geysermc.common.form.response.CustomFormResponse; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Getter -@JsonAdapter(FormAdaptor.class) -public final class CustomFormImpl extends FormImpl implements CustomForm { - private final String title; - private final FormImage icon; - private final List content; - - private CustomFormImpl(String title, FormImage icon, List content) { - super(FormType.CUSTOM_FORM); - - this.title = title; - this.icon = icon; - this.content = Collections.unmodifiableList(content); - } - - public static CustomFormImpl of(String title, FormImage icon, List content) { - return new CustomFormImpl(title, icon, content); - } - - public CustomFormResponseImpl parseResponse(String data) { - if (isClosed(data)) { - return CustomFormResponseImpl.closed(); - } - return CustomFormResponseImpl.of(this, data); - } - - public static final class Builder extends FormImpl.Builder - implements CustomForm.Builder { - - private final List components = new ArrayList<>(); - private FormImage icon; - - public Builder icon(FormImage.Type type, String data) { - icon = FormImageImpl.of(type, data); - return this; - } - - public Builder iconPath(String path) { - return icon(FormImage.Type.PATH, path); - } - - public Builder iconUrl(String url) { - return icon(FormImage.Type.URL, url); - } - - public Builder component(Component component) { - components.add(component); - return this; - } - - public Builder dropdown(DropdownComponent.Builder dropdownBuilder) { - return component(dropdownBuilder.translateAndBuild(this::translate)); - } - - public Builder dropdown(String text, int defaultOption, String... options) { - List optionsList = new ArrayList<>(); - for (String option : options) { - optionsList.add(translate(option)); - } - return component(DropdownComponent.of(translate(text), optionsList, defaultOption)); - } - - public Builder dropdown(String text, String... options) { - return dropdown(text, -1, options); - } - - public Builder input(String text, String placeholder, String defaultText) { - return component(InputComponent.of( - translate(text), translate(placeholder), translate(defaultText) - )); - } - - public Builder input(String text, String placeholder) { - return component(InputComponent.of(translate(text), translate(placeholder))); - } - - public Builder input(String text) { - return component(InputComponent.of(translate(text))); - } - - public Builder label(String text) { - return component(LabelComponent.of(translate(text))); - } - - public Builder slider(String text, float min, float max, int step, float defaultValue) { - return component(SliderComponent.of(text, min, max, step, defaultValue)); - } - - public Builder slider(String text, float min, float max, int step) { - return slider(text, min, max, step, -1); - } - - public Builder slider(String text, float min, float max, float defaultValue) { - return slider(text, min, max, -1, defaultValue); - } - - public Builder slider(String text, float min, float max) { - return slider(text, min, max, -1, -1); - } - - public Builder stepSlider(StepSliderComponent.Builder stepSliderBuilder) { - return component(stepSliderBuilder.translateAndBuild(this::translate)); - } - - public Builder stepSlider(String text, int defaultStep, String... steps) { - List stepsList = new ArrayList<>(); - for (String option : steps) { - stepsList.add(translate(option)); - } - return component(StepSliderComponent.of(translate(text), stepsList, defaultStep)); - } - - public Builder stepSlider(String text, String... steps) { - return stepSlider(text, -1, steps); - } - - public Builder toggle(String text, boolean defaultValue) { - return component(ToggleComponent.of(translate(text), defaultValue)); - } - - public Builder toggle(String text) { - return component(ToggleComponent.of(translate(text))); - } - - @Override - public CustomFormImpl build() { - CustomFormImpl form = of(title, icon, components); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java deleted file mode 100644 index e9d648b8b..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/FormImpl.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.Gson; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.FormBuilder; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.Forms; -import org.geysermc.common.form.response.FormResponse; - -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -@Getter -public abstract class FormImpl implements Form { - protected static final Gson GSON = Forms.getGson(); - - private final FormType type; - protected String hardcodedJsonData = null; - @Setter protected Consumer responseHandler; - - public FormImpl(FormType type) { - this.type = type; - } - - @Override - public String getJsonData() { - if (hardcodedJsonData != null) { - return hardcodedJsonData; - } - return GSON.toJson(this); - } - - @Override - public boolean isClosed(String response) { - return response == null || response.isEmpty() || response.equalsIgnoreCase("null"); - } - - public static abstract class Builder, F extends Form> - implements FormBuilder { - - protected String title = ""; - - protected BiFunction translationHandler = null; - protected BiConsumer biResponseHandler; - protected Consumer responseHandler; - protected String locale; - - @Override - public T title(String title) { - this.title = translate(title); - return self(); - } - - @Override - public T translator(BiFunction translator, String locale) { - this.translationHandler = translator; - this.locale = locale; - return title(title); - } - - @Override - public T translator(BiFunction translator) { - return translator(translator, locale); - } - - @Override - public T responseHandler(BiConsumer responseHandler) { - biResponseHandler = responseHandler; - return self(); - } - - @Override - public T responseHandler(Consumer responseHandler) { - this.responseHandler = responseHandler; - return self(); - } - - @Override - public abstract F build(); - - protected String translate(String text) { - if (translationHandler != null && text != null && !text.isEmpty()) { - return translationHandler.apply(text, locale); - } - return text; - } - - @SuppressWarnings("unchecked") - protected T self() { - return (T) this; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java deleted file mode 100644 index b047dc59d..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/ModalFormImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.ModalForm; -import org.geysermc.common.form.impl.response.ModalFormResponseImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; -import org.geysermc.common.form.response.ModalFormResponse; - -@Getter -@JsonAdapter(FormAdaptor.class) -public final class ModalFormImpl extends FormImpl implements ModalForm { - private final String title; - private final String content; - private final String button1; - private final String button2; - - private ModalFormImpl(String title, String content, String button1, String button2) { - super(FormType.MODAL_FORM); - - this.title = title; - this.content = content; - this.button1 = button1; - this.button2 = button2; - } - - public static ModalFormImpl of(String title, String content, String button1, String button2) { - return new ModalFormImpl(title, content, button1, button2); - } - - public ModalFormResponse parseResponse(String data) { - if (isClosed(data)) { - return ModalFormResponseImpl.closed(); - } - data = data.trim(); - - if ("true".equals(data)) { - return ModalFormResponseImpl.of(0, button1); - } else if ("false".equals(data)) { - return ModalFormResponseImpl.of(1, button2); - } - return ModalFormResponseImpl.invalid(); - } - - public static final class Builder extends FormImpl.Builder - implements ModalForm.Builder { - - private String content = ""; - private String button1 = ""; - private String button2 = ""; - - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button1(String button1) { - this.button1 = translate(button1); - return this; - } - - public Builder button2(String button2) { - this.button2 = translate(button2); - return this; - } - - @Override - public ModalForm build() { - ModalFormImpl form = of(title, content, button1, button2); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java b/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java deleted file mode 100644 index d5633ec05..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/SimpleFormImpl.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl; - -import com.google.gson.annotations.JsonAdapter; -import lombok.Getter; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.SimpleForm; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.impl.component.ButtonComponentImpl; -import org.geysermc.common.form.impl.response.SimpleFormResponseImpl; -import org.geysermc.common.form.impl.util.FormAdaptor; -import org.geysermc.common.form.response.SimpleFormResponse; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Getter -@JsonAdapter(FormAdaptor.class) -public final class SimpleFormImpl extends FormImpl implements SimpleForm { - private final String title; - private final String content; - private final List buttons; - - private SimpleFormImpl(String title, String content, List buttons) { - super(FormType.SIMPLE_FORM); - - this.title = title; - this.content = content; - this.buttons = Collections.unmodifiableList(buttons); - } - - public static SimpleFormImpl of(String title, String content, List buttons) { - return new SimpleFormImpl(title, content, buttons); - } - - public SimpleFormResponse parseResponse(String data) { - if (isClosed(data)) { - return SimpleFormResponseImpl.closed(); - } - data = data.trim(); - - int buttonId; - try { - buttonId = Integer.parseInt(data); - } catch (Exception exception) { - return SimpleFormResponseImpl.invalid(); - } - - if (buttonId >= buttons.size()) { - return SimpleFormResponseImpl.invalid(); - } - - return SimpleFormResponseImpl.of(buttonId, buttons.get(buttonId)); - } - - public static final class Builder extends FormImpl.Builder - implements SimpleForm.Builder { - - private final List buttons = new ArrayList<>(); - private String content = ""; - - public Builder content(String content) { - this.content = translate(content); - return this; - } - - public Builder button(String text, FormImage.Type type, String data) { - buttons.add(ButtonComponentImpl.of(translate(text), type, data)); - return this; - } - - public Builder button(String text, FormImage image) { - buttons.add(ButtonComponentImpl.of(translate(text), image)); - return this; - } - - public Builder button(String text) { - buttons.add(ButtonComponentImpl.of(translate(text))); - return this; - } - - @Override - public SimpleForm build() { - SimpleFormImpl form = of(title, content, buttons); - if (biResponseHandler != null) { - form.setResponseHandler(response -> biResponseHandler.accept(form, response)); - return form; - } - - form.setResponseHandler(responseHandler); - return form; - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java deleted file mode 100644 index 0efac1a6d..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/ButtonComponentImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.impl.util.FormImageImpl; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ButtonComponentImpl implements ButtonComponent { - private final String text; - private final FormImageImpl image; - - public static ButtonComponentImpl of(String text, FormImage image) { - return new ButtonComponentImpl(text, (FormImageImpl) image); - } - - public static ButtonComponentImpl of(String text, FormImage.Type type, String data) { - return of(text, FormImageImpl.of(type, data)); - } - - public static ButtonComponentImpl of(String text) { - return of(text, null); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/Component.java b/common/src/main/java/org/geysermc/common/form/impl/component/Component.java deleted file mode 100644 index ff2f29f87..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/Component.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; - -import java.util.Objects; - -@Getter -public abstract class Component { - private final ComponentType type; - private final String text; - - Component(ComponentType type, String text) { - Objects.requireNonNull(type, "Type cannot be null"); - Objects.requireNonNull(text, "Text cannot be null"); - - this.type = type; - this.text = text; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java deleted file mode 100644 index ae175ab74..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/DropdownComponentImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.DropdownComponent; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; - -@Getter -public final class DropdownComponentImpl extends Component implements DropdownComponent { - private final List options; - @SerializedName("default") - private final int defaultOption; - - private DropdownComponentImpl(String text, List options, int defaultOption) { - super(ComponentType.DROPDOWN, text); - this.options = Collections.unmodifiableList(options); - this.defaultOption = defaultOption; - } - - public static DropdownComponentImpl of(String text, List options, int defaultOption) { - if (defaultOption == -1 || defaultOption >= options.size()) { - defaultOption = 0; - } - return new DropdownComponentImpl(text, options, defaultOption); - } - - public static class Builder implements DropdownComponent.Builder { - private final List options = new ArrayList<>(); - private String text; - private int defaultOption = 0; - - @Override - public Builder text(String text) { - this.text = text; - return this; - } - - @Override - public Builder option(String option, boolean isDefault) { - options.add(option); - if (isDefault) { - defaultOption = options.size() - 1; - } - return this; - } - - @Override - public Builder option(String option) { - return option(option, false); - } - - @Override - public Builder defaultOption(int defaultOption) { - this.defaultOption = defaultOption; - return this; - } - - @Override - public DropdownComponentImpl build() { - return of(text, options, defaultOption); - } - - @Override - public DropdownComponentImpl translateAndBuild(Function translator) { - for (int i = 0; i < options.size(); i++) { - options.set(i, translator.apply(options.get(i))); - } - - return of(translator.apply(text), options, defaultOption); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java deleted file mode 100644 index ba741304b..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/InputComponentImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.InputComponent; - -@Getter -public final class InputComponentImpl extends Component implements InputComponent { - private final String placeholder; - @SerializedName("default") - private final String defaultText; - - private InputComponentImpl(String text, String placeholder, String defaultText) { - super(ComponentType.INPUT, text); - this.placeholder = placeholder; - this.defaultText = defaultText; - } - - public static InputComponentImpl of(String text, String placeholder, String defaultText) { - return new InputComponentImpl(text, placeholder, defaultText); - } - - public static InputComponentImpl of(String text, String placeholder) { - return new InputComponentImpl(text, placeholder, ""); - } - - public static InputComponentImpl of(String text) { - return new InputComponentImpl(text, "", ""); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java deleted file mode 100644 index 4dacb8406..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/LabelComponentImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.LabelComponent; - -@Getter -public final class LabelComponentImpl extends Component implements LabelComponent { - private LabelComponentImpl(String text) { - super(ComponentType.LABEL, text); - } - - public static LabelComponentImpl of(String text) { - return new LabelComponentImpl(text); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java deleted file mode 100644 index 29fc18dc8..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/SliderComponentImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.SliderComponent; - -@Getter -public final class SliderComponentImpl extends Component implements SliderComponent { - private final float min; - private final float max; - private final int step; - @SerializedName("default") - private final float defaultValue; - - private SliderComponentImpl(String text, float min, float max, int step, float defaultValue) { - super(ComponentType.SLIDER, text); - this.min = min; - this.max = max; - this.step = step; - this.defaultValue = defaultValue; - } - - public static SliderComponentImpl of(String text, float min, float max, int step, - float defaultValue) { - min = Math.max(min, 0f); - max = Math.max(max, min); - - if (step < 1) { - step = 1; - } - - if (defaultValue == -1f) { - defaultValue = (int) Math.floor(min + max / 2D); - } - - return new SliderComponentImpl(text, min, max, step, defaultValue); - } - - public static SliderComponentImpl of(String text, float min, float max, int step) { - return of(text, min, max, step, -1); - } - - public static SliderComponentImpl of(String text, float min, float max, float defaultValue) { - return of(text, min, max, -1, defaultValue); - } - - public static SliderComponentImpl of(String text, float min, float max) { - return of(text, min, max, -1, -1); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java deleted file mode 100644 index dc87c54a5..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/StepSliderComponentImpl.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.StepSliderComponent; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; - -@Getter -public final class StepSliderComponentImpl extends Component implements StepSliderComponent { - private final List steps; - @SerializedName("default") - private final int defaultStep; - - private StepSliderComponentImpl(String text, List steps, int defaultStep) { - super(ComponentType.STEP_SLIDER, text); - this.steps = Collections.unmodifiableList(steps); - this.defaultStep = defaultStep; - } - - public static StepSliderComponentImpl of(String text, List steps, int defaultStep) { - if (text == null) { - text = ""; - } - - if (defaultStep >= steps.size() || defaultStep == -1) { - defaultStep = 0; - } - - return new StepSliderComponentImpl(text, steps, defaultStep); - } - - public static StepSliderComponentImpl of(String text, int defaultStep, String... steps) { - return of(text, Arrays.asList(steps), defaultStep); - } - - public static StepSliderComponentImpl of(String text, String... steps) { - return of(text, 0, steps); - } - - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(String text) { - return builder().text(text); - } - - public static final class Builder implements StepSliderComponent.Builder { - private final List steps = new ArrayList<>(); - private String text; - private int defaultStep; - - public Builder text(String text) { - this.text = text; - return this; - } - - public Builder step(String step, boolean defaultStep) { - steps.add(step); - if (defaultStep) { - this.defaultStep = steps.size() - 1; - } - return this; - } - - public Builder step(String step) { - return step(step, false); - } - - public Builder defaultStep(int defaultStep) { - this.defaultStep = defaultStep; - return this; - } - - public StepSliderComponentImpl build() { - return of(text, steps, defaultStep); - } - - public StepSliderComponentImpl translateAndBuild(Function translator) { - for (int i = 0; i < steps.size(); i++) { - steps.set(i, translator.apply(steps.get(i))); - } - - return of(translator.apply(text), steps, defaultStep); - } - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java b/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java deleted file mode 100644 index 0e29462b3..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/component/ToggleComponentImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.component; - -import com.google.gson.annotations.SerializedName; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.component.ToggleComponent; - -public final class ToggleComponentImpl extends Component implements ToggleComponent { - @SerializedName("default") - private final boolean defaultValue; - - private ToggleComponentImpl(String text, boolean defaultValue) { - super(ComponentType.TOGGLE, text); - this.defaultValue = defaultValue; - } - - public static ToggleComponentImpl of(String text, boolean defaultValue) { - return new ToggleComponentImpl(text, defaultValue); - } - - public static ToggleComponentImpl of(String text) { - return of(text, false); - } - - public boolean getDefaultValue() { - return defaultValue; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java deleted file mode 100644 index cc0ce64b7..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/response/CustomFormResponseImpl.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.response; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonPrimitive; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.geysermc.common.form.component.Component; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.response.CustomFormResponse; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -public final class CustomFormResponseImpl implements CustomFormResponse { - private static final Gson GSON = new Gson(); - private static final CustomFormResponseImpl CLOSED = - new CustomFormResponseImpl(true, false, null, null); - private static final CustomFormResponseImpl INVALID = - new CustomFormResponseImpl(false, true, null, null); - private final boolean closed; - private final boolean invalid; - - private final JsonArray responses; - private final List componentTypes; - - private int index = -1; - - public static CustomFormResponseImpl closed() { - return CLOSED; - } - - public static CustomFormResponseImpl invalid() { - return INVALID; - } - - public static CustomFormResponseImpl of(CustomFormImpl form, String responseData) { - JsonArray responses = GSON.fromJson(responseData, JsonArray.class); - List types = new ArrayList<>(); - for (Component component : form.getContent()) { - types.add(component.getType()); - } - return of(types, responses); - } - - public static CustomFormResponseImpl of(List componentTypes, - JsonArray responses) { - if (componentTypes.size() != responses.size()) { - return invalid(); - } - - return new CustomFormResponseImpl(false, false, responses, - Collections.unmodifiableList(componentTypes)); - } - - @Override - @SuppressWarnings("unchecked") - public T next(boolean includeLabels) { - if (!hasNext()) { - return null; - } - - while (++index < responses.size()) { - ComponentType type = componentTypes.get(index); - if (type == ComponentType.LABEL && !includeLabels) { - continue; - } - return (T) getDataFromType(type, index); - } - return null; // we don't have anything to check anymore - } - - @Override - public T next() { - return next(false); - } - - @Override - public void skip(int amount) { - index += amount; - } - - @Override - public void skip() { - skip(1); - } - - @Override - public void index(int index) { - this.index = index; - } - - @Override - public boolean hasNext() { - return responses.size() > index + 1; - } - - @Override - public JsonPrimitive get(int index) { - try { - return responses.get(index).getAsJsonPrimitive(); - } catch (IllegalStateException exception) { - wrongType(index, "a primitive"); - return null; - } - } - - @Override - public int getDropdown(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "dropdown"); - } - return primitive.getAsInt(); - } - - @Override - public String getInput(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isString()) { - wrongType(index, "input"); - } - return primitive.getAsString(); - } - - @Override - public float getSlider(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "slider"); - } - return primitive.getAsFloat(); - } - - @Override - public int getStepSlide(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isNumber()) { - wrongType(index, "step slider"); - } - return primitive.getAsInt(); - } - - @Override - public boolean getToggle(int index) { - JsonPrimitive primitive = get(index); - if (!primitive.isBoolean()) { - wrongType(index, "toggle"); - } - return primitive.getAsBoolean(); - } - - private Object getDataFromType(ComponentType type, int index) { - switch (type) { - case DROPDOWN: - return getDropdown(index); - case INPUT: - return getInput(index); - case SLIDER: - return getSlider(index); - case STEP_SLIDER: - return getStepSlide(index); - case TOGGLE: - return getToggle(index); - default: - return null; // label e.g. is always null - } - } - - private void wrongType(int index, String expected) { - throw new IllegalStateException(String.format( - "Expected %s on %s, got %s", - expected, index, responses.get(index).toString())); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java deleted file mode 100644 index 6d20431cd..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/response/ModalFormResponseImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.response; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.response.FormResponse; -import org.geysermc.common.form.response.ModalFormResponse; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class ModalFormResponseImpl implements ModalFormResponse { - private static final ModalFormResponseImpl CLOSED = - new ModalFormResponseImpl(true, false, -1, null); - private static final ModalFormResponseImpl INVALID = - new ModalFormResponseImpl(false, true, -1, null); - private final boolean closed; - private final boolean invalid; - - private final int clickedButtonId; - private final String clickedButtonText; - - public static ModalFormResponseImpl closed() { - return CLOSED; - } - - public static ModalFormResponseImpl invalid() { - return INVALID; - } - - public static ModalFormResponseImpl of(int clickedButtonId, String clickedButtonText) { - return new ModalFormResponseImpl(false, false, clickedButtonId, clickedButtonText); - } - - public boolean getResult() { - return clickedButtonId == 0; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java b/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java deleted file mode 100644 index 24bc39366..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/response/SimpleFormResponseImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.response; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.response.SimpleFormResponse; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class SimpleFormResponseImpl implements SimpleFormResponse { - private static final SimpleFormResponseImpl CLOSED = - new SimpleFormResponseImpl(true, false, -1, null); - private static final SimpleFormResponseImpl INVALID = - new SimpleFormResponseImpl(false, true, -1, null); - private final boolean closed; - private final boolean invalid; - - private final int clickedButtonId; - private final ButtonComponent clickedButton; - - public static SimpleFormResponseImpl closed() { - return CLOSED; - } - - public static SimpleFormResponseImpl invalid() { - return INVALID; - } - - public static SimpleFormResponseImpl of(int clickedButtonId, ButtonComponent clickedButton) { - return new SimpleFormResponseImpl(false, false, clickedButtonId, clickedButton); - } - - public String getClickedButtonText() { - return clickedButton.getText(); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java deleted file mode 100644 index 11d7eefe4..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/util/FormAdaptor.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.util; - -import com.google.gson.*; -import com.google.gson.reflect.TypeToken; -import org.geysermc.common.form.FormImage; -import org.geysermc.common.form.component.ButtonComponent; -import org.geysermc.common.form.component.Component; -import org.geysermc.common.form.component.ComponentType; -import org.geysermc.common.form.impl.CustomFormImpl; -import org.geysermc.common.form.impl.FormImpl; -import org.geysermc.common.form.impl.ModalFormImpl; -import org.geysermc.common.form.impl.SimpleFormImpl; -import org.geysermc.common.form.impl.component.ButtonComponentImpl; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - -public final class FormAdaptor implements JsonDeserializer>, JsonSerializer> { - private static final Type LIST_BUTTON_TYPE = - new TypeToken>() {}.getType(); - - @Override - public FormImpl deserialize(JsonElement jsonElement, Type typeOfT, - JsonDeserializationContext context) - throws JsonParseException { - - if (!jsonElement.isJsonObject()) { - throw new JsonParseException("Form has to be a JsonObject"); - } - JsonObject json = jsonElement.getAsJsonObject(); - - if (typeOfT == SimpleFormImpl.class) { - String title = json.get("title").getAsString(); - String content = json.get("content").getAsString(); - List buttons = context - .deserialize(json.get("buttons"), LIST_BUTTON_TYPE); - return SimpleFormImpl.of(title, content, buttons); - } - - if (typeOfT == ModalFormImpl.class) { - String title = json.get("title").getAsString(); - String content = json.get("content").getAsString(); - String button1 = json.get("button1").getAsString(); - String button2 = json.get("button2").getAsString(); - return ModalFormImpl.of(title, content, button1, button2); - } - - if (typeOfT == CustomFormImpl.class) { - String title = json.get("title").getAsString(); - FormImage icon = context.deserialize(json.get("icon"), FormImageImpl.class); - List content = new ArrayList<>(); - - JsonArray contentArray = json.getAsJsonArray("content"); - for (JsonElement contentElement : contentArray) { - String typeName = contentElement.getAsJsonObject().get("type").getAsString(); - - ComponentType type = ComponentType.getByName(typeName); - if (type == null) { - throw new JsonParseException("Failed to find Component type " + typeName); - } - - content.add(context.deserialize(contentElement, type.getComponentClass())); - } - return CustomFormImpl.of(title, icon, content); - } - return null; - } - - @Override - public JsonElement serialize(FormImpl src, Type typeOfSrc, JsonSerializationContext context) { - JsonObject result = new JsonObject(); - result.add("type", context.serialize(src.getType())); - - if (typeOfSrc == SimpleFormImpl.class) { - SimpleFormImpl form = (SimpleFormImpl) src; - - result.addProperty("title", form.getTitle()); - result.addProperty("content", form.getContent()); - result.add("buttons", context.serialize(form.getButtons(), LIST_BUTTON_TYPE)); - return result; - } - - if (typeOfSrc == ModalFormImpl.class) { - ModalFormImpl form = (ModalFormImpl) src; - - result.addProperty("title", form.getTitle()); - result.addProperty("content", form.getContent()); - result.addProperty("button1", form.getButton1()); - result.addProperty("button2", form.getButton2()); - return result; - } - - if (typeOfSrc == CustomFormImpl.class) { - CustomFormImpl form = (CustomFormImpl) src; - - result.addProperty("title", form.getTitle()); - result.add("icon", context.serialize(form.getIcon())); - result.add("content", context.serialize(form.getContent())); - return result; - } - return null; - } -} diff --git a/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java b/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java deleted file mode 100644 index 4b8248e94..000000000 --- a/common/src/main/java/org/geysermc/common/form/impl/util/FormImageImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.impl.util; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.FormImage; - -@Getter -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class FormImageImpl implements FormImage { - private final Type type; - private final String data; - - public static FormImageImpl of(Type type, String data) { - return new FormImageImpl(type, data); - } - - public static FormImageImpl of(String type, String data) { - return of(Type.getByName(type), data); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java deleted file mode 100644 index d4d0fd99e..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/CustomFormResponse.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -import com.google.gson.JsonArray; -import com.google.gson.JsonPrimitive; -import org.geysermc.common.form.component.ComponentType; - -import java.util.List; - -public interface CustomFormResponse extends FormResponse { - JsonArray getResponses(); - - List getComponentTypes(); - - T next(boolean includeLabels); - - T next(); - - void skip(int amount); - - void skip(); - - void index(int index); - - boolean hasNext(); - - JsonPrimitive get(int index); - - int getDropdown(int index); - - String getInput(int index); - - float getSlider(int index); - - int getStepSlide(int index); - - boolean getToggle(int index); -} diff --git a/common/src/main/java/org/geysermc/common/form/response/FormResponse.java b/common/src/main/java/org/geysermc/common/form/response/FormResponse.java deleted file mode 100644 index 7c62236f6..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/FormResponse.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -public interface FormResponse { - boolean isClosed(); - boolean isInvalid(); - - default boolean isCorrect() { - return !isClosed() && !isInvalid(); - } -} diff --git a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java deleted file mode 100644 index 6e594e0ee..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/ModalFormResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -public interface ModalFormResponse extends FormResponse { - int getClickedButtonId(); - - String getClickedButtonText(); - - boolean getResult(); -} diff --git a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java b/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java deleted file mode 100644 index 528a79ed1..000000000 --- a/common/src/main/java/org/geysermc/common/form/response/SimpleFormResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.common.form.response; - -import org.geysermc.common.form.component.ButtonComponent; - -public interface SimpleFormResponse extends FormResponse { - int getClickedButtonId(); - - ButtonComponent getClickedButton(); -} diff --git a/connector/pom.xml b/connector/pom.xml index 7c44ddfd2..3fb7634c0 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -15,7 +15,6 @@ org.geysermc common 1.2.0-SNAPSHOT - compile com.fasterxml.jackson.dataformat diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 5f0a844b0..c461b1d07 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -57,8 +57,6 @@ import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.FormBuilder; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; @@ -76,6 +74,8 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.*; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.util.FormBuilder; import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.util.BedrockData; @@ -607,7 +607,7 @@ public class GeyserSession implements CommandSender { return this.upstream.getAddress(); } - public void sendForm(Form form) { + public void sendForm(Form form) { formCache.showForm(form); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 1f112e9ce..26589a82e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; -import org.geysermc.common.form.Form; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.cumulus.Form; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -41,16 +41,16 @@ import java.util.function.Consumer; @RequiredArgsConstructor public class FormCache { private final AtomicInteger formId = new AtomicInteger(0); - private final Int2ObjectMap> forms = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap forms = new Int2ObjectOpenHashMap<>(); private final GeyserSession session; - public int addForm(Form form) { + public int addForm(Form form) { int windowId = formId.getAndIncrement(); forms.put(windowId, form); return windowId; } - public int showForm(Form form) { + public int showForm(Form form) { int windowId = addForm(form); ModalFormRequestPacket formRequestPacket = new ModalFormRequestPacket(); @@ -62,15 +62,15 @@ public class FormCache { NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); latencyPacket.setFromServer(true); latencyPacket.setTimestamp(-System.currentTimeMillis()); - session.getConnector().getGeneralThreadPool().schedule(() -> - session.sendUpstreamPacket(latencyPacket), + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(latencyPacket), 500, TimeUnit.MILLISECONDS); return windowId; } public void handleResponse(ModalFormResponsePacket response) { - Form form = forms.get(response.getFormId()); + Form form = forms.get(response.getFormId()); if (form == null) { return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index 01bf07b9c..99dde0f6c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -27,15 +27,14 @@ package org.geysermc.connector.network.translators.bedrock; import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket; import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket; -import org.geysermc.common.form.CustomForm; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.SettingsUtils; +import org.geysermc.cumulus.CustomForm; @Translator(packet = ServerSettingsRequestPacket.class) public class BedrockServerSettingsRequestTranslator extends PacketTranslator { - @Override public void translate(ServerSettingsRequestPacket packet, GeyserSession session) { CustomForm window = SettingsUtils.buildForm(session); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java index 57d64ac75..337dc0b74 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaPluginMessageTranslator.java @@ -28,13 +28,13 @@ package org.geysermc.connector.network.translators.java; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; -import org.geysermc.common.form.Form; -import org.geysermc.common.form.FormType; -import org.geysermc.common.form.Forms; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.Forms; +import org.geysermc.cumulus.util.FormType; import java.nio.charset.StandardCharsets; @@ -63,7 +63,7 @@ public class JavaPluginMessageTranslator extends PacketTranslator form = Forms.fromJson(dataString, type.getTypeClass()); + Form form = Forms.fromJson(dataString, type); form.setResponseHandler(response -> { byte[] raw = response.getBytes(StandardCharsets.UTF_8); byte[] finalData = new byte[raw.length + 2]; diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 84997deb6..af41a70ec 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -34,14 +34,14 @@ import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.SimpleForm; -import org.geysermc.common.form.response.CustomFormResponse; -import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; +import org.geysermc.cumulus.CustomForm; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.CustomFormResponse; +import org.geysermc.cumulus.response.SimpleFormResponse; import javax.crypto.SecretKey; import java.io.IOException; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index d3d1c703b..e4d762a57 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -27,12 +27,12 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; -import org.geysermc.common.form.CustomForm; -import org.geysermc.common.form.component.DropdownComponent; -import org.geysermc.common.form.response.CustomFormResponse; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.WorldManager; +import org.geysermc.cumulus.CustomForm; +import org.geysermc.cumulus.component.DropdownComponent; +import org.geysermc.cumulus.response.CustomFormResponse; public class SettingsUtils { diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index 41084504a..ea0034c96 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -28,11 +28,11 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.MagicValues; import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.data.game.statistic.*; -import org.geysermc.common.form.SimpleForm; -import org.geysermc.common.form.response.SimpleFormResponse; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; import java.util.Map; import java.util.regex.Matcher; From 9500da2ef0901a7288b63e229d8bafd716ff389d Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 12 Dec 2020 01:39:09 +0100 Subject: [PATCH 17/51] Small changes --- .../connector/network/session/cache/FormCache.java | 2 +- .../connector/utils/LoginEncryptionUtils.java | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index 26589a82e..cdd8a01ac 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -77,7 +77,7 @@ public class FormCache { Consumer responseConsumer = form.getResponseHandler(); if (responseConsumer != null) { - responseConsumer.accept(response.getFormData().trim()); + responseConsumer.accept(response.getFormData()); } removeWindow(response.getFormId()); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index af41a70ec..39e9e1738 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -154,17 +154,15 @@ public class LoginEncryptionUtils { } public static void buildAndShowLoginWindow(GeyserSession session) { - String userLanguage = session.getClientData().getLanguageCode(); - session.sendForm( SimpleForm.builder() - .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.notice.title") .content("geyser.auth.login.form.notice.desc") .button("geyser.auth.login.form.notice.btn_login") // id = 0 .button("geyser.auth.login.form.notice.btn_disconnect") .responseHandler((form, responseData) -> { - SimpleFormResponse response = form.parseResponse(responseData.trim()); + SimpleFormResponse response = form.parseResponse(responseData); if (!response.isCorrect()) { buildAndShowLoginWindow(session); return; @@ -180,16 +178,15 @@ public class LoginEncryptionUtils { } public static void buildAndShowLoginDetailsWindow(GeyserSession session) { - String userLanguage = session.getLocale(); session.sendForm( CustomForm.builder() - .translator(LanguageUtils::getPlayerLocaleString, userLanguage) + .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.details.title") .label("geyser.auth.login.form.details.desc") .input("geyser.auth.login.form.details.email", "account@geysermc.org", "") .input("geyser.auth.login.form.details.pass", "123456", "") .responseHandler((form, responseData) -> { - CustomFormResponse response = form.parseResponse(responseData.trim()); + CustomFormResponse response = form.parseResponse(responseData); if (!response.isCorrect()) { buildAndShowLoginDetailsWindow(session); return; From e69ad0e560073651ca38bcb267b01f4762f9e0bd Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 15 Dec 2020 22:31:14 +0100 Subject: [PATCH 18/51] Geyser can compile now --- .../connector/command/defaults/SettingsCommand.java | 6 +++--- .../connector/network/session/auth/BedrockClientData.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java index 8f09c7747..2b72b77c7 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/SettingsCommand.java @@ -56,9 +56,9 @@ public class SettingsCommand extends GeyserCommand { } } } - if (session == null) return; - SettingsUtils.buildForm(session); - session.sendForm(session.getSettingsForm(), SettingsUtils.SETTINGS_FORM_ID); + if (session != null) { + session.sendForm(SettingsUtils.buildForm(session)); + } } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 905245ee4..f864d022d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -31,7 +31,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Charsets; import lombok.Getter; -import org.geysermc.connector.utils.SkinProvider; +import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.RawSkin; From 2f1acb1e6f9429bc9c09ad69bb862cf59235648e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 19 Dec 2020 22:45:34 +0100 Subject: [PATCH 19/51] Separate method for Base64 length calc. Added offset method in RawSkin --- .../geysermc/floodgate/util/Base64Utils.java | 35 +++++++++++++++++++ .../org/geysermc/floodgate/util/RawSkin.java | 11 ++++-- 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java new file mode 100644 index 000000000..388b54838 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +public class Base64Utils { + public static int getEncodedLength(int length) { + if (length <= 0) { + return -1; + } + return 4 * ((length + 2) / 3); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index 152cbec5a..6759ffa3c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -45,13 +45,18 @@ public final class RawSkin { } public static RawSkin decode(byte[] data) throws InvalidFormatException { + return decode(data, 0); + } + + public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { + // offset is an amount of bytes before the Base64 starts if (data == null) { return null; } - int maxEncodedLength = 4 * (((64 * 64 * 4 + 9) + 2) / 3); + int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); // if the RawSkin is longer then the max Java Edition skin length - if (data.length > maxEncodedLength) { + if ((data.length - offset) > maxEncodedLength) { throw new InvalidFormatException(format( "Encoded data cannot be longer then %s bytes! Got %s", maxEncodedLength, data.length @@ -59,7 +64,7 @@ public final class RawSkin { } // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if (data.length < 4 * ((9 + 2) / 3)) { + if ((data.length - offset) < Base64Utils.getEncodedLength(9)) { throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); } From d6c2507bb5480d29ab8ca37358f96bd22a7f74df Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 20 Dec 2020 17:34:46 +0100 Subject: [PATCH 20/51] Fixed some bugs --- .../geysermc/floodgate/util/BedrockData.java | 5 +++-- .../org/geysermc/floodgate/util/RawSkin.java | 21 ++++++++++++------- .../connector/utils/PluginMessageUtils.java | 7 +------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 08c1e28d3..cc8f9eeea 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -76,7 +76,7 @@ public final class BedrockData { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, Boolean.parseBoolean(split[9]), split.length + linkedPlayer, "1".equals(split[8]), split.length ); } @@ -93,6 +93,7 @@ public final class BedrockData { // The format is the same as the order of the fields in this class return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + - fromProxy + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null"); + (fromProxy ? 1 : 0) + '\0' + + (linkedPlayer != null ? linkedPlayer.toString() : "null"); } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java index 6759ffa3c..470b7e24a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java @@ -26,7 +26,6 @@ package org.geysermc.floodgate.util; import lombok.AllArgsConstructor; -import lombok.ToString; import java.nio.ByteBuffer; import java.util.Base64; @@ -34,7 +33,6 @@ import java.util.Base64; import static java.lang.String.format; @AllArgsConstructor -@ToString public final class RawSkin { public int width; public int height; @@ -44,11 +42,20 @@ public final class RawSkin { private RawSkin() { } - public static RawSkin decode(byte[] data) throws InvalidFormatException { - return decode(data, 0); + public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { + if (data == null || offset < 0 || data.length <= offset) { + return null; + } + if (offset == 0) { + return decode(data); + } + + byte[] rawSkin = new byte[data.length - offset]; + System.arraycopy(data, offset, rawSkin, 0, rawSkin.length); + return decode(rawSkin); } - public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { + public static RawSkin decode(byte[] data) throws InvalidFormatException { // offset is an amount of bytes before the Base64 starts if (data == null) { return null; @@ -56,7 +63,7 @@ public final class RawSkin { int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); // if the RawSkin is longer then the max Java Edition skin length - if ((data.length - offset) > maxEncodedLength) { + if (data.length > maxEncodedLength) { throw new InvalidFormatException(format( "Encoded data cannot be longer then %s bytes! Got %s", maxEncodedLength, data.length @@ -64,7 +71,7 @@ public final class RawSkin { } // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if ((data.length - offset) < Base64Utils.getEncodedLength(9)) { + if (data.length < Base64Utils.getEncodedLength(9)) { throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 4c7547d3a..475380ab3 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -42,12 +42,7 @@ public class PluginMessageUtils { .put(data) .array(); - data = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); - FLOODGATE_REGISTER_DATA = - ByteBuffer.allocate(data.length + getVarIntLength(data.length)) - .put(writeVarInt(data.length)) - .put(data) - .array(); + FLOODGATE_REGISTER_DATA = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); } /** From 9fbf7047a1db4845e48965c9752ef4ce6ee1b451 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 31 Dec 2020 17:18:39 +0100 Subject: [PATCH 21/51] Fixed a NoSuchMethodError --- .../main/java/org/geysermc/floodgate/crypto/AesCipher.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java index 2627584f6..f602f4be0 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java @@ -31,6 +31,7 @@ import lombok.RequiredArgsConstructor; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.Key; import java.security.SecureRandom; @@ -99,13 +100,15 @@ public final class AesCipher implements FloodgateCipher { } ivLength = buffer.position() - mark - 1; // don't include the splitter itself - buffer.position(mark); // reset to the pre-while index + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(mark); // reset to the pre-while index } byte[] iv = new byte[ivLength]; buffer.get(iv); - buffer.position(buffer.position() + 1); // skip splitter + // don't remove this cast, it'll cause problems if you remove it + ((Buffer) buffer).position(buffer.position() + 1); // skip splitter byte[] cipherText = new byte[buffer.remaining()]; buffer.get(cipherText); From ce2734d3b97aa36728a32e7517e8a9324f167fec Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 12 Jan 2021 20:55:11 +0100 Subject: [PATCH 22/51] Allow BedrockData and LinkedPlayer cloning --- .../main/java/org/geysermc/floodgate/util/BedrockData.java | 7 ++++++- .../java/org/geysermc/floodgate/util/LinkedPlayer.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 9064b3f36..5f449fe2d 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -36,7 +36,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) -public final class BedrockData { +public final class BedrockData implements Cloneable { public static final int EXPECTED_LENGTH = 10; private final String version; @@ -96,4 +96,9 @@ public final class BedrockData { (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null"); } + + @Override + public BedrockData clone() throws CloneNotSupportedException { + return (BedrockData) super.clone(); + } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 1e8d67c27..950f0eb55 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -34,7 +34,7 @@ import java.util.UUID; @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public final class LinkedPlayer { +public final class LinkedPlayer implements Cloneable { /** * The Java username of the linked player */ @@ -74,4 +74,9 @@ public final class LinkedPlayer { public String toString() { return javaUsername + ';' + javaUniqueId.toString() + ';' + bedrockId.toString(); } + + @Override + public LinkedPlayer clone() throws CloneNotSupportedException { + return (LinkedPlayer) super.clone(); + } } From ad7ffabb6d6f74190d7ddbcb0bf81a5ca726e072 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 24 Jan 2021 01:29:47 +0100 Subject: [PATCH 23/51] Make the identifier more unique --- .../floodgate/crypto/FloodgateCipher.java | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java index 4869531e2..69c8f9f6d 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java @@ -37,9 +37,24 @@ import java.security.Key; * Responsible for both encrypting and decrypting data */ public interface FloodgateCipher { - byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); + // use invalid username characters at the beginning and the end of the identifier, + // to make sure that it doesn't get messed up with usernames + byte[] IDENTIFIER = "^Floodgate^".getBytes(StandardCharsets.UTF_8); int HEADER_LENGTH = IDENTIFIER.length; + static boolean hasHeader(String data) { + if (data.length() < IDENTIFIER.length) { + return false; + } + + for (int i = 0; i < IDENTIFIER.length; i++) { + if (IDENTIFIER[i] != data.charAt(i)) { + return false; + } + } + return true; + } + /** * Initializes the instance by giving it the key it needs to encrypt or decrypt data * @@ -57,8 +72,7 @@ public interface FloodgateCipher { byte[] encrypt(byte[] data) throws Exception; /** - * Encrypts data from a String.
- * This method internally calls {@link #encrypt(byte[])} + * Encrypts data from a String.
This method internally calls {@link #encrypt(byte[])} * * @param data the data to encrypt * @return the encrypted data @@ -78,9 +92,8 @@ public interface FloodgateCipher { byte[] decrypt(byte[] data) throws Exception; /** - * Decrypts a byte[] and turn it into a String.
- * This method internally calls {@link #decrypt(byte[])} - * and converts the returned byte[] into a String. + * Decrypts a byte[] and turn it into a String.
This method internally calls {@link + * #decrypt(byte[])} and converts the returned byte[] into a String. * * @param data the data to encrypt * @return the decrypted data in a UTF-8 String @@ -95,9 +108,8 @@ public interface FloodgateCipher { } /** - * Decrypts a String.
- * This method internally calls {@link #decrypt(byte[])} - * by converting the UTF-8 String into a byte[] + * Decrypts a String.
This method internally calls {@link #decrypt(byte[])} by converting + * the UTF-8 String into a byte[] * * @param data the data to decrypt * @return the decrypted data in a byte[] @@ -108,8 +120,8 @@ public interface FloodgateCipher { } /** - * Checks if the header is valid. - * This method will throw an InvalidFormatException when the header is invalid. + * Checks if the header is valid. This method will throw an InvalidFormatException when the + * header is invalid. * * @param data the data to check * @throws InvalidFormatException when the header is invalid @@ -142,19 +154,6 @@ public interface FloodgateCipher { } } - static boolean hasHeader(String data) { - if (data.length() < IDENTIFIER.length) { - return false; - } - - for (int i = 0; i < IDENTIFIER.length; i++) { - if (IDENTIFIER[i] != data.charAt(i)) { - return false; - } - } - return true; - } - @Data @AllArgsConstructor class HeaderResult { From cf149b58e07d80e49dd7b9b8cd974e0d27304cad Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 2 Feb 2021 00:46:46 +0100 Subject: [PATCH 24/51] Fixed remaining merge conflicts --- .../connector/command/CommandManager.java | 2 +- .../command/defaults/AdvancementsCommand.java | 13 +- .../network/session/GeyserSession.java | 4 +- .../session/cache/AdvancementsCache.java | 261 ++++++++---------- .../java/JavaAdvancementsTabTranslator.java | 7 +- .../connector/utils/LoginEncryptionUtils.java | 7 +- 6 files changed, 120 insertions(+), 174 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 9f675ae81..71eb2c742 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -53,7 +53,7 @@ public abstract class CommandManager { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancementsCommand( "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java index 2ef23381b..8ace83840 100644 --- a/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java +++ b/connector/src/main/java/org/geysermc/connector/command/defaults/AdvancementsCommand.java @@ -25,25 +25,20 @@ package org.geysermc.connector.command.defaults; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.command.GeyserCommand; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.AdvancementsCache; public class AdvancementsCommand extends GeyserCommand { - - public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) { + public AdvancementsCommand(String name, String description, String permission) { super(name, description, permission); } @Override public void execute(GeyserSession session, CommandSender sender, String[] args) { - if (session == null) return; - - SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm(); - session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID); + if (session != null) { + session.getAdvancementsCache().buildAndShowMenuForm(); + } } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index acfd8ae21..8bdb7f2b1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -489,7 +489,7 @@ public class GeyserSession implements CommandSender { MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID); MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode(); - LoginEncryptionUtils.showMicrosoftCodeWindow(this, response); + LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response); // This just looks cool SetTimePacket packet = new SetTimePacket(); @@ -605,7 +605,7 @@ public class GeyserSession implements CommandSender { // Let the user know there locale may take some time to download // as it has to be extracted from a JAR - if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { + if (locale.equalsIgnoreCase("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { // This should probably be left hardcoded as it will only show for en_us clients sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time"); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java index 369967acc..d20eb11dd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -29,26 +29,20 @@ import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; import lombok.Getter; import lombok.Setter; -import org.geysermc.common.window.SimpleFormWindow; -import org.geysermc.common.window.button.FormButton; -import org.geysermc.common.window.response.SimpleFormResponse; +import lombok.experimental.Accessors; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.GeyserAdvancement; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; +import org.geysermc.cumulus.SimpleForm; +import org.geysermc.cumulus.response.SimpleFormResponse; import java.util.HashMap; import java.util.List; import java.util.Map; public class AdvancementsCache { - - // Different form IDs - public static final int ADVANCEMENTS_MENU_FORM_ID = 1341; - public static final int ADVANCEMENTS_LIST_FORM_ID = 1342; - public static final int ADVANCEMENT_INFO_FORM_ID = 1343; - /** * Stores the player's advancement progress */ @@ -64,7 +58,7 @@ public class AdvancementsCache { /** * Stores player's chosen advancement's ID and title for use in form creators. */ - @Setter + @Setter @Accessors(chain = true) private String currentAdvancementCategoryId = null; private final GeyserSession session; @@ -74,73 +68,128 @@ public class AdvancementsCache { } /** - * Build a form with all advancement categories - * - * @return The built advancement category menu + * Build and send a form with all advancement categories */ - public SimpleFormWindow buildMenuForm() { - // Cache the language for cleaner access - String language = session.getClientData().getLanguageCode(); + public void buildAndShowMenuForm() { + SimpleForm.Builder builder = + SimpleForm.builder() + .translator(LocaleUtils::getLocaleString, session.getLocale()) + .title("gui.advancements"); - // Created menu window for advancement categories - SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), ""); + boolean hasAdvancements = false; for (Map.Entry advancement : storedAdvancements.entrySet()) { if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language))); + hasAdvancements = true; + builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale())); } } - if (window.getButtons().isEmpty()) { - window.setContent(LocaleUtils.getLocaleString("advancements.empty", language)); + if (!hasAdvancements) { + builder.content("advancements.empty"); } - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + return; + } + + String id = ""; + + int advancementIndex = 0; + for (Map.Entry advancement : storedAdvancements.entrySet()) { + if (advancement.getValue().getParentId() == null) { // Root advancement + if (advancementIndex == response.getClickedButtonId()) { + id = advancement.getKey(); + break; + } else { + advancementIndex++; + } + } + } + + if (!id.equals("")) { + if (id.equals(currentAdvancementCategoryId)) { + // The server thinks we are already on this tab + buildAndShowListForm(); + } else { + // Send a packet indicating that we intend to open this particular advancement window + ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); + session.sendDownstreamPacket(packet); + // Wait for a response there + } + } + }); + + session.sendForm(builder); } /** - * Builds the list of advancements - * - * @return The built list form + * Build and send the list of advancements */ - public SimpleFormWindow buildListForm() { - // Cache the language for easier access - String language = session.getLocale(); - String id = currentAdvancementCategoryId; + public void buildAndShowListForm() { GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId); + String language = session.getLocale(); - // Create the window - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language), - MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); + SimpleForm.Builder builder = + SimpleForm.builder() + .title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language)) + .content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language)); - if (id != null) { - for (Map.Entry advancementEntry : storedAdvancements.entrySet()) { - GeyserAdvancement advancement = advancementEntry.getValue(); + if (currentAdvancementCategoryId != null) { + for (GeyserAdvancement advancement : storedAdvancements.values()) { if (advancement != null) { if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) { - boolean earned = isEarned(advancement); - - if (earned || !advancement.getDisplayData().isShowToast()) { - window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } else { - window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n")); - } + boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast(); + builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n'); } } } } - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); + builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language)); - return window; + builder.responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (!response.isCorrect()) { + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + return; + } + + GeyserAdvancement advancement = null; + int advancementIndex = 0; + // Loop around to find the advancement that the client pressed + for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { + if (advancementEntry.getParentId() != null && + currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { + if (advancementIndex == response.getClickedButtonId()) { + advancement = advancementEntry; + break; + } else { + advancementIndex++; + } + } + } + + if (advancement != null) { + buildAndShowInfoForm(advancement); + } else { + buildAndShowMenuForm(); + // Indicate that we have closed the current advancement tab + session.sendDownstreamPacket(new ClientAdvancementTabPacket()); + } + }); + + session.sendForm(builder); } /** * Builds the advancement display info based on the chosen category * * @param advancement The advancement used to create the info display - * @return The information for the chosen advancement */ - public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) { + public void buildAndShowInfoForm(GeyserAdvancement advancement) { // Cache language for easier access String language = session.getLocale(); @@ -160,16 +209,24 @@ public class AdvancementsCache { Parent Advancement: Minecraft // If relevant */ - String content = description + "\n\n§f" + - earnedString + "\n"; + String content = description + "\n\n§f" + earnedString + "\n"; if (!currentAdvancementCategoryId.equals(advancement.getParentId())) { // Only display the parent if it is not the category content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language)); } - SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content); - window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language))); - return window; + session.sendForm( + SimpleForm.builder() + .title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle())) + .content(content) + .button(LanguageUtils.getPlayerLocaleString("gui.back", language)) + .responseHandler((form, responseData) -> { + SimpleFormResponse response = form.parseResponse(responseData); + if (response.isCorrect()) { + buildAndShowListForm(); + } + }) + ); } /** @@ -209,108 +266,6 @@ public class AdvancementsCache { return earned; } - /** - * Handle the menu form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleMenuForm(String response) { - SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID); - menuForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse(); - - String id = ""; - if (formResponse != null && formResponse.getClickedButton() != null) { - int advancementIndex = 0; - for (Map.Entry advancement : storedAdvancements.entrySet()) { - if (advancement.getValue().getParentId() == null) { // Root advancement - if (advancementIndex == formResponse.getClickedButtonId()) { - id = advancement.getKey(); - break; - } else { - advancementIndex++; - } - } - } - } - if (!id.equals("")) { - if (id.equals(currentAdvancementCategoryId)) { - // The server thinks we are already on this tab - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } else { - // Send a packet indicating that we intend to open this particular advancement window - ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id); - session.sendDownstreamPacket(packet); - // Wait for a response there - } - } - - return true; - } - - /** - * Handle the list form response (Advancement category choice) - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleListForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - GeyserAdvancement advancement = null; - int advancementIndex = 0; - // Loop around to find the advancement that the client pressed - for (GeyserAdvancement advancementEntry : storedAdvancements.values()) { - if (advancementEntry.getParentId() != null && - currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) { - if (advancementIndex == formResponse.getClickedButtonId()) { - advancement = advancementEntry; - break; - } else { - advancementIndex++; - } - } - } - if (advancement != null) { - session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID); - } else { - session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID); - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - } else { - // Indicate that we have closed the current advancement tab - session.sendDownstreamPacket(new ClientAdvancementTabPacket()); - } - - return true; - } - - /** - * Handle the info form response - * - * @param response The response string to parse - * @return True if the form was parsed correctly, false if not - */ - public boolean handleInfoForm(String response) { - SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID); - listForm.setResponse(response); - - SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse(); - - if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) { - session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID); - } - - return true; - } - public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) { String base = "\u00a7"; if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java index 17a3b3792..80b9f9155 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaAdvancementsTabTranslator.java @@ -27,7 +27,6 @@ package org.geysermc.connector.network.translators.java; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementTabPacket; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -36,10 +35,10 @@ import org.geysermc.connector.network.translators.Translator; */ @Translator(packet = ServerAdvancementTabPacket.class) public class JavaAdvancementsTabTranslator extends PacketTranslator { - @Override public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { - session.getAdvancementsCache().setCurrentAdvancementCategoryId(packet.getTabId()); - session.sendForm(session.getAdvancementsCache().buildListForm(), AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID); + session.getAdvancementsCache() + .setCurrentAdvancementCategoryId(packet.getTabId()) + .buildAndShowListForm(); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index e330cbf70..00c7aea0f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -169,7 +169,7 @@ public class LoginEncryptionUtils { .translator(LanguageUtils::getPlayerLocaleString, session.getLocale()) .title("geyser.auth.login.form.notice.title") .content("geyser.auth.login.form.notice.desc") - .button("geyser.auth.login.form.notice.btn_login.mojang") //todo optional + .optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled) .button("geyser.auth.login.form.notice.btn_login.microsoft") .button("geyser.auth.login.form.notice.btn_disconnect") .responseHandler((form, responseData) -> { @@ -179,9 +179,6 @@ public class LoginEncryptionUtils { return; } - int microsoftButton = isPasswordAuthentication ? 1 : 0; - int disconnectButton = isPasswordAuthentication ? 2 : 1; - if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) { session.setMicrosoftAccount(false); buildAndShowLoginDetailsWindow(session); @@ -272,6 +269,6 @@ public class LoginEncryptionUtils { session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale())); } }) - ) + ); } } From 52ddf8c556721bc97bcf16700c409ace66edd99f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 12 Feb 2021 22:22:45 +0100 Subject: [PATCH 25/51] Moved skin uploading to the global api --- .../geysermc/floodgate/util/BedrockData.java | 27 ++- .../org/geysermc/floodgate/util/RawSkin.java | 106 --------- .../floodgate/util/WebsocketEventType.java | 40 ++++ connector/pom.xml | 5 + .../geysermc/connector/GeyserConnector.java | 3 + .../network/session/GeyserSession.java | 21 +- .../network/session/auth/AuthData.java | 22 +- .../session/auth/BedrockClientData.java | 67 ------ .../connector/skin/FloodgateSkinUploader.java | 205 ++++++++++++++++++ .../geysermc/connector/utils/Constants.java | 43 ++++ .../connector/utils/LoginEncryptionUtils.java | 3 +- .../connector/utils/PluginMessageUtils.java | 21 +- 12 files changed, 363 insertions(+), 200 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/RawSkin.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java create mode 100644 connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/Constants.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 5f449fe2d..4ea5ee78e 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -37,7 +37,7 @@ import lombok.Getter; @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData implements Cloneable { - public static final int EXPECTED_LENGTH = 10; + public static final int EXPECTED_LENGTH = 12; private final String version; private final String username; @@ -50,19 +50,24 @@ public final class BedrockData implements Cloneable { private final LinkedPlayer linkedPlayer; private final boolean fromProxy; + private final int subscribeId; + private final String verifyCode; + private final int dataLength; public static BedrockData of(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, boolean fromProxy) { + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode) { return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, - uiProfile, ip, linkedPlayer, fromProxy, EXPECTED_LENGTH); + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); } public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - return of(version, username, xuid, deviceOs, languageCode, - uiProfile, inputMode, ip, null, false); + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode) { + return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, + false, subscribeId, verifyCode); } public static BedrockData fromString(String data) { @@ -75,13 +80,14 @@ public final class BedrockData implements Cloneable { // The format is the same as the order of the fields in this class return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], - Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, "1".equals(split[8]), split.length + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length ); } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, dataLength); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + dataLength); } public boolean hasPlayerLink() { @@ -94,7 +100,8 @@ public final class BedrockData implements Cloneable { return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + (fromProxy ? 1 : 0) + '\0' + - (linkedPlayer != null ? linkedPlayer.toString() : "null"); + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + + subscribeId + '\0' + verifyCode; } @Override diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java deleted file mode 100644 index 470b7e24a..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.floodgate.util; - -import lombok.AllArgsConstructor; - -import java.nio.ByteBuffer; -import java.util.Base64; - -import static java.lang.String.format; - -@AllArgsConstructor -public final class RawSkin { - public int width; - public int height; - public byte[] data; - public boolean alex; - - private RawSkin() { - } - - public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { - if (data == null || offset < 0 || data.length <= offset) { - return null; - } - if (offset == 0) { - return decode(data); - } - - byte[] rawSkin = new byte[data.length - offset]; - System.arraycopy(data, offset, rawSkin, 0, rawSkin.length); - return decode(rawSkin); - } - - public static RawSkin decode(byte[] data) throws InvalidFormatException { - // offset is an amount of bytes before the Base64 starts - if (data == null) { - return null; - } - - int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); - // if the RawSkin is longer then the max Java Edition skin length - if (data.length > maxEncodedLength) { - throw new InvalidFormatException(format( - "Encoded data cannot be longer then %s bytes! Got %s", - maxEncodedLength, data.length - )); - } - - // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if (data.length < Base64Utils.getEncodedLength(9)) { - throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); - } - - data = Base64.getDecoder().decode(data); - - ByteBuffer buffer = ByteBuffer.wrap(data); - - RawSkin skin = new RawSkin(); - skin.width = buffer.getInt(); - skin.height = buffer.getInt(); - if (buffer.remaining() - 1 != (skin.width * skin.height * 4)) { - throw new InvalidFormatException(format( - "Expected skin length to be %s, got %s", - (skin.width * skin.height * 4), buffer.remaining() - )); - } - skin.data = new byte[buffer.remaining() - 1]; - buffer.get(skin.data); - skin.alex = buffer.get() == 1; - return skin; - } - - public byte[] encode() { - // 2 x int + 1 = 9 bytes - ByteBuffer buffer = ByteBuffer.allocate(9 + data.length); - buffer.putInt(width); - buffer.putInt(height); - buffer.put(data); - buffer.put((byte) (alex ? 1 : 0)); - return Base64.getEncoder().encode(buffer.array()); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java new file mode 100644 index 000000000..1527f2360 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +public enum WebsocketEventType { + SUBSCRIBER_CREATED, + SUBSCRIBERS_COUNT, + ADDED_TO_QUEUE, + SKIN_UPLOADED, + CREATOR_DISCONNECTED; + + public static final WebsocketEventType[] VALUES = values(); + + public static WebsocketEventType getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } +} diff --git a/connector/pom.xml b/connector/pom.xml index 5e78fcfc3..04fc918fa 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -22,6 +22,11 @@ 2.9.8 compile
+ + org.java-websocket + Java-Websocket + 1.5.1 + com.fasterxml.jackson.datatype jackson-datatype-jsr310 diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 38c2aa29a..4dc78df1c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -56,6 +56,7 @@ import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; @@ -108,6 +109,7 @@ public class GeyserConnector { private AuthType authType; private FloodgateCipher cipher; + private FloodgateSkinUploader skinUploader; private boolean shuttingDown = false; @@ -203,6 +205,7 @@ public class GeyserConnector { cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + skinUploader = new FloodgateSkinUploader(this).start(); } catch (Exception exception) { logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 8bdb7f2b1..722cd2e45 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -67,10 +67,10 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; @@ -85,6 +85,7 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; import org.geysermc.cumulus.Form; @@ -553,7 +554,9 @@ public class GeyserSession implements CommandSender { byte[] encryptedData; try { + FloodgateSkinUploader skinUploader = connector.getSkinUploader(); FloodgateCipher cipher = connector.getCipher(); + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.getName(), @@ -562,7 +565,9 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getSession().getAddress().getAddress().getHostAddress(), + skinUploader.getId(), + skinUploader.getVerifyCode() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); @@ -570,13 +575,7 @@ public class GeyserSession implements CommandSender { return; } - byte[] rawSkin = clientData.getAndTransformImage("Skin").encode(); - byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1]; - System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length); - finalData[encryptedData.length] = 0x21; // splitter - System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length); - - String finalDataString = new String(finalData, StandardCharsets.UTF_8); + String finalDataString = new String(encryptedData, StandardCharsets.UTF_8); HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( @@ -639,6 +638,10 @@ public class GeyserSession implements CommandSender { if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { SkinManager.handleBedrockSkin(playerEntity, clientData); } + + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + getAuthData().upload(connector); } PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java index ba9e13548..463276891 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java @@ -25,16 +25,26 @@ package org.geysermc.connector.network.session.auth; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.connector.GeyserConnector; import java.util.UUID; -@Getter -@AllArgsConstructor +@RequiredArgsConstructor public class AuthData { + @Getter private final String name; + @Getter private final UUID UUID; + @Getter private final String xboxUUID; - private String name; - private UUID UUID; - private String xboxUUID; + private final JsonNode certChainData; + private final String clientData; + + public void upload(GeyserConnector connector) { + // we can't upload the skin in LoginEncryptionUtil since the global server would return + // the skin too fast, that's why we upload it after we know for sure that the target server + // is ready to handle the result of the global server + connector.getSkinUploader().uploadSkin(certChainData, clientData); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 7a7069d07..aed98b09c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -34,10 +34,8 @@ import lombok.Getter; import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; -import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; -import java.awt.image.BufferedImage; import java.util.Base64; import java.util.UUID; @@ -115,77 +113,12 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - private static RawSkin getLegacyImage(byte[] imageData, boolean alex) { - if (imageData == null) { - return null; - } - - // width * height * 4 (rgba) - switch (imageData.length) { - case 8192: - return new RawSkin(64, 32, imageData, alex); - case 16384: - return new RawSkin(64, 64, imageData, alex); - case 32768: - return new RawSkin(64, 128, imageData, alex); - case 65536: - return new RawSkin(128, 128, imageData, alex); - default: - throw new IllegalArgumentException("Unknown legacy skin size"); - } - } - public void setJsonData(JsonNode data) { if (this.jsonData == null && data != null) { this.jsonData = data; } } - /** - * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
- * Internally only used for Skins, but can be used for Capes too - */ - public RawSkin getImage(String name) { - if (jsonData == null || !jsonData.has(name + "Data")) { - return null; - } - - boolean alex = false; - if (name.equals("Skin")) { - alex = isAlex(); - } - - byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); - if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { - return new RawSkin( - jsonData.get(name + "ImageWidth").asInt(), - jsonData.get(name + "ImageHeight").asInt(), - image, alex - ); - } - return getLegacyImage(image, alex); - } - - public RawSkin getAndTransformImage(String name) { - RawSkin skin = getImage(name); - if (skin != null && (skin.width > 64 || skin.height > 64)) { - BufferedImage scaledImage = - SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); - - int max = Math.max(skin.width, skin.height); - while (max > 64) { - max /= 2; - scaledImage = SkinProvider.scale(scaledImage); - } - - byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage); - skin.width = scaledImage.getWidth(); - skin.height = scaledImage.getHeight(); - skin.data = skinData; - } - return skin; - } - public boolean isAlex() { try { byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java new file mode 100644 index 000000000..e710a3591 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.skin; + +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.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.Constants; +import org.geysermc.connector.utils.PluginMessageUtils; +import org.geysermc.floodgate.util.WebsocketEventType; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +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; + +import static org.geysermc.connector.utils.PluginMessageUtils.getSkinChannel; + +public final class FloodgateSkinUploader { + private final ObjectMapper JACKSON = new ObjectMapper(); + private final List skinQueue = new ArrayList<>(); + + private final GeyserLogger logger; + private final WebSocketClient client; + + @Getter private int id; + @Getter private String verifyCode; + @Getter private int subscribersCount; + + public FloodgateSkinUploader(GeyserConnector connector) { + this.logger = connector.getLogger(); + this.client = new WebSocketClient(Constants.SKIN_UPLOAD_URI) { + @Override + public void onOpen(ServerHandshake handshake) { + setConnectionLostTimeout(11); + + Iterator queueIterator = skinQueue.iterator(); + while (isOpen() && queueIterator.hasNext()) { + send(queueIterator.next()); + queueIterator.remove(); + } + } + + @Override + public void onMessage(String message) { + System.out.println(message); + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(message); + if (node.has("error")) { + logger.error("Got an error: " + node.get("error").asText()); + return; + } + + int typeId = node.get("event_id").asInt(); + WebsocketEventType type = WebsocketEventType.getById(typeId); + if (type == null) { + logger.warning(String.format( + "Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!", + typeId)); + return; + } + + switch (type) { + case SUBSCRIBER_CREATED: + id = node.get("id").asInt(); + verifyCode = node.get("verify_code").asText(); + break; + case SUBSCRIBERS_COUNT: + subscribersCount = node.get("subscribers_count").asInt(); + break; + case SKIN_UPLOADED: + // if Geyser is the only subscriber we have send it to the server manually + if (subscribersCount != 1) { + break; + } + + String xuid = node.get("xuid").asText(); + String value = node.get("value").asText(); + String signature = node.get("signature").asText(); + ; + + GeyserSession session = connector.getPlayerByXuid(xuid); + if (session != null) { + byte[] bytes = (value + '\0' + signature) + .getBytes(StandardCharsets.UTF_8); + PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + } + break; + } + } catch (Exception e) { + logger.error("Error while receiving a message", e); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (reason != null && !reason.isEmpty()) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(reason); + // info means that the uploader itself did nothing wrong + if (node.has("info")) { + String info = node.get("info").asText(); + logger.debug("Got disconnected from the skin uploader: " + info); + } + // error means that the uploader did something wrong + if (node.has("error")) { + String error = node.get("error").asText(); + logger.info("Got disconnected from the skin uploader: " + error); + } + // it can't be something else then info or error, so we won't handle anything other than that. + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); + } catch (Exception e) { + logger.error("Error while handling onClose", e); + } + } + } + + @Override + public void onError(Exception ex) { + logger.error("Got an error", ex); + } + }; + } + + public void uploadSkin(JsonNode chainData, String clientData) { + if (chainData == null || !chainData.isArray() || clientData == null) { + return; + } + + ObjectNode node = JACKSON.createObjectNode(); + node.set("chain_data", chainData); + node.put("client_data", clientData); + + // The reason why I don't like Jackson + String jsonString; + try { + jsonString = JACKSON.writeValueAsString(node); + } catch (Exception e) { + logger.error("Failed to upload skin", e); + return; + } + + if (client.isOpen()) { + client.send(jsonString); + return; + } + skinQueue.add(jsonString); + } + + private void reconnectLater(GeyserConnector connector) { + long additionalTime = ThreadLocalRandom.current().nextInt(7); + connector.getGeneralThreadPool().schedule(() -> { + try { + if (!client.connectBlocking()) { + reconnectLater(connector); + } + } catch (InterruptedException ignored) { + reconnectLater(connector); + } + }, 8 + additionalTime, TimeUnit.SECONDS); + } + + public FloodgateSkinUploader start() { + client.connect(); + return this; + } + + public void stop() { + client.close(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java new file mode 100644 index 000000000..8dcd63d3e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import java.net.URI; +import java.net.URISyntaxException; + +public final class Constants { + public static final URI SKIN_UPLOAD_URI; + + static { + URI skinUploadUri = null; + try { + skinUploadUri = new URI("wss://api.geysermc.org/ws"); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + SKIN_UPLOAD_URI = skinUploadUri; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 00c7aea0f..f68ee0e4c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -118,7 +118,8 @@ public class LoginEncryptionUtils { session.setAuthenticationData(new AuthData( extraData.get("displayName").asText(), UUID.fromString(extraData.get("identity").asText()), - extraData.get("XUID").asText() + extraData.get("XUID").asText(), + certChainData, clientData )); if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 4e0fd6074..1569002f9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,12 +25,15 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import java.nio.ByteBuffer; public class PluginMessageUtils { + private static final String SKIN_CHANNEL = "floodgate:skin"; private static final byte[] GEYSER_BRAND_DATA; private static final byte[] FLOODGATE_REGISTER_DATA; @@ -42,7 +45,7 @@ public class PluginMessageUtils { .put(data) .array(); - FLOODGATE_REGISTER_DATA = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); + FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8); } /** @@ -63,6 +66,22 @@ public class PluginMessageUtils { return FLOODGATE_REGISTER_DATA; } + /** + * Returns the skin channel used in Floodgate + */ + public static String getSkinChannel() { + return SKIN_CHANNEL; + } + + public static void sendMessage(GeyserSession session, String channel, byte[] data) { + byte[] finalData = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); + session.sendDownstreamPacket(new ServerPluginMessagePacket(channel, finalData)); + } + private static byte[] writeVarInt(int value) { byte[] data = new byte[getVarIntLength(value)]; int index = 0; From c79979e3e383f611a5035c1a031be3b0be424ff8 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 16 Feb 2021 18:54:04 +0100 Subject: [PATCH 26/51] Added timestamp to BedrockData --- .../org/geysermc/floodgate/util/BedrockData.java | 16 +++++++++------- .../org/geysermc/floodgate/util/InputMode.java | 2 +- .../connector/skin/FloodgateSkinUploader.java | 3 +-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 4ea5ee78e..cbf49e126 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -26,8 +26,8 @@ package org.geysermc.floodgate.util; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; /** * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This @@ -35,9 +35,9 @@ import lombok.Getter; * present in the API module of the Floodgate repo) */ @Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData implements Cloneable { - public static final int EXPECTED_LENGTH = 12; + public static final int EXPECTED_LENGTH = 13; private final String version; private final String username; @@ -53,6 +53,7 @@ public final class BedrockData implements Cloneable { private final int subscribeId; private final String verifyCode; + private final long timestamp; private final int dataLength; public static BedrockData of(String version, String username, String xuid, int deviceOs, @@ -60,7 +61,8 @@ public final class BedrockData implements Cloneable { LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, String verifyCode) { return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, - uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, + System.currentTimeMillis(), EXPECTED_LENGTH); } public static BedrockData of(String version, String username, String xuid, int deviceOs, @@ -81,12 +83,12 @@ public final class BedrockData implements Cloneable { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, - "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length ); } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1, dataLength); } @@ -101,7 +103,7 @@ public final class BedrockData implements Cloneable { languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + - subscribeId + '\0' + verifyCode; + subscribeId + '\0' + verifyCode + '\0' + timestamp; } @Override diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java index 9664e18ae..d49d2ea84 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -29,7 +29,7 @@ package org.geysermc.floodgate.util; public enum InputMode { UNKNOWN, KEYBOARD_MOUSE, - TOUCH, // I guess Touch? + TOUCH, CONTROLLER, VR; diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index e710a3591..991db08bc 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -74,7 +74,6 @@ public final class FloodgateSkinUploader { @Override public void onMessage(String message) { - System.out.println(message); // The reason why I don't like Jackson try { JsonNode node = JACKSON.readTree(message); @@ -109,7 +108,6 @@ public final class FloodgateSkinUploader { String xuid = node.get("xuid").asText(); String value = node.get("value").asText(); String signature = node.get("signature").asText(); - ; GeyserSession session = connector.getPlayerByXuid(xuid); if (session != null) { @@ -182,6 +180,7 @@ public final class FloodgateSkinUploader { } private void reconnectLater(GeyserConnector connector) { + //todo doesn't work long additionalTime = ThreadLocalRandom.current().nextInt(7); connector.getGeneralThreadPool().schedule(() -> { try { From 66867edbc3e81669c64ea752bae631585849b9cc Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 25 Feb 2021 02:46:34 +0100 Subject: [PATCH 27/51] Fixed missing dependency --- connector/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/connector/pom.xml b/connector/pom.xml index 7d08b5c60..6fb2c127c 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -21,6 +21,12 @@ 1.2.0-SNAPSHOT compile
+ + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.10.2 + compile + com.fasterxml.jackson.dataformat jackson-dataformat-yaml From 0832e7d65cfe8c4abb6348ac25eef679f3efa912 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 25 Feb 2021 20:55:00 +0100 Subject: [PATCH 28/51] Fixes issue when both Geyser and Floodgate are on the same server --- bootstrap/spigot/src/main/resources/plugin.yml | 2 +- .../src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index fee71ab1f..ea655f902 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ name: ${outputName}-Spigot author: ${project.organization.name} website: ${project.organization.url} version: ${project.version} -softdepend: ["ViaVersion"] +softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 commands: geyser: diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 950f0eb55..7d67a44a0 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -57,7 +57,7 @@ public final class LinkedPlayer implements Cloneable { return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); } - static LinkedPlayer fromString(String data) { + public static LinkedPlayer fromString(String data) { String[] split = data.split(";"); if (split.length != 3) { return null; From e7eca7f7b945fdd7148234c7101f5906a4d4c492 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 27 Feb 2021 13:14:40 +0100 Subject: [PATCH 29/51] Fixed reconnecting to the api --- .../connector/skin/FloodgateSkinUploader.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 991db08bc..ad3310c8d 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -25,6 +25,7 @@ package org.geysermc.connector.skin; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -38,6 +39,7 @@ import org.geysermc.floodgate.util.WebsocketEventType; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; +import java.net.ConnectException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; @@ -138,18 +140,21 @@ public final class FloodgateSkinUploader { String error = node.get("error").asText(); logger.info("Got disconnected from the skin uploader: " + error); } - // it can't be something else then info or error, so we won't handle anything other than that. - // try to reconnect (which will make a new id and verify token) after a few seconds - reconnectLater(connector); + } catch (JsonProcessingException ignored) { + // ignore invalid json } catch (Exception e) { logger.error("Error while handling onClose", e); } } + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); } @Override public void onError(Exception ex) { - logger.error("Got an error", ex); + if (!(ex instanceof ConnectException)) { + logger.error("Got an error", ex); + } } }; } @@ -180,17 +185,10 @@ public final class FloodgateSkinUploader { } private void reconnectLater(GeyserConnector connector) { - //todo doesn't work long additionalTime = ThreadLocalRandom.current().nextInt(7); - connector.getGeneralThreadPool().schedule(() -> { - try { - if (!client.connectBlocking()) { - reconnectLater(connector); - } - } catch (InterruptedException ignored) { - reconnectLater(connector); - } - }, 8 + additionalTime, TimeUnit.SECONDS); + // we don't have to check the result. onClose will handle that for us + connector.getGeneralThreadPool() + .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); } public FloodgateSkinUploader start() { From a2d3ccfb2f1bae1da6c1fb799ac2c1ef84f27c4a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Mar 2021 19:57:52 +0100 Subject: [PATCH 30/51] Whoops. Forgot to update Geyser to match recent global api changes --- .../connector/skin/FloodgateSkinUploader.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index ad3310c8d..9f1a515a0 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -103,16 +103,25 @@ public final class FloodgateSkinUploader { 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(); - String value = node.get("value").asText(); - String signature = node.get("signature").asText(); - GeyserSession session = connector.getPlayerByXuid(xuid); + if (session != null) { + if (!node.get("success").asBoolean()) { + logger.info("Failed to upload skin for " + session.getName()); + return; + } + + JsonNode data = node.get("data"); + + String value = data.get("value").asText(); + String signature = data.get("signature").asText(); + byte[] bytes = (value + '\0' + signature) .getBytes(StandardCharsets.UTF_8); PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); From cea16270c49eb083c1c1586f119222e83ea92094 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Mar 2021 21:49:42 +0100 Subject: [PATCH 31/51] Fixed typo in artifactId --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 6fb2c127c..107b4144b 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -35,7 +35,7 @@ org.java-websocket - Java-Websocket + Java-WebSocket 1.5.1 From 4c6c397f37e0bba80a56f3e4389c1eb22ce2d1c3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 01:46:33 +0100 Subject: [PATCH 32/51] Fixed merge conflicts --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- .../network/translators/java/JavaJoinGameTranslator.java | 2 +- .../network/translators/java/JavaPluginMessageTranslator.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3a40474f1..5e279c121 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -195,7 +195,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); - if (authType == AuthType.FLOODGATE) { + if (defaultAuthType == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 17b5087ec..3e37f9eae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -97,7 +97,7 @@ public class JavaJoinGameTranslator extends PacketTranslator { @Override public void translate(ServerPluginMessagePacket packet, GeyserSession session) { - // The only plugin messages to listen for are Floodgate plugin messages - if (session.getConnector().getAuthType() != AuthType.FLOODGATE) { + // The only plugin messages it has to listen for are Floodgate plugin messages + if (session.getConnector().getDefaultAuthType() != AuthType.FLOODGATE) { return; } From 644ece124fbc505b8c9ee957b959108bc85765ba Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 01:49:08 +0100 Subject: [PATCH 33/51] Bumped Geyser version to 1.3.0-SNAPSHOT --- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 2 +- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- connector/pom.xml | 4 ++-- pom.xml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 10855aed4..5bbff0647 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 4b61a65c3..d58cb2ca4 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-parent pom diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index d1cba8aea..dc9a95d5f 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-spigot @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 97c4ac8a4..adeaa91de 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 831239f66..6610af3b5 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-standalone @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 58eee1f77..5dadc83c4 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 0f4d5421b..be90f0146 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT common diff --git a/connector/pom.xml b/connector/pom.xml index 4ae7e1383..4e244dd00 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT connector @@ -20,7 +20,7 @@ org.geysermc common - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 011b320f4..959b7f953 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From 2caf811750c3b195de05ff8ca42434be6b7c74c6 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 02:02:20 +0100 Subject: [PATCH 34/51] Deploy Floodgate 2.0 builds --- Jenkinsfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 09e88e86e..d76a6f737 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,10 @@ pipeline { stage ('Deploy') { when { - branch "master" + anyOf { + branch "master" + branch "floodgate-2.0" + } } steps { From 677a8d68f659247fa73a9b7b44d3dfe006e79d5a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 24 Mar 2021 15:50:13 +0100 Subject: [PATCH 35/51] Add an additional key searching fallback for Floodgate 2.0 --- .../org/geysermc/connector/FloodgateKeyLoader.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index 9b9ee5681..7f5bca8c2 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -33,9 +33,18 @@ import java.nio.file.Path; public class FloodgateKeyLoader { public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + if (!config.getRemote().getAuthType().equals("floodgate")) { + return geyserDataFolder.resolve(config.getFloodgateKeyFile()); + } + Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); - if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { + if (config.getFloodgateKeyFile().equals("public-key.pem")) { + logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead"); + floodgateKey = geyserDataFolder.resolve("key.pem"); + } + + if (!Files.exists(floodgateKey)) { if (floodgate != null) { Path autoKey = floodgateDataFolder.resolve("key.pem"); if (Files.exists(autoKey)) { From 5c12dc8e1515b6fb67439acd5e722349d435f06c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Apr 2021 00:37:58 +0200 Subject: [PATCH 36/51] Don't use a blocking algorithm for generating keys on unix-like systems This should fix GeyserMC/Floodgate#125 --- .../geysermc/floodgate/crypto/AesKeyProducer.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java index 5217b4cf7..bb2be89f8 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -29,7 +29,9 @@ package org.geysermc.floodgate.crypto; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Locale; public final class AesKeyProducer implements KeyProducer { public static int KEY_SIZE = 128; @@ -38,7 +40,7 @@ public final class AesKeyProducer implements KeyProducer { public SecretKey produce() { try { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); - keyGenerator.init(KEY_SIZE, SecureRandom.getInstanceStrong()); + keyGenerator.init(KEY_SIZE, getSecureRandom()); return keyGenerator.generateKey(); } catch (Exception exception) { throw new RuntimeException(exception); @@ -53,4 +55,14 @@ public final class AesKeyProducer implements KeyProducer { throw new RuntimeException(exception); } } + + private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { + // use Windows-PRNG for windows (default impl is SHA1PRNG) + // default impl for unix-like systems is NativePRNG. + if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win")) { + return SecureRandom.getInstance("Windows-PRNG"); + } else { + return new SecureRandom(); + } + } } From a5a849c0598566744b5e08724a5ff849c88b9636 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 1 Apr 2021 00:57:47 +0200 Subject: [PATCH 37/51] Use a better name to indicate Windows --- .../main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java index bb2be89f8..59080c195 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -59,7 +59,7 @@ public final class AesKeyProducer implements KeyProducer { private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { // use Windows-PRNG for windows (default impl is SHA1PRNG) // default impl for unix-like systems is NativePRNG. - if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("win")) { + if (System.getProperty("os.name").startsWith("Windows")) { return SecureRandom.getInstance("Windows-PRNG"); } else { return new SecureRandom(); From 23c3db28efe9c6202c438bd0dc7fb27f9f6e782e Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 3 Apr 2021 19:49:44 +0200 Subject: [PATCH 38/51] Another attempt to fix key generation --- .../geysermc/floodgate/crypto/AesKeyProducer.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java index 59080c195..faec0ad10 100644 --- a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java +++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java @@ -31,7 +31,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.Locale; public final class AesKeyProducer implements KeyProducer { public static int KEY_SIZE = 128; @@ -58,11 +57,18 @@ public final class AesKeyProducer implements KeyProducer { private SecureRandom getSecureRandom() throws NoSuchAlgorithmException { // use Windows-PRNG for windows (default impl is SHA1PRNG) - // default impl for unix-like systems is NativePRNG. if (System.getProperty("os.name").startsWith("Windows")) { return SecureRandom.getInstance("Windows-PRNG"); } else { - return new SecureRandom(); + try { + // NativePRNG (which should be the default on unix-systems) can still block your + // system. Even though it isn't as bad as NativePRNGBlocking, we still try to + // prevent that if possible + return SecureRandom.getInstance("NativePRNGNonBlocking"); + } catch (NoSuchAlgorithmException ignored) { + // at this point we just have to go with the default impl even if it blocks + return new SecureRandom(); + } } } } From 21c8a389e30ee9b882834d7dc8121694d5d97460 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 3 Apr 2021 19:50:35 +0200 Subject: [PATCH 39/51] Fixed an issue with forwarding player links --- .../java/org/geysermc/floodgate/util/BedrockData.java | 3 +-- .../geysermc/connector/skin/FloodgateSkinUploader.java | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index cbf49e126..81a6307a2 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -101,9 +101,8 @@ public final class BedrockData implements Cloneable { // The format is the same as the order of the fields in this class return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + - (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + - subscribeId + '\0' + verifyCode + '\0' + timestamp; + (fromProxy ? 1 : 0) + '\0' + subscribeId + '\0' + verifyCode + '\0' + timestamp; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 9f1a515a0..d61d3b632 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -39,6 +39,7 @@ import org.geysermc.floodgate.util.WebsocketEventType; 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; @@ -161,9 +162,13 @@ public final class FloodgateSkinUploader { @Override public void onError(Exception ex) { - if (!(ex instanceof ConnectException)) { - logger.error("Got an error", ex); + if (ex instanceof ConnectException || ex instanceof SSLException) { + if (logger.isDebug()) { + logger.error("[debug] Got an error", ex); + } + return; } + logger.error("Got an error", ex); } }; } From 11b10e285778dd0014b6c9d3bc06a5eb4f62301f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 17 Apr 2021 17:39:08 +0200 Subject: [PATCH 40/51] Allow skin uploader to log messages instead of closing the connection --- .../floodgate/util/WebsocketEventType.java | 3 ++- .../connector/skin/FloodgateSkinUploader.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java index 1527f2360..1025fcdba 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -30,7 +30,8 @@ public enum WebsocketEventType { SUBSCRIBERS_COUNT, ADDED_TO_QUEUE, SKIN_UPLOADED, - CREATOR_DISCONNECTED; + CREATOR_DISCONNECTED, + LOG_MESSAGE; public static final WebsocketEventType[] VALUES = values(); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index d61d3b632..243685188 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -128,6 +128,23 @@ public final class FloodgateSkinUploader { PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); } break; + case LOG_MESSAGE: + String logMessage = node.get("message").asText(); + switch (node.get("priority").asInt()) { + case -1: + logger.debug("Got debug message from skin uploader: " + logMessage); + break; + case 0: + logger.info("Got info message from skin uploader: " +logMessage); + break; + case 1: + logger.error("Got error message from skin uploader: " + logMessage); + break; + default: + logger.info(logMessage); + break; + } + break; } } catch (Exception e) { logger.error("Error while receiving a message", e); From 14894c8f27844484538ff06607826adeeaf0135c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 17 Apr 2021 17:40:53 +0200 Subject: [PATCH 41/51] Allow skin uploader to log messages instead of closing the connection --- .../org/geysermc/connector/skin/FloodgateSkinUploader.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 243685188..dd541864a 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -132,13 +132,13 @@ public final class FloodgateSkinUploader { String logMessage = node.get("message").asText(); switch (node.get("priority").asInt()) { case -1: - logger.debug("Got debug message from skin uploader: " + logMessage); + logger.debug("Got a message from skin uploader: " + logMessage); break; case 0: - logger.info("Got info message from skin uploader: " +logMessage); + logger.info("Got a message from skin uploader: " +logMessage); break; case 1: - logger.error("Got error message from skin uploader: " + logMessage); + logger.error("Got a message from skin uploader: " + logMessage); break; default: logger.info(logMessage); From cfa2805e0031af3d200e3fce5a65cd9936cb12cd Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 26 May 2021 01:55:58 +0200 Subject: [PATCH 42/51] Make sure that the time we use is always the same across servers --- .../floodgate/time/SntpClientUtils.java | 91 +++++++++++++++++++ .../geysermc/floodgate/time/TimeSyncer.java | 59 ++++++++++++ .../geysermc/floodgate/util/BedrockData.java | 27 ++++-- .../geysermc/connector/GeyserConnector.java | 9 +- .../network/session/GeyserSession.java | 3 +- .../geysermc/connector/utils/Constants.java | 1 + 6 files changed, 179 insertions(+), 11 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java create mode 100644 common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java diff --git a/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java b/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java new file mode 100644 index 000000000..9e7f2c1b0 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/time/SntpClientUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.time; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.nio.ByteBuffer; + +/* + * Thanks: + * https://datatracker.ietf.org/doc/html/rfc1769 + * https://github.com/jonsagara/SimpleNtpClient + * https://stackoverflow.com/a/29138806 + */ +public final class SntpClientUtils { + private static final int NTP_PORT = 123; + + private static final int NTP_PACKET_SIZE = 48; + private static final int NTP_MODE = 3; // client + private static final int NTP_VERSION = 3; + private static final int RECEIVE_TIME_POSITION = 32; + + private static final long NTP_TIME_OFFSET = ((365L * 70L) + 17L) * 24L * 60L * 60L; + + public static long requestTimeOffset(String host, int timeout) { + try (DatagramSocket socket = new DatagramSocket()) { + socket.setSoTimeout(timeout); + + InetAddress address = InetAddress.getByName(host); + + ByteBuffer buff = ByteBuffer.allocate(NTP_PACKET_SIZE); + + DatagramPacket request = new DatagramPacket( + buff.array(), NTP_PACKET_SIZE, address, NTP_PORT + ); + + // mode is in the least signification 3 bits + // version is in bits 3-5 + buff.put((byte) (NTP_MODE | (NTP_VERSION << 3))); + + long originateTime = System.currentTimeMillis(); + socket.send(request); + + DatagramPacket response = new DatagramPacket(buff.array(), NTP_PACKET_SIZE); + socket.receive(response); + + long responseTime = System.currentTimeMillis(); + + // everything before isn't important for us + buff.position(RECEIVE_TIME_POSITION); + + long receiveTime = readTimestamp(buff); + long transmitTime = readTimestamp(buff); + + return ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2; + } catch (Exception ignored) { + } + return Long.MIN_VALUE; + } + + private static long readTimestamp(ByteBuffer buffer) { + //todo look into the ntp 2036 problem + long seconds = buffer.getInt() & 0xffffffffL; + long fraction = buffer.getInt() & 0xffffffffL; + return ((seconds - NTP_TIME_OFFSET) * 1000) + ((fraction * 1000) / 0x100000000L); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java new file mode 100644 index 000000000..2d74e4b77 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.time; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public final class TimeSyncer { + private final ExecutorService executorService; + private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset + + public TimeSyncer(String timeServer) { + ScheduledExecutorService service = Executors.newScheduledThreadPool(1); + service.scheduleWithFixedDelay(() -> { + // 5 tries to get the time offset + for (int i = 0; i < 5; i++) { + long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); + if (offset != Long.MIN_VALUE) { + timeOffset = offset; + return; + } + } + }, 0, 30, TimeUnit.MINUTES); + executorService = service; + } + + public void shutdown() { + executorService.shutdown(); + } + + public long getTimeOffset() { + return timeOffset; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 81a6307a2..46c1d3d1a 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -28,6 +28,7 @@ package org.geysermc.floodgate.util; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.geysermc.floodgate.time.TimeSyncer; /** * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This @@ -56,20 +57,28 @@ public final class BedrockData implements Cloneable { private final long timestamp; private final int dataLength; - public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, - String verifyCode) { + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode, TimeSyncer timeSyncer) { + + long realMillis = System.currentTimeMillis(); + if (timeSyncer.getTimeOffset() != Long.MIN_VALUE) { + realMillis += timeSyncer.getTimeOffset(); + } + return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, - System.currentTimeMillis(), EXPECTED_LENGTH); + realMillis, EXPECTED_LENGTH); } - public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip, - int subscribeId, String verifyCode) { + public static BedrockData of( + String version, String username, String xuid, int deviceOs, + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode, TimeSyncer timeSyncer) { return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, - false, subscribeId, verifyCode); + false, subscribeId, verifyCode, timeSyncer); } public static BedrockData fromString(String data) { diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index bac915437..d1122b65f 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -62,6 +62,7 @@ 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.time.TimeSyncer; import org.jetbrains.annotations.Contract; import javax.naming.directory.Attribute; @@ -79,7 +80,6 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector { - public static final ObjectMapper JSON_MAPPER = new ObjectMapper() .enable(JsonParser.Feature.IGNORE_UNDEFINED) .enable(JsonParser.Feature.ALLOW_COMMENTS) @@ -106,6 +106,7 @@ public class GeyserConnector { @Setter private AuthType defaultAuthType; + private TimeSyncer timeSyncer; private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; @@ -198,6 +199,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); if (defaultAuthType == AuthType.FLOODGATE) { + timeSyncer = new TimeSyncer(Constants.NTP_SERVER); try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); @@ -353,6 +355,7 @@ public class GeyserConnector { generalThreadPool.shutdown(); bedrockServer.close(); + timeSyncer.shutdown(); players.clear(); defaultAuthType = null; this.getCommandManager().getCommands().clear(); @@ -431,6 +434,10 @@ public class GeyserConnector { return bootstrap.getWorldManager(); } + public TimeSyncer getTimeSyncer() { + return timeSyncer; + } + /** * Whether to use XML reflections in the jar or manually find the reflections. * Will return true if the version number is not 'DEV' and the platform is not Fabric. diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index b922903b6..15d246fee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -691,7 +691,8 @@ public class GeyserSession implements CommandSender { clientData.getCurrentInputMode().ordinal(), upstream.getAddress().getAddress().getHostAddress(), skinUploader.getId(), - skinUploader.getVerifyCode() + skinUploader.getVerifyCode(), + connector.getTimeSyncer() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java index 8dcd63d3e..5b21d045c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Constants.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -30,6 +30,7 @@ import java.net.URISyntaxException; public final class Constants { public static final URI SKIN_UPLOAD_URI; + public static final String NTP_SERVER = "time.cloudflare.com"; static { URI skinUploadUri = null; From 934fc12b161f10c4a4e325b69d0ef3ba0a65b29a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 26 May 2021 20:20:59 +0200 Subject: [PATCH 43/51] Warn if we were unable to check if the system clock is accurate --- .../org/geysermc/floodgate/time/TimeSyncer.java | 13 ++++++++++++- .../org/geysermc/floodgate/util/BedrockData.java | 8 +------- .../connector/network/session/GeyserSession.java | 8 ++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java index 2d74e4b77..1e385b2c6 100644 --- a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -37,7 +37,7 @@ public final class TimeSyncer { public TimeSyncer(String timeServer) { ScheduledExecutorService service = Executors.newScheduledThreadPool(1); service.scheduleWithFixedDelay(() -> { - // 5 tries to get the time offset + // 5 tries to get the time offset, since UDP doesn't guaranty a response for (int i = 0; i < 5; i++) { long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); if (offset != Long.MIN_VALUE) { @@ -56,4 +56,15 @@ public final class TimeSyncer { public long getTimeOffset() { return timeOffset; } + + public long getRealMillis() { + if (hasUsefulOffset()) { + return System.currentTimeMillis() + getTimeOffset(); + } + return System.currentTimeMillis(); + } + + public boolean hasUsefulOffset() { + return timeOffset != Long.MIN_VALUE; + } } diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 46c1d3d1a..4f4325a9b 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -62,15 +62,9 @@ public final class BedrockData implements Cloneable { String languageCode, int uiProfile, int inputMode, String ip, LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, String verifyCode, TimeSyncer timeSyncer) { - - long realMillis = System.currentTimeMillis(); - if (timeSyncer.getTimeOffset() != Long.MIN_VALUE) { - realMillis += timeSyncer.getTimeOffset(); - } - return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, - realMillis, EXPECTED_LENGTH); + timeSyncer.getRealMillis(), EXPECTED_LENGTH); } public static BedrockData of( diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 9068323e6..eddc4c5a1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -695,6 +695,14 @@ public class GeyserSession implements CommandSender { skinUploader.getVerifyCode(), connector.getTimeSyncer() ).toString()); + + if (!connector.getTimeSyncer().hasUsefulOffset()) { + connector.getLogger().warning( + "We couldn't make sure that your system clock is accurate. " + + "This can cause issues with logging in." + ); + } + } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode())); From 360e2f4b9a6efd35d7876e32e99ab6311ac6f342 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 30 May 2021 00:22:11 +0200 Subject: [PATCH 44/51] Always replace spaces in usernames when using Floodgate Bungeecord recently started checking usernames for spaces in the login start packet. To resolve this we just always send the username without spaces in the login start packet. Floodgate is still able to get the real username of the Bedrock player and Floodgate is also still in charge of the final username. --- .../connector/network/session/GeyserSession.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index eddc4c5a1..68e7f5510 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -581,7 +581,14 @@ public class GeyserSession implements CommandSender { protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken()); } else { - protocol = new MinecraftProtocol(username); + // always replace spaces when using Floodgate, + // as usernames with spaces cause issues with Bungeecord's login cycle + String validUsername = username; + if (remoteAuthType == AuthType.FLOODGATE) { + validUsername = username.replace(' ', '_'); + } + + protocol = new MinecraftProtocol(validUsername); } connectDownstream(); From db583135eb2e3760a85c81d7bae9fab764ec3b99 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 30 May 2021 00:51:04 +0200 Subject: [PATCH 45/51] Improved the comment added in the previous commit --- .../org/geysermc/connector/network/session/GeyserSession.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 68e7f5510..b30b181b6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -582,7 +582,9 @@ public class GeyserSession implements CommandSender { protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken()); } else { // always replace spaces when using Floodgate, - // as usernames with spaces cause issues with Bungeecord's login cycle + // as usernames with spaces cause issues with Bungeecord's login cycle. + // However, this doesn't affect the final username as Floodgate is still in charge of that. + // So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear. String validUsername = username; if (remoteAuthType == AuthType.FLOODGATE) { validUsername = username.replace(' ', '_'); From 2f2e2cc28553162447e691aef83fb6f6c4661b9f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 May 2021 21:36:25 -0400 Subject: [PATCH 46/51] Only upload the skin on Floodgate --- .../connector/network/session/GeyserSession.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index b30b181b6..1bf2c316b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -732,6 +732,8 @@ public class GeyserSession implements CommandSender { address = handshakePacket.getHostname(); } + System.out.println((address + addressSuffix).getBytes(StandardCharsets.UTF_8).length); + event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), address + addressSuffix, @@ -802,9 +804,11 @@ public class GeyserSession implements CommandSender { SkinManager.handleBedrockSkin(playerEntity, clientData); } - // We'll send the skin upload a bit after the handshake packet (aka this packet), - // because otherwise the global server returns the data too fast. - getAuthData().upload(connector); + if (remoteAuthType == AuthType.FLOODGATE) { + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + getAuthData().upload(connector); + } } PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); From 5376ba49b822e10f30f1f8b15e57e47c180f8b6a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 30 May 2021 21:37:32 -0400 Subject: [PATCH 47/51] ._. --- .../org/geysermc/connector/network/session/GeyserSession.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 1bf2c316b..ead9d1834 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -732,8 +732,6 @@ public class GeyserSession implements CommandSender { address = handshakePacket.getHostname(); } - System.out.println((address + addressSuffix).getBytes(StandardCharsets.UTF_8).length); - event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), address + addressSuffix, From 209a4ffc427b1bca2d2f313cdc6c8ec0a98f1545 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Mon, 31 May 2021 16:54:02 +0200 Subject: [PATCH 48/51] Use the correct plugin message packet. Thanks, Camo --- .../org/geysermc/connector/utils/PluginMessageUtils.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 1569002f9..a914f699e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,7 +25,7 @@ package org.geysermc.connector.utils; -import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.ClientPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; @@ -74,12 +74,7 @@ public class PluginMessageUtils { } public static void sendMessage(GeyserSession session, String channel, byte[] data) { - byte[] finalData = - ByteBuffer.allocate(data.length + getVarIntLength(data.length)) - .put(writeVarInt(data.length)) - .put(data) - .array(); - session.sendDownstreamPacket(new ServerPluginMessagePacket(channel, finalData)); + session.sendDownstreamPacket(new ClientPluginMessagePacket(channel, data)); } private static byte[] writeVarInt(int value) { From 50b51f5f5755b3c26c77ffbf5a47f62f1ba1a8e5 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 5 Jun 2021 23:12:33 +0200 Subject: [PATCH 49/51] Added the initial version of news --- .../org/geysermc/floodgate/news/NewsItem.java | 139 ++++++++++++++ .../floodgate/news/NewsItemAction.java | 44 +++++ .../floodgate/news/NewsItemMessage.java | 89 +++++++++ .../org/geysermc/floodgate/news/NewsType.java | 59 ++++++ .../news/data/BuildSpecificData.java | 60 ++++++ .../floodgate/news/data/CheckAfterData.java | 42 +++++ .../floodgate/news/data/ItemData.java | 29 +++ .../geysermc/floodgate/time/TimeSyncer.java | 7 +- .../floodgate/util/WebsocketEventType.java | 64 ++++++- .../geysermc/connector/GeyserConnector.java | 20 ++ .../connector/skin/FloodgateSkinUploader.java | 6 +- .../geysermc/connector/utils/Constants.java | 18 +- .../geysermc/connector/utils/NewsHandler.java | 175 ++++++++++++++++++ .../geysermc/connector/utils/WebUtils.java | 3 +- 14 files changed, 735 insertions(+), 20 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsItem.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/NewsType.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java create mode 100644 common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java new file mode 100644 index 000000000..2f6bb4852 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItem.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.ItemData; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public final class NewsItem { + private final int id; + private final String project; + private final boolean active; + private final NewsType type; + private final ItemData data; + private final boolean priority; + private final String message; + private final Set actions; + private final String url; + + private NewsItem(int id, String project, boolean active, NewsType type, ItemData data, + boolean priority, String message, Set actions, String url) { + this.id = id; + this.project = project; + this.active = active; + this.type = type; + this.data = data; + this.priority = priority; + this.message = message; + this.actions = Collections.unmodifiableSet(actions); + this.url = url; + } + + public static NewsItem readItem(JsonObject newsItem) { + NewsType newsType = NewsType.getByName(newsItem.get("type").getAsString()); + if (newsType == null) { + return null; + } + + JsonObject messageObject = newsItem.getAsJsonObject("message"); + NewsItemMessage itemMessage = NewsItemMessage.getById(messageObject.get("id").getAsInt()); + + String message = "Received an unknown news message type. Please update"; + if (itemMessage != null) { + message = itemMessage.getFormattedMessage(messageObject.getAsJsonArray("args")); + } + + Set actions = new HashSet<>(); + for (JsonElement actionElement : newsItem.getAsJsonArray("actions")) { + NewsItemAction action = NewsItemAction.getByName(actionElement.getAsString()); + if (action != null) { + actions.add(action); + } + } + + return new NewsItem( + newsItem.get("id").getAsInt(), + newsItem.get("project").getAsString(), + newsItem.get("active").getAsBoolean(), + newsType, + newsType.read(newsItem.getAsJsonObject("data")), + newsItem.get("priority").getAsBoolean(), + message, + actions, + newsItem.get("url").getAsString() + ); + } + + public int getId() { + return id; + } + + public String getProject() { + return project; + } + + public boolean isActive() { + return active; + } + + public NewsType getType() { + return type; + } + + public ItemData getData() { + return data; + } + + @SuppressWarnings("unchecked") + public T getDataAs(Class type) { + return (T) data; + } + + public boolean isPriority() { + return priority; + } + + public String getRawMessage() { + return message; + } + + public String getMessage() { + return message + " See " + getUrl() + " for more information."; + } + + public Set getActions() { + return actions; + } + + public String getUrl() { + return url; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java new file mode 100644 index 000000000..78a8e4ed3 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemAction.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +public enum NewsItemAction { + ON_SERVER_STARTED, + ON_OPERATOR_JOIN, + BROADCAST_TO_CONSOLE, + BROADCAST_TO_OPERATORS; + + private static final NewsItemAction[] VALUES = values(); + + public static NewsItemAction getByName(String actionName) { + for (NewsItemAction type : VALUES) { + if (type.name().equalsIgnoreCase(actionName)) { + return type; + } + } + return null; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java new file mode 100644 index 000000000..b11605fb4 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsItemMessage.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonArray; + +// {} is used for things that have to be filled in by the server, +// {@} is for things that have to be filled in by us +public enum NewsItemMessage { + UPDATE_AVAILABLE("There is an update available for {}. The newest version is: {}"), + UPDATE_RECOMMENDED(UPDATE_AVAILABLE + ". Your version is quite old, updating is recommend."), + UPDATE_HIGHLY_RECOMMENDED(UPDATE_AVAILABLE + ". We highly recommend updating because some important changes have been made."), + UPDATE_ANCIENT_VERSION(UPDATE_AVAILABLE + ". You are running an ancient version, updating is recommended."), + + DOWNTIME_GENERIC("The {} is temporarily going down for maintenance soon."), + DOWNTIME_WITH_START("The {} is temporarily going down for maintenance on {}."), + DOWNTIME_TIMEFRAME(DOWNTIME_WITH_START + " The maintenance is expected to last till {}."); + + private static final NewsItemMessage[] VALUES = values(); + + private final String messageFormat; + private final String[] messageSplitted; + + NewsItemMessage(String messageFormat) { + this.messageFormat = messageFormat; + this.messageSplitted = messageFormat.split(" "); + } + + public static NewsItemMessage getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } + + public String getMessageFormat() { + return messageFormat; + } + + public String getFormattedMessage(JsonArray serverArguments) { + int serverArgumentsIndex = 0; + + StringBuilder message = new StringBuilder(); + for (String split : messageSplitted) { + if (message.length() > 0) { + message.append(' '); + } + + String result = split; + + if (serverArgumentsIndex < serverArguments.size()) { + String argument = serverArguments.get(serverArgumentsIndex).getAsString(); + result = result.replace("{}", argument); + if (!result.equals(split)) { + serverArgumentsIndex++; + } + } + + message.append(result); + } + return message.toString(); + } + + + @Override + public String toString() { + return getMessageFormat(); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/NewsType.java b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java new file mode 100644 index 000000000..c2848535e --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/NewsType.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news; + +import com.google.gson.JsonObject; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; +import org.geysermc.floodgate.news.data.ItemData; + +import java.util.function.Function; + +public enum NewsType { + BUILD_SPECIFIC(BuildSpecificData::read), + CHECK_AFTER(CheckAfterData::read); + + private static final NewsType[] VALUES = values(); + + private final Function readFunction; + + NewsType(Function readFunction) { + this.readFunction = readFunction; + } + + public static NewsType getByName(String newsType) { + for (NewsType type : VALUES) { + if (type.name().equalsIgnoreCase(newsType)) { + return type; + } + } + return null; + } + + public ItemData read(JsonObject data) { + return readFunction.apply(data); + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java new file mode 100644 index 000000000..d415f4200 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/BuildSpecificData.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonObject; + +public final class BuildSpecificData implements ItemData { + private String branch; + + private boolean allAffected; + private int affectedGreaterThan; + private int affectedLessThan; + + public static BuildSpecificData read(JsonObject data) { + BuildSpecificData updateData = new BuildSpecificData(); + updateData.branch = data.get("branch").getAsString(); + + JsonObject affectedBuilds = data.getAsJsonObject("affected_builds"); + if (affectedBuilds.has("all")) { + updateData.allAffected = affectedBuilds.get("all").getAsBoolean(); + } + if (!updateData.allAffected) { + updateData.affectedGreaterThan = affectedBuilds.get("gt").getAsInt(); + updateData.affectedLessThan = affectedBuilds.get("lt").getAsInt(); + } + return updateData; + } + + public boolean isAffected(String branch, int buildId) { + return this.branch.equals(branch) && + (allAffected || buildId > affectedGreaterThan && buildId < affectedLessThan); + } + + public String getBranch() { + return branch; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java new file mode 100644 index 000000000..92d01689b --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/CheckAfterData.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +import com.google.gson.JsonObject; + +public class CheckAfterData implements ItemData { + private long checkAfter; + + public static CheckAfterData read(JsonObject data) { + CheckAfterData checkAfterData = new CheckAfterData(); + checkAfterData.checkAfter = data.get("check_after").getAsLong(); + return checkAfterData; + } + + public long getCheckAfter() { + return checkAfter; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java new file mode 100644 index 000000000..122ee775d --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/news/data/ItemData.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.news.data; + +public interface ItemData { +} diff --git a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java index 1e385b2c6..3fe089e06 100644 --- a/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java +++ b/common/src/main/java/org/geysermc/floodgate/time/TimeSyncer.java @@ -25,18 +25,16 @@ package org.geysermc.floodgate.time; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public final class TimeSyncer { - private final ExecutorService executorService; + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private long timeOffset = Long.MIN_VALUE; // value when it failed to get the offset public TimeSyncer(String timeServer) { - ScheduledExecutorService service = Executors.newScheduledThreadPool(1); - service.scheduleWithFixedDelay(() -> { + executorService.scheduleWithFixedDelay(() -> { // 5 tries to get the time offset, since UDP doesn't guaranty a response for (int i = 0; i < 5; i++) { long offset = SntpClientUtils.requestTimeOffset(timeServer, 3000); @@ -46,7 +44,6 @@ public final class TimeSyncer { } } }, 0, 30, TimeUnit.MINUTES); - executorService = service; } public void shutdown() { diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java index 1025fcdba..f24dc8145 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -26,16 +26,66 @@ package org.geysermc.floodgate.util; public enum WebsocketEventType { - SUBSCRIBER_CREATED, - SUBSCRIBERS_COUNT, - ADDED_TO_QUEUE, - SKIN_UPLOADED, - CREATOR_DISCONNECTED, - LOG_MESSAGE; + /** + * Sent once we successfully connected to the server + */ + SUBSCRIBER_CREATED(0), + /** + * Sent every time a subscriber got added or disconnected + */ + SUBSCRIBER_COUNT(1), + /** + * Sent once the creator disconnected. After this packet the server will automatically close the + * connection once the queue size (sent in {@link #ADDED_TO_QUEUE} and {@link #SKIN_UPLOADED} + * reaches 0. + */ + CREATOR_DISCONNECTED(4), - public static final WebsocketEventType[] VALUES = values(); + /** + * Sent every time a skin got added to the upload queue + */ + ADDED_TO_QUEUE(2), + /** + * Sent every time a skin got successfully uploaded + */ + SKIN_UPLOADED(3), + + /** + * Sent every time a news item was added + */ + NEWS_ADDED(6), + + /** + * Sent when the server wants you to know something. Currently used for violations that aren't + * bad enough to close the connection + */ + LOG_MESSAGE(5); + + private static final WebsocketEventType[] VALUES; + + static { + WebsocketEventType[] values = values(); + VALUES = new WebsocketEventType[values.length]; + for (WebsocketEventType value : values) { + VALUES[value.id] = value; + } + } + + /** + * The ID is based of the time it got added. However, to keep the enum organized as time goes on, + * it looks nicer to sort the events based of categories. + */ + private final int id; + + WebsocketEventType(int id) { + this.id = id; + } public static WebsocketEventType getById(int id) { return VALUES.length > id ? VALUES[id] : null; } + + public int getId() { + return id; + } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 52f3680e2..87a698be3 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -64,6 +64,7 @@ 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.news.NewsItemAction; import org.geysermc.floodgate.time.TimeSyncer; import org.jetbrains.annotations.Contract; @@ -111,6 +112,7 @@ public class GeyserConnector { private TimeSyncer timeSyncer; private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; + private final NewsHandler newsHandler; private boolean shuttingDown = false; @@ -213,6 +215,21 @@ public class GeyserConnector { } } + String branch = "unknown"; + int buildNumber = -1; + try { + Properties gitProperties = new Properties(); + gitProperties.load(FileUtils.getResource("git.properties")); + branch = gitProperties.getProperty("git.branch"); + String build = gitProperties.getProperty("git.build.number"); + if (build != null) { + buildNumber = Integer.parseInt(build); + } + } catch (Exception e) { + logger.error("Failed to read git.properties", e); + } + newsHandler = new NewsHandler(branch, buildNumber); + CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls(); @@ -326,6 +343,8 @@ public class GeyserConnector { if (platformType == PlatformType.STANDALONE) { logger.warning(LanguageUtils.getLocaleStringLog("geyser.core.movement_warn")); } + + newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED); } public void shutdown() { @@ -371,6 +390,7 @@ public class GeyserConnector { generalThreadPool.shutdown(); bedrockServer.close(); timeSyncer.shutdown(); + newsHandler.shutdown(); players.clear(); defaultAuthType = null; this.getCommandManager().getCommands().clear(); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index dd541864a..beed78e05 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -63,7 +63,7 @@ public final class FloodgateSkinUploader { public FloodgateSkinUploader(GeyserConnector connector) { this.logger = connector.getLogger(); - this.client = new WebSocketClient(Constants.SKIN_UPLOAD_URI) { + this.client = new WebSocketClient(Constants.GLOBAL_API_WS_URI) { @Override public void onOpen(ServerHandshake handshake) { setConnectionLostTimeout(11); @@ -99,7 +99,7 @@ public final class FloodgateSkinUploader { id = node.get("id").asInt(); verifyCode = node.get("verify_code").asText(); break; - case SUBSCRIBERS_COUNT: + case SUBSCRIBER_COUNT: subscribersCount = node.get("subscribers_count").asInt(); break; case SKIN_UPLOADED: @@ -145,6 +145,8 @@ public final class FloodgateSkinUploader { break; } break; + case NEWS_ADDED: + //todo } } catch (Exception e) { logger.error("Error while receiving a message", e); diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java index 5b21d045c..a0cc9bf80 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Constants.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -27,18 +27,28 @@ package org.geysermc.connector.utils; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; public final class Constants { - public static final URI SKIN_UPLOAD_URI; + public static final URI GLOBAL_API_WS_URI; public static final String NTP_SERVER = "time.cloudflare.com"; + public static final Set NEWS_PROJECT_LIST = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("geyser", "floodgate")) + ); + + public static final String NEWS_OVERVIEW_URL = "https://api.geysermc.org/v1/news"; + static { - URI skinUploadUri = null; + URI wsUri = null; try { - skinUploadUri = new URI("wss://api.geysermc.org/ws"); + wsUri = new URI("wss://api.geysermc.org/ws"); } catch (URISyntaxException e) { e.printStackTrace(); } - SKIN_UPLOAD_URI = skinUploadUri; + GLOBAL_API_WS_URI = wsUri; } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java new file mode 100644 index 000000000..aa8fe2efc --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/NewsHandler.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonSyntaxException; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.common.ChatColor; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.floodgate.news.NewsItem; +import org.geysermc.floodgate.news.NewsItemAction; +import org.geysermc.floodgate.news.data.BuildSpecificData; +import org.geysermc.floodgate.news.data.CheckAfterData; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class NewsHandler { + private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); + private final GeyserLogger logger = GeyserConnector.getInstance().getLogger(); + private final Gson gson = new Gson(); + + private final Map activeNewsItems = new HashMap<>(); + private final String branch; + private final int build; + + private boolean geyserStarted; + + public NewsHandler(String branch, int build) { + this.branch = branch; + this.build = build; + + executorService.scheduleWithFixedDelay(this::checkNews, 0, 30, TimeUnit.MINUTES); + } + + private void schedule(long delayMs) { + executorService.schedule(this::checkNews, delayMs, TimeUnit.MILLISECONDS); + } + + private void checkNews() { + try { + String body = WebUtils.getBody(Constants.NEWS_OVERVIEW_URL); + JsonArray array = gson.fromJson(body, JsonArray.class); + + try { + for (JsonElement newsItemElement : array) { + NewsItem newsItem = NewsItem.readItem(newsItemElement.getAsJsonObject()); + if (newsItem != null) { + addNews(newsItem); + } + } + } catch (Exception e) { + if (logger.isDebug()) { + logger.error("Error while reading news item", e); + } + } + } catch (JsonSyntaxException ignored) {} + } + + public void setGeyserStarted() { + geyserStarted = true; + } + + public void handleNews(GeyserSession session, NewsItemAction action) { + for (NewsItem news : getActiveNews(action)) { + handleNewsItem(session, news, action); + } + } + + private void handleNewsItem(GeyserSession session, NewsItem news, NewsItemAction action) { + switch (action) { + case ON_SERVER_STARTED: + if (!geyserStarted) { + return; + } + case BROADCAST_TO_CONSOLE: + logger.info(news.getMessage()); + break; + case ON_OPERATOR_JOIN: + //todo doesn't work, it's called before we know the op level. +// if (session != null && session.getOpPermissionLevel() >= 2) { +// session.sendMessage(ChatColor.GREEN + news.getMessage()); +// } + break; + case BROADCAST_TO_OPERATORS: + for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) { + if (player.getOpPermissionLevel() >= 2) { + session.sendMessage(ChatColor.GREEN + news.getMessage()); + } + } + break; + } + } + + public Collection getActiveNews() { + return activeNewsItems.values(); + } + + public Collection getActiveNews(NewsItemAction action) { + List news = new ArrayList<>(); + for (NewsItem item : getActiveNews()) { + if (item.getActions().contains(action)) { + news.add(item); + } + } + return news; + } + + public void addNews(NewsItem item) { + if (activeNewsItems.containsKey(item.getId())) { + if (!item.isActive()) { + activeNewsItems.remove(item.getId()); + } + return; + } + + if (!Constants.NEWS_PROJECT_LIST.contains(item.getProject())) { + return; + } + + switch (item.getType()) { + case BUILD_SPECIFIC: + if (!item.getDataAs(BuildSpecificData.class).isAffected(branch, build)) { + return; + } + break; + case CHECK_AFTER: + long checkAfter = item.getDataAs(CheckAfterData.class).getCheckAfter(); + long delayMs = System.currentTimeMillis() - checkAfter; + schedule(delayMs > 0 ? delayMs : 0); + break; + } + + activeNewsItems.put(item.getId(), item); + activateNews(item); + } + + private void activateNews(NewsItem item) { + for (NewsItemAction action : item.getActions()) { + handleNewsItem(null, item, action); + } + } + + public void shutdown() { + executorService.shutdown(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index ba008da41..cf9f0e1fb 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -45,9 +45,8 @@ public class WebUtils { * @return Body contents or error message if the request fails */ public static String getBody(String reqURL) { - URL url = null; try { - url = new URL(reqURL); + URL url = new URL(reqURL); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("User-Agent", "Geyser-" + GeyserConnector.getInstance().getPlatformType().toString() + "/" + GeyserConnector.VERSION); // Otherwise Java 8 fails on checking updates From 01492647fcfb5b24000abc4bc88470b6f03a2e9b Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 6 Jun 2021 00:53:58 +0200 Subject: [PATCH 50/51] Some small changes --- .../geysermc/floodgate/util/Base64Utils.java | 2 +- .../floodgate/util/FloodgateConfigHolder.java | 2 +- .../geysermc/floodgate/util/LinkedPlayer.java | 2 +- connector/pom.xml | 4 +++ .../geysermc/connector/GeyserConnector.java | 4 ++- .../connector/command/CommandManager.java | 2 +- .../network/session/GeyserSession.java | 6 ++-- .../session/auth/BedrockClientData.java | 29 ------------------- .../session/cache/AdvancementsCache.java | 3 +- .../network/session/cache/FormCache.java | 17 ++++++----- .../BedrockNetworkStackLatencyTranslator.java | 1 - .../java/JavaAdvancementsTabTranslator.java | 7 +++-- .../java/JavaStatisticsTranslator.java | 1 + .../connector/utils/LoginEncryptionUtils.java | 3 +- .../connector/utils/StatisticsUtils.java | 4 +-- 15 files changed, 33 insertions(+), 54 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java index 4c527b948..326fa2590 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java +++ b/common/src/main/java/org/geysermc/floodgate/util/Base64Utils.java @@ -25,7 +25,7 @@ package org.geysermc.floodgate.util; -public class Base64Utils { +public final class Base64Utils { public static int getEncodedLength(int length) { if (length <= 0) { return -1; diff --git a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java index 386213ada..d33840bb4 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java +++ b/common/src/main/java/org/geysermc/floodgate/util/FloodgateConfigHolder.java @@ -28,7 +28,7 @@ package org.geysermc.floodgate.util; import lombok.Getter; import lombok.Setter; -public class FloodgateConfigHolder { +public final class FloodgateConfigHolder { @Getter @Setter private static Object config; diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 7d67a44a0..f91bfafbc 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -48,7 +48,7 @@ public final class LinkedPlayer implements Cloneable { */ private final UUID bedrockId; /** - * If the LinkedPlayer is send from a different platform. For example the LinkedPlayer is from + * If the LinkedPlayer is sent from a different platform. For example the LinkedPlayer is from * Bungee but the data has been sent to the Bukkit server. */ private boolean fromDifferentPlatform = false; diff --git a/connector/pom.xml b/connector/pom.xml index 1ed01a0c4..b7bfc9d3a 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -39,6 +39,7 @@ org.java-websocket Java-WebSocket 1.5.1 + compile com.github.CloudburstMC.Protocol @@ -214,11 +215,13 @@ org.reflections reflections 0.9.11 + compile org.dom4j dom4j 2.1.3 + compile net.kyori @@ -254,6 +257,7 @@ com.github.GeyserMC MCAuthLib 0e48a094f2 + compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 87a698be3..8dfa12eb4 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -109,7 +109,7 @@ public class GeyserConnector { @Setter private AuthType defaultAuthType; - private TimeSyncer timeSyncer; + private final TimeSyncer timeSyncer; private FloodgateCipher cipher; private FloodgateSkinUploader skinUploader; private final NewsHandler newsHandler; @@ -202,6 +202,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); + TimeSyncer timeSyncer = null; if (defaultAuthType == AuthType.FLOODGATE) { timeSyncer = new TimeSyncer(Constants.NTP_SERVER); try { @@ -214,6 +215,7 @@ public class GeyserConnector { logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); } } + this.timeSyncer = timeSyncer; String branch = "unknown"; int buildNumber = -1; diff --git a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java index 71eb2c742..f62fd8539 100644 --- a/connector/src/main/java/org/geysermc/connector/command/CommandManager.java +++ b/connector/src/main/java/org/geysermc/connector/command/CommandManager.java @@ -53,7 +53,7 @@ public abstract class CommandManager { registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version")); registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings")); registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics")); - registerCommand(new AdvancementsCommand( "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); + registerCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements")); } public void registerCommand(GeyserCommand command) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index ead9d1834..f901fe9a1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -140,10 +140,10 @@ public class GeyserSession implements CommandSender { private ChunkCache chunkCache; private EntityCache entityCache; private EntityEffectCache effectCache; + private final FormCache formCache; private final PreferencesCache preferencesCache; private final TagCache tagCache; private WorldCache worldCache; - private FormCache formCache; private final Int2ObjectMap teleportMap = new Int2ObjectOpenHashMap<>(); private final PlayerInventory playerInventory; @@ -445,12 +445,13 @@ public class GeyserSession implements CommandSender { this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); + this.formCache = new FormCache(this); this.preferencesCache = new PreferencesCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); - this.formCache = new FormCache(this); this.collisionManager = new CollisionManager(this); + this.playerEntity = new SessionPlayerEntity(this); collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition()); @@ -851,7 +852,6 @@ public class GeyserSession implements CommandSender { this.entityCache = null; this.effectCache = null; this.worldCache = null; - this.formCache = null; closed = true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index d87590e5f..fc4d1164a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -25,26 +25,18 @@ package org.geysermc.connector.network.session.auth; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.Charsets; import lombok.Getter; -import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.UiProfile; -import java.util.Base64; import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Getter public final class BedrockClientData { - @JsonIgnore - private JsonNode jsonData; - @JsonProperty(value = "GameVersion") private String gameVersion; @JsonProperty(value = "ServerAddress") @@ -115,27 +107,6 @@ public final class BedrockClientData { @JsonProperty(value = "PlayFabId") private String playFabId; - public void setJsonData(JsonNode data) { - if (this.jsonData == null && data != null) { - this.jsonData = data; - } - } - - public boolean isAlex() { - try { - byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); - String geometryName = - SkinProvider.OBJECT_MAPPER - .readTree(bytes) - .get("geometry").get("default") - .asText(); - return "geometry.humanoid.customSlim".equals(geometryName); - } catch (Exception exception) { - exception.printStackTrace(); - return false; - } - } - public DeviceOs getDeviceOs() { return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java index d20eb11dd..98ec5b262 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.advancement.Advancement; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket; import lombok.Getter; import lombok.Setter; -import lombok.experimental.Accessors; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.GeyserAdvancement; @@ -58,7 +57,7 @@ public class AdvancementsCache { /** * Stores player's chosen advancement's ID and title for use in form creators. */ - @Setter @Accessors(chain = true) + @Setter private String currentAdvancementCategoryId = null; private final GeyserSession session; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java index cdd8a01ac..1cdcf228b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java @@ -33,6 +33,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.RequiredArgsConstructor; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.cumulus.Form; +import org.geysermc.cumulus.SimpleForm; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -58,13 +59,15 @@ public class FormCache { formRequestPacket.setFormData(form.getJsonData()); session.sendUpstreamPacket(formRequestPacket); - // Hack to fix the url image loading bug - NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); - latencyPacket.setFromServer(true); - latencyPacket.setTimestamp(-System.currentTimeMillis()); - session.getConnector().getGeneralThreadPool().schedule( - () -> session.sendUpstreamPacket(latencyPacket), - 500, TimeUnit.MILLISECONDS); + // Hack to fix the (url) image loading bug + if (form instanceof SimpleForm) { + NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket(); + latencyPacket.setFromServer(true); + latencyPacket.setTimestamp(-System.currentTimeMillis()); + session.getConnector().getGeneralThreadPool().schedule( + () -> session.sendUpstreamPacket(latencyPacket), + 500, TimeUnit.MILLISECONDS); + } return windowId; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 577469fdd..208e7c75d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -52,7 +52,6 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { @Override public void translate(ServerAdvancementTabPacket packet, GeyserSession session) { - session.getAdvancementsCache() - .setCurrentAdvancementCategoryId(packet.getTabId()) - .buildAndShowListForm(); + AdvancementsCache advancementsCache = session.getAdvancementsCache(); + advancementsCache.setCurrentAdvancementCategoryId(packet.getTabId()); + advancementsCache.buildAndShowListForm(); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java index 5d3164c0d..247808041 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaStatisticsTranslator.java @@ -33,6 +33,7 @@ import org.geysermc.connector.utils.StatisticsUtils; @Translator(packet = ServerStatisticsPacket.class) public class JavaStatisticsTranslator extends PacketTranslator { + @Override public void translate(ServerStatisticsPacket packet, GeyserSession session) { session.updateStatistics(packet.getStatistics()); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index ce9f23272..d3d5fa67d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -134,7 +134,6 @@ public class LoginEncryptionUtils { JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()); BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class); - data.setJsonData(clientDataJson); session.setClientData(data); if (EncryptionUtils.canUseEncryption()) { @@ -243,7 +242,7 @@ public class LoginEncryptionUtils { } /** - * Promts the user between either OAuth code login or manual password authentication + * Prompts the user between either OAuth code login or manual password authentication */ public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) { session.sendForm( diff --git a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java index f2181d871..e59807d75 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/StatisticsUtils.java @@ -180,8 +180,8 @@ public class StatisticsUtils { session.sendForm( builder.content(content.toString()) .button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo") - .responseHandler((form1, responseData1) -> { - SimpleFormResponse response1 = form.parseResponse(responseData1); + .responseHandler((form1, subFormResponseData) -> { + SimpleFormResponse response1 = form.parseResponse(subFormResponseData); if (response1.isCorrect()) { buildAndSendStatisticsMenu(session); } From ab8ce77cf0de8df4ee069078db9b1ac3074ba6a3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sun, 6 Jun 2021 01:05:22 +0200 Subject: [PATCH 51/51] Some small changes --- .../src/main/java/org/geysermc/floodgate/util/UiProfile.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java index 58d615eaa..af1121f3c 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java +++ b/common/src/main/java/org/geysermc/floodgate/util/UiProfile.java @@ -27,7 +27,8 @@ package org.geysermc.floodgate.util; public enum UiProfile { - CLASSIC, POCKET; + CLASSIC, + POCKET; private static final UiProfile[] VALUES = values();