1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-30 20:29:19 +00:00

Merge branch 'master' into feature/networking-api

This commit is contained in:
RednedEpic
2025-11-09 18:04:27 +00:00
42 changed files with 21175 additions and 84 deletions

View File

@@ -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.90 - 1.21.114 and Minecraft Java 1.21.9 - 1.21.10. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
Geyser is currently supporting Minecraft Bedrock 1.21.90 - 1.21.120 and Minecraft Java 1.21.9 - 1.21.10. 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.

View File

@@ -52,7 +52,7 @@ public interface PriorityOption extends ResourcePackOption<Integer> {
*/
static PriorityOption priority(int priority) {
if (priority < -100 || priority > 100) {
throw new IllegalArgumentException("Priority must be between 0 and 10 inclusive!");
throw new IllegalArgumentException("Priority must be between -100 and 100 inclusive!");
}
return GeyserApi.api().provider(PriorityOption.class, priority);
}

View File

@@ -216,7 +216,11 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
public void initialize() {
// Setup encryption early so we don't start if we can't auth
EncryptionUtils.getMojangPublicKey();
try {
EncryptionUtils.getMojangPublicKey();
} catch (Throwable e) {
throw new RuntimeException("Cannot setup authentication! Are you offline? ", e);
}
long startupTime = System.currentTimeMillis();

View File

@@ -161,7 +161,7 @@ public record EntityDefinition<T extends Entity>(EntityFactory<T> factory, Entit
if (identifier == null && type != null) {
identifier = "minecraft:" + type.name().toLowerCase(Locale.ROOT);
}
GeyserEntityProperties registeredProperties = propertiesBuilder == null ? null : propertiesBuilder.build();
GeyserEntityProperties registeredProperties = propertiesBuilder == null ? new GeyserEntityProperties() : propertiesBuilder.build();
EntityDefinition<T> definition = new EntityDefinition<>(factory, type, identifier, width, height, offset, registeredProperties, translators);
if (register && definition.entityType() != null) {
Registries.ENTITY_DEFINITIONS.get().putIfAbsent(definition.entityType(), definition);

View File

@@ -1272,7 +1272,7 @@ public final class EntityDefinitions {
if (propertyId.vanilla()) {
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
}
FloatProperty property = new FloatProperty(propertyId, min, max, defaultValue);
FloatProperty property = new FloatProperty(propertyId, max, min, defaultValue);
registerProperty(identifier, property);
return property;
}
@@ -1284,7 +1284,7 @@ public final class EntityDefinitions {
if (propertyId.vanilla()) {
throw new IllegalArgumentException("Cannot register custom property in vanilla namespace! " + propertyId);
}
IntProperty property = new IntProperty(propertyId, min, max, defaultValue);
IntProperty property = new IntProperty(propertyId, max, min, defaultValue);
registerProperty(identifier, property);
return property;
}
@@ -1339,7 +1339,7 @@ public final class EntityDefinitions {
});
for (var definition : Registries.ENTITY_DEFINITIONS.get().values()) {
if (definition.registeredProperties() != null) {
if (!definition.registeredProperties().isEmpty()) {
Registries.BEDROCK_ENTITY_PROPERTIES.get().add(definition.registeredProperties().toNbtMap(definition.identifier()));
}
}

View File

@@ -50,13 +50,8 @@ public class GeyserEntityProperties {
private final static Pattern ENTITY_PROPERTY_PATTERN = Pattern.compile("^[a-z0-9_.:-]*:[a-z0-9_.:-]*$");
private final ObjectArrayList<PropertyType<?, ?>> properties;
private final Object2IntMap<String> propertyIndices;
private GeyserEntityProperties() {
this.properties = new ObjectArrayList<>();
this.propertyIndices = new Object2IntOpenHashMap<>();
}
private ObjectArrayList<PropertyType<?, ?>> properties;
private Object2IntMap<String> propertyIndices;
public NbtMap toNbtMap(String entityType) {
NbtMapBuilder mapBuilder = NbtMap.builder();
@@ -75,6 +70,11 @@ public class GeyserEntityProperties {
throw new IllegalStateException("Cannot add properties outside the GeyserDefineEntityProperties event!");
}
if (properties == null || propertyIndices == null) {
this.properties = new ObjectArrayList<>(0);
this.propertyIndices = new Object2IntOpenHashMap<>(0);
}
if (this.properties.size() > 32) {
throw new IllegalArgumentException("Cannot register more than 32 properties for entity type " + entityType);
}
@@ -94,7 +94,11 @@ public class GeyserEntityProperties {
}
public @NonNull List<PropertyType<?, ?>> getProperties() {
return properties;
return properties == null ? List.of() : properties;
}
public boolean isEmpty() {
return properties == null || properties.isEmpty();
}
public int getPropertyIndex(String name) {

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.entity.properties.type;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.entity.property.type.GeyserEnumEntityProperty;
import org.geysermc.geyser.api.util.Identifier;
@@ -35,7 +36,7 @@ import java.util.Locale;
public record EnumProperty<E extends Enum<E>>(
Identifier identifier,
Class<E> enumClass,
E defaultValue
@NonNull E defaultValue
) implements AbstractEnumProperty<E>, GeyserEnumEntityProperty<E> {
public EnumProperty {

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.entity.properties.type;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.entity.FloatEntityProperty;
import org.geysermc.geyser.api.entity.property.type.GeyserFloatEntityProperty;
@@ -34,7 +35,7 @@ public record FloatProperty(
Identifier identifier,
float max,
float min,
Float defaultValue
@Nullable Float defaultValue
) implements PropertyType<Float, FloatEntityProperty>, GeyserFloatEntityProperty {
public FloatProperty {
@@ -42,7 +43,7 @@ public record FloatProperty(
throw new IllegalArgumentException("Cannot create float entity property (%s) with a minimum value (%s) greater than maximum (%s)!"
.formatted(identifier, min, max));
}
if (defaultValue < min || defaultValue > max) {
if (defaultValue != null && (defaultValue < min || defaultValue > max)) {
throw new IllegalArgumentException("Cannot create float entity property (%s) with a default value (%s) outside of the range (%s - %s)!"
.formatted(identifier, defaultValue, min, max));
}

View File

@@ -25,6 +25,7 @@
package org.geysermc.geyser.entity.properties.type;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.entity.IntEntityProperty;
import org.geysermc.geyser.GeyserImpl;
@@ -35,7 +36,7 @@ public record IntProperty(
Identifier identifier,
int max,
int min,
Integer defaultValue
@Nullable Integer defaultValue
) implements PropertyType<Integer, IntEntityProperty>, GeyserIntEntityProperty {
public IntProperty {
@@ -43,7 +44,7 @@ public record IntProperty(
throw new IllegalArgumentException("Cannot create int entity property (%s) with a minimum value (%s) greater than maximum (%s)!"
.formatted(identifier, min, max));
}
if (defaultValue < min || defaultValue > max) {
if (defaultValue != null && (defaultValue < min || defaultValue > max)) {
throw new IllegalArgumentException("Cannot create int entity property (%s) with a default value (%s) outside of the range (%s - %s)!"
.formatted(identifier, defaultValue, min, max));
}

View File

@@ -167,7 +167,7 @@ public class Entity implements GeyserEntity {
this.valid = false;
this.propertyManager = definition.registeredProperties() == null ? null : new GeyserEntityPropertyManager(definition.registeredProperties());
this.propertyManager = definition.registeredProperties().isEmpty() ? null : new GeyserEntityPropertyManager(definition.registeredProperties());
setPosition(position);
setAirSupply(getMaxAir());

View File

@@ -210,6 +210,13 @@ public class MinecartEntity extends Entity implements Tickable {
return Vector3f.from(0, getYaw(), 0);
}
@Override
public boolean doesJumpDismount() {
// This is a little bit misleading because jumping is literally the only way to dismount for Touch users.
// Therefore, do this so we won't lock jumping to let Touch user able to dismount.
return false;
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {

View File

@@ -162,7 +162,7 @@ public class BlockInventoryHolder extends InventoryHolder {
* a block to hold the inventory that's wildly out of range.
*/
protected boolean checkInteractionPosition(GeyserSession session) {
return session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition());
return session.getLastInteractionPlayerPosition().distance(session.getPlayerEntity().getPosition()) < 2;
}
/**

View File

@@ -28,6 +28,7 @@ package org.geysermc.geyser.item.hashing;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.KeybindComponent;
import net.kyori.adventure.text.NBTComponent;
import net.kyori.adventure.text.ObjectComponent;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.TextComponent;
@@ -39,6 +40,9 @@ import net.kyori.adventure.text.format.ShadowColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.object.ObjectContents;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import org.geysermc.geyser.item.hashing.data.ObjectContentsType;
import java.util.function.Supplier;
@@ -54,6 +58,17 @@ public interface ComponentHasher {
}
});
MinecraftHasher<PlayerHeadObjectContents.ProfileProperty> PROFILE_PROPERTY = MinecraftHasher.mapBuilder(builder -> builder
.accept("name", MinecraftHasher.STRING, PlayerHeadObjectContents.ProfileProperty::name)
.accept("value", MinecraftHasher.STRING, PlayerHeadObjectContents.ProfileProperty::value)
.accept("signature", MinecraftHasher.STRING, PlayerHeadObjectContents.ProfileProperty::signature));
MinecraftHasher<PlayerHeadObjectContents> RESOLVABLE_PROFILE = MinecraftHasher.mapBuilder(builder -> builder
.optionalNullable("name", MinecraftHasher.STRING, PlayerHeadObjectContents::name)
.optionalNullable("id", MinecraftHasher.UUID, PlayerHeadObjectContents::id)
.optionalList("properties", PROFILE_PROPERTY, PlayerHeadObjectContents::profileProperties)
.optionalNullable("texture", MinecraftHasher.KEY, PlayerHeadObjectContents::texture));
MinecraftHasher<NamedTextColor> NAMED_COLOR = MinecraftHasher.STRING.cast(NamedTextColor::toString);
MinecraftHasher<TextColor> DIRECT_COLOR = MinecraftHasher.STRING.cast(TextColor::asHexString);
@@ -151,6 +166,13 @@ public interface ComponentHasher {
.optional("interpret", MinecraftHasher.BOOL, NBTComponent::interpret, false)
.optionalNullable("separator", COMPONENT, NBTComponent::separator)); // TODO source key, needs kyori update?
MinecraftHasher<ObjectContentsType> OBJECT_CONTENTS_TYPE = MinecraftHasher.fromEnum(ObjectContentsType::getName);
MapBuilder<ObjectContents> OBJECT_CONTENTS = MapBuilder.dispatch("object", OBJECT_CONTENTS_TYPE, ObjectContentsType::fromContents, ObjectContentsType::mapBuilder);
MinecraftHasher<ObjectComponent> OBJECT_COMPONENT = component(builder -> builder
.accept(OBJECT_CONTENTS, ObjectComponent::contents));
MinecraftHasher<Component> ACTUAL_COMPONENT = (component, encoder) -> {
if (component instanceof TextComponent text) {
return TEXT_COMPONENT.hash(text, encoder);
@@ -164,8 +186,10 @@ public interface ComponentHasher {
return SELECTOR_COMPONENT.hash(selector, encoder);
} else if (component instanceof NBTComponent<?,?> nbt) {
return NBT_COMPONENT.hash(nbt, encoder);
} else if (component instanceof ObjectComponent object) {
return OBJECT_COMPONENT.hash(object, encoder);
}
throw new IllegalStateException("Unimplemented component hasher: " + component);
throw new UnsupportedOperationException("Unimplemented component hasher: " + component);
};
private static <T extends Component> MinecraftHasher<T> component(MapBuilder<T> componentBuilder) {

View File

@@ -57,6 +57,34 @@ public interface MapBuilder<Type> extends UnaryOperator<MapHasher<Type>> {
return builder -> builder;
}
/**
* Delegates to {@link MapBuilder#dispatch(String, MinecraftHasher, Function, Function)}, uses {@code "type"} as the {@code typeKey}.
*
* @see MapBuilder#dispatch(String, MinecraftHasher, Function, Function)
*/
static <Type, Dispatched> MapBuilder<Dispatched> dispatch(MinecraftHasher<Type> typeHasher, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> hashDispatch) {
return dispatch("type", typeHasher, typeExtractor, hashDispatch);
}
/**
* Creates a map builder that dispatches a {@link Type} from a {@link Dispatched} using {@code typeExtractor}, puts this as the {@code typeKey} key in the map using the given {@code typeHasher},
* and uses a {@link MapBuilder} provided by {@code mapDispatch} to build the rest of the map.
*
* <p>This can be used to create map builders that build an abstract type or interface into a map with different keys depending on the type.</p>
*
* @param typeKey the key to store the {@link Type} in.
* @param typeHasher the hasher used to encode the {@link Type}.
* @param typeExtractor the function that extracts a {@link Type} from a {@link Dispatched}.
* @param mapDispatch the function that provides a {@link MapBuilder} based on a {@link Type}.
* @param <Type> the type of the {@code typeKey}.
* @param <Dispatched> the type of the new map builder.
*/
static <Type, Dispatched> MapBuilder<Dispatched> dispatch(String typeKey, MinecraftHasher<Type> typeHasher, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> mapDispatch) {
return builder -> builder
.accept(typeKey, typeHasher, typeExtractor)
.accept(typeExtractor, mapDispatch);
}
/**
* Returns a function that creates a map builder from an NBT map. The builder simply adds all keys from the NBT map.
*

View File

@@ -216,6 +216,8 @@ public interface MinecraftHasher<Type> {
* Delegates to {@link MinecraftHasher#dispatch(String, Function, Function)}, uses {@code "type"} as the {@code typeKey}.
*
* @see MinecraftHasher#dispatch(String, Function, Function)
* @see MapBuilder#dispatch(MinecraftHasher, Function, Function)
* @see MapBuilder#dispatch(String, MinecraftHasher, Function, Function)
*/
default <Dispatched> MinecraftHasher<Dispatched> dispatch(Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> hashDispatch) {
return dispatch("type", typeExtractor, hashDispatch);
@@ -227,15 +229,17 @@ public interface MinecraftHasher<Type> {
*
* <p>This can be used to create hashers that hash an abstract type or interface into a map with different keys depending on the type.</p>
*
* <p>Internally this simply delegates to and wraps {@link MapBuilder#dispatch(String, MinecraftHasher, Function, Function)} in {@link MinecraftHasher#mapBuilder(MapBuilder)},
* using {@code this} as {@code typeHasher}.</p>
*
* @param typeKey the key to store the {@link Type} in.
* @param typeExtractor the function that extracts a {@link Type} from a {@link Dispatched}.
* @param mapDispatch the function that provides a {@link MapBuilder} based on a {@link Type}.
* @param <Dispatched> the type of the new hasher.
* @see MapBuilder#dispatch(String, MinecraftHasher, Function, Function)
*/
default <Dispatched> MinecraftHasher<Dispatched> dispatch(String typeKey, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> mapDispatch) {
return mapBuilder(builder -> builder
.accept(typeKey, this, typeExtractor)
.accept(typeExtractor, mapDispatch));
return mapBuilder(MapBuilder.dispatch(typeKey, this, typeExtractor, mapDispatch));
}
/**

View File

@@ -304,7 +304,7 @@ public interface RegistryHasher<DirectType> extends MinecraftHasher<Integer> {
MinecraftHasher<ConsumeEffectType> CONSUME_EFFECT_TYPE = enumRegistry();
MinecraftHasher<ConsumeEffect> CONSUME_EFFECT = CONSUME_EFFECT_TYPE.dispatch(ConsumeEffectType::fromEffect, type -> type.getBuilder().cast());
MinecraftHasher<ConsumeEffect> CONSUME_EFFECT = CONSUME_EFFECT_TYPE.dispatch(ConsumeEffectType::fromEffect, ConsumeEffectType::mapBuilder);
MinecraftHasher<SuspiciousStewEffect> SUSPICIOUS_STEW_EFFECT = MinecraftHasher.mapBuilder(builder -> builder
.accept("id", EFFECT_ID, SuspiciousStewEffect::getMobEffectId)

View File

@@ -25,7 +25,6 @@
package org.geysermc.geyser.item.hashing.data;
import lombok.Getter;
import org.geysermc.geyser.item.hashing.MapBuilder;
import org.geysermc.geyser.item.hashing.MinecraftHasher;
import org.geysermc.geyser.item.hashing.RegistryHasher;
@@ -44,7 +43,6 @@ public enum ConsumeEffectType {
.accept("sound", RegistryHasher.SOUND_EVENT, ConsumeEffect.PlaySound::sound));
private final Class<? extends ConsumeEffect> clazz;
@Getter
private final MapBuilder<? extends ConsumeEffect> builder;
<T extends ConsumeEffect> ConsumeEffectType(Class<T> clazz) {
@@ -57,6 +55,10 @@ public enum ConsumeEffectType {
this.builder = builder;
}
public MapBuilder<ConsumeEffect> mapBuilder() {
return builder.cast();
}
public static ConsumeEffectType fromEffect(ConsumeEffect effect) {
Class<? extends ConsumeEffect> clazz = effect.getClass();
for (ConsumeEffectType type : values()) {

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2025 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.geyser.item.hashing.data;
import lombok.Getter;
import net.kyori.adventure.text.object.ObjectContents;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import net.kyori.adventure.text.object.SpriteObjectContents;
import org.geysermc.geyser.item.hashing.ComponentHasher;
import org.geysermc.geyser.item.hashing.MapBuilder;
import org.geysermc.geyser.item.hashing.MinecraftHasher;
import java.util.function.Function;
public enum ObjectContentsType {
ATLAS("atlas", SpriteObjectContents.class,
builder -> builder
.optional("atlas", MinecraftHasher.KEY, SpriteObjectContents::atlas, SpriteObjectContents.DEFAULT_ATLAS)
.accept("sprite", MinecraftHasher.KEY, SpriteObjectContents::sprite)),
PLAYER("player", PlayerHeadObjectContents.class,
builder -> builder
.accept("player", ComponentHasher.RESOLVABLE_PROFILE, Function.identity())
.optional("hat", MinecraftHasher.BOOL, PlayerHeadObjectContents::hat, true));
@Getter
private final String name;
private final MapBuilder<? extends ObjectContents> builder;
@SuppressWarnings("unused") // So Java knows what T we are talking about
<T extends ObjectContents> ObjectContentsType(String name, Class<T> clazz, MapBuilder<T> builder) {
this.name = name;
this.builder = builder;
}
public MapBuilder<ObjectContents> mapBuilder() {
return builder.cast();
}
public static ObjectContentsType fromContents(ObjectContents contents) {
if (contents instanceof SpriteObjectContents) {
return ATLAS;
} else if (contents instanceof PlayerHeadObjectContents) {
return PLAYER;
}
throw new UnsupportedOperationException("Don't know how to hash object contents of type " + contents.getClass());
}
}

View File

@@ -74,7 +74,6 @@ import java.util.function.Function;
*/
// Lots of unchecked casting happens here. It should all be handled properly.
@SuppressWarnings("unchecked")
// TODO only log some things once (like was done in vault translator)
public final class ItemStackParser {
private static final Map<DataComponentType<?>, DataComponentParser<?, ?>> PARSERS = new Reference2ObjectOpenHashMap<>();

View File

@@ -352,7 +352,7 @@ public class CollisionManager {
return vector.getX() * vector.getX() + vector.getZ() * vector.getZ();
}
private Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld, boolean walkOnLava) {
public Vector3d correctMovementForCollisions(Vector3d movement, BoundingBox boundingBox, boolean checkWorld, boolean walkOnLava) {
double movementX = movement.getX();
double movementY = movement.getY();
double movementZ = movement.getZ();

View File

@@ -42,6 +42,11 @@ import org.cloudburstmc.protocol.bedrock.codec.v712.serializer.MobArmorEquipment
import org.cloudburstmc.protocol.bedrock.codec.v748.serializer.InventoryContentSerializer_v748;
import org.cloudburstmc.protocol.bedrock.codec.v748.serializer.InventorySlotSerializer_v748;
import org.cloudburstmc.protocol.bedrock.codec.v776.serializer.BossEventSerializer_v776;
import org.cloudburstmc.protocol.bedrock.codec.v818.serializer.LoginSerializer_v818;
import org.cloudburstmc.protocol.bedrock.data.auth.AuthPayload;
import org.cloudburstmc.protocol.bedrock.data.auth.AuthType;
import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload;
import org.cloudburstmc.protocol.bedrock.data.auth.TokenPayload;
import org.cloudburstmc.protocol.bedrock.packet.AnvilDamagePacket;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.BossEventPacket;
@@ -59,6 +64,7 @@ import org.cloudburstmc.protocol.bedrock.packet.GameTestRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.cloudburstmc.protocol.bedrock.packet.LabTablePacket;
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
import org.cloudburstmc.protocol.bedrock.packet.MapCreateLockedCopyPacket;
import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.MobArmorEquipmentPacket;
@@ -83,7 +89,13 @@ import org.cloudburstmc.protocol.bedrock.packet.SettingsCommandPacket;
import org.cloudburstmc.protocol.bedrock.packet.SimpleEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.SubChunkRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.SubClientLoginPacket;
import org.cloudburstmc.protocol.common.util.Preconditions;
import org.cloudburstmc.protocol.common.util.VarInts;
import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException;
import java.util.List;
import java.util.Map;
/**
* Processes the Bedrock codec to remove or modify unused or unsafe packets and fields.
@@ -242,6 +254,38 @@ class CodecProcessor {
}
};
private static final BedrockPacketSerializer<LoginPacket> LOGIN_PACKET_BEDROCK_PACKET_SERIALIZER = new LoginSerializer_v818() {
@Override
protected AuthPayload readAuthJwt(String authJwt) {
try {
Map<String, Object> payload = JsonUtil.parseJson(authJwt);
Preconditions.checkArgument(payload.containsKey("AuthenticationType"), "Missing AuthenticationType in JWT");
int authTypeOrdinal = ((Number)payload.get("AuthenticationType")).intValue();
if (authTypeOrdinal >= 0 && authTypeOrdinal < AuthType.values().length - 1) {
AuthType authType = AuthType.values()[authTypeOrdinal + 1];
if (payload.containsKey("Certificate") && payload.get("Certificate") instanceof String certJson && !certJson.isEmpty()) {
Map<String, Object> certData = JsonUtil.parseJson(certJson);
if (certData.containsKey("chain") && certData.get("chain") instanceof List) {
List<String> chain = (List)certData.get("chain");
return new CertificateChainPayload(chain, authType);
} else {
throw new IllegalArgumentException("Invalid Certificate chain in JWT");
}
} else if (payload.containsKey("Token") && payload.get("Token") instanceof String token && !token.isEmpty()) {
return new TokenPayload(token, authType);
} else {
throw new IllegalArgumentException("Invalid AuthPayload in JWT");
}
} else {
throw new IllegalArgumentException("Invalid AuthenticationType ordinal: " + authTypeOrdinal);
}
} catch (JoseException e) {
throw new IllegalArgumentException("Failed to parse auth payload", e);
}
}
};
@SuppressWarnings("unchecked")
static BedrockCodec processCodec(BedrockCodec codec) {
BedrockPacketSerializer<BossEventPacket> bossEventSerializer;
@@ -305,6 +349,11 @@ class CodecProcessor {
.updateSerializer(PlayerInputPacket.class, ILLEGAL_SERIALIZER);
}
// TODO remove once global api is updated
if (codec.getProtocolVersion() >= 818) {
codecBuilder.updateSerializer(LoginPacket.class, LOGIN_PACKET_BEDROCK_PACKET_SERIALIZER);
}
if (!Boolean.getBoolean("Geyser.ReceiptPackets")) {
codecBuilder.updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER);
codecBuilder.updateSerializer(PurchaseReceiptPacket.class, IGNORED_SERIALIZER);

View File

@@ -33,6 +33,7 @@ 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.codec.v844.Bedrock_v844;
import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859;
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_v819.CODEC, "1.21.93", "1.21.94");
register(Bedrock_v827.CODEC, "1.21.100", "1.21.101");
register(Bedrock_v844.CODEC, "1.21.111", "1.21.112", "1.21.113", "1.21.114");
register(Bedrock_v859.CODEC, "1.21.120");
MinecraftVersion latestBedrock = SUPPORTED_BEDROCK_VERSIONS.get(SUPPORTED_BEDROCK_VERSIONS.size() - 1);
DEFAULT_BEDROCK_VERSION = latestBedrock.versionString();

View File

@@ -125,11 +125,6 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(CraftingEventPacket packet) {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(EntityEventPacket packet) {
return defaultHandler(packet);
@@ -165,11 +160,6 @@ public class LoggingPacketHandler implements BedrockPacketHandler {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(ItemFrameDropItemPacket packet) {
return defaultHandler(packet);
}
@Override
public PacketSignal handle(LabTablePacket packet) {
return defaultHandler(packet);

View File

@@ -33,6 +33,7 @@ import com.google.common.collect.Interners;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -47,6 +48,7 @@ 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.codec.v844.Bedrock_v844;
import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859;
import org.cloudburstmc.protocol.bedrock.data.BlockPropertyData;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.geysermc.geyser.GeyserImpl;
@@ -124,6 +126,7 @@ public final class BlockRegistryPopulator {
.put(ObjectIntPair.of("1_21_90", Bedrock_v819.CODEC.getProtocolVersion()), Conversion827_819::remapBlock)
.put(ObjectIntPair.of("1_21_100", Bedrock_v827.CODEC.getProtocolVersion()), Conversion844_827::remapBlock)
.put(ObjectIntPair.of("1_21_110", Bedrock_v844.CODEC.getProtocolVersion()), tag -> tag)
.put(ObjectIntPair.of("1_21_110", Bedrock_v859.CODEC.getProtocolVersion()), tag -> tag)
.build();
// We can keep this strong as nothing should be garbage collected

View File

@@ -49,6 +49,7 @@ 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.codec.v844.Bedrock_v844;
import org.cloudburstmc.protocol.bedrock.codec.v859.Bedrock_v859;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
@@ -196,6 +197,7 @@ public class ItemRegistryPopulator {
paletteVersions.add(new PaletteVersion("1_21_93", Bedrock_v819.CODEC.getProtocolVersion(), eightOneNineFallbacks, Conversion844_827::remapItem));
paletteVersions.add(new PaletteVersion("1_21_100", Bedrock_v827.CODEC.getProtocolVersion(), eightTwoSevenFallbacks, Conversion844_827::remapItem));
paletteVersions.add(new PaletteVersion("1_21_110", Bedrock_v844.CODEC.getProtocolVersion()));
paletteVersions.add(new PaletteVersion("1_21_120", Bedrock_v859.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
@@ -209,13 +211,6 @@ public class ItemRegistryPopulator {
throw new AssertionError("Unable to load Java runtime item IDs", e);
}
NbtMap vanillaComponents;
try (InputStream stream = bootstrap.getResourceOrThrow("bedrock/item_components.nbt")) {
vanillaComponents = (NbtMap) NbtUtils.createGZIPReader(stream, true, true).readTag();
} catch (Exception e) {
throw new AssertionError("Unable to load Bedrock item components", e);
}
boolean customItemsAllowed = GeyserImpl.getInstance().getConfig().isAddNonBedrockItems();
// List values here is important compared to HashSet - we need to preserve the order of what's given to us
@@ -242,6 +237,13 @@ public class ItemRegistryPopulator {
throw new AssertionError("Unable to load Bedrock runtime item IDs", e);
}
NbtMap vanillaComponents;
try (InputStream stream = bootstrap.getResourceOrThrow("bedrock/item_components.%s.nbt".formatted(palette.version()))) {
vanillaComponents = (NbtMap) NbtUtils.createGZIPReader(stream, true, true).readTag();
} catch (Exception e) {
throw new AssertionError("Unable to load Bedrock item components", e);
}
// Used for custom items
int nextFreeBedrockId = 0;
Int2ObjectMap<ItemDefinition> registry = new Int2ObjectOpenHashMap<>();

View File

@@ -276,6 +276,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
@Setter
private List<String> certChainData;
@Setter
private String token;
@NonNull
@Setter

View File

@@ -515,7 +515,7 @@ public class BlockBreakHandler {
protected boolean mayBreak(float progress, boolean bedrockDestroyed) {
// We're tolerant here to account for e.g. obsidian breaking speeds not matching 1:1 :(
return (serverSideBlockBreaking && progress >= 1.0F) || (bedrockDestroyed && progress >= 0.7F);
return (serverSideBlockBreaking && progress >= 1.0F) || (bedrockDestroyed && progress >= 0.65F);
}
protected void destroyBlock(BlockState state, Vector3i vector, Direction direction, boolean instamine) {

View File

@@ -190,15 +190,22 @@ public final class FloodgateSkinUploader {
};
}
public void uploadSkin(List<String> chainData, String clientData) {
if (chainData == null || clientData == null) {
public void uploadSkin(GeyserSession session) {
List<String> chainData = session.getCertChainData();
String token = session.getToken();
String clientData = session.getClientData().getOriginalString();
if ((chainData == null && token == null) || clientData == null) {
return;
}
ObjectNode node = JACKSON.createObjectNode();
ArrayNode chainDataNode = JACKSON.createArrayNode();
chainData.forEach(chainDataNode::add);
node.set("chain_data", chainDataNode);
if (chainData != null) {
ArrayNode chainDataNode = JACKSON.createArrayNode();
chainData.forEach(chainDataNode::add);
node.set("chain_data", chainDataNode);
} else {
node.put("token", token);
}
node.put("client_data", clientData);
// The reason why I don't like Jackson
@@ -218,7 +225,7 @@ public final class FloodgateSkinUploader {
}
private void reconnectLater(GeyserImpl geyser) {
// we ca only reconnect when the thread pool is open
// we can only reconnect when the thread pool is open
if (geyser.getScheduledThread().isShutdown() || closed) {
logger.info("The skin uploader has been closed");
return;
@@ -241,4 +248,4 @@ public final class FloodgateSkinUploader {
client.close();
}
}
}
}

View File

@@ -530,10 +530,10 @@ public final class ItemTranslator {
}
if (mapping.getJavaItem().equals(Items.PLAYER_HEAD)) {
/*CustomSkull customSkull = getCustomSkull(itemStack.getComponent(DataComponentTypes.PROFILE));
CustomSkull customSkull = getCustomSkull(itemStack.getComponent(DataComponentTypes.PROFILE));
if (customSkull != null) {
itemDefinition = session.getItemMappings().getCustomBlockItemDefinitions().get(customSkull.getCustomBlockData());
}*/ // TODO
}
}
ItemDefinition definition = CustomItemTranslator.getCustomItem(itemStack.getComponents(), mapping);

View File

@@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player.input;
import org.cloudburstmc.math.GenericMath;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.InputMode;
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
@@ -40,6 +41,7 @@ import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity;
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
import org.geysermc.geyser.level.physics.BoundingBox;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@@ -270,11 +272,31 @@ public final class BedrockPlayerAuthInputTranslator extends PacketTranslator<Pla
}
if (sendMovement) {
vehicle.setOnGround(packet.getInputData().contains(PlayerAuthInputData.VERTICAL_COLLISION) && session.getPlayerEntity().getLastTickEndVelocity().getY() < 0);
// We only need to determine onGround status this way for client predicted vehicles.
// For other vehicle, Geyser already handle it in VehicleComponent or the Java server handle it.
if (packet.getInputData().contains(PlayerAuthInputData.IN_CLIENT_PREDICTED_IN_VEHICLE)) {
Vector3f position = vehicle.getPosition();
if (vehicle instanceof BoatEntity) {
position = position.down(vehicle.getDefinition().offset());
}
final BoundingBox box = new BoundingBox(
position.up(vehicle.getBoundingBoxHeight() / 2f).toDouble(),
vehicle.getBoundingBoxWidth(), vehicle.getBoundingBoxHeight(), vehicle.getBoundingBoxWidth()
);
// Manually calculate the vertical collision ourselves, the VERTICAL_COLLISION input data is inaccurate inside a vehicle!
Vector3d movement = session.getPlayerEntity().getLastTickEndVelocity().toDouble();
Vector3d correctedMovement = session.getCollisionManager().correctMovementForCollisions(movement, box, true, false);
vehicle.setOnGround(correctedMovement.getY() != movement.getY() && session.getPlayerEntity().getLastTickEndVelocity().getY() < 0);
}
Vector3f vehiclePosition = packet.getPosition();
Vector2f vehicleRotation = packet.getVehicleRotation();
if (vehicleRotation == null) {
return; // If the client just got in or out of a vehicle for example.
return; // If the client just got in or out of a vehicle for example. Or if this vehicle isn't client predicted.
}
if (session.getWorldBorder().isPassingIntoBorderBoundaries(vehiclePosition, false)) {

View File

@@ -66,11 +66,12 @@ public class JavaLoginFinishedTranslator extends PacketTranslator<ClientboundLog
// because otherwise the global server returns the data too fast.
// We upload it after we know for sure that the target server
// is ready to handle the result of the global server.
session.getGeyser().getSkinUploader().uploadSkin(session.getCertChainData(), session.getClientData().getOriginalString());
session.getGeyser().getSkinUploader().uploadSkin(session);
}
// We no longer need these variables; they're just taking up space in memory now
session.setCertChainData(null);
session.setToken(null);
session.getClientData().setOriginalString(null);
// configuration phase stuff that the vanilla client replies with after receiving the GameProfilePacket

View File

@@ -85,8 +85,12 @@ public class JavaAnimateTranslator extends PacketTranslator<ClientboundAnimatePa
session.sendUpstreamPacket(offHandPacket);
return;
}
case CRITICAL_HIT -> animatePacket.setAction(AnimatePacket.Action.CRITICAL_HIT);
case CRITICAL_HIT -> {
animatePacket.setData(55);
animatePacket.setAction(AnimatePacket.Action.CRITICAL_HIT);
}
case ENCHANTMENT_CRITICAL_HIT -> {
animatePacket.setData(15);
animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT); // Unsure if this does anything
// Spawn custom particle

View File

@@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
import org.cloudburstmc.protocol.bedrock.data.auth.AuthPayload;
import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload;
import org.cloudburstmc.protocol.bedrock.data.auth.TokenPayload;
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
@@ -51,7 +52,6 @@ import org.geysermc.geyser.text.GeyserLocale;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.List;
import java.util.function.BiConsumer;
public class LoginEncryptionUtils {
@@ -81,13 +81,13 @@ public class LoginEncryptionUtils {
long issuedAt = rawIssuedAt != null ? rawIssuedAt : -1;
IdentityData extraData = result.identityClaims().extraData;
// TODO!!! identity won't persist
session.setAuthData(new AuthData(extraData.displayName, extraData.identity, extraData.xuid, issuedAt));
if (authPayload instanceof CertificateChainPayload certificateChainPayload) {
if (authPayload instanceof TokenPayload tokenPayload) {
session.setToken(tokenPayload.getToken());
} else if (authPayload instanceof CertificateChainPayload certificateChainPayload) {
session.setCertChainData(certificateChainPayload.getChain());
} else {
GeyserImpl.getInstance().getLogger().warning("Received new auth payload!");
session.setCertChainData(List.of());
GeyserImpl.getInstance().getLogger().warning("Unknown auth payload! Skin uploading will not work");
}
PublicKey identityPublicKey = result.identityClaims().parsedIdentityPublicKey();

View File

@@ -76,7 +76,7 @@ public class WebUtils {
con.setRequestProperty("User-Agent", getUserAgent()); // Otherwise Java 8 fails on checking updates
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
checkResponseCode(con);
return connectionToString(con);
} catch (UnknownHostException e) {
throw new IllegalStateException("Unable to resolve requested url (%s)! Are you offline?".formatted(reqURL), e);
@@ -94,6 +94,7 @@ public class WebUtils {
con.setRequestProperty("User-Agent", getUserAgent());
con.setConnectTimeout(10000);
con.setReadTimeout(10000);
checkResponseCode(con);
return GeyserImpl.JSON_MAPPER.readTree(con.getInputStream());
}
@@ -107,6 +108,7 @@ public class WebUtils {
try {
HttpURLConnection con = (HttpURLConnection) new URL(reqURL).openConnection();
con.setRequestProperty("User-Agent", getUserAgent());
checkResponseCode(con);
InputStream in = con.getInputStream();
Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
@@ -270,24 +272,38 @@ public class WebUtils {
}
/**
* Get the string output from the passed {@link HttpURLConnection}
*
* @param con The connection to get the string from
* @return The body of the returned page
* @throws IOException If the request fails
* Gets the string output from the passed {@link HttpURLConnection},
* or logs the error message.
*/
private static String connectionToString(HttpURLConnection con) throws IOException {
checkResponseCode(con);
return inputStreamToString(con.getInputStream(), con::disconnect);
}
/**
* Throws an exception if there is an error stream to avoid further issues
*/
private static void checkResponseCode(HttpURLConnection con) throws IOException {
// Send the request (we dont use this but its required for getErrorStream() to work)
con.getResponseCode();
// Read the error message if there is one if not just read normally
InputStream inputStream = con.getErrorStream();
if (inputStream == null) {
inputStream = con.getInputStream();
InputStream errorStream = con.getErrorStream();
if (errorStream != null) {
throw new IOException(inputStreamToString(errorStream, null));
}
}
/**
* Get the string output from the passed {@link InputStream}
*
* @param stream The input stream to get the string from
* @return The body of the returned page
* @throws IOException If the request fails
*/
private static String inputStreamToString(InputStream stream, @Nullable Runnable onFinish) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
@@ -295,7 +311,9 @@ public class WebUtils {
content.append("\n");
}
con.disconnect();
if (onFinish != null) {
onFinish.run();
}
}
return content.toString();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,14 @@ netty = "4.2.7.Final"
guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1"
protocol-connection = "3.0.0.Beta8-20250929.213851-8"
protocol-common = "3.0.0.Beta8-20250929.213851-8"
protocol-codec = "3.0.0.Beta8-20250929.213851-8"
protocol-connection = "3.0.0.Beta10-20251014.180344-2"
protocol-common = "3.0.0.Beta10-20251014.180344-2"
protocol-codec = "3.0.0.Beta10-20251014.180344-2"
raknet = "1.0.0.CR3-20250811.214335-20"
minecraftauth = "4.1.1"
mcprotocollib = "1.21.9-20251020.140136-16"
adventure = "4.24.0"
adventure-platform = "4.3.0"
mcprotocollib = "1.21.9-20251029.184056-18"
adventure = "4.25.0"
adventure-platform = "4.4.1"
junit = "5.9.2"
checkerframework = "3.19.0"
log4j = "2.20.0"