From 4c0e2e3e5d2b6e93515f859567bc4a1f0d7b023e Mon Sep 17 00:00:00 2001 From: Mitch Date: Thu, 26 Feb 2026 23:13:34 -0800 Subject: [PATCH 1/3] passing item_name through to client added config with explanation,. Populating custom_model_data for 1.21.4+ clients for the resource pack to work better. Not passing this back in to server. --- .../viabackwards/ViaBackwardsConfig.java | 7 +++ .../viabackwards/api/ViaBackwardsConfig.java | 9 ++++ .../BackwardsStructuredItemRewriter.java | 54 ++++++++++++++++--- .../resources/assets/viabackwards/config.yml | 6 +++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/com/viaversion/viabackwards/ViaBackwardsConfig.java b/common/src/main/java/com/viaversion/viabackwards/ViaBackwardsConfig.java index e4ea4a96..9eca8419 100644 --- a/common/src/main/java/com/viaversion/viabackwards/ViaBackwardsConfig.java +++ b/common/src/main/java/com/viaversion/viabackwards/ViaBackwardsConfig.java @@ -47,6 +47,7 @@ public class ViaBackwardsConfig extends Config implements com.viaversion.viaback private boolean dialogsViaChests; private DialogStyleConfig dialogStyleConfig; private boolean codeOfConductAsDialog; + private boolean passOriginalItemNameToResourcePacks; public ViaBackwardsConfig(File configFile, Logger logger) { super(configFile, logger); @@ -75,6 +76,7 @@ private void loadFields() { dialogsViaChests = getBoolean("dialogs-via-chests", true); dialogStyleConfig = loadDialogStyleConfig(getSection("dialog-style")); codeOfConductAsDialog = getBoolean("code-of-conduct-as-dialog", true); + passOriginalItemNameToResourcePacks = getBoolean("pass-original-item-name-to-resource-packs", true); } private DialogStyleConfig loadDialogStyleConfig(final ConfigSection section) { @@ -179,6 +181,11 @@ public boolean codeOfConductAsDialog() { return codeOfConductAsDialog; } + @Override + public boolean passOriginalItemNameToResourcePacks() { + return passOriginalItemNameToResourcePacks; + } + @Override public URL getDefaultConfigURL() { return getClass().getClassLoader().getResource("assets/viabackwards/config.yml"); diff --git a/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java b/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java index 807a466e..4d41e58e 100644 --- a/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java +++ b/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java @@ -133,4 +133,13 @@ public interface ViaBackwardsConfig extends Config { * @return true if enabled */ boolean codeOfConductAsDialog(); + + /** + * Injects the original vanilla 1.21.4+ item name into custom_model_data strings for resource packs. + * Disable if your server creates custom items using modern items as their base. + * Tip: For server custom items, always base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. + * + * @return true if enabled + */ + boolean passOriginalItemNameToResourcePacks(); } diff --git a/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java b/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java index 9e5c1320..f3948f57 100644 --- a/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java +++ b/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java @@ -24,6 +24,7 @@ import com.viaversion.nbt.tag.ListTag; import com.viaversion.nbt.tag.StringTag; import com.viaversion.nbt.tag.Tag; +import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.data.BackwardsMappingData; import com.viaversion.viabackwards.api.data.MappedItem; @@ -70,17 +71,42 @@ protected void backupInconvertibleData(final UserConnection connection, final It customTag.putInt(nbtTagName("id"), item.identifier()); // Save original id // Add custom model data - if (mappedItem.customModelData() != null) { + if (mappedItem.customModelData() != null || ViaBackwards.getConfig().passOriginalItemNameToResourcePacks()) { if (connection.getProtocolInfo().protocolVersion().newerThanOrEqualTo(ProtocolVersion.v1_21_4)) { - if (!dataContainer.has(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4)) { - dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, new CustomModelData1_21_4( - new float[]{mappedItem.customModelData().floatValue()}, + CustomModelData1_21_4 customModelData = dataContainer.get(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); + if (customModelData == null) { + customModelData = new CustomModelData1_21_4( + mappedItem.customModelData() != null ? new float[]{mappedItem.customModelData().floatValue()} : new float[0], new boolean[0], new String[0], EMPTY_INT_ARRAY - )); + ); + dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, customModelData); } - } else if (!dataContainer.has(StructuredDataKey.CUSTOM_MODEL_DATA1_20_5)) { + + if (ViaBackwards.getConfig().passOriginalItemNameToResourcePacks() && mappingData.getFullItemMappings() != null) { + final String identifier = mappingData.getFullItemMappings().identifier(item.identifier()); + if (identifier != null) { + final String injection = "viabackwards:" + Key.stripMinecraftNamespace(identifier); + boolean exists = false; + for (final String s : customModelData.strings()) { + if (s.equals(injection)) { + exists = true; + break; + } + } + if (!exists) { + final String[] newStrings = new String[customModelData.strings().length + 1]; + System.arraycopy(customModelData.strings(), 0, newStrings, 0, customModelData.strings().length); + newStrings[customModelData.strings().length] = injection; + dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, new CustomModelData1_21_4( + customModelData.floats(), customModelData.booleans(), newStrings, customModelData.colors() + )); + customTag.putBoolean(nbtTagName("injected_cmd_string"), true); + } + } + } + } else if (mappedItem.customModelData() != null && !dataContainer.has(StructuredDataKey.CUSTOM_MODEL_DATA1_20_5)) { dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_20_5, mappedItem.customModelData()); } } @@ -99,6 +125,22 @@ protected void restoreBackupData(final Item item, final StructuredDataContainer item.setIdentifier(originalTag.asInt()); removeCustomTag(container, customData); } + + if (removeBackupTag(customData, "injected_cmd_string") != null) { + final CustomModelData1_21_4 customModelData = container.get(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); + if (customModelData != null && customModelData.strings() != null) { + final List strings = new java.util.ArrayList<>(java.util.Arrays.asList(customModelData.strings())); + if (strings.removeIf(s -> s.startsWith("viabackwards:"))) { + if (strings.isEmpty() && customModelData.floats().length == 0 && customModelData.booleans().length == 0 && customModelData.colors().length == 0) { + container.remove(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); + } else { + container.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, new CustomModelData1_21_4( + customModelData.floats(), customModelData.booleans(), strings.toArray(new String[0]), customModelData.colors() + )); + } + } + } + } } protected void saveListTag(CompoundTag tag, ListTag original, String name) { diff --git a/common/src/main/resources/assets/viabackwards/config.yml b/common/src/main/resources/assets/viabackwards/config.yml index 0d99df5a..7ab1460c 100644 --- a/common/src/main/resources/assets/viabackwards/config.yml +++ b/common/src/main/resources/assets/viabackwards/config.yml @@ -68,3 +68,9 @@ dialog-style: # Note that this is not supported for clients below 1.21.6 right now due to missing support for # dialogs during the configuration phase. code-of-conduct-as-dialog: true +# +# Passes the original vanilla 1.21.4+ item name into custom_model_data strings. +# This allows client resource packs to restore the appearance of newer items entirely missing from this older version. +# Disable if your server creates custom items using modern items as their base. +# Tip: For server custom items, always base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. +pass-original-item-name-to-resource-packs: true From 96e2fa7c966935531d651ecbabbf168d7ed21696 Mon Sep 17 00:00:00 2001 From: Mitch Date: Sun, 1 Mar 2026 17:25:02 -0800 Subject: [PATCH 2/3] Refactor: Use ArrayUtil and Custom Tag Marker for the injection --- .../viabackwards/api/ViaBackwardsConfig.java | 2 +- .../BackwardsStructuredItemRewriter.java | 33 ++++++++++++------- .../resources/assets/viabackwards/config.yml | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java b/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java index 4d41e58e..b16e54a4 100644 --- a/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java +++ b/common/src/main/java/com/viaversion/viabackwards/api/ViaBackwardsConfig.java @@ -137,7 +137,7 @@ public interface ViaBackwardsConfig extends Config { /** * Injects the original vanilla 1.21.4+ item name into custom_model_data strings for resource packs. * Disable if your server creates custom items using modern items as their base. - * Tip: For server custom items, always base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. + * Tip: For server custom items, base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. * * @return true if enabled */ diff --git a/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java b/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java index f3948f57..44d16aa0 100644 --- a/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java +++ b/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java @@ -41,6 +41,7 @@ import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.rewriter.StructuredItemRewriter; +import com.viaversion.viaversion.util.ArrayUtil; import com.viaversion.viaversion.util.Key; import java.util.ArrayList; import java.util.List; @@ -87,22 +88,18 @@ protected void backupInconvertibleData(final UserConnection connection, final It if (ViaBackwards.getConfig().passOriginalItemNameToResourcePacks() && mappingData.getFullItemMappings() != null) { final String identifier = mappingData.getFullItemMappings().identifier(item.identifier()); if (identifier != null) { - final String injection = "viabackwards:" + Key.stripMinecraftNamespace(identifier); boolean exists = false; for (final String s : customModelData.strings()) { - if (s.equals(injection)) { + if (s.equals(identifier)) { exists = true; break; } } if (!exists) { - final String[] newStrings = new String[customModelData.strings().length + 1]; - System.arraycopy(customModelData.strings(), 0, newStrings, 0, customModelData.strings().length); - newStrings[customModelData.strings().length] = injection; dataContainer.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, new CustomModelData1_21_4( - customModelData.floats(), customModelData.booleans(), newStrings, customModelData.colors() + customModelData.floats(), customModelData.booleans(), ArrayUtil.add(customModelData.strings(), identifier), customModelData.colors() )); - customTag.putBoolean(nbtTagName("injected_cmd_string"), true); + customTag.putString(nbtTagName("injected_cmd_string"), identifier); } } } @@ -126,16 +123,28 @@ protected void restoreBackupData(final Item item, final StructuredDataContainer removeCustomTag(container, customData); } - if (removeBackupTag(customData, "injected_cmd_string") != null) { + final Tag injectedCmdTag = removeBackupTag(customData, "injected_cmd_string"); + if (injectedCmdTag instanceof StringTag stringTag) { final CustomModelData1_21_4 customModelData = container.get(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); if (customModelData != null && customModelData.strings() != null) { - final List strings = new java.util.ArrayList<>(java.util.Arrays.asList(customModelData.strings())); - if (strings.removeIf(s -> s.startsWith("viabackwards:"))) { - if (strings.isEmpty() && customModelData.floats().length == 0 && customModelData.booleans().length == 0 && customModelData.colors().length == 0) { + final String target = stringTag.getValue(); + final String[] oldStrings = customModelData.strings(); + + int index = -1; + for (int i = 0; i < oldStrings.length; i++) { + if (oldStrings[i].equals(target)) { + index = i; + break; + } + } + + if (index != -1) { + final String[] newStrings = ArrayUtil.remove(oldStrings, index); + if (newStrings.length == 0 && customModelData.floats().length == 0 && customModelData.booleans().length == 0 && customModelData.colors().length == 0) { container.remove(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); } else { container.set(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4, new CustomModelData1_21_4( - customModelData.floats(), customModelData.booleans(), strings.toArray(new String[0]), customModelData.colors() + customModelData.floats(), customModelData.booleans(), newStrings, customModelData.colors() )); } } diff --git a/common/src/main/resources/assets/viabackwards/config.yml b/common/src/main/resources/assets/viabackwards/config.yml index 7ab1460c..f2f7ea6c 100644 --- a/common/src/main/resources/assets/viabackwards/config.yml +++ b/common/src/main/resources/assets/viabackwards/config.yml @@ -72,5 +72,5 @@ code-of-conduct-as-dialog: true # Passes the original vanilla 1.21.4+ item name into custom_model_data strings. # This allows client resource packs to restore the appearance of newer items entirely missing from this older version. # Disable if your server creates custom items using modern items as their base. -# Tip: For server custom items, always base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. +# Tip: For server custom items, base them on items in the game before 1.21.4 (e.g. saddle) to ensure compatibility. pass-original-item-name-to-resource-packs: true From 5ac9a12f82d637b2783f7adf7d15830e8026fe4c Mon Sep 17 00:00:00 2001 From: mfishma Date: Thu, 5 Mar 2026 14:08:36 -0800 Subject: [PATCH 3/3] Avoid double-injection of tags when rewriting To assure there's only one tag per item, made a shared VB|injected_cmd marker during the first rewrite, then check for it in subsequent versions to skip any duplicate injections. Also optimized the NBT tag name lookup to be a bit snappier. --- .../api/rewriters/BackwardsStructuredItemRewriter.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java b/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java index 44d16aa0..83a24b86 100644 --- a/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java +++ b/common/src/main/java/com/viaversion/viabackwards/api/rewriters/BackwardsStructuredItemRewriter.java @@ -53,9 +53,13 @@ public class BackwardsStructuredItemRewriter> extends StructuredItemRewriter { private static final int[] EMPTY_INT_ARRAY = new int[0]; + private static final String INJECTED_CMD_MARKER = "VB|injected_cmd"; + + private final String nbtTagName; public BackwardsStructuredItemRewriter(T protocol) { super(protocol); + this.nbtTagName = "VB|" + protocol.getClass().getSimpleName(); } @Override @@ -87,7 +91,7 @@ protected void backupInconvertibleData(final UserConnection connection, final It if (ViaBackwards.getConfig().passOriginalItemNameToResourcePacks() && mappingData.getFullItemMappings() != null) { final String identifier = mappingData.getFullItemMappings().identifier(item.identifier()); - if (identifier != null) { + if (identifier != null && !customTag.contains(INJECTED_CMD_MARKER)) { boolean exists = false; for (final String s : customModelData.strings()) { if (s.equals(identifier)) { @@ -100,6 +104,7 @@ protected void backupInconvertibleData(final UserConnection connection, final It customModelData.floats(), customModelData.booleans(), ArrayUtil.add(customModelData.strings(), identifier), customModelData.colors() )); customTag.putString(nbtTagName("injected_cmd_string"), identifier); + customTag.putBoolean(INJECTED_CMD_MARKER, true); } } } @@ -125,6 +130,7 @@ protected void restoreBackupData(final Item item, final StructuredDataContainer final Tag injectedCmdTag = removeBackupTag(customData, "injected_cmd_string"); if (injectedCmdTag instanceof StringTag stringTag) { + customData.remove(INJECTED_CMD_MARKER); final CustomModelData1_21_4 customModelData = container.get(StructuredDataKey.CUSTOM_MODEL_DATA1_21_4); if (customModelData != null && customModelData.strings() != null) { final String target = stringTag.getValue(); @@ -364,6 +370,6 @@ protected Holder restoreSoundEventHolder(final CompoundTag tag, fina @Override public String nbtTagName() { - return "VB|" + protocol.getClass().getSimpleName(); + return this.nbtTagName; } }