From 115d7097063662dc1ff160bf2e8846c6576f038c Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 19 Jun 2025 15:19:59 +0200 Subject: [PATCH 1/4] Update Bedrock protocol library --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 756f55fb2..3c9a58efc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,9 +9,9 @@ netty = "4.2.1.Final" guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 websocket = "1.5.1" -protocol-connection = "3.0.0.Beta7-20250618.210427-9" -protocol-common = "3.0.0.Beta7-20250618.210427-9" -protocol-codec = "3.0.0.Beta7-20250618.210427-9" +protocol-connection = "3.0.0.Beta7-20250619.102015-10" +protocol-common = "3.0.0.Beta7-20250619.102015-10" +protocol-codec = "3.0.0.Beta7-20250619.102015-10" raknet = "1.0.0.CR3-20250218.160705-18" minecraftauth = "4.1.1" mcprotocollib = "1.21.5-20250509.144049-29" From baec73d38d4a5866ab126767660698529313fc1d Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Mon, 9 Jun 2025 00:12:15 +0200 Subject: [PATCH 2/4] Attempt to resolve pack download issues for consoles --- .../geyser/network/UpstreamPacketHandler.java | 49 +++++++++++++++++-- .../geyser/pack/GeyserResourcePack.java | 2 +- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index f54ab8229..ec606f205 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -52,6 +52,7 @@ import org.cloudburstmc.protocol.bedrock.packet.ResourcePacksInfoPacket; import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket; import org.cloudburstmc.protocol.common.PacketSignal; import org.cloudburstmc.protocol.common.util.Zlib; +import org.geysermc.api.util.BedrockPlatform; import org.geysermc.geyser.Constants; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.bedrock.SessionInitializeEvent; @@ -80,7 +81,10 @@ import java.nio.channels.SeekableByteChannel; import java.util.ArrayDeque; import java.util.Deque; import java.util.OptionalInt; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; public class UpstreamPacketHandler extends LoggingPacketHandler { @@ -88,6 +92,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { private final Deque packsToSend = new ArrayDeque<>(); private final CompressionStrategy compressionStrategy; + private static final int PACKET_SEND_DELAY = 4 * 50; // DELAY THE SEND OF PACKETS TO AVOID BURSTING SLOWER AND/OR HIGHER PING CLIENTS + private final Queue chunkRequestQueue = new ConcurrentLinkedQueue<>(); + private boolean sendingChunks = false; + private SessionLoadResourcePacksEventImpl resourcePackLoadEvent; public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) { @@ -297,13 +305,33 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public PacketSignal handle(ResourcePackChunkRequestPacket packet) { + chunkRequestQueue.add(packet); + if (isConsole()) { + if (!sendingChunks) { + sendingChunks = true; + processNextChunk(); + } + } else { + processNextChunk(); + } + return PacketSignal.HANDLED; + } + + public void processNextChunk() { + ResourcePackChunkRequestPacket packet = chunkRequestQueue.poll(); + if (packet == null) { + sendingChunks = false; + return; + } + ResourcePackHolder holder = this.resourcePackLoadEvent.getPacks().get(packet.getPackId()); if (holder == null) { GeyserImpl.getInstance().getLogger().debug("Client {0} tried to request pack id {1} not sent to it!", session.bedrockUsername(), packet.getPackId()); + sendingChunks = false; session.disconnect("disconnectionScreen.resourcePack"); - return PacketSignal.HANDLED; + return; } ResourcePack pack = holder.pack(); @@ -316,7 +344,8 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (!resourcePackLoadEvent.value(pack.uuid(), ResourcePackOption.Type.FALLBACK, true)) { session.disconnect("Unable to provide downloaded resource pack. Contact an administrator!"); - return PacketSignal.HANDLED; + sendingChunks = false; + return; } } @@ -339,14 +368,19 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { data.setData(Unpooled.wrappedBuffer(packData)); - session.sendUpstreamPacket(data); + if (isConsole()) { + // Also flushes packets + // Avoids bursting slower / delayed clients + session.sendUpstreamPacketImmediately(data); + GeyserImpl.getInstance().getScheduledThread().schedule(this::processNextChunk, PACKET_SEND_DELAY, TimeUnit.MILLISECONDS); + } else { + session.sendUpstreamPacket(data); + } // Check if it is the last chunk and send next pack in queue when available. if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSend.isEmpty()) { sendPackDataInfo(packsToSend.pop()); } - - return PacketSignal.HANDLED; } private void sendPackDataInfo(String id) { @@ -394,4 +428,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.sendUpstreamPacket(data); } + + private boolean isConsole() { + BedrockPlatform platform = session.platform(); + return platform == BedrockPlatform.PS4 || platform == BedrockPlatform.XBOX || platform == BedrockPlatform.NX; + } } diff --git a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java index 306bb9aa8..1afd1a05a 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java +++ b/core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java @@ -41,7 +41,7 @@ public record GeyserResourcePack( /** * The size of each chunk to use when sending the resource packs to clients in bytes */ - public static final int CHUNK_SIZE = 102400; + public static final int CHUNK_SIZE = 1024 * 256; public static class Builder implements ResourcePack.Builder { From b96373a33fbfd07ec3cc2c0e4170ef405f3655f2 Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Thu, 19 Jun 2025 22:50:51 +0200 Subject: [PATCH 3/4] Resolve some CDN pack related issues --- .../SessionLoadResourcePacksEventImpl.java | 10 +++++- .../geyser/network/UpstreamPacketHandler.java | 17 +++++---- .../org/geysermc/geyser/util/WebUtils.java | 36 +++++++++++++++++-- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java index cfa93fd3b..241f5d170 100644 --- a/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java +++ b/core/src/main/java/org/geysermc/geyser/event/type/SessionLoadResourcePacksEventImpl.java @@ -36,6 +36,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent; import org.geysermc.geyser.api.pack.ResourcePack; import org.geysermc.geyser.api.pack.ResourcePackManifest; +import org.geysermc.geyser.api.pack.UrlPackCodec; import org.geysermc.geyser.api.pack.exception.ResourcePackException; import org.geysermc.geyser.api.pack.option.PriorityOption; import org.geysermc.geyser.api.pack.option.ResourcePackOption; @@ -205,7 +206,7 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE ResourcePackManifest.Header header = pack.manifest().header(); entries.add(new ResourcePacksInfoPacket.Entry( header.uuid(), header.version().toString(), pack.codec().size(), pack.contentKey(), - subpackName(pack), header.uuid().toString(), false, false, false, subpackName(pack)) + subpackName(pack), header.uuid().toString(), false, false, false, cdnUrl(pack)) ); } @@ -229,4 +230,11 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE private String subpackName(GeyserResourcePack pack) { return value(pack.uuid(), ResourcePackOption.Type.SUBPACK, ""); } + + private String cdnUrl(GeyserResourcePack pack) { + if (pack.codec() instanceof UrlPackCodec urlPackCodec) { + return urlPackCodec.url(); + } + return ""; + } } diff --git a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java index ec606f205..d91f42e3b 100644 --- a/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java @@ -92,9 +92,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { private final Deque packsToSend = new ArrayDeque<>(); private final CompressionStrategy compressionStrategy; - private static final int PACKET_SEND_DELAY = 4 * 50; // DELAY THE SEND OF PACKETS TO AVOID BURSTING SLOWER AND/OR HIGHER PING CLIENTS + // Avoid overloading consoles when downloading larger resource packs + private static final int PACKET_SEND_DELAY = 4 * 50; private final Queue chunkRequestQueue = new ConcurrentLinkedQueue<>(); - private boolean sendingChunks = false; + private boolean currentlySendingChunks = false; private SessionLoadResourcePacksEventImpl resourcePackLoadEvent; @@ -305,10 +306,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { @Override public PacketSignal handle(ResourcePackChunkRequestPacket packet) { + // Resolve some console pack downloading issues. + // See for reference chunkRequestQueue.add(packet); if (isConsole()) { - if (!sendingChunks) { - sendingChunks = true; + if (!currentlySendingChunks) { + currentlySendingChunks = true; processNextChunk(); } } else { @@ -320,7 +323,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public void processNextChunk() { ResourcePackChunkRequestPacket packet = chunkRequestQueue.poll(); if (packet == null) { - sendingChunks = false; + currentlySendingChunks = false; return; } @@ -329,7 +332,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (holder == null) { GeyserImpl.getInstance().getLogger().debug("Client {0} tried to request pack id {1} not sent to it!", session.bedrockUsername(), packet.getPackId()); - sendingChunks = false; + currentlySendingChunks = false; session.disconnect("disconnectionScreen.resourcePack"); return; } @@ -344,7 +347,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { if (!resourcePackLoadEvent.value(pack.uuid(), ResourcePackOption.Type.FALLBACK, true)) { session.disconnect("Unable to provide downloaded resource pack. Contact an administrator!"); - sendingChunks = false; + currentlySendingChunks = false; return; } } diff --git a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java index ba17a559a..8bac97b1b 100644 --- a/core/src/main/java/org/geysermc/geyser/util/WebUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/WebUtils.java @@ -33,8 +33,18 @@ import org.geysermc.geyser.GeyserLogger; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; -import java.io.*; -import java.net.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLEncoder; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -44,6 +54,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Stream; +import java.util.zip.ZipFile; public class WebUtils { @@ -183,6 +194,27 @@ public class WebUtils { .formatted(url, downloadSize, size)); } + try { + boolean shouldDeleteEnclosing = false; + var originalZip = downloadLocation; + try (ZipFile zip = new ZipFile(downloadLocation.toFile())) { + // This can (or should???) contain a zip + if (zip.stream().allMatch(name -> name.getName().endsWith(".zip"))) { + // Unzip the pack, as that's what we're after + downloadLocation = REMOTE_PACK_CACHE.resolve(url.hashCode() + "_" + System.currentTimeMillis() + "_unzipped.zip"); + Files.copy(zip.getInputStream(zip.entries().nextElement()), downloadLocation, StandardCopyOption.REPLACE_EXISTING); + shouldDeleteEnclosing = true; + } + } finally { + if (shouldDeleteEnclosing) { + // We don't need the original zip anymore + Files.delete(originalZip); + } + } + } catch (IOException e) { + throw new IllegalArgumentException("Encountered exception while reading downloaded resource pack at url: %s".formatted(url), e); + } + try { Files.write( packMetadata, From 00ceaa9782203c78c75528fcb7f2b42c545e2c4f Mon Sep 17 00:00:00 2001 From: onebeastchris Date: Fri, 20 Jun 2025 23:51:03 +0200 Subject: [PATCH 4/4] Set Bedrock xuid's in chat and player list packets --- .../java/org/geysermc/geyser/GeyserImpl.java | 4 ++++ .../org/geysermc/geyser/skin/SkinManager.java | 3 +++ .../java/JavaDisguisedChatTranslator.java | 2 +- .../java/JavaPlayerChatTranslator.java | 2 +- .../translator/text/MessageTranslator.java | 20 +++++++++++++++++-- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index a8213b8a2..28b306c66 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -42,6 +42,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; +import org.cloudburstmc.protocol.bedrock.util.EncryptionUtils; import org.geysermc.api.Geyser; import org.geysermc.cumulus.form.Form; import org.geysermc.cumulus.form.util.FormBuilder; @@ -215,6 +216,9 @@ public class GeyserImpl implements GeyserApi, EventRegistrar { } public void initialize() { + // Setup encryption early so we don't start if we can't auth + EncryptionUtils.getMojangPublicKey(); + long startupTime = System.currentTimeMillis(); GeyserLogger logger = bootstrap.getGeyserLogger(); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index d194994bc..b59163489 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -111,8 +111,11 @@ public class SkinManager { String xuid = ""; GeyserSession playerSession = GeyserImpl.getInstance().connectionByUuid(uuid); + // Prefer looking up xuid using the session to catch linked players if (playerSession != null) { xuid = playerSession.getAuthData().xuid(); + } else if (uuid.version() == 0) { + xuid = Long.toString(uuid.getLeastSignificantBits()); } PlayerListPacket.Entry entry; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java index 67bdb6f9a..61999b389 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaDisguisedChatTranslator.java @@ -36,6 +36,6 @@ public class JavaDisguisedChatTranslator extends PacketTranslator chatTypeHolder, Component targetName, Component sender) { + public static void handleChatPacket(GeyserSession session, Component message, Holder chatTypeHolder, Component targetName, Component sender, @Nullable UUID senderUuid) { TextPacket textPacket = new TextPacket(); textPacket.setPlatformChatId(""); textPacket.setSourceName(""); - textPacket.setXuid(session.getAuthData().xuid()); + + if (senderUuid == null) { + textPacket.setXuid(session.getAuthData().xuid()); + } else { + String xuid = ""; + GeyserSession playerSession = GeyserImpl.getInstance().connectionByUuid(senderUuid); + + // Prefer looking up xuid using the session to catch linked players + if (playerSession != null) { + xuid = playerSession.getAuthData().xuid(); + } else if (senderUuid.version() == 0) { + xuid = Long.toString(senderUuid.getLeastSignificantBits()); + } + textPacket.setXuid(xuid); + } textPacket.setType(TextPacket.Type.CHAT); textPacket.setNeedsTranslation(false);