diff --git a/README.md b/README.md
index cc31106e5..c9da69674 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
## Supported Versions
-Geyser is currently supporting Minecraft Bedrock 1.21.70 - 1.21.94 and Minecraft Java 1.21.7 - 1.21.8. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
+Geyser is currently supporting Minecraft Bedrock 1.21.70 - 1.21.100 and Minecraft Java 1.21.7 - 1.21.8. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
## Setting Up
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
diff --git a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
index b7a678aa6..23971305d 100644
--- a/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
+++ b/api/src/main/java/org/geysermc/geyser/api/connection/GeyserConnection.java
@@ -26,6 +26,7 @@
package org.geysermc.geyser.api.connection;
import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.connection.Connection;
@@ -35,8 +36,11 @@ import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.entity.EntityData;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
+import org.geysermc.geyser.api.skin.SkinData;
+import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
@@ -118,6 +122,50 @@ public interface GeyserConnection extends Connection, CommandSource {
*/
void sendCommand(String command);
+ /**
+ * Gets the hostname or ip address the player used to join this Geyser instance.
+ * Example:
+ *
+ * - {@code test.geysermc.org}
+ * - {@code 127.0.0.1}
+ * - {@code 06e9:c755:4eff:5f13:9b4c:4b21:9df2:6a73}
+ *
+ *
+ * @throws NoSuchElementException if called before the session is fully initialized
+ * @return the ip address or hostname string the player used to join
+ * @since 2.8.3
+ */
+ @NonNull
+ String joinAddress();
+
+ /**
+ * Gets the port the player used to join this Geyser instance.
+ * Example:
+ *
+ * - {@code 19132}
+ * - {@code 2202}
+ *
+ *
+ * @throws NoSuchElementException if called before the session is fully initialized
+ * @return the port the player used to join
+ * @since 2.8.3
+ */
+ @Positive
+ int joinPort();
+
+ /**
+ * Applies a skin to a player seen by this Geyser connection.
+ * If the uuid matches the {@link GeyserConnection#javaUuid()}, this
+ * will update the skin of this Geyser connection.
+ * If the player uuid provided is not known to this connection, this method
+ * will silently return.
+ *
+ * @param player which player this skin should be applied to
+ * @param skinData the skin data to apply
+ * @since 2.8.3
+ */
+ void sendSkin(@NonNull UUID player, @NonNull SkinData skinData);
+
/**
* @param javaId the Java entity ID to look up.
* @return a {@link GeyserEntity} if present in this connection's entity tracker.
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index b376919cd..5c7f34499 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -148,10 +148,10 @@ inner class GitInfo {
tasks.register("downloadBedrockData") {
urls = listOf(
"https://raw.githubusercontent.com/CloudburstMC/Data/master/entity_identifiers.dat",
- "https://raw.githubusercontent.com/CloudburstMC/Data/master/biome_definitions.dat",
"https://raw.githubusercontent.com/CloudburstMC/Data/master/block_palette.nbt",
"https://raw.githubusercontent.com/CloudburstMC/Data/master/creative_items.json",
- "https://raw.githubusercontent.com/CloudburstMC/Data/master/runtime_item_states.json"
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/runtime_item_states.json",
+ "https://raw.githubusercontent.com/CloudburstMC/Data/master/stripped_biome_definitions.json"
)
suffixedFiles = listOf("block_palette.nbt", "creative_items.json", "runtime_item_states.json")
diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
index 2ba8683d1..791008025 100644
--- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
+++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
@@ -33,6 +33,7 @@ import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
+import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.api.util.MinecraftVersion;
import org.geysermc.geyser.impl.MinecraftVersionImpl;
@@ -87,6 +88,7 @@ public final class GameProtocol {
register(Bedrock_v800.CODEC, "1.21.80", "1.21.81", "1.21.82", "1.21.83", "1.21.84");
register(Bedrock_v818.CODEC, "1.21.90", "1.21.91", "1.21.92");
register(Bedrock_v819.CODEC, "1.21.93", "1.21.94");
+ register(Bedrock_v827.CODEC, "1.21.100");
MinecraftVersion latestBedrock = SUPPORTED_BEDROCK_VERSIONS.get(SUPPORTED_BEDROCK_VERSIONS.size() - 1);
DEFAULT_BEDROCK_VERSION = latestBedrock.versionString();
diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
index 50dd483c6..738658a8d 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java
@@ -45,6 +45,8 @@ import org.cloudburstmc.nbt.NbtUtils;
import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
+import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
+import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl;
@@ -120,6 +122,8 @@ public final class BlockRegistryPopulator {
.put(ObjectIntPair.of("1_21_70", Bedrock_v786.CODEC.getProtocolVersion()), Conversion800_786::remapBlock)
.put(ObjectIntPair.of("1_21_80", Bedrock_v800.CODEC.getProtocolVersion()), tag -> tag)
.put(ObjectIntPair.of("1_21_90", Bedrock_v818.CODEC.getProtocolVersion()), tag -> tag)
+ .put(ObjectIntPair.of("1_21_90", Bedrock_v819.CODEC.getProtocolVersion()), tag -> tag)
+ .put(ObjectIntPair.of("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()), tag -> tag)
.build();
// We can keep this strong as nothing should be garbage collected
diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
index 545a8df05..8c509f2b1 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java
@@ -49,6 +49,7 @@ import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
import org.cloudburstmc.protocol.bedrock.codec.v819.Bedrock_v819;
+import org.cloudburstmc.protocol.bedrock.codec.v827.Bedrock_v827;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
@@ -147,6 +148,7 @@ public class ItemRegistryPopulator {
paletteVersions.add(new PaletteVersion("1_21_80", Bedrock_v800.CODEC.getProtocolVersion(), fallbacks1_21_80));
paletteVersions.add(new PaletteVersion("1_21_90", Bedrock_v818.CODEC.getProtocolVersion(), Map.of(Items.MUSIC_DISC_LAVA_CHICKEN, Items.MUSIC_DISC_CHIRP)));
paletteVersions.add(new PaletteVersion("1_21_93", Bedrock_v819.CODEC.getProtocolVersion()));
+ paletteVersions.add(new PaletteVersion("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
index 1c5c14c18..cab6964b8 100644
--- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
+++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
@@ -46,6 +46,7 @@ import net.raphimc.minecraftauth.step.java.StepMCProfile;
import net.raphimc.minecraftauth.step.java.StepMCToken;
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -120,6 +121,7 @@ import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import org.geysermc.geyser.api.event.bedrock.SessionDisconnectEvent;
import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
import org.geysermc.geyser.api.network.RemoteServer;
+import org.geysermc.geyser.api.skin.SkinData;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.command.GeyserCommandSource;
@@ -132,6 +134,7 @@ import org.geysermc.geyser.entity.type.BoatEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.entity.type.Tickable;
+import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.erosion.AbstractGeyserboundPacketHandler;
@@ -149,7 +152,6 @@ import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.BlockItem;
-import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.BedrockDimension;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.level.physics.CollisionManager;
@@ -184,6 +186,7 @@ import org.geysermc.geyser.session.cache.waypoint.WaypointCache;
import org.geysermc.geyser.session.dialog.BuiltInDialog;
import org.geysermc.geyser.session.dialog.Dialog;
import org.geysermc.geyser.session.dialog.DialogManager;
+import org.geysermc.geyser.skin.SkinManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.text.MessageTranslator;
@@ -234,6 +237,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
@@ -1577,6 +1582,33 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.sendCommandPacket(command);
}
+ @Override
+ public @NonNull String joinAddress() {
+ String combined = Optional.ofNullable(clientData).orElseThrow().getServerAddress();
+ int index = combined.lastIndexOf(":");
+ return combined.substring(0, index);
+ }
+
+ @Override
+ public @Positive int joinPort() {
+ String combined = Optional.ofNullable(clientData).orElseThrow().getServerAddress();
+ int index = combined.lastIndexOf(":");
+ return Integer.parseInt(combined.substring(index + 1));
+ }
+
+ @Override
+ public void sendSkin(@NonNull UUID player, @NonNull SkinData skinData) {
+ Objects.requireNonNull(player, "player uuid must not be null!");
+ Objects.requireNonNull(skinData, "skinData must not be null!");
+
+ PlayerEntity entity = this.entityCache.getPlayerEntity(player);
+ if (entity == null) {
+ return;
+ }
+
+ SkinManager.sendSkinPacket(this, entity, skinData);
+ }
+
@Override
public void openPauseScreenAdditions() {
List