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