diff --git a/build-data/paper.at b/build-data/paper.at index 6e0efb1ce2fa..c8eda68f0b3d 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -31,6 +31,7 @@ public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket y public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket yRot public net.minecraft.network.protocol.game.ServerboundMovePlayerPacket z public net.minecraft.network.syncher.SynchedEntityData getItem(Lnet/minecraft/network/syncher/EntityDataAccessor;)Lnet/minecraft/network/syncher/SynchedEntityData$DataItem; +public net.minecraft.resources.RegistryOps (Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/resources/RegistryOps$RegistryInfoLookup;)V public net.minecraft.resources.RegistryOps lookupProvider public net.minecraft.resources.RegistryOps$HolderLookupAdapter public net.minecraft.server.Main forceUpgrade(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;Lcom/mojang/datafixers/DataFixer;ZLjava/util/function/BooleanSupplier;Lnet/minecraft/core/RegistryAccess;Z)V diff --git a/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch index 6e42e1ffbd8f..fbc07c278208 100644 --- a/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch +++ b/paper-server/patches/sources/net/minecraft/network/chat/ComponentSerialization.java.patch @@ -1,79 +1,28 @@ --- a/net/minecraft/network/chat/ComponentSerialization.java +++ b/net/minecraft/network/chat/ComponentSerialization.java -@@ -35,9 +_,31 @@ - - public class ComponentSerialization { - public static final Codec CODEC = Codec.recursive("Component", ComponentSerialization::createCodec); -- public static final StreamCodec STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistries(CODEC); -+ public static final StreamCodec STREAM_CODEC = createTranslationAware(net.minecraft.nbt.NbtAccounter::defaultQuota); // Paper - adventure - public static final StreamCodec> OPTIONAL_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs::optional); -- public static final StreamCodec TRUSTED_STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(CODEC); -+ // Paper start - adventure; use locale from bytebuf for translation -+ public static final ThreadLocal DONT_RENDER_TRANSLATABLES = ThreadLocal.withInitial(() -> false); -+ public static final StreamCodec TRUSTED_STREAM_CODEC = createTranslationAware(net.minecraft.nbt.NbtAccounter::unlimitedHeap); -+ private static StreamCodec createTranslationAware(final java.util.function.Supplier sizeTracker) { -+ return new StreamCodec<>() { -+ final StreamCodec streamCodec = ByteBufCodecs.tagCodec(sizeTracker); -+ @Override -+ public Component decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) { -+ net.minecraft.nbt.Tag tag = this.streamCodec.decode(registryFriendlyByteBuf); -+ RegistryOps registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE); -+ return CODEC.parse(registryOps, tag).getOrThrow(error -> new io.netty.handler.codec.DecoderException("Failed to decode: " + error + " " + tag)); -+ } -+ -+ @Override -+ public void encode(RegistryFriendlyByteBuf registryFriendlyByteBuf, Component object) { -+ RegistryOps registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE); -+ net.minecraft.nbt.Tag tag = (DONT_RENDER_TRANSLATABLES.get() ? CODEC : ComponentSerialization.localizedCodec(registryFriendlyByteBuf.adventure$locale)) -+ .encodeStart(registryOps, object).getOrThrow(error -> new io.netty.handler.codec.EncoderException("Failed to encode: " + error + " " + object)); -+ this.streamCodec.encode(registryFriendlyByteBuf, tag); -+ } -+ }; -+ } -+ // Paper end - adventure; use locale from bytebuf for translation - public static final StreamCodec> TRUSTED_OPTIONAL_STREAM_CODEC = TRUSTED_STREAM_CODEC.apply( +@@ -42,6 +_,7 @@ ByteBufCodecs::optional ); -@@ -93,7 +_,25 @@ - return ExtraCodecs.orCompressed(contentsCodec, discriminatorCodec); - } + public static final StreamCodec TRUSTED_CONTEXT_FREE_STREAM_CODEC = ByteBufCodecs.fromCodecTrusted(CODEC); ++ public static final ThreadLocal DONT_RENDER_TRANSLATABLES = ThreadLocal.withInitial(() -> false); // Paper - adventure; conditionally render translatable components -+ // Paper start - adventure; create separate codec for each locale -+ private static final java.util.Map> LOCALIZED_CODECS = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ public static Codec localizedCodec(final java.util.@org.checkerframework.checker.nullness.qual.Nullable Locale locale) { -+ if (locale == null) { -+ return CODEC; -+ } -+ return LOCALIZED_CODECS.computeIfAbsent(locale, -+ loc -> Codec.recursive("Component", selfCodec -> createCodec(selfCodec, loc))); -+ } -+ // Paper end - adventure; create separate codec for each locale -+ - private static Codec createCodec(final Codec topSerializer) { -+ // Paper start - adventure; create separate codec for each locale -+ return createCodec(topSerializer, null); -+ } -+ -+ private static Codec createCodec(Codec topSerializer, java.util.@org.jspecify.annotations.Nullable Locale locale) { -+ // Paper end - adventure; create separate codec for each locale - ExtraCodecs.LateBoundIdMapper> contentTypes = new ExtraCodecs.LateBoundIdMapper<>(); - bootstrap(contentTypes); - MapCodec compressedContentsCodec = createLegacyComponentMatcher(contentTypes, ComponentContents::codec, "type"); -@@ -105,6 +_,34 @@ + public static Codec flatRestrictedCodec(final int maxFlatSize) { + return new Codec() { +@@ -105,6 +_,35 @@ ) .apply(i, MutableComponent::new) ); -+ // Paper start - adventure; create separate codec for each locale ++ // Paper start - adventure + final Codec origCodec = fullCodec; + fullCodec = new Codec<>() { + @Override -+ public DataResult> decode(final DynamicOps ops, final T input) { ++ public DataResult> decode(final DynamicOps ops, final T input) { + return origCodec.decode(ops, input); + } + + @Override + public DataResult encode(final Component input, final DynamicOps ops, final T prefix) { ++ final java.util.Locale locale = !DONT_RENDER_TRANSLATABLES.get() && ops instanceof io.papermc.paper.util.LocaleAwareOps localeAwareOps ? localeAwareOps.locale() : null; + final net.kyori.adventure.text.Component adventureComponent; + if (input instanceof io.papermc.paper.adventure.AdventureComponent adv) { + adventureComponent = adv.adventure$component(); @@ -91,7 +40,7 @@ + return origCodec.toString() + "[AdventureComponentAware]"; + } + }; -+ // Paper end - adventure; create separate codec for each locale ++ // Paper end - adventure return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(topSerializer.listOf())), fullCodec) .xmap( specialOrComponent -> specialOrComponent.map( diff --git a/paper-server/patches/sources/net/minecraft/network/codec/ByteBufCodecs.java.patch b/paper-server/patches/sources/net/minecraft/network/codec/ByteBufCodecs.java.patch index c6eed07a1593..66c2a324ebc8 100644 --- a/paper-server/patches/sources/net/minecraft/network/codec/ByteBufCodecs.java.patch +++ b/paper-server/patches/sources/net/minecraft/network/codec/ByteBufCodecs.java.patch @@ -15,7 +15,23 @@ if (result == null) { throw new DecoderException("Expected non-null compound tag"); } else { -@@ -418,6 +_,48 @@ +@@ -381,7 +_,7 @@ + + @Override + public void encode(final B output, final V value) { +- T payload = (T)codec.encodeStart(ops, value).getOrThrow(msg -> new EncoderException("Failed to encode: " + msg + " " + value)); ++ T payload = (T)codec.encodeStart(new io.papermc.paper.util.LocaleAwareOps.Delegating<>(ops, output instanceof FriendlyByteBuf friendlyByteBuf ? friendlyByteBuf.adventure$locale : null), value).getOrThrow(msg -> new EncoderException("Failed to encode: " + msg + " " + value)); // Paper - server-side translations everywhere + original.encode(output, payload); + } + }; +@@ -412,12 +_,54 @@ + @Override + public void encode(final RegistryFriendlyByteBuf output, final T value) { + RegistryOps ops = output.registryAccess().createSerializationContext(NbtOps.INSTANCE); +- Tag tag = codec.encodeStart(ops, value).getOrThrow(msg -> new EncoderException("Failed to encode: " + msg + " " + value)); ++ Tag tag = codec.encodeStart(new io.papermc.paper.util.LocaleAwareOps.Registry<>(NbtOps.INSTANCE, output.registryAccess(), output.adventure$locale), value).getOrThrow(msg -> new EncoderException("Failed to encode: " + msg + " " + value)); // Paper - server-side translations everywhere + tagCodec.encode(output, tag); + } }; } diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch deleted file mode 100644 index e9de3c694052..000000000000 --- a/paper-server/patches/sources/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java.patch +++ /dev/null @@ -1,30 +0,0 @@ ---- a/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java -+++ b/net/minecraft/network/protocol/login/ClientboundLoginDisconnectPacket.java -@@ -14,8 +_,25 @@ - - public record ClientboundLoginDisconnectPacket(Component reason) implements Packet { - private static final RegistryOps OPS = RegistryAccess.EMPTY.createSerializationContext(JsonOps.INSTANCE); -- public static final StreamCodec STREAM_CODEC = StreamCodec.composite( -- ByteBufCodecs.lenientJson(262144).apply(ByteBufCodecs.fromCodec(OPS, ComponentSerialization.CODEC)), -+ // Paper start - localized codec -+ // In the login phase, buffer.adventure$locale field is most likely null, but plugins may use internals to set it via the channel attribute -+ public static final StreamCodec STREAM_CODEC = StreamCodec.composite( -+ new StreamCodec<>() { -+ -+ private static final net.minecraft.network.codec.StreamCodec LENIENT_JSON = ByteBufCodecs.lenientJson(net.minecraft.network.FriendlyByteBuf.MAX_COMPONENT_STRING_LENGTH); -+ @Override -+ public Component decode(final net.minecraft.network.FriendlyByteBuf buffer) { -+ java.util.Locale bufLocale = buffer.adventure$locale; -+ return LENIENT_JSON.apply(ByteBufCodecs.fromCodec(OPS, ComponentSerialization.localizedCodec(bufLocale == null ? java.util.Locale.US : bufLocale))).decode(buffer); -+ } -+ -+ @Override -+ public void encode(final net.minecraft.network.FriendlyByteBuf buffer, final Component value) { -+ java.util.Locale bufLocale = buffer.adventure$locale; -+ LENIENT_JSON.apply(ByteBufCodecs.fromCodec(OPS, ComponentSerialization.localizedCodec(bufLocale == null ? java.util.Locale.US : bufLocale))).encode(buffer, value); -+ } -+ }, -+ // Paper end - localized codec - ClientboundLoginDisconnectPacket::reason, - ClientboundLoginDisconnectPacket::new - ); diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch index 401e8689924f..d24636931b30 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/network/ServerConnectionListener.java +++ b/net/minecraft/server/network/ServerConnectionListener.java -@@ -50,9 +_,31 @@ +@@ -50,13 +_,36 @@ this.running = true; } @@ -33,6 +33,11 @@ this.channels .add( new ServerBootstrap() + .channel(eventLoopGroupHolder.serverChannelCls()) ++ .childAttr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE, java.util.Locale.US) // Paper - server-side translation everywhere + .childHandler( + new ChannelInitializer() { + { @@ -80,22 +_,64 @@ Connection connection = (Connection)(rateLimitPacketsPerSecond > 0 ? new RateKickingConnection(rateLimitPacketsPerSecond) diff --git a/paper-server/src/main/java/io/papermc/paper/util/LocaleAwareOps.java b/paper-server/src/main/java/io/papermc/paper/util/LocaleAwareOps.java new file mode 100644 index 000000000000..6ae6354faab4 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/LocaleAwareOps.java @@ -0,0 +1,39 @@ +package io.papermc.paper.util; + +import com.mojang.serialization.DynamicOps; +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.DelegatingOps; +import net.minecraft.resources.RegistryOps; +import java.util.Locale; + +public interface LocaleAwareOps { + Locale locale(); + + class Delegating extends DelegatingOps implements LocaleAwareOps { + private final Locale locale; + + public Delegating(final DynamicOps delegate, final Locale locale) { + super(delegate); + this.locale = locale; + } + + @Override + public Locale locale() { + return locale; + } + } + + class Registry extends RegistryOps implements LocaleAwareOps { + private final Locale locale; + + public Registry(final DynamicOps parent, final HolderLookup.Provider lookupProvider, final Locale locale) { + super(parent, new RegistryOps.HolderLookupAdapter(lookupProvider)); + this.locale = locale; + } + + @Override + public Locale locale() { + return locale; + } + } +}