* 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.
*/
@@ -445,10 +439,10 @@ 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.windowCache = new WindowCache(this);
this.collisionManager = new CollisionManager(this);
@@ -577,7 +571,16 @@ 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.
+ // 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(' ', '_');
+ }
+
+ protocol = new MinecraftProtocol(validUsername);
}
connectDownstream();
@@ -606,7 +609,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();
@@ -651,24 +654,6 @@ public class GeyserSession implements CommandSender {
*/
private void connectDownstream() {
boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE;
- final PublicKey publicKey;
-
- if (floodgate) {
- PublicKey key = null;
- try {
- key = EncryptionUtil.getKeyFromFile(
- connector.getConfig().getFloodgateKeyPath(),
- 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"));
- }
// Start ticking
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
@@ -690,22 +675,40 @@ public class GeyserSession implements CommandSender {
if (event.getPacket() instanceof HandshakePacket) {
String addressSuffix;
if (floodgate) {
- String encrypted = "";
+ byte[] encryptedData;
+
try {
- encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
+ FloodgateSkinUploader skinUploader = connector.getSkinUploader();
+ FloodgateCipher cipher = connector.getCipher();
+
+ 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(),
- upstream.getAddress().getAddress().getHostAddress()
- ));
+ upstream.getAddress().getAddress().getHostAddress(),
+ skinUploader.getId(),
+ 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()));
+ return;
}
- addressSuffix = '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted;
+ addressSuffix = '\0' + new String(encryptedData, StandardCharsets.UTF_8);
} else {
addressSuffix = "";
}
@@ -788,6 +791,12 @@ public class GeyserSession implements CommandSender {
if (remoteAuthType == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
SkinManager.handleBedrockSkin(playerEntity, clientData);
}
+
+ 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);
@@ -832,7 +841,6 @@ public class GeyserSession implements CommandSender {
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
- this.windowCache = null;
closed = true;
}
@@ -973,10 +981,6 @@ public class GeyserSession implements CommandSender {
return clientData.getLanguageCode();
}
- 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
this.renderDistance = renderDistance;
@@ -990,8 +994,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(FormBuilder, ?> formBuilder) {
+ formCache.showForm(formBuilder.build());
}
private void startGame() {
@@ -1050,7 +1058,7 @@ public class GeyserSession implements CommandSender {
settings.setRewindHistorySize(0);
settings.setServerAuthoritativeBlockBreaking(false);
startGamePacket.setPlayerMovementSettings(settings);
-
+
upstream.sendPacket(startGamePacket);
}
@@ -1168,7 +1176,7 @@ public class GeyserSession implements CommandSender {
/**
* Queue a packet to be sent to player.
- *
+ *
* @param packet the bedrock packet from the NukkitX protocol lib
*/
public void sendUpstreamPacket(BedrockPacket packet) {
@@ -1233,7 +1241,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/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 16e06c066..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,17 +25,18 @@
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")
@@ -77,9 +78,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")
@@ -106,18 +107,19 @@ public class BedrockClientData {
@JsonProperty(value = "PlayFabId")
private String playFabId;
- public enum UIProfile {
- @JsonEnumDefaultValue
- CLASSIC,
- POCKET
+ public DeviceOs getDeviceOs() {
+ return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN;
}
- public enum InputMode {
- @JsonEnumDefaultValue
- UNKNOWN,
- KEYBOARD_MOUSE,
- TOUCH, // I guess Touch?
- CONTROLLER,
- VR
+ 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/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
index 369967acc..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,26 +29,19 @@ 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 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
*/
@@ -74,73 +67,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