Implemented item component hashing to fix inventory issues with 1.21.5 clients on <=1.21.4 servers

This commit is contained in:
RaphiMC 2025-03-28 19:37:43 +01:00
parent c54c4327f3
commit 50b1907ca6
No known key found for this signature in database
GPG key ID: 0F6BB0657A03AC94
7 changed files with 728 additions and 0 deletions

View file

@ -98,6 +98,7 @@ dependencies {
include("net.raphimc.netminecraft:all:3.1.0") {
exclude group: "com.google.code.gson", module: "gson"
}
include "net.lenni0451.mcstructs:itemcomponents:3.0.0"
include("net.raphimc:MinecraftAuth:4.1.1") {
exclude group: "com.google.code.gson", module: "gson"
exclude group: "org.slf4j", module: "slf4j-api"

View file

@ -0,0 +1,104 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2025 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.injection.mixins;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.RegistryEntry;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.StructuredItem;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.Types1_21_5;
import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap;
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.Protocol1_21_4To1_21_5;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPacket1_21_5;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.BlockItemPacketRewriter1_21_5;
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2;
import com.viaversion.viaversion.rewriter.StructuredItemRewriter;
import com.viaversion.viaversion.util.Key;
import net.lenni0451.mcstructs.core.Identifier;
import net.raphimc.viaproxy.protocoltranslator.viaproxy.item_component_hasher.ItemComponentHashStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Mixin(value = BlockItemPacketRewriter1_21_5.class, remap = false)
public abstract class MixinBlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<ClientboundPacket1_21_2, ServerboundPacket1_21_5, Protocol1_21_4To1_21_5> {
@Unique
private static final boolean DEBUG = false;
public MixinBlockItemPacketRewriter1_21_5() {
super(null, null, null, null, null);
}
@Inject(method = "registerPackets", at = @At("RETURN"))
private void appendPacketHandlers() {
this.protocol.appendClientbound(ClientboundConfigurationPackets1_21.REGISTRY_DATA, wrapper -> {
wrapper.resetReader();
final String key = Key.namespaced(wrapper.passthrough(Types.STRING)); // key
final RegistryEntry[] entries = wrapper.passthrough(Types.REGISTRY_ENTRY_ARRAY); // entries
final ItemComponentHashStorage itemComponentHasher = wrapper.user().get(ItemComponentHashStorage.class);
if (key.equals("minecraft:enchantment")) {
final List<Identifier> identifiers = new ArrayList<>();
for (RegistryEntry entry : entries) {
identifiers.add(Identifier.of(entry.key()));
}
itemComponentHasher.setEnchantmentRegistry(identifiers);
}
});
}
@Inject(method = "handleItemToClient", at = @At("RETURN"))
private void trackItemHashes(UserConnection connection, Item item, CallbackInfoReturnable<Item> cir) {
final ItemComponentHashStorage itemComponentHasher = connection.get(ItemComponentHashStorage.class);
for (StructuredData<?> structuredData : item.dataContainer().data().values()) {
itemComponentHasher.trackStructuredData(structuredData);
}
}
@Inject(method = "convertHashedItemToStructuredItem", at = @At("RETURN"))
private void addMoreDataToStructuredItem(UserConnection connection, HashedItem hashedItem, CallbackInfoReturnable<StructuredItem> cir) {
final ItemComponentHashStorage itemComponentHasher = connection.get(ItemComponentHashStorage.class);
final Map<StructuredDataKey<?>, StructuredData<?>> structuredDataMap = cir.getReturnValue().dataContainer().data();
for (Int2IntMap.Entry hashEntry : hashedItem.dataHashesById().int2IntEntrySet()) {
final StructuredData<?> structuredData = itemComponentHasher.getStructuredData(hashEntry.getIntKey(), hashEntry.getIntValue());
if (structuredData != null) {
structuredDataMap.put(structuredData.key(), structuredData);
} else if (DEBUG) {
this.protocol.getLogger().warning("Failed to find structured data for hash: " + hashEntry.getIntValue() + " with id: " + hashEntry.getIntKey());
}
}
for (int dataId : hashedItem.removedDataIds()) {
final StructuredDataKey<?> structuredDataKey = Types1_21_5.STRUCTURED_DATA.key(dataId);
structuredDataMap.put(structuredDataKey, StructuredData.empty(structuredDataKey, dataId));
}
}
}

View file

@ -0,0 +1,41 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2025 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.injection.mixins;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.Protocol1_21_4To1_21_5;
import net.raphimc.viaproxy.protocoltranslator.viaproxy.item_component_hasher.ItemComponentHashStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Protocol1_21_4To1_21_5.class)
public abstract class MixinProtocol1_21_4To1_21_5 {
@Shadow
public abstract MappingData getMappingData();
@Inject(method = "init", at = @At("RETURN"))
private void addItemComponentHashStorage(UserConnection connection, CallbackInfo ci) {
connection.put(new ItemComponentHashStorage(connection, this.getMappingData()));
}
}

View file

@ -0,0 +1,357 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2025 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.protocoltranslator.viaproxy.item_component_hasher;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.minecraft.GameProfile;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.data.*;
import com.viaversion.viaversion.libs.fastutil.ints.Int2IntMap;
import com.viaversion.viaversion.libs.fastutil.objects.Reference2ObjectOpenHashMap;
import com.viaversion.viaversion.util.Pair;
import net.lenni0451.mcstructs.converter.impl.v1_21_5.NbtConverter_v1_21_5;
import net.lenni0451.mcstructs.core.Identifier;
import net.lenni0451.mcstructs.itemcomponents.ItemComponent;
import net.lenni0451.mcstructs.itemcomponents.ItemComponentMap;
import net.lenni0451.mcstructs.itemcomponents.ItemComponentRegistry;
import net.lenni0451.mcstructs.itemcomponents.impl.v1_20_5.Types_v1_20_5;
import net.lenni0451.mcstructs.itemcomponents.impl.v1_21_2.Types_v1_21_2;
import net.lenni0451.mcstructs.itemcomponents.impl.v1_21_4.Types_v1_21_4;
import net.lenni0451.mcstructs.itemcomponents.impl.v1_21_5.Types_v1_21_5;
import net.lenni0451.mcstructs.nbt.tags.CompoundTag;
import net.lenni0451.mcstructs.text.TextComponent;
import net.lenni0451.mcstructs.text.serializer.TextComponentCodec;
import java.util.*;
import java.util.function.Function;
public class ItemComponentConverter {
private static final Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> NOT_IMPLEMENTED = structuredData -> null;
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> passthroughValueFunction(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, structuredData.value());
}
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> noValueFunction(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, null);
}
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> convertNbtFunction(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, NbtConverter.viaToMcStructs((Tag) structuredData.value()));
}
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> convertTextComponentFunction(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, convertTextComponent((Tag) structuredData.value()));
}
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> convertTextComponentArrayFunction(final ItemComponent<?> itemComponent) {
return structuredData -> {
final Tag[] tags = (Tag[]) structuredData.value();
final List<TextComponent> textComponents = new ArrayList<>(tags.length);
for (Tag tag : tags) {
textComponents.add(convertTextComponent(tag));
}
return new Pair<>(itemComponent, textComponents);
};
}
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> stringToIdentifierFunction(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, Identifier.of((String) structuredData.value()));
}
private static <T extends Enum<?>> Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> intToEnumFunction(final ItemComponent<T> itemComponent, final Class<T> enumClass) {
final Enum<?>[] enumConstants = enumClass.getEnumConstants();
return structuredData -> new Pair<>(itemComponent, enumConstants[(Integer) structuredData.value()]);
}
private static Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> passthroughNbtCodec(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, itemComponent.getCodec().deserialize(NbtConverter_v1_21_5.INSTANCE, NbtConverter.viaToMcStructs((Tag) structuredData.value())).getOrThrow());
}
private static Types_v1_20_5.FireworkExplosions convertFireworkExplosion(final FireworkExplosion viaFireworkExplosion) {
final Types_v1_20_5.FireworkExplosions.ExplosionShape explosionShape = Types_v1_20_5.FireworkExplosions.ExplosionShape.values()[viaFireworkExplosion.shape()];
return new Types_v1_20_5.FireworkExplosions(explosionShape, intArrayToIntList(viaFireworkExplosion.colors()), intArrayToIntList(viaFireworkExplosion.fadeColors()), viaFireworkExplosion.hasTrail(), viaFireworkExplosion.hasTwinkle());
}
private static TextComponent convertTextComponent(final Tag tag) {
if (tag != null) {
return TextComponentCodec.V1_21_5.deserialize(NbtConverter.viaToMcStructs(tag));
} else {
return null;
}
}
private static List<Integer> intArrayToIntList(final int[] array) {
final List<Integer> list = new ArrayList<>(array.length);
for (int i : array) {
list.add(i);
}
return list;
}
private final RegistryAccess registryAccess;
private final Map<StructuredDataKey<?>, Function<StructuredData<?>, Pair<ItemComponent<?>, Object>>> conversionFunctions = new Reference2ObjectOpenHashMap<>();
public ItemComponentConverter(final RegistryAccess registryAccess) {
this.registryAccess = registryAccess;
this.conversionFunctions.put(StructuredDataKey.CUSTOM_DATA, convertNbtFunction(ItemComponentRegistry.V1_21_5.CUSTOM_DATA));
this.conversionFunctions.put(StructuredDataKey.MAX_STACK_SIZE, passthroughValueFunction(ItemComponentRegistry.V1_21_5.MAX_STACK_SIZE));
this.conversionFunctions.put(StructuredDataKey.MAX_DAMAGE, passthroughValueFunction(ItemComponentRegistry.V1_21_5.MAX_DAMAGE));
this.conversionFunctions.put(StructuredDataKey.DAMAGE, passthroughValueFunction(ItemComponentRegistry.V1_21_5.DAMAGE));
this.conversionFunctions.put(StructuredDataKey.UNBREAKABLE1_21_5, noValueFunction(ItemComponentRegistry.V1_21_5.UNBREAKABLE));
this.conversionFunctions.put(StructuredDataKey.CUSTOM_NAME, convertTextComponentFunction(ItemComponentRegistry.V1_21_5.CUSTOM_NAME));
this.conversionFunctions.put(StructuredDataKey.ITEM_NAME, convertTextComponentFunction(ItemComponentRegistry.V1_21_5.ITEM_NAME));
this.conversionFunctions.put(StructuredDataKey.ITEM_MODEL, stringToIdentifierFunction(ItemComponentRegistry.V1_21_5.ITEM_MODEL));
this.conversionFunctions.put(StructuredDataKey.LORE, convertTextComponentArrayFunction(ItemComponentRegistry.V1_21_5.LORE));
this.conversionFunctions.put(StructuredDataKey.RARITY, intToEnumFunction(ItemComponentRegistry.V1_21_5.RARITY, Types_v1_20_5.Rarity.class));
this.conversionFunctions.put(StructuredDataKey.ENCHANTMENTS1_21_5, this.convertEnchantmentsFunction(ItemComponentRegistry.V1_21_5.ENCHANTMENTS));
this.conversionFunctions.put(StructuredDataKey.CAN_PLACE_ON1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.CAN_BREAK1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, structuredData -> {
final CustomModelData1_21_4 viaCustomModelData = (CustomModelData1_21_4) structuredData.value();
final List<Float> floats = new ArrayList<>(viaCustomModelData.floats().length);
for (float f : viaCustomModelData.floats()) {
floats.add(f);
}
final List<Boolean> booleans = new ArrayList<>(viaCustomModelData.booleans().length);
for (boolean b : viaCustomModelData.booleans()) {
booleans.add(b);
}
return new Pair<>(ItemComponentRegistry.V1_21_5.CUSTOM_MODEL_DATA, new Types_v1_21_4.CustomModelData(floats, booleans, Arrays.asList(viaCustomModelData.strings()), intArrayToIntList(viaCustomModelData.colors())));
});
this.conversionFunctions.put(StructuredDataKey.TOOLTIP_DISPLAY, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.REPAIR_COST, passthroughValueFunction(ItemComponentRegistry.V1_21_5.REPAIR_COST));
this.conversionFunctions.put(StructuredDataKey.CREATIVE_SLOT_LOCK, noValueFunction(ItemComponentRegistry.V1_21_5.CREATIVE_SLOT_LOCK));
this.conversionFunctions.put(StructuredDataKey.ENCHANTMENT_GLINT_OVERRIDE, passthroughValueFunction(ItemComponentRegistry.V1_21_5.ENCHANTMENT_GLINT_OVERRIDE));
this.conversionFunctions.put(StructuredDataKey.INTANGIBLE_PROJECTILE, noValueFunction(ItemComponentRegistry.V1_21_5.INTANGIBLE_PROJECTILE));
this.conversionFunctions.put(StructuredDataKey.FOOD1_21_2, structuredData -> {
final FoodProperties1_21_2 viaFoodProperties = (FoodProperties1_21_2) structuredData.value();
return new Pair<>(ItemComponentRegistry.V1_21_5.FOOD, new Types_v1_21_2.Food(viaFoodProperties.nutrition(), viaFoodProperties.saturationModifier(), viaFoodProperties.canAlwaysEat()));
});
this.conversionFunctions.put(StructuredDataKey.CONSUMABLE1_21_2, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.USE_REMAINDER1_21_5, this.convertItemFunction(ItemComponentRegistry.V1_21_5.USE_REMAINDER));
this.conversionFunctions.put(StructuredDataKey.USE_COOLDOWN, structuredData -> {
final UseCooldown viaUseCooldown = (UseCooldown) structuredData.value();
return new Pair<>(ItemComponentRegistry.V1_21_5.USE_COOLDOWN, new Types_v1_21_2.UseCooldown(viaUseCooldown.seconds(), Identifier.of(viaUseCooldown.cooldownGroup())));
});
this.conversionFunctions.put(StructuredDataKey.DAMAGE_RESISTANT, structuredData -> {
final DamageResistant viaDamageResistant = (DamageResistant) structuredData.value();
return new Pair<>(ItemComponentRegistry.V1_21_5.DAMAGE_RESISTANT, new Types_v1_21_2.DamageResistant(Identifier.of(viaDamageResistant.typesTagKey())));
});
this.conversionFunctions.put(StructuredDataKey.TOOL1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.WEAPON, structuredData -> {
final Weapon viaWeapon = (Weapon) structuredData.value();
return new Pair<>(ItemComponentRegistry.V1_21_5.WEAPON, new Types_v1_21_5.Weapon(viaWeapon.itemDamagePerAttack(), viaWeapon.disableBlockingForSeconds()));
});
this.conversionFunctions.put(StructuredDataKey.ENCHANTABLE, structuredData -> {
return new Pair<>(ItemComponentRegistry.V1_21_5.ENCHANTABLE, new Types_v1_21_2.Enchantable((int) structuredData.value()));
});
this.conversionFunctions.put(StructuredDataKey.EQUIPPABLE1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.REPAIRABLE, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.GLIDER, noValueFunction(ItemComponentRegistry.V1_21_5.GLIDER));
this.conversionFunctions.put(StructuredDataKey.TOOLTIP_STYLE, stringToIdentifierFunction(ItemComponentRegistry.V1_21_5.TOOLTIP_STYLE));
this.conversionFunctions.put(StructuredDataKey.DEATH_PROTECTION, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.BLOCKS_ATTACKS, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.STORED_ENCHANTMENTS1_21_5, this.convertEnchantmentsFunction(ItemComponentRegistry.V1_21_5.STORED_ENCHANTMENTS));
this.conversionFunctions.put(StructuredDataKey.DYED_COLOR1_21_5, structuredData -> {
return new Pair<>(ItemComponentRegistry.V1_21_5.DYED_COLOR, ((DyedColor) structuredData.value()).rgb());
});
this.conversionFunctions.put(StructuredDataKey.MAP_COLOR, passthroughValueFunction(ItemComponentRegistry.V1_21_5.MAP_COLOR));
this.conversionFunctions.put(StructuredDataKey.MAP_ID, passthroughValueFunction(ItemComponentRegistry.V1_21_5.MAP_ID));
this.conversionFunctions.put(StructuredDataKey.MAP_DECORATIONS, passthroughNbtCodec(ItemComponentRegistry.V1_21_5.MAP_DECORATIONS));
this.conversionFunctions.put(StructuredDataKey.MAP_POST_PROCESSING, intToEnumFunction(ItemComponentRegistry.V1_21_5.MAP_POST_PROCESSING, Types_v1_20_5.MapPostProcessing.class));
this.conversionFunctions.put(StructuredDataKey.CHARGED_PROJECTILES1_21_5, convertItemArrayFunction(ItemComponentRegistry.V1_21_5.CHARGED_PROJECTILES));
this.conversionFunctions.put(StructuredDataKey.BUNDLE_CONTENTS1_21_5, convertItemArrayFunction(ItemComponentRegistry.V1_21_5.BUNDLE_CONTENTS));
this.conversionFunctions.put(StructuredDataKey.POTION_CONTENTS1_21_2, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.POTION_DURATION_SCALE, passthroughValueFunction(ItemComponentRegistry.V1_21_5.POTION_DURATION_SCALE));
this.conversionFunctions.put(StructuredDataKey.SUSPICIOUS_STEW_EFFECTS, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.WRITABLE_BOOK_CONTENT, structuredData -> {
final FilterableString[] viaPages = (FilterableString[]) structuredData.value();
final List<Types_v1_20_5.RawFilteredPair<String>> pages = new ArrayList<>(viaPages.length);
for (FilterableString viaPage : viaPages) {
pages.add(new Types_v1_20_5.RawFilteredPair<>(viaPage.raw(), viaPage.filtered()));
}
return new Pair<>(ItemComponentRegistry.V1_21_5.WRITABLE_BOOK_CONTENT, new Types_v1_20_5.WritableBook(pages));
});
this.conversionFunctions.put(StructuredDataKey.WRITTEN_BOOK_CONTENT, structuredData -> {
final WrittenBook viaWrittenBook = (WrittenBook) structuredData.value();
final Types_v1_20_5.RawFilteredPair<String> title = new Types_v1_20_5.RawFilteredPair<>(viaWrittenBook.title().raw(), viaWrittenBook.title().filtered());
final List<Types_v1_20_5.RawFilteredPair<TextComponent>> pages = new ArrayList<>(viaWrittenBook.pages().length);
for (FilterableComponent viaPage : viaWrittenBook.pages()) {
pages.add(new Types_v1_20_5.RawFilteredPair<>(convertTextComponent(viaPage.raw()), convertTextComponent(viaPage.filtered())));
}
return new Pair<>(ItemComponentRegistry.V1_21_5.WRITTEN_BOOK_CONTENT, new Types_v1_20_5.WrittenBook(title, viaWrittenBook.author(), viaWrittenBook.generation(), pages, viaWrittenBook.resolved()));
});
this.conversionFunctions.put(StructuredDataKey.TRIM1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.DEBUG_STICK_STATE, passthroughNbtCodec(ItemComponentRegistry.V1_21_5.DEBUG_STICK_STATE));
this.conversionFunctions.put(StructuredDataKey.ENTITY_DATA, convertNbtFunction(ItemComponentRegistry.V1_21_5.ENTITY_DATA));
this.conversionFunctions.put(StructuredDataKey.BUCKET_ENTITY_DATA, convertNbtFunction(ItemComponentRegistry.V1_21_5.BUCKET_ENTITY_DATA));
this.conversionFunctions.put(StructuredDataKey.BLOCK_ENTITY_DATA, convertNbtFunction(ItemComponentRegistry.V1_21_5.BLOCK_ENTITY_DATA));
this.conversionFunctions.put(StructuredDataKey.INSTRUMENT1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.PROVIDES_TRIM_MATERIAL, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.OMINOUS_BOTTLE_AMPLIFIER, passthroughValueFunction(ItemComponentRegistry.V1_21_5.OMINOUS_BOTTLE_AMPLIFIER));
this.conversionFunctions.put(StructuredDataKey.JUKEBOX_PLAYABLE1_21_5, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.PROVIDES_BANNER_PATTERNS, stringToIdentifierFunction(ItemComponentRegistry.V1_21_5.PROVIDES_BANNER_PATTERNS));
this.conversionFunctions.put(StructuredDataKey.RECIPES, passthroughNbtCodec(ItemComponentRegistry.V1_21_5.RECIPES));
this.conversionFunctions.put(StructuredDataKey.LODESTONE_TRACKER, structuredData -> {
final LodestoneTracker viaLodestoneTracker = (LodestoneTracker) structuredData.value();
final Types_v1_20_5.LodestoneTracker.GlobalPos targetGlobalPos;
if (viaLodestoneTracker.position() != null) {
final Types_v1_20_5.BlockPos targetPos = new Types_v1_20_5.BlockPos(viaLodestoneTracker.position().x(), viaLodestoneTracker.position().y(), viaLodestoneTracker.position().z());
targetGlobalPos = new Types_v1_20_5.LodestoneTracker.GlobalPos(Identifier.of(viaLodestoneTracker.position().dimension()), targetPos);
} else {
targetGlobalPos = null;
}
return new Pair<>(ItemComponentRegistry.V1_21_5.LODESTONE_TRACKER, new Types_v1_20_5.LodestoneTracker(targetGlobalPos, viaLodestoneTracker.tracked()));
});
this.conversionFunctions.put(StructuredDataKey.FIREWORK_EXPLOSION, structuredData -> {
return new Pair<>(ItemComponentRegistry.V1_21_5.FIREWORK_EXPLOSION, convertFireworkExplosion((FireworkExplosion) structuredData.value()));
});
this.conversionFunctions.put(StructuredDataKey.FIREWORKS, structuredData -> {
final Fireworks viaFireworks = (Fireworks) structuredData.value();
final List<Types_v1_20_5.FireworkExplosions> explosions = new ArrayList<>(viaFireworks.explosions().length);
for (FireworkExplosion viaFireworkExplosion : viaFireworks.explosions()) {
explosions.add(convertFireworkExplosion(viaFireworkExplosion));
}
return new Pair<>(ItemComponentRegistry.V1_21_5.FIREWORKS, new Types_v1_20_5.Fireworks(viaFireworks.flightDuration(), explosions));
});
this.conversionFunctions.put(StructuredDataKey.PROFILE, structuredData -> {
final GameProfile viaProfile = (GameProfile) structuredData.value();
final Map<String, List<Types_v1_20_5.GameProfile.Property>> properties = new HashMap<>();
for (GameProfile.Property viaProperty : viaProfile.properties()) {
final List<Types_v1_20_5.GameProfile.Property> propertyList = properties.computeIfAbsent(viaProperty.name(), k -> new ArrayList<>());
propertyList.add(new Types_v1_20_5.GameProfile.Property(viaProperty.name(), viaProperty.value(), viaProperty.signature()));
}
return new Pair<>(ItemComponentRegistry.V1_21_5.PROFILE, new Types_v1_20_5.GameProfile(viaProfile.name(), viaProfile.id(), properties));
});
this.conversionFunctions.put(StructuredDataKey.NOTE_BLOCK_SOUND, stringToIdentifierFunction(ItemComponentRegistry.V1_21_5.NOTE_BLOCK_SOUND));
this.conversionFunctions.put(StructuredDataKey.BANNER_PATTERNS, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.BASE_COLOR, intToEnumFunction(ItemComponentRegistry.V1_21_5.BASE_COLOR, Types_v1_20_5.DyeColor.class));
this.conversionFunctions.put(StructuredDataKey.POT_DECORATIONS, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.CONTAINER1_21_5, structuredData -> {
final Item[] items = (Item[]) structuredData.value();
final List<Types_v1_20_5.ContainerSlot> slots = new ArrayList<>();
for (int i = 0; i < items.length; i++) {
final Item item = items[i];
if (!item.isEmpty()) {
slots.add(new Types_v1_20_5.ContainerSlot(i, this.convertItemStack(item)));
}
}
return new Pair<>(ItemComponentRegistry.V1_21_5.CONTAINER, slots);
});
this.conversionFunctions.put(StructuredDataKey.BLOCK_STATE, structuredData -> {
return new Pair<>(ItemComponentRegistry.V1_21_5.BLOCK_STATE, ((BlockStateProperties) structuredData.value()).properties());
});
this.conversionFunctions.put(StructuredDataKey.BEES, structuredData -> {
final Bee[] viaBees = (Bee[]) structuredData.value();
final List<Types_v1_20_5.BeeData> beeData = new ArrayList<>();
for (Bee viaBee : viaBees) {
beeData.add(new Types_v1_20_5.BeeData((CompoundTag) NbtConverter.viaToMcStructs(viaBee.entityData()), viaBee.ticksInHive(), viaBee.minTicksInHive()));
}
return new Pair<>(ItemComponentRegistry.V1_21_5.BEES, beeData);
});
this.conversionFunctions.put(StructuredDataKey.LOCK, passthroughNbtCodec(ItemComponentRegistry.V1_21_5.LOCK));
this.conversionFunctions.put(StructuredDataKey.CONTAINER_LOOT, passthroughNbtCodec(ItemComponentRegistry.V1_21_5.CONTAINER_LOOT));
this.conversionFunctions.put(StructuredDataKey.BREAK_SOUND, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.VILLAGER_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.VILLAGER_VARIANT, Types_v1_21_5.VillagerVariant.class));
this.conversionFunctions.put(StructuredDataKey.WOLF_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.WOLF_SOUND_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.WOLF_COLLAR, intToEnumFunction(ItemComponentRegistry.V1_21_5.WOLF_COLLAR, Types_v1_20_5.DyeColor.class));
this.conversionFunctions.put(StructuredDataKey.FOX_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.FOX_VARIANT, Types_v1_21_5.FoxVariant.class));
this.conversionFunctions.put(StructuredDataKey.SALMON_SIZE, intToEnumFunction(ItemComponentRegistry.V1_21_5.SALMON_SIZE, Types_v1_21_5.SalmonSize.class));
this.conversionFunctions.put(StructuredDataKey.PARROT_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.PARROT_VARIANT, Types_v1_21_5.ParrotVariant.class));
this.conversionFunctions.put(StructuredDataKey.TROPICAL_FISH_PATTERN, intToEnumFunction(ItemComponentRegistry.V1_21_5.TROPICAL_FISH_PATTERN, Types_v1_21_5.TropicalFishPattern.class));
this.conversionFunctions.put(StructuredDataKey.TROPICAL_FISH_BASE_COLOR, intToEnumFunction(ItemComponentRegistry.V1_21_5.TROPICAL_FISH_BASE_COLOR, Types_v1_20_5.DyeColor.class));
this.conversionFunctions.put(StructuredDataKey.TROPICAL_FISH_PATTERN_COLOR, intToEnumFunction(ItemComponentRegistry.V1_21_5.TROPICAL_FISH_PATTERN_COLOR, Types_v1_20_5.DyeColor.class));
this.conversionFunctions.put(StructuredDataKey.MOOSHROOM_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.MOOSHROOM_VARIANT, Types_v1_21_5.MooshroomVariant.class));
this.conversionFunctions.put(StructuredDataKey.RABBIT_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.RABBIT_VARIANT, Types_v1_21_5.RabbitVariant.class));
this.conversionFunctions.put(StructuredDataKey.PIG_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.COW_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.CHICKEN_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.FROG_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.HORSE_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.HORSE_VARIANT, Types_v1_21_5.HorseVariant.class));
this.conversionFunctions.put(StructuredDataKey.PAINTING_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.LLAMA_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.LLAMA_VARIANT, Types_v1_21_5.LlamaVariant.class));
this.conversionFunctions.put(StructuredDataKey.AXOLOTL_VARIANT, intToEnumFunction(ItemComponentRegistry.V1_21_5.AXOLOTL_VARIANT, Types_v1_21_5.AxolotlVariant.class));
this.conversionFunctions.put(StructuredDataKey.CAT_VARIANT, NOT_IMPLEMENTED);
this.conversionFunctions.put(StructuredDataKey.CAT_COLLAR, intToEnumFunction(ItemComponentRegistry.V1_21_5.CAT_COLLAR, Types_v1_20_5.DyeColor.class));
this.conversionFunctions.put(StructuredDataKey.SHEEP_COLOR, intToEnumFunction(ItemComponentRegistry.V1_21_5.SHEEP_COLOR, Types_v1_20_5.DyeColor.class));
this.conversionFunctions.put(StructuredDataKey.SHULKER_COLOR, intToEnumFunction(ItemComponentRegistry.V1_21_5.SHULKER_COLOR, Types_v1_20_5.DyeColor.class));
}
public Pair<ItemComponent<?>, Object> viaToMcStructs(final StructuredData<?> structuredData) {
final Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> conversionFunction = this.conversionFunctions.get(structuredData.key());
if (conversionFunction == null) {
throw new UnsupportedOperationException("Unsupported structured data key: " + structuredData.key());
}
return conversionFunction.apply(structuredData);
}
private Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> convertItemFunction(final ItemComponent<?> itemComponent) {
return structuredData -> new Pair<>(itemComponent, this.convertItemStack((Item) structuredData.value()));
}
private Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> convertItemArrayFunction(final ItemComponent<?> itemComponent) {
return structuredData -> {
final Item[] items = (Item[]) structuredData.value();
final List<Types_v1_20_5.ItemStack> itemStacks = new ArrayList<>(items.length);
for (Item item : items) {
itemStacks.add(this.convertItemStack(item));
}
return new Pair<>(itemComponent, itemStacks);
};
}
private Function<StructuredData<?>, Pair<ItemComponent<?>, Object>> convertEnchantmentsFunction(final ItemComponent<?> itemComponent) {
return structuredData -> {
final Enchantments viaEnchantments = (Enchantments) structuredData.value();
final Map<Identifier, Integer> enchantments = new HashMap<>();
for (Int2IntMap.Entry entry : viaEnchantments.enchantments().int2IntEntrySet()) {
enchantments.put(this.registryAccess.getEnchantment(entry.getIntKey()), entry.getIntValue());
}
return new Pair<>(itemComponent, enchantments);
};
}
private Types_v1_20_5.ItemStack convertItemStack(final Item item) {
final Types_v1_20_5.ItemStack itemStack = new Types_v1_20_5.ItemStack(this.registryAccess.getItem(item.identifier()), item.amount(), new ItemComponentMap(ItemComponentRegistry.V1_21_5));
final ItemComponentMap itemComponentMap = itemStack.getComponents();
for (StructuredData<?> structuredData : item.dataContainer().data().values()) {
if (structuredData.isPresent()) {
final Pair<ItemComponent<?>, Object> itemComponent = viaToMcStructs(structuredData);
if (itemComponent != null) {
itemComponentMap.set(itemComponent.key(), cast(itemComponent.value()));
}
} else {
itemComponentMap.markForRemoval(ItemComponentRegistry.V1_21_5.getComponentList().getById(structuredData.id()).get());
}
}
return itemStack;
}
private static <T> T cast(final Object o) {
return (T) o;
}
}

View file

@ -0,0 +1,99 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2025 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.protocoltranslator.viaproxy.item_component_hasher;
import com.google.common.cache.CacheBuilder;
import com.viaversion.viaversion.api.connection.StoredObject;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.util.Pair;
import net.lenni0451.mcstructs.converter.impl.v1_21_5.HashConverter_v1_21_5;
import net.lenni0451.mcstructs.core.Identifier;
import net.lenni0451.mcstructs.itemcomponents.ItemComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ItemComponentHashStorage extends StoredObject {
private final Map<Long, StructuredData<?>> hashToStructuredData = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(256).<Long, StructuredData<?>>build().asMap();
private final List<Identifier> enchantmentRegistry = new ArrayList<>();
private final ItemComponentConverter itemComponentConverter;
public ItemComponentHashStorage(final UserConnection user, final MappingData mappingData) {
super(user);
this.itemComponentConverter = new ItemComponentConverter(new RegistryAccess() {
@Override
public Identifier getItem(final int networkId) {
final String identifier = mappingData.getFullItemMappings().mappedIdentifier(networkId);
if (identifier != null) {
return Identifier.of(identifier);
} else {
return Identifier.of("viaproxy", "unknown/item/" + networkId);
}
}
@Override
public Identifier getEnchantment(final int networkId) {
if (networkId >= 0 && networkId < ItemComponentHashStorage.this.enchantmentRegistry.size()) {
return ItemComponentHashStorage.this.enchantmentRegistry.get(networkId);
} else {
return Identifier.of("viaproxy", "unknown/enchantment/" + networkId);
}
}
});
}
public void setEnchantmentRegistry(final List<Identifier> enchantmentRegistry) {
this.enchantmentRegistry.clear();
this.enchantmentRegistry.addAll(enchantmentRegistry);
}
public void trackStructuredData(final StructuredData<?> structuredData) {
if (structuredData.isEmpty()) {
return;
}
final Pair<ItemComponent<?>, Object> itemComponent = this.itemComponentConverter.viaToMcStructs(structuredData);
if (itemComponent == null) {
return;
}
final int hash = itemComponent.key().getCodec().serialize(HashConverter_v1_21_5.CRC32C, cast(itemComponent.value())).getOrThrow().asInt();
final long key = (long) structuredData.id() << 32 | hash;
if (!this.hashToStructuredData.containsKey(key)) {
this.hashToStructuredData.put(key, structuredData.copy());
}
}
public StructuredData<?> getStructuredData(final int componentId, final int hash) {
final long key = (long) componentId << 32 | hash;
if (this.hashToStructuredData.containsKey(key)) {
return this.hashToStructuredData.get(key).copy();
} else {
return null;
}
}
private static <T> T cast(final Object o) {
return (T) o;
}
}

View file

@ -0,0 +1,98 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2025 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.protocoltranslator.viaproxy.item_component_hasher;
import com.viaversion.nbt.tag.*;
import net.lenni0451.mcstructs.nbt.NbtTag;
import java.util.Collections;
import java.util.Map;
public class NbtConverter {
public static Tag mcStructsToVia(final NbtTag tag) {
if (tag == null) return null;
return switch (tag.getNbtType()) {
case END -> throw new UnsupportedOperationException();
case BYTE -> new ByteTag(tag.asByteTag().getValue());
case SHORT -> new ShortTag(tag.asShortTag().getValue());
case INT -> new IntTag(tag.asIntTag().getValue());
case LONG -> new LongTag(tag.asLongTag().getValue());
case FLOAT -> new FloatTag(tag.asFloatTag().getValue());
case DOUBLE -> new DoubleTag(tag.asDoubleTag().getValue());
case BYTE_ARRAY -> new ByteArrayTag(tag.asByteArrayTag().getValue());
case STRING -> new StringTag(tag.asStringTag().getValue());
case LIST -> {
final ListTag<? super Tag> listTag = new ListTag<>(Collections.emptyList());
for (NbtTag subTag : tag.asListTag()) {
listTag.add(mcStructsToVia(subTag));
}
yield listTag;
}
case COMPOUND -> {
final CompoundTag compoundTag = new CompoundTag();
for (Map.Entry<String, NbtTag> entry : tag.asCompoundTag()) {
compoundTag.put(entry.getKey(), mcStructsToVia(entry.getValue()));
}
yield compoundTag;
}
case INT_ARRAY -> new IntArrayTag(tag.asIntArrayTag().getValue());
case LONG_ARRAY -> new LongArrayTag(tag.asLongArrayTag().getValue());
};
}
public static NbtTag viaToMcStructs(final Tag tag) {
if (tag == null) return null;
if (tag instanceof ByteTag byteTag) {
return new net.lenni0451.mcstructs.nbt.tags.ByteTag(byteTag.asByte());
} else if (tag instanceof ShortTag shortTag) {
return new net.lenni0451.mcstructs.nbt.tags.ShortTag(shortTag.asShort());
} else if (tag instanceof IntTag intTag) {
return new net.lenni0451.mcstructs.nbt.tags.IntTag(intTag.asInt());
} else if (tag instanceof LongTag longTag) {
return new net.lenni0451.mcstructs.nbt.tags.LongTag(longTag.asLong());
} else if (tag instanceof FloatTag floatTag) {
return new net.lenni0451.mcstructs.nbt.tags.FloatTag(floatTag.asFloat());
} else if (tag instanceof DoubleTag doubleTag) {
return new net.lenni0451.mcstructs.nbt.tags.DoubleTag(doubleTag.asDouble());
} else if (tag instanceof ByteArrayTag byteArrayTag) {
return new net.lenni0451.mcstructs.nbt.tags.ByteArrayTag(byteArrayTag.getValue());
} else if (tag instanceof StringTag stringTag) {
return new net.lenni0451.mcstructs.nbt.tags.StringTag(stringTag.getValue());
} else if (tag instanceof ListTag<?> listTag) {
final net.lenni0451.mcstructs.nbt.tags.ListTag<NbtTag> mcListTag = new net.lenni0451.mcstructs.nbt.tags.ListTag<>();
for (Tag subTag : listTag) {
mcListTag.add(viaToMcStructs(subTag));
}
return mcListTag;
} else if (tag instanceof CompoundTag compoundTag) {
final net.lenni0451.mcstructs.nbt.tags.CompoundTag mcCompoundTag = new net.lenni0451.mcstructs.nbt.tags.CompoundTag();
for (Map.Entry<String, Tag> entry : compoundTag.entrySet()) {
mcCompoundTag.add(entry.getKey(), viaToMcStructs(entry.getValue()));
}
return mcCompoundTag;
} else if (tag instanceof IntArrayTag intArrayTag) {
return new net.lenni0451.mcstructs.nbt.tags.IntArrayTag(intArrayTag.getValue());
} else if (tag instanceof LongArrayTag longArrayTag) {
return new net.lenni0451.mcstructs.nbt.tags.LongArrayTag(longArrayTag.getValue());
} else {
throw new UnsupportedOperationException("Unknown tag type: " + tag.getClass().getName());
}
}
}

View file

@ -0,0 +1,28 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2025 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.protocoltranslator.viaproxy.item_component_hasher;
import net.lenni0451.mcstructs.core.Identifier;
public interface RegistryAccess {
Identifier getItem(final int networkId);
Identifier getEnchantment(final int networkId);
}