Compare commits

...
Author SHA1 Message Date
Nassim Jahnke ad24236c6b 5.12.2 Release 2026-10-27 15:59:29 +01:00
Nassim Jahnke ab503f497a Update dependencies 2026-10-27 15:56:10 +01:00
Nassim Jahnke 3afc3ee1e1 Add warning for servers running 1.20.5-1.21.4
Effectively unfixable Vanilla protocol exploits make supporting these versions effectively impossible.
2026-10-27 15:56:10 +01:00
Nassim Jahnke cb55edf220 Don't stringify complex packet values in errors
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-OHZWL7WM.
2026-10-27 15:54:07 +01:00
Nassim Jahnke 9f4803b832 Limit serverbound data nesting depth
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-1V7UVEKT.
2026-10-27 15:54:06 +01:00
Nassim Jahnke 88419cdd71 Use arraycopy for primitive array copies
Discovered by Anthropic's OSS Scanner, as ANT-2026-NH253ZCQ.
2026-10-27 15:33:35 +01:00
Nassim Jahnke 2579ac2153 Apply vanilla limits to more serverbound counts
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-GVS9G86J.
2026-10-27 15:33:35 +01:00
Nassim Jahnke 9d2f9f10e7 Don't cache missing paths for unknown versions
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-JJN86714.
2026-10-27 15:33:35 +01:00
Nassim Jahnke 218fc88313 Limit profile key lengths
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-8Z8EOA8N.
2026-10-27 15:33:35 +01:00
Nassim Jahnke 357cb229bc Mask and randomize 1.21.5 item hash keys
Discovered by Anthropic's OSS Scanner, as vulnerabilities ANT-2026-UGG51ZRR and ANT-2026-Z4170OQA.
2026-10-27 15:33:34 +01:00
Nassim Jahnke d89ea4c7be Release packets sent after the channel closed
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-X0V9ES7R.
2026-10-27 15:33:34 +01:00
Nassim Jahnke be08ce39f6 Don't back up nested items in 1.20.3->1.20.5
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-01EM4SCC.
2026-10-27 15:33:34 +01:00
Nassim Jahnke f71f4722e0 Range check original hash ids
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-AECM0IZO.
2026-10-27 10:30:35 +01:00
Nassim Jahnke 62ea10d662 Only convert container items once
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-0RVR8JDH.
2026-10-27 10:30:35 +01:00
Nassim Jahnke ca49a1b4ad Don't presize large arrays
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-0DCBHFG1.
2026-10-27 10:30:35 +01:00
Nassim Jahnke 746740accf Update map types in some data reading
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-HV60WOZ3.
2026-10-27 10:30:35 +01:00
Nassim Jahnke b90f379538 Deactivate on unknown handshake intent
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-RJ1MXTAQ.
2026-10-27 10:30:35 +01:00
Nassim Jahnke 8b9a78aa02 Avoid exceptions in UUID parsing
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-65KLCX8F.
2026-10-27 10:30:35 +01:00
Nassim Jahnke 3487e8ce5f Add sanity checks for config state switching
Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-891675B2.
2026-10-27 08:24:44 +01:00
Florian Reuth 73dc869595 Remap trim item sprites to the items atlas in 1.21.9->1.21.11 (#5122) 2026-10-23 10:48:16 +02:00
Nassim Jahnke 66c064ad7d Update deps, move from minotaur to mod-publish-plugin
Minotaur is barely maintained, and mod-publish-plugin lets us remove the annoying version handling
2026-10-21 13:36:23 +02:00
Nassim Jahnke 8d56c71974 Handle hidden_components in show item hovers
Fixes #5128
2026-10-19 20:31:20 +02:00
Nassim Jahnke 28d16ec7d3 Init lastWarning correctly 2026-10-19 20:31:14 +02:00
Florian Reuth fc4b13f0a1 Fix up checks in 1.12.2->1.13 wall connection handling (#5124) 2026-10-19 14:02:26 +02:00
Nassim Jahnke b712c0055d Add some missing sound rewriting 2026-10-18 22:07:58 +02:00
mfishma 08776de832 [ci skip] Update README (#5118) 2026-10-12 18:40:57 +02:00
Florian Reuth 77b7054629 Remove duplicated items tag in 1.20.3->1.20.5 (#5121) 2026-10-12 18:40:14 +02:00
Nassim Jahnke 63769b3661 Fix continuous block breaking sounds
Fixes #5120
2026-10-11 20:08:24 +02:00
mfishma 12d75daf04 Fix item_name restoration when added_custom_name is present (#5109) 2026-10-11 09:48:29 +02:00
Florian Reuth 413268eb15 Delete empty tag in 1.12.2->1.13 (#5115) 2026-10-11 09:41:02 +02:00
Florian Reuth b27e2a5514 Handle existing entity tag properly in 1.12.2->1.13 (#5114) 2026-10-11 09:40:49 +02:00
Florian Reuth c59dfb4d93 Fix NPE if color is missing in patterns in 1.12.2->1.13 (#5113) 2026-10-11 09:40:31 +02:00
Florian Reuth f66cbd6941 Remove empty tags in 1.8->1.9 (#5112) 2026-10-11 09:40:16 +02:00
mfishma 1574f343f7 Downgrade new 26.3 map decorations to target_x (#5110) 2026-10-11 09:39:41 +02:00
Nassim Jahnke 022fb96f83 Handle visible items in block entities
Fixes #1351
2026-10-09 16:43:07 +02:00
Nassim Jahnke 7cbc751fea Bump version to 5.12.2-SNAPSHOT 2026-10-09 16:42:46 +02:00
Nassim Jahnke 366f08d951 Separate Fabric platform on modrinth
... or it'll fail when specifying versions below 1.14
2026-10-08 16:05:13 +02:00
Nassim Jahnke d628f09334 5.12.1 Release 2026-10-08 15:41:05 +02:00
Nassim JahnkeandWhiteProject1 6fecd58773 Check full type length if possible
Closes #5104

Co-authored-by: WhiteProject1 <Odunyasi167@gmail.com>
2026-10-08 15:38:29 +02:00
冰砚炽andNassim Jahnke da7619c82d Fix custom enchantment value_check predicate is not rewritten for 26.3 clients (#5101)
Co-authored-by: Nassim Jahnke <nassim@njahnke.dev>
2026-10-08 15:12:20 +02:00
Nassim Jahnke b83268efa0 Update deps 2026-10-08 11:02:33 +02:00
Florian Reuth 309c3e57c1 Remove map number tag again in 1.16.4->1.17 (#5099) 2026-10-04 08:55:53 +02:00
Florian Reuth 34a445efcd Copy mutable item before updating in 1.20.3->1.20.5 (#5102) 2026-10-04 08:54:12 +02:00
Florian Reuth 3a949d70eb Don't error on invalid filtered pages in 1.20.2->1.20.3 (#5098) 2026-10-02 14:55:26 +02:00
Rocco 99bfa389b4 Fix backwards conversion of new 1.20.5 trim patterns (#5078) 2026-10-01 19:07:09 +02:00
Florian Reuth 327d4739f5 Fix HIDE_ADDITIONAL_TOOLTIP disappearing in 1.21.4->1.21.5 (#5092) 2026-10-01 18:43:08 +02:00
Florian Reuth 3a7a8e06a8 Backup & restore map color in 26.2->26.3 (#5096) 2026-10-01 15:06:47 +02:00
Florian Reuth 85db19200f Upgrade empty swing animation as well in 26.2->26.3 (#5097) 2026-10-01 15:03:49 +02:00
Florian Reuth 226609b864 Don't lose bypassed_by tag key in 1.21.11->26.1 (#5095) 2026-09-30 20:56:50 +02:00
Florian Reuth 7049f7eaf1 Fix bees not spawning after creative mode in 1.21.7->1.21.9 (#5094) 2026-09-30 20:55:49 +02:00
Florian Reuth a062d0905b Don't use random unique id for attribute conversion in 1.20.5->1.21 (#5093) 2026-09-30 08:43:07 +02:00
Florian Reuth 7ac58c104b Fix Consumable animation hashing between 1.21.5 and 1.21.11 (#5087) 2026-09-29 18:37:42 +02:00
Florian Reuth 7a6e838eb1 Inline damage type for 1.21.11 (#5090) 2026-09-29 18:37:24 +02:00
Florian Reuth 57bc53346c Fix sulfur_cube_content network type (#5091) 2026-09-29 16:03:05 +02:00
terbin 3df7c76c8c Add statistic mappings for 1.16.2->1.17 (#5089) 2026-09-29 14:46:34 +02:00
Florian Reuth 3f35005051 Fix default-count item template hashing (#5088) 2026-09-29 14:17:24 +02:00
Florian Reuth f9d21e073b Fix conditional hashing of a bunch of item data components (#5082) 2026-09-29 14:12:08 +02:00
Florian Reuth e813796d9a Handle remaining components in registry data (#5077) 2026-09-28 19:31:54 +02:00
Florian Reuth 271b6e7ff7 Fix splash potion sounds not playing in 26.2->26.3 (#5076) 2026-09-28 19:30:57 +02:00
LeonTG 7bf4a30075 Fix ender dragon having the wrong dragon phase in 1.8->1.9 (#5070) 2026-09-28 08:55:09 +02:00
PolangandFlorian Reuth 414e978829 Skip removal markers in 1.20.5+ item cost type (#5073)
Co-authored-by: Florian Reuth <git@florianreuth.de>
2026-09-28 08:45:15 +02:00
Nassim Jahnke 637a81e569 Handle dripstone particles dimension attribute registry data
Fixes #5074
2026-09-27 14:21:22 +02:00
Florian Reuth b4023127f6 Force set health back >0 in 1.19.1->1.19.3 (#5061) 2026-09-20 14:54:56 +02:00
Nassim Jahnke 46a61058dd Add missing tags across protocols, reduce stored tags size 2026-09-20 13:06:35 +02:00
Nassim Jahnke 66b90a5b2a Add missing tag for newly placed solid blocks letting through rain
Fixes #5060
2026-09-20 11:59:58 +02:00
Florian Reuth 8f03d6bcef Cancel axe stripping for pre 1.13, shovel dirth paths for pre 1.9 (#5057) 2026-09-20 09:44:19 +02:00
122 changed files with 1301 additions and 438 deletions

No files matched your search

+3 -3
View File
@@ -15,12 +15,12 @@ jobs:
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6.3.0
uses: gradle/actions/setup-gradle@v6.4.0
with:
cache-disabled: false
cache-provider: basic
- name: Set up JDK 17
uses: actions/setup-java@v5.7.0
uses: actions/setup-java@v6.1.0
with:
distribution: 'temurin'
java-version: 17
@@ -30,7 +30,7 @@ jobs:
- name: Upload Artifacts to GitHub
# Only upload on the preview repository as there is no CI job for it.
if: ${{ github.repository == 'ViaVersion/ViaVersionDev' && github.ref_name == 'preview' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: Artifacts
path: build/libs/
+3 -3
View File
@@ -20,12 +20,12 @@ jobs:
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6.3.0
uses: gradle/actions/setup-gradle@v6.4.0
with:
cache-disabled: false
cache-provider: basic
- name: Set up JDK 17
uses: actions/setup-java@v5.7.0
uses: actions/setup-java@v6.1.0
with:
distribution: 'temurin'
java-version: 17
@@ -40,5 +40,5 @@ jobs:
- name: Publish to Modrinth
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
run: ./gradlew modrinth --stacktrace
run: ./gradlew publishMods --stacktrace
continue-on-error: true
+13 -8
View File
@@ -14,18 +14,23 @@ or as [a standalone proxy](https://github.com/ViaVersion/ViaProxy) to join from
any server version from the past decade. **See [HERE](https://viaversion.com) for an overview of the different Via\*
projects.**
Note that ViaVersion will be able to **run best on either Paper servers or
through [ViaFabricPlus](https://github.com/ViaVersion/ViaFabricPlus?tab=readme-ov-file#viafabricplus)** due to having
direct access to client/server state and more extensive API.
**Note:** While ViaVersion works reliably across all listed platforms, it has the highest performance and state accuracy on **Paper** servers and through **[ViaFabricPlus](https://github.com/ViaVersion/ViaFabricPlus)** on clients, since both offer more extensive APIs and direct access to client/server state.
Supported Versions:
### Supported Versions
![Table (https://ci.viaversion.com/userContent/images/T.png)](https://ci.viaversion.com/userContent/images/T.png)
| Players joining with… | Install |
| :--- | :--- |
| **Newer** versions than the server | ViaVersion |
| **Older** versions than the server, down to 1.9 | ViaVersion + [ViaBackwards](https://github.com/ViaVersion/ViaBackwards) |
| **1.7.x – 1.8.x** | ViaVersion + [ViaBackwards](https://github.com/ViaVersion/ViaBackwards) + [ViaRewind](https://github.com/ViaVersion/ViaRewind) |
**User Docs:** https://docs.viaversion.com
If you're stuck, try [this tool](https://viaversion.com/setup) to work out what plugins you need.
**User Docs:** [docs.viaversion.com](https://docs.viaversion.com)
Snapshot Support
----------
Snapshot support
--------
**ViaVersion will only be released a few days *after* a Minecraft update** unless the protocol changes of the update
were trivial. If you want early-access, usually days or even weeks before the final release, you can subscribe to
either:
@@ -500,6 +500,13 @@ public interface ViaVersionConfig extends Config {
*/
boolean handleInvalidItemCount();
/**
* Backs up the original data of nested items (e.g. in containers or bundles) for 1.20.5+ clients on 1.20.4 and older servers.
*
* @return true if enabled
*/
boolean backupNestedItems();
/**
* Tries to cancel block break/place sounds sent by 1.8 servers to 1.9+ clients to prevent them from playing twice
*
@@ -26,6 +26,8 @@ import com.viaversion.nbt.tag.ByteArrayTag;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.function.IntConsumer;
@@ -49,6 +51,12 @@ public final class IdRanges {
return ids;
}
public static IntList decodeAsList(final ByteArrayTag rangesTag) {
final IntList ids = new IntArrayList();
forEachId(rangesTag, ids::add);
return ids;
}
public static void forEachId(final ByteArrayTag rangesTag, final IntConsumer consumer) {
final ByteBuf buf = Unpooled.wrappedBuffer(rangesTag.getValue());
int prevEnd = 0;
@@ -22,6 +22,7 @@
*/
package com.viaversion.viaversion.api.data;
import com.viaversion.nbt.tag.ByteArrayTag;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.IntArrayTag;
import com.viaversion.nbt.tag.ListTag;
@@ -198,8 +199,14 @@ public class MappingDataBase implements MappingData {
final List<TagData> tagsList = new ArrayList<>(tags.size());
for (final Map.Entry<String, Tag> entry : tag.entrySet()) {
final IntArrayTag entries = (IntArrayTag) entry.getValue();
tagsList.add(new TagData(entry.getKey(), entries.getValue()));
final int[] entries;
if (entry.getValue() instanceof final ByteArrayTag rangesTag) {
entries = IdRanges.decodeAsList(rangesTag).toIntArray();
} else {
// Old format / if the values need to be sorted
entries = ((IntArrayTag) entry.getValue()).getValue();
}
tagsList.add(new TagData(entry.getKey(), entries));
}
this.tags.put(type, tagsList);
@@ -79,8 +79,8 @@ public record PaintingVariant(int width, int height, String assetId, @Nullable T
.write("width", Types.INT, value.width())
.write("height", Types.INT, value.height())
.write("asset_id", Types.IDENTIFIER, Key.of(value.assetId()))
.writeOptional("title", Types.TRUSTED_TAG, value.title())
.writeOptional("author", Types.TRUSTED_TAG, value.author()));
.writeOptional("title", Types.TEXT_COMPONENT_TAG, value.title())
.writeOptional("author", Types.TEXT_COMPONENT_TAG, value.author()));
}
@Override
@@ -39,4 +39,11 @@ public record SoundEvent(String identifier, @Nullable Float fixedRange) {
}
return holder.updateId(soundRewriteFunction, () -> UNKNOWN);
}
public static @Nullable Holder<SoundEvent> rewriteOptionalHolder(@Nullable final Holder<SoundEvent> holder, final Int2IntFunction soundRewriteFunction) {
if (holder == null) {
return null;
}
return holder.updateId(soundRewriteFunction, () -> null);
}
}
@@ -30,7 +30,6 @@ import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.PaintingVariant;
import com.viaversion.viaversion.api.minecraft.RegistryKey;
import com.viaversion.viaversion.api.minecraft.ResolvableProfile;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_20_5;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_11;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys1_21_2;
@@ -48,6 +47,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.Bee;
import com.viaversion.viaversion.api.minecraft.item.data.BlockEntityData;
import com.viaversion.viaversion.api.minecraft.item.data.BlockStateProperties;
import com.viaversion.viaversion.api.minecraft.item.data.BlocksAttacks;
import com.viaversion.viaversion.api.minecraft.item.data.BreakSound;
import com.viaversion.viaversion.api.minecraft.item.data.BrewingFuel;
import com.viaversion.viaversion.api.minecraft.item.data.Compostable;
import com.viaversion.viaversion.api.minecraft.item.data.consumable.Consumable1_21_2;
@@ -55,7 +55,6 @@ import com.viaversion.viaversion.api.minecraft.item.data.CookingFuel;
import com.viaversion.viaversion.api.minecraft.item.data.CustomModelData1_21_4;
import com.viaversion.viaversion.api.minecraft.item.data.DamageResistant1_21_2;
import com.viaversion.viaversion.api.minecraft.item.data.DamageResistant26_1;
import com.viaversion.viaversion.api.minecraft.item.data.DamageType;
import com.viaversion.viaversion.api.minecraft.item.data.DeathProtection;
import com.viaversion.viaversion.api.minecraft.item.data.DebugStickState;
import com.viaversion.viaversion.api.minecraft.item.data.DyedColor;
@@ -125,7 +124,7 @@ public record StructuredDataKey<T>(String identifier, Type<T> type) {
public static final StructuredDataKey<Unit> UNBREAKABLE1_21_5 = new StructuredDataKey<>("unbreakable", Types.EMPTY);
public static final StructuredDataKey<Tag> CUSTOM_NAME = new StructuredDataKey<>("custom_name", Types.TEXT_COMPONENT_TAG);
public static final StructuredDataKey<Float> MINIMUM_ATTACK_CHARGE = new StructuredDataKey<>("minimum_attack_charge", Types.FLOAT);
public static final StructuredDataKey<DamageType> DAMAGE_TYPE1_21_11 = new StructuredDataKey<>("damage_type", DamageType.TYPE1_21_11);
public static final StructuredDataKey<Either<Integer, String>> DAMAGE_TYPE1_21_11 = new StructuredDataKey<>("damage_type", new SynchronizedRegistryEitherType(RegistryKey.of("damage_type")));
public static final StructuredDataKey<Integer> DAMAGE_TYPE26_1 = new StructuredDataKey<>("damage_type", new SynchronizedRegistryValueType(RegistryKey.of("damage_type")));
public static final StructuredDataKey<Tag> ITEM_NAME = new StructuredDataKey<>("item_name", Types.TEXT_COMPONENT_TAG);
public static final StructuredDataKey<ItemModel> ITEM_MODEL = new StructuredDataKey<>("item_model", ItemModel.TYPE);
@@ -153,6 +152,8 @@ public record StructuredDataKey<T>(String identifier, Type<T> type) {
public static final StructuredDataKey<FoodProperties1_20_5> FOOD1_21 = new StructuredDataKey<>("food", FoodProperties1_20_5.TYPE1_21);
public static final StructuredDataKey<FoodProperties1_21_2> FOOD1_21_2 = new StructuredDataKey<>("food", FoodProperties1_21_2.TYPE);
public static final StructuredDataKey<Consumable1_21_2> CONSUMABLE1_21_2 = new StructuredDataKey<>("consumable", Consumable1_21_2.TYPE1_21_2);
// 1.21.5-1.21.9 only, animation id 5 was still named spear
public static final StructuredDataKey<Consumable1_21_2> CONSUMABLE1_21_5 = new StructuredDataKey<>("consumable", Consumable1_21_2.TYPE1_21_5);
public static final StructuredDataKey<Consumable1_21_2> CONSUMABLE26_3 = new StructuredDataKey<>("consumable", Consumable1_21_2.TYPE26_3);
public static final StructuredDataKey<UseCooldown> USE_COOLDOWN = new StructuredDataKey<>("use_cooldown", UseCooldown.TYPE);
public static final StructuredDataKey<Unit> FIRE_RESISTANT = new StructuredDataKey<>("fire_resistant", Types.EMPTY);
@@ -238,7 +239,7 @@ public record StructuredDataKey<T>(String identifier, Type<T> type) {
public static final StructuredDataKey<Tag> LOCK1_20_5 = new StructuredDataKey<>("lock", Types.TAG);
public static final StructuredDataKey<LockCode> LOCK1_21_2 = new StructuredDataKey<>("lock", LockCode.TYPE);
public static final StructuredDataKey<CompoundTag> CONTAINER_LOOT = new StructuredDataKey<>("container_loot", Types.COMPOUND_TAG);
public static final StructuredDataKey<Holder<SoundEvent>> BREAK_SOUND = new StructuredDataKey<>("break_sound", Types.SOUND_EVENT);
public static final StructuredDataKey<BreakSound> BREAK_SOUND = new StructuredDataKey<>("break_sound", BreakSound.TYPE);
public static final StructuredDataKey<Compostable> COMPOSTABLE = new StructuredDataKey<>("compostable", Compostable.TYPE);
public static final StructuredDataKey<CookingFuel> COOKING_FUEL = new StructuredDataKey<>("cooking_fuel", CookingFuel.TYPE);
public static final StructuredDataKey<BrewingFuel> BREWING_FUEL = new StructuredDataKey<>("brewing_fuel", BrewingFuel.TYPE);
@@ -33,7 +33,8 @@ public class StructuredDataKeys1_20_5 extends VersionedStructuredDataKeys {
public final StructuredDataKey<Item[]> bundleContents;
public StructuredDataKeys1_20_5(final VersionedTypesHolder types) {
this.container = add("container", types.itemArray());
// Also includes later vanilla limits
this.container = add("container", types.itemArray().withMaxLength(256));
this.chargedProjectiles = add("charged_projectiles", types.itemArray().withMaxLength(1024));
this.bundleContents = add("bundle_contents", types.itemArray().withMaxLength(1024));
}
@@ -32,6 +32,6 @@ public class StructuredDataKeys26_2 extends StructuredDataKeys1_21_11 {
public StructuredDataKeys26_2(final VersionedTypesHolder types) {
super(types);
this.sulfurCubeContent = add("sulfur_cube_content", types.item());
this.sulfurCubeContent = add("sulfur_cube_content", types.itemTemplate());
}
}
@@ -27,7 +27,7 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.Copyable;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
public record BlockStateProperties(Map<String, String> properties) implements Copyable {
@@ -36,7 +36,7 @@ public record BlockStateProperties(Map<String, String> properties) implements Co
@Override
public BlockStateProperties read(final ByteBuf buffer) {
final int size = Types.VAR_INT.readPrimitive(buffer);
final Map<String, String> properties = new Object2ObjectOpenHashMap<>();
final Map<String, String> properties = new HashMap<>();
for (int i = 0; i < size; i++) {
properties.put(Types.STRING.read(buffer), Types.STRING.read(buffer));
}
@@ -64,6 +64,6 @@ public record BlockStateProperties(Map<String, String> properties) implements Co
@Override
public BlockStateProperties copy() {
return new BlockStateProperties(new Object2ObjectOpenHashMap<>(properties));
return new BlockStateProperties(new HashMap<>(properties));
}
}
@@ -1,126 +0,0 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2026 ViaVersion and contributors
*
* 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.
*/
package com.viaversion.viaversion.api.minecraft.item.data;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData.MappingType;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.ArrayType;
import com.viaversion.viaversion.util.Copyable;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
// TODO move block state provider rewriting to registry data rewriter
public record BlockTransformData(CompoundTag blockStateProvider, Holder<SoundEvent> sound, int transformParticle,
int[] disallowedFaces, @Nullable String loot, int dropStrategy,
boolean updateFromNeighbors, int transformType,
boolean consumeOnUse, int itemDamagePerUse) implements Rewritable, Copyable {
public static final Type<BlockTransformData> TYPE = new Type<>(BlockTransformData.class) {
@Override
public BlockTransformData read(final ByteBuf buffer) {
final CompoundTag blockStateProvider = Types.COMPOUND_TAG.read(buffer);
final Holder<SoundEvent> sound = Types.SOUND_EVENT.read(buffer);
final int transformParticle = Types.VAR_INT.readPrimitive(buffer);
final int[] disallowedFaces = Types.VAR_INT_ARRAY_PRIMITIVE.read(buffer);
final String loot = Types.OPTIONAL_STRING.read(buffer);
final int dropStrategy = Types.VAR_INT.readPrimitive(buffer);
final boolean updateFromNeighbors = Types.BOOLEAN.read(buffer);
final int transformType = Types.VAR_INT.readPrimitive(buffer);
final boolean consumeOnUse = Types.BOOLEAN.read(buffer);
final int itemDamagePerUse = Types.VAR_INT.readPrimitive(buffer);
return new BlockTransformData(blockStateProvider, sound, transformParticle, disallowedFaces, loot, dropStrategy, updateFromNeighbors, transformType, consumeOnUse, itemDamagePerUse);
}
@Override
public void write(final ByteBuf buffer, final BlockTransformData value) {
Types.COMPOUND_TAG.write(buffer, value.blockStateProvider);
Types.SOUND_EVENT.write(buffer, value.sound);
Types.VAR_INT.writePrimitive(buffer, value.transformParticle);
Types.VAR_INT_ARRAY_PRIMITIVE.write(buffer, value.disallowedFaces);
Types.OPTIONAL_STRING.write(buffer, value.loot);
Types.VAR_INT.writePrimitive(buffer, value.dropStrategy);
Types.BOOLEAN.write(buffer, value.updateFromNeighbors);
Types.VAR_INT.writePrimitive(buffer, value.transformType);
Types.BOOLEAN.write(buffer, value.consumeOnUse);
Types.VAR_INT.writePrimitive(buffer, value.itemDamagePerUse);
}
@Override
public void write(final Ops ops, final BlockTransformData data) {
final Holder<SoundEvent> defaultSound = Holder.of(ops.context().registryAccess().id(MappingType.SOUND, "intentionally_empty"));
ops.writeMap(map -> map
.write("block_state_provider", Types.COMPOUND_TAG, data.blockStateProvider)
.writeOptional("sound", Types.SOUND_EVENT, data.sound, defaultSound)
.writeOptional("particle", EnumTypes.TRANSFORM_PARTICLE, data.transformParticle, 0)
.writeOptional("disallowed_faces", EnumTypes.DIRECTION.new EnumArrayType(), data.disallowedFaces, new int[0])
.writeOptional("loot", Types.STRING, data.loot)
.writeOptional("drop_strategy", Types.VAR_INT, data.dropStrategy, 1)
.writeOptional("update_from_neighbors", Types.BOOLEAN, data.updateFromNeighbors, true)
.writeOptional("transform_type", Types.VAR_INT, data.transformType, 0)
.writeOptional("consume_on_use", Types.BOOLEAN, data.consumeOnUse, true)
.writeOptional("item_damage_per_use", Types.VAR_INT, data.itemDamagePerUse, 0)
);
}
};
public static final ArrayType<BlockTransformData> ARRAY_TYPE = new ArrayType<>(TYPE);
@Override
public BlockTransformData rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
CompoundTag updatedTag = blockStateProvider().copy();
if (protocol.getRegistryDataRewriter().updateBlockStateProvider(updatedTag)) {
// Back it up clientbound so server data doesn't disappear; remove it for newly spawned items serverbound.
// Given it's not even encoded properly over the network and would otherwise require keeping full block state mappings
// and is not needed on the client, we only do the bare minimum to guarantee compatibility.
if (clientbound) {
updatedTag.put("backup", this.blockStateProvider);
} else {
final CompoundTag backupTag = updatedTag.getCompoundTag("backup");
updatedTag = backupTag != null ? backupTag : dummyBlockStateProvider();
}
}
return new BlockTransformData(updatedTag, sound, transformParticle, disallowedFaces, loot, dropStrategy, updateFromNeighbors, transformType, consumeOnUse, itemDamagePerUse);
}
private CompoundTag dummyBlockStateProvider() {
final CompoundTag state = new CompoundTag();
state.putString("Name", "dirt");
final CompoundTag tag = new CompoundTag();
tag.putString("type", "simple");
tag.put("state", state);
return tag;
}
@Override
public BlockTransformData copy() {
return new BlockTransformData(blockStateProvider.copy(), sound, transformParticle, disallowedFaces, loot, dropStrategy, updateFromNeighbors, transformType, consumeOnUse, itemDamagePerUse);
}
}
@@ -130,8 +130,8 @@ public record BlocksAttacks(
@Override
public BlocksAttacks rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
final Holder<SoundEvent> blockSound = SoundEvent.rewriteHolder(this.blockSound, Rewritable.soundRewriteFunction(protocol, clientbound));
final Holder<SoundEvent> disableSound = SoundEvent.rewriteHolder(this.disableSound, Rewritable.soundRewriteFunction(protocol, clientbound));
final Holder<SoundEvent> blockSound = SoundEvent.rewriteOptionalHolder(this.blockSound, Rewritable.soundRewriteFunction(protocol, clientbound));
final Holder<SoundEvent> disableSound = SoundEvent.rewriteOptionalHolder(this.disableSound, Rewritable.soundRewriteFunction(protocol, clientbound));
return new BlocksAttacks(this.blockDelaySeconds, this.disableCooldownScale, this.damageReductions, this.itemDamage, this.bypassedBy, blockSound, disableSound);
}
@@ -22,18 +22,22 @@
*/
package com.viaversion.viaversion.api.minecraft.item.data;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.TransformingType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.EitherType;
import com.viaversion.viaversion.util.Either;
import com.viaversion.viaversion.util.Rewritable;
/**
* 1.21.11-only damage type.
*
* @param id registry int or string id
*/
public record DamageType(Either<Integer, String> id) {
public record BreakSound(Holder<SoundEvent> sound) implements Rewritable {
public static final Type<DamageType> TYPE1_21_11 = TransformingType.of(new EitherType<>(Types.VAR_INT, Types.STRING), DamageType.class, DamageType::new, DamageType::id);
public static final Type<BreakSound> TYPE = TransformingType.of(Types.SOUND_EVENT, BreakSound.class, BreakSound::new, BreakSound::sound);
@Override
public BreakSound rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
final Holder<SoundEvent> sound = SoundEvent.rewriteHolder(this.sound, Rewritable.soundRewriteFunction(protocol, clientbound));
return this.sound == sound ? this : new BreakSound(sound);
}
}
@@ -22,27 +22,36 @@
*/
package com.viaversion.viaversion.api.minecraft.item.data;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.minecraft.item.data.consumable.ConsumeEffect;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.TransformingType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.util.Copyable;
import com.viaversion.viaversion.util.Rewritable;
public record DeathProtection(ConsumeEffect<?>[] deathEffects) implements Copyable {
public record DeathProtection(ConsumeEffect<?>[] deathEffects) implements Copyable, Rewritable {
public static final Type<DeathProtection> TYPE1_21_2 = new TransformingType<>(ConsumeEffect.ARRAY_TYPE1_21_2, DeathProtection.class, DeathProtection::new, DeathProtection::deathEffects) {
@Override
public void write(final Ops ops, final DeathProtection value) {
ops.writeMap(map -> map.write("death_effects", ConsumeEffect.ARRAY_TYPE1_21_2, value.deathEffects));
ops.writeMap(map -> map.writeOptional("death_effects", ConsumeEffect.ARRAY_TYPE1_21_2, value.deathEffects, new ConsumeEffect<?>[0]));
}
};
public static final Type<DeathProtection> TYPE26_3 = new TransformingType<>(ConsumeEffect.ARRAY_TYPE26_3, DeathProtection.class, DeathProtection::new, DeathProtection::deathEffects) {
@Override
public void write(final Ops ops, final DeathProtection value) {
ops.writeMap(map -> map.write("death_effects", ConsumeEffect.ARRAY_TYPE26_3, value.deathEffects));
ops.writeMap(map -> map.writeOptional("death_effects", ConsumeEffect.ARRAY_TYPE26_3, value.deathEffects, new ConsumeEffect<?>[0]));
}
};
@Override
public DeathProtection rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
final ConsumeEffect<?>[] deathEffects = ConsumeEffect.rewrite(this.deathEffects, connection, protocol, clientbound);
return deathEffects == this.deathEffects ? this : new DeathProtection(deathEffects);
}
@Override
public DeathProtection copy() {
return new DeathProtection(Copyable.copy(deathEffects));
@@ -27,6 +27,7 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.Copyable;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.Limit;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -41,7 +42,7 @@ public record Enchantments(Int2IntMap enchantments, boolean showInTooltip) imple
@Override
public Enchantments read(final ByteBuf buffer) {
final Int2IntMap enchantments = new Int2IntOpenHashMap();
final int size = Types.VAR_INT.readPrimitive(buffer);
final int size = Limit.max(Types.VAR_INT.readPrimitive(buffer), 256);
for (int i = 0; i < size; i++) {
final int id = Types.VAR_INT.readPrimitive(buffer);
final int level = Types.VAR_INT.readPrimitive(buffer);
@@ -65,7 +66,7 @@ public record Enchantments(Int2IntMap enchantments, boolean showInTooltip) imple
@Override
public Enchantments read(final ByteBuf buffer) {
final Int2IntMap enchantments = new Int2IntOpenHashMap();
final int size = Types.VAR_INT.readPrimitive(buffer);
final int size = Limit.max(Types.VAR_INT.readPrimitive(buffer), 256);
for (int i = 0; i < size; i++) {
final int id = Types.VAR_INT.readPrimitive(buffer);
final int level = Types.VAR_INT.readPrimitive(buffer);
@@ -47,11 +47,8 @@ public final class EnumTypes {
public static final EnumType AXOLOTL_VARIANT = new EnumType("lucy", "wild", "gold", "cyan", "blue");
public static final EnumType EQUIPMENT_SLOT = new EnumType("mainhand", "feet", "legs", "chest", "head", "offhand", "body", "saddle");
public static final EnumType SWING_ANIMATION1_21_11 = new EnumType("none", "whack", "stab");
public static final EnumType ITEM_USE_ANIMATION1_21_5 = new EnumType("none", "eat", "drink", "block", "bow", "spear", "crossbow", "spyglass", "toot_horn", "brush", "bundle");
public static final EnumType ITEM_USE_ANIMATION = new EnumType("none", "eat", "drink", "block", "bow", "trident", "crossbow", "spyglass", "toot_horn", "brush", "bundle", "spear");
public static final EnumType DIRECTION = new EnumType("down", "up", "north", "south", "west", "east");
public static final EnumType TRANSFORM_PARTICLE = new EnumType("none", "scrape", "wax_on", "wax_off");
public static final EnumType TRAMNSFORM_DROP_STRATEGY = new EnumType("clicked_face", "from_middle");
public static final EnumType TRAMNSFORM_TYPE = new EnumType("single_block", "copper_chest");
// Enums with non-ordinal ids
public static final FakeEnumType RABBIT_VARIANT = new FakeEnumType(List.of("brown", "white", "black", "white_splotched", "gold", "salt"), of(99, "evil"));
// Hardcoded registries
@@ -34,7 +34,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final class FilterableComponent extends Filterable<Tag> implements Copyable, Rewritable {
public static final Type<FilterableComponent> TYPE = new FilterableType<>(Types.TAG, Types.OPTIONAL_TAG, FilterableComponent.class) {
public static final Type<FilterableComponent> TYPE = new FilterableType<>(Types.TEXT_COMPONENT_TAG, Types.OPTIONAL_TAG, FilterableComponent.class) {
@Override
protected FilterableComponent create(final Tag raw, final Tag filtered) {
return new FilterableComponent(raw, filtered);
@@ -67,7 +67,7 @@ public record Instrument1_21_2(Holder<SoundEvent> soundEvent, float useDuration,
.write("sound_event", Types.SOUND_EVENT, value.soundEvent())
.write("use_duration", Types.FLOAT, value.useDuration())
.write("range", Types.FLOAT, value.range())
.write("description", Types.TAG, value.description()));
.write("description", Types.TEXT_COMPONENT_TAG, value.description()));
}
@Override
@@ -103,8 +103,8 @@ public record Instrument1_21_2(Holder<SoundEvent> soundEvent, float useDuration,
.write("sound_event", Types.SOUND_EVENT, value.soundEvent())
.write("use_duration", Types.FLOAT, value.useDuration())
.write("range", Types.FLOAT, value.range())
.write("durability_damage", Types.VAR_INT, value.durabilityDamage())
.write("description", Types.TAG, value.description()));
.writeOptional("durability_damage", Types.VAR_INT, value.durabilityDamage(), 0)
.write("description", Types.TEXT_COMPONENT_TAG, value.description()));
}
@Override
@@ -22,19 +22,22 @@
*/
package com.viaversion.viaversion.api.minecraft.item.data;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.OptionalType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public record KineticWeapon(int contactCooldownTicks, int delayTicks, @Nullable Condition dismountConditions,
@Nullable Condition knockbackConditions, @Nullable Condition damageConditions,
float forwardMovement, float damageMultiplier,
@Nullable Holder<SoundEvent> sound, @Nullable Holder<SoundEvent> hitSound) {
@Nullable Holder<SoundEvent> sound, @Nullable Holder<SoundEvent> hitSound) implements Rewritable {
public static final Type<KineticWeapon> TYPE = new Type<>(KineticWeapon.class) {
@Override
@@ -80,6 +83,13 @@ public record KineticWeapon(int contactCooldownTicks, int delayTicks, @Nullable
}
};
@Override
public KineticWeapon rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
final Holder<SoundEvent> sound = SoundEvent.rewriteOptionalHolder(this.sound, Rewritable.soundRewriteFunction(protocol, clientbound));
final Holder<SoundEvent> hitSound = SoundEvent.rewriteOptionalHolder(this.hitSound, Rewritable.soundRewriteFunction(protocol, clientbound));
return sound == this.sound && hitSound == this.hitSound ? this : new KineticWeapon(contactCooldownTicks, delayTicks, dismountConditions, knockbackConditions, damageConditions, forwardMovement, damageMultiplier, sound, hitSound);
}
public record Condition(int maxDurationTicks, float minSpeed, float minRelativeSpeed) {
public static final Type<Condition> TYPE = new Type<>(Condition.class) {
@@ -22,16 +22,19 @@
*/
package com.viaversion.viaversion.api.minecraft.item.data;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public record PiercingWeapon(boolean dealsKnockback, boolean dismounts,
@Nullable Holder<SoundEvent> sound, @Nullable Holder<SoundEvent> hitSound) {
@Nullable Holder<SoundEvent> sound, @Nullable Holder<SoundEvent> hitSound) implements Rewritable {
public static final Type<PiercingWeapon> TYPE = new Type<>(PiercingWeapon.class) {
@Override
@@ -61,4 +64,11 @@ public record PiercingWeapon(boolean dealsKnockback, boolean dismounts,
);
}
};
@Override
public PiercingWeapon rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
final Holder<SoundEvent> sound = SoundEvent.rewriteOptionalHolder(this.sound, Rewritable.soundRewriteFunction(protocol, clientbound));
final Holder<SoundEvent> hitSound = SoundEvent.rewriteOptionalHolder(this.hitSound, Rewritable.soundRewriteFunction(protocol, clientbound));
return sound == this.sound && hitSound == this.hitSound ? this : new PiercingWeapon(dealsKnockback, dismounts, sound, hitSound);
}
}
@@ -29,16 +29,19 @@ import com.viaversion.viaversion.api.type.TransformingType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.Copyable;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.Rewritable;
public record PotDecorations1_20_5(int[] itemIds) implements Copyable, Rewritable {
private static final Key BRICK = Key.ofPath("brick");
public static final Type<PotDecorations1_20_5> TYPE = new TransformingType<>(Types.VAR_INT_ARRAY_PRIMITIVE, PotDecorations1_20_5.class, PotDecorations1_20_5::new, PotDecorations1_20_5::itemIds) {
@Override
public void write(final Ops ops, final PotDecorations1_20_5 value) {
// Vanilla always encodes all 4 sides, filling missing ones with bricks
ops.writeList(list -> {
for (final int itemId : value.itemIds) {
list.write(Types.IDENTIFIER, ops.context().registryAccess().item(itemId));
for (int i = 0; i < 4; i++) {
list.write(Types.IDENTIFIER, i < value.itemIds.length ? ops.context().registryAccess().item(value.itemIds[i]) : BRICK);
}
});
}
@@ -40,7 +40,7 @@ public record PotionEffectData(int amplifier, int duration, boolean ambient, boo
final boolean ambient = buffer.readBoolean();
final boolean showParticles = buffer.readBoolean();
final boolean showIcon = buffer.readBoolean();
final PotionEffectData hiddenEffect = OPTIONAL_TYPE.read(buffer);
final PotionEffectData hiddenEffect = OPTIONAL_TYPE.readTrackingDepth(buffer);
return new PotionEffectData(amplifier, duration, ambient, showParticles, showIcon, hiddenEffect);
}
@@ -52,10 +52,10 @@ public record SignText(Tag[] messages, Tag @Nullable [] filteredMessages, int co
@Override
public void write(final Ops ops, final SignText value) {
ops.writeMap(map -> map
.write("messages", Types.SIGN_TEXT, value.messages)
.writeOptional("filtered_messages", Types.SIGN_TEXT, value.filteredMessages)
.write("color", Types.VAR_INT, value.color, 15)
.write("has_glowing_text", Types.BOOLEAN, value.hasGlowingText, false));
.write("messages", Types.TEXT_COMPONENT_TAG_ARRAY, value.messages)
.writeOptional("filtered_messages", Types.TEXT_COMPONENT_TAG_ARRAY, value.filteredMessages)
.write("color", EnumTypes.DYE_COLOR, value.color)
.write("has_glowing_text", Types.BOOLEAN, value.hasGlowingText));
}
};
}
@@ -28,6 +28,7 @@ import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
@@ -42,7 +43,7 @@ public record TooltipDisplay(boolean hideTooltip,
public TooltipDisplay read(final ByteBuf buffer) {
final boolean hideTooltip = buffer.readBoolean();
final IntSortedSet hiddenComponents = new IntLinkedOpenHashSet();
final int size = Types.VAR_INT.readPrimitive(buffer);
final int size = Limit.max(Types.VAR_INT.readPrimitive(buffer), 256);
for (int i = 0; i < size; i++) {
hiddenComponents.add(Types.VAR_INT.readPrimitive(buffer));
}
@@ -31,6 +31,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.EnumTypes;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.EnumType;
import com.viaversion.viaversion.util.Copyable;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
@@ -39,16 +40,19 @@ public record Consumable1_21_2(float consumeSeconds, int animationType, Holder<S
boolean hasConsumeParticles,
ConsumeEffect<?>[] consumeEffects) implements Copyable, Rewritable {
public static final Type<Consumable1_21_2> TYPE1_21_2 = new ConsumableType(ConsumeEffect.ARRAY_TYPE1_21_2);
public static final Type<Consumable1_21_2> TYPE26_3 = new ConsumableType(ConsumeEffect.ARRAY_TYPE26_3);
public static final Type<Consumable1_21_2> TYPE1_21_2 = new ConsumableType(ConsumeEffect.ARRAY_TYPE1_21_2, EnumTypes.ITEM_USE_ANIMATION);
public static final Type<Consumable1_21_2> TYPE1_21_5 = new ConsumableType(ConsumeEffect.ARRAY_TYPE1_21_2, EnumTypes.ITEM_USE_ANIMATION1_21_5);
public static final Type<Consumable1_21_2> TYPE26_3 = new ConsumableType(ConsumeEffect.ARRAY_TYPE26_3, EnumTypes.ITEM_USE_ANIMATION);
private static final class ConsumableType extends Type<Consumable1_21_2> {
private final Type<ConsumeEffect<?>[]> consumeEffectType;
private final EnumType animationType;
private ConsumableType(final Type<ConsumeEffect<?>[]> consumeEffectType) {
private ConsumableType(final Type<ConsumeEffect<?>[]> consumeEffectType, final EnumType animationType) {
super(Consumable1_21_2.class);
this.consumeEffectType = consumeEffectType;
this.animationType = animationType;
}
@Override
@@ -75,7 +79,7 @@ public record Consumable1_21_2(float consumeSeconds, int animationType, Holder<S
final Holder<SoundEvent> defaultSound = Holder.of(ops.context().registryAccess().id(MappingData.MappingType.SOUND, "entity.generic.eat"));
ops.writeMap(map -> map
.writeOptional("consume_seconds", Types.FLOAT, value.consumeSeconds, 1.6F)
.writeOptional("animation", EnumTypes.ITEM_USE_ANIMATION, value.animationType, 1)
.writeOptional("animation", animationType, value.animationType, 1)
.writeOptional("sound", Types.SOUND_EVENT, value.sound, defaultSound)
.writeOptional("has_consume_particles", Types.BOOLEAN, value.hasConsumeParticles, true)
.writeOptional("on_consume_effects", consumeEffectType, value.consumeEffects, new ConsumeEffect<?>[0]));
@@ -85,7 +89,9 @@ public record Consumable1_21_2(float consumeSeconds, int animationType, Holder<S
@Override
public Consumable1_21_2 rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
final Holder<SoundEvent> soundHolder = SoundEvent.rewriteHolder(this.sound, Rewritable.soundRewriteFunction(protocol, clientbound));
return soundHolder == this.sound ? this : new Consumable1_21_2(consumeSeconds, animationType, soundHolder, hasConsumeParticles, consumeEffects);
final ConsumeEffect<?>[] consumeEffects = ConsumeEffect.rewrite(this.consumeEffects, connection, protocol, clientbound);
return soundHolder == this.sound && consumeEffects == this.consumeEffects ? this
: new Consumable1_21_2(consumeSeconds, animationType, soundHolder, hasConsumeParticles, consumeEffects);
}
@Override
@@ -22,20 +22,23 @@
*/
package com.viaversion.viaversion.api.minecraft.item.data.consumable;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.HolderSet;
import com.viaversion.viaversion.api.minecraft.SoundEvent;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.minecraft.item.data.EnumTypes;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.ArrayType;
import com.viaversion.viaversion.api.type.types.misc.HolderSetType;
import com.viaversion.viaversion.api.type.types.misc.HolderType;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
public record ConsumeEffect<T>(int id, Type<T> type, T value) {
public record ConsumeEffect<T>(int id, Type<T> type, T value) implements Rewritable {
private static final HolderSetType REMOVE_EFFECTS_TYPE = new HolderSetType() {
@Override
@@ -56,7 +59,7 @@ public record ConsumeEffect<T>(int id, Type<T> type, T value) {
@Override
public void write(final Ops ops, final Float value) {
ops.writeMap(map -> map.write("diameter", Types.FLOAT, value, 16F));
ops.writeMap(map -> map.writeOptional("diameter", Types.FLOAT, value, 16F));
}
};
public static final Type<TeleportRandomlyConsumeEffect> TELEPORT_RANDOMLY_TYPE26_3 = new Type<>(TeleportRandomlyConsumeEffect.class) {
@@ -76,8 +79,8 @@ public record ConsumeEffect<T>(int id, Type<T> type, T value) {
@Override
public void write(final Ops ops, final TeleportRandomlyConsumeEffect value) {
ops.writeMap(map -> map
.write("diameter", Types.FLOAT, value.diameter(), 16F)
.write("directional_particles", Types.BOOLEAN, value.directionalParticles(), true)
.writeOptional("diameter", Types.FLOAT, value.diameter(), 16F)
.writeOptional("directional_particles", Types.BOOLEAN, value.directionalParticles(), true)
);
}
};
@@ -123,6 +126,32 @@ public record ConsumeEffect<T>(int id, Type<T> type, T value) {
return new ConsumeEffect<>(id, type, (T) value);
}
@Override
public ConsumeEffect<T> rewrite(final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
if (this.type != PLAY_SOUND_TYPE) {
return this;
}
//noinspection unchecked
final Holder<SoundEvent> sound = (Holder<SoundEvent>) this.value;
final Holder<SoundEvent> rewrittenSound = SoundEvent.rewriteHolder(sound, Rewritable.soundRewriteFunction(protocol, clientbound));
return rewrittenSound == sound ? this : ConsumeEffect.of(this.id, this.type, rewrittenSound);
}
public static ConsumeEffect<?>[] rewrite(final ConsumeEffect<?>[] effects, final UserConnection connection, final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
ConsumeEffect<?>[] rewrittenEffects = effects;
for (int i = 0; i < effects.length; i++) {
final ConsumeEffect<?> rewrittenEffect = effects[i].rewrite(connection, protocol, clientbound);
if (rewrittenEffect != effects[i]) {
if (rewrittenEffects == effects) {
rewrittenEffects = effects.clone();
}
rewrittenEffects[i] = rewrittenEffect;
}
}
return rewrittenEffects;
}
void writeValue(final ByteBuf buf) {
this.type.write(buf, this.value);
}
@@ -32,7 +32,7 @@ import com.viaversion.viaversion.util.Copyable;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.Rewritable;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import java.util.HashMap;
import java.util.Map;
public record ArmorTrimMaterial1_20_5(String assetName, int itemId, float itemModelIndex,
@@ -57,7 +57,7 @@ public record ArmorTrimMaterial1_20_5(String assetName, int itemId, float itemMo
final float itemModelIndex = buffer.readFloat();
final int overrideArmorMaterialsSize = Types.VAR_INT.readPrimitive(buffer);
final Map<String, String> overrideArmorMaterials = new Object2ObjectArrayMap<>();
final Map<String, String> overrideArmorMaterials = new HashMap<>();
for (int i = 0; i < overrideArmorMaterialsSize; i++) {
final int key = Types.VAR_INT.readPrimitive(buffer);
final String value = Types.STRING.read(buffer);
@@ -91,7 +91,7 @@ public record ArmorTrimMaterial1_20_5(String assetName, int itemId, float itemMo
final float itemModelIndex = buffer.readFloat();
final int overrideArmorMaterialsSize = Types.VAR_INT.readPrimitive(buffer);
final Map<String, String> overrideArmorMaterials = new Object2ObjectArrayMap<>();
final Map<String, String> overrideArmorMaterials = new HashMap<>();
for (int i = 0; i < overrideArmorMaterialsSize; i++) {
final String key = Types.STRING.read(buffer);
final String value = Types.STRING.read(buffer);
@@ -124,7 +124,7 @@ public record ArmorTrimMaterial1_20_5(String assetName, int itemId, float itemMo
final int item = Types.VAR_INT.readPrimitive(buffer);
final int overrideArmorMaterialsSize = Types.VAR_INT.readPrimitive(buffer);
final Map<String, String> overrideArmorMaterials = new Object2ObjectArrayMap<>();
final Map<String, String> overrideArmorMaterials = new HashMap<>();
for (int i = 0; i < overrideArmorMaterialsSize; i++) {
final String key = Types.STRING.read(buffer);
final String value = Types.STRING.read(buffer);
@@ -155,7 +155,7 @@ public record ArmorTrimMaterial1_20_5(String assetName, int itemId, float itemMo
final String assetName = Types.STRING.read(buffer);
final int overrideArmorMaterialsSize = Types.VAR_INT.readPrimitive(buffer);
final Map<String, String> overrideArmorMaterials = new Object2ObjectArrayMap<>();
final Map<String, String> overrideArmorMaterials = new HashMap<>();
for (int i = 0; i < overrideArmorMaterialsSize; i++) {
final String key = Types.STRING.read(buffer);
final String value = Types.STRING.read(buffer);
@@ -207,6 +207,6 @@ public record ArmorTrimMaterial1_20_5(String assetName, int itemId, float itemMo
@Override
public ArmorTrimMaterial1_20_5 copy() {
return new ArmorTrimMaterial1_20_5(assetName, itemId, itemModelIndex, new Object2ObjectArrayMap<>(overrideArmorMaterials), description);
return new ArmorTrimMaterial1_20_5(assetName, itemId, itemModelIndex, new HashMap<>(overrideArmorMaterials), description);
}
}
@@ -165,7 +165,7 @@ public class PacketTracker {
// Warning system
private long warningPeriodStart = System.nanoTime();
private long lastWarning = Long.MIN_VALUE;
private long lastWarning = warningPeriodStart - SECOND_NANOS;
private int warnings;
public RateTracker(int windowSize) {
@@ -23,6 +23,8 @@
package com.viaversion.viaversion.api.type;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.util.NestingLimiter;
import io.netty.buffer.ByteBuf;
/**
* Type for buffer reading and writing.
@@ -63,6 +65,22 @@ public abstract class Type<T> implements ByteBufReader<T>, ByteBufWriter<T>, Cod
return typeName != null && !typeName.isEmpty() ? typeName : this.getClass().getName();
}
/**
* Reads a value and counts it towards the nesting depth limit of the current thread, to be used by recursive types.
*
* @param buffer buffer to read from
* @return read value
* @see NestingLimiter
*/
public T readTrackingDepth(final ByteBuf buffer) {
NestingLimiter.increment();
try {
return read(buffer);
} finally {
NestingLimiter.decrement();
}
}
@Override
public void write(Ops ops, T value) {
throw new UnsupportedOperationException("Write operation not supported for type: " + getTypeName());
@@ -227,6 +227,7 @@ public final class Types {
public static final Type<Tag[]> TAG_ARRAY = new ArrayType<>(TAG);
public static final Type<Tag> OPTIONAL_TAG = TagType.OptionalTagType.type();
public static final Type<Tag> TEXT_COMPONENT_TAG = new TextComponentTagType(); // only strictly needed for hashing
public static final Type<Tag[]> TEXT_COMPONENT_TAG_ARRAY = new ArrayType<>(Types.TEXT_COMPONENT_TAG);
public static final Type<Tag> CUSTOM_CLICK_ACTION_TAG = new LengthPrefixedTagType(65536);
public static final FixedLengthArrayType<Tag> SIGN_TEXT = new FixedLengthArrayType<>(TAG, 4);
@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.List;
public class ArrayType<T> extends Type<T[]> {
private static final int MAX_PRESIZED_LENGTH = 128;
protected final Type<T> elementType;
private final int maxLength;
@@ -63,7 +64,7 @@ public class ArrayType<T> extends Type<T[]> {
throw new IllegalArgumentException("Array length " + amount + " is longer than maximum " + maxLength);
}
return amount < Short.MAX_VALUE ? readArray(buffer, amount) : readList(buffer, amount);
return amount <= MAX_PRESIZED_LENGTH ? readArray(buffer, amount) : readList(buffer, amount);
}
protected T[] readArray(ByteBuf buffer, int length) {
@@ -111,4 +112,14 @@ public class ArrayType<T> extends Type<T[]> {
public ArrayType<T> withMaxLength(final int maxLength) {
return new ArrayType<>(this.getOutputClass(), this.elementType, maxLength);
}
static int checkLength(final int length, final int elementBytes, final int readable) {
if (length < 0) {
throw new IllegalArgumentException("Length " + length + " is negative");
}
if ((long) length * elementBytes > readable) {
throw new IllegalArgumentException("Length " + length + " is larger than readable bytes");
}
return length;
}
}
@@ -57,7 +57,7 @@ public class BooleanArrayType extends Type<boolean[]> {
@Override
public boolean[] read(final ByteBuf buffer) {
final int length = this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length;
Preconditions.checkArgument(buffer.isReadable(length), "Length is fewer than readable bytes");
Preconditions.checkArgument(buffer.isReadable(length), "Length is larger than readable bytes");
final boolean[] array = new boolean[length];
for (int i = 0; i < length; i++) {
array[i] = buffer.readBoolean();
@@ -32,15 +32,30 @@ import io.netty.buffer.ByteBuf;
public class ByteArrayType extends Type<byte[]> {
private final int length;
private final int maxLength;
public ByteArrayType(final int length) {
super(byte[].class);
this.length = length;
this(length, -1);
}
public ByteArrayType() {
this(-1, -1);
}
private ByteArrayType(final int length, final int maxLength) {
super(byte[].class);
this.length = -1;
this.length = length;
this.maxLength = maxLength;
}
/**
* Returns a length-prefixed byte array type that rejects arrays longer than the given maximum.
*
* @param maxLength maximum array length
* @return length-prefixed byte array type with the given maximum length
*/
public static ByteArrayType withMaxLength(final int maxLength) {
return new ByteArrayType(-1, maxLength);
}
@Override
@@ -48,6 +63,7 @@ public class ByteArrayType extends Type<byte[]> {
if (this.length != -1) {
Preconditions.checkArgument(length == object.length, "Length does not match expected length");
} else {
checkMaxLength(object.length);
Types.VAR_INT.writePrimitive(buffer, object.length);
}
buffer.writeBytes(object);
@@ -55,13 +71,20 @@ public class ByteArrayType extends Type<byte[]> {
@Override
public byte[] read(final ByteBuf buffer) {
final int length = this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length;
Preconditions.checkArgument(buffer.isReadable(length), "Length is fewer than readable bytes");
final int length = this.length == -1 ? checkMaxLength(Types.VAR_INT.readPrimitive(buffer)) : this.length;
Preconditions.checkArgument(buffer.isReadable(length), "Length is larger than readable bytes");
final byte[] array = new byte[length];
buffer.readBytes(array);
return array;
}
private int checkMaxLength(final int length) {
if (maxLength != -1 && length > maxLength) {
throw new IllegalArgumentException("Byte array length " + length + " is longer than maximum " + maxLength);
}
return length;
}
@Override
public void write(final Ops ops, final byte[] value) {
ops.writeBytes(value);
@@ -27,6 +27,8 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import static com.viaversion.viaversion.api.type.types.ArrayType.checkLength;
public class FloatArrayType extends Type<float[]> {
private final int length;
@@ -55,8 +57,7 @@ public class FloatArrayType extends Type<float[]> {
@Override
public float[] read(final ByteBuf buffer) {
final int length = this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length;
Preconditions.checkArgument(buffer.isReadable(length), "Length is fewer than readable bytes");
final int length = checkLength(this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length, Float.BYTES, buffer.readableBytes());
final float[] array = new float[length];
for (int i = 0; i < length; i++) {
array[i] = buffer.readFloat();
@@ -28,6 +28,8 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import static com.viaversion.viaversion.api.type.types.ArrayType.checkLength;
public class IntArrayType extends Type<int[]> {
private final int length;
@@ -56,8 +58,7 @@ public class IntArrayType extends Type<int[]> {
@Override
public int[] read(final ByteBuf buffer) {
final int length = this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length;
Preconditions.checkArgument(buffer.isReadable(length), "Length is fewer than readable bytes");
final int length = checkLength(this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length, Integer.BYTES, buffer.readableBytes());
final int[] array = new int[length];
for (int i = 0; i < length; i++) {
array[i] = buffer.readInt();
@@ -28,6 +28,8 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import static com.viaversion.viaversion.api.type.types.ArrayType.checkLength;
public class LongArrayType extends Type<long[]> {
private final int length;
@@ -43,8 +45,7 @@ public class LongArrayType extends Type<long[]> {
@Override
public long[] read(final ByteBuf buffer) {
final int length = this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length;
Preconditions.checkArgument(buffer.isReadable(length), "Length is fewer than readable bytes");
final int length = checkLength(this.length == -1 ? Types.VAR_INT.readPrimitive(buffer) : this.length, Long.BYTES, buffer.readableBytes());
return readFixedLength(buffer, length);
}
@@ -34,6 +34,7 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
public class HashedItemType1_21_5 extends Type<HashedItem> {
private static final int MAX_DATA_TYPE_ID = 255; // Needs adjusting if we ever get beyond this
public HashedItemType1_21_5() {
super(HashedItem.class);
@@ -51,7 +52,7 @@ public class HashedItemType1_21_5 extends Type<HashedItem> {
final int addedComponentsSize = Limit.max(Types.VAR_INT.readPrimitive(buffer), 256);
final Int2IntMap dataHashes = new Int2IntOpenHashMap(addedComponentsSize);
for (int i = 0; i < addedComponentsSize; i++) {
final int dataType = Types.VAR_INT.readPrimitive(buffer);
final int dataType = readDataType(buffer);
final int hash = Types.INT.readPrimitive(buffer);
dataHashes.put(dataType, hash);
}
@@ -59,12 +60,20 @@ public class HashedItemType1_21_5 extends Type<HashedItem> {
final int removedComponentsSize = Limit.max(Types.VAR_INT.readPrimitive(buffer), 256);
final IntSet removedData = new IntOpenHashSet(removedComponentsSize);
for (int i = 0; i < removedComponentsSize; i++) {
final int dataType = Types.VAR_INT.readPrimitive(buffer);
final int dataType = readDataType(buffer);
removedData.add(dataType);
}
return new HashedStructuredItem(id, amount, dataHashes, removedData);
}
private static int readDataType(final ByteBuf buffer) {
final int dataType = Types.VAR_INT.readPrimitive(buffer);
if (dataType < 0 || dataType > MAX_DATA_TYPE_ID) {
throw new IllegalArgumentException("Invalid data component type " + dataType);
}
return dataType;
}
@Override
public void write(final ByteBuf buffer, final HashedItem value) {
if (value.isEmpty()) {
@@ -34,7 +34,6 @@ import io.netty.buffer.ByteBuf;
// Very similar to normal items (and just results in an item), except it allows non-positive amounts and has id/amount swapped because ???
public final class ItemCostType1_20_5 extends Type<Item> {
private static final StructuredData<?>[] EMPTY_DATA_ARRAY = new StructuredData[0];
private final Type<StructuredData<?>[]> dataArrayType;
public ItemCostType1_20_5(final Type<StructuredData<?>[]> dataArrayType) {
@@ -54,7 +53,8 @@ public final class ItemCostType1_20_5 extends Type<Item> {
public void write(final ByteBuf buffer, final Item object) {
Types.VAR_INT.writePrimitive(buffer, object.identifier());
Types.VAR_INT.writePrimitive(buffer, object.amount());
dataArrayType.write(buffer, object.dataContainer().data().values().toArray(EMPTY_DATA_ARRAY));
// Item costs are exact-match predicates without removal markers, so empty data can't be written
dataArrayType.write(buffer, object.dataContainer().data().values().stream().filter(StructuredData::isPresent).toArray(StructuredData[]::new));
}
public static final class OptionalItemCostType extends OptionalType<Item> {
@@ -22,6 +22,7 @@
*/
package com.viaversion.viaversion.api.type.types.item;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
@@ -55,6 +56,16 @@ public class ItemTemplateType26_1 extends ItemType1_20_5 {
writeData(buffer, object);
}
@Override
public void write(final Ops ops, final Item item) {
// Unlike item stacks, templates omit the default count
ops.writeMap(map -> {
map.write("id", Types.IDENTIFIER, ops.context().registryAccess().item(item.identifier()))
.writeOptional("count", Types.VAR_INT, item.amount(), 1);
writeComponents(map, item);
});
}
public static final class OptionalItemTemplateType extends OptionalType<Item> {
public OptionalItemTemplateType(final Type<Item> type) {
@@ -127,22 +127,25 @@ public class ItemType1_20_5 extends Type<Item> {
ops.writeMap(map -> {
map.write("id", Types.IDENTIFIER, ops.context().registryAccess().item(item.identifier()))
.write("count", Types.VAR_INT, item.amount());
writeComponents(map, item);
});
}
if (item.dataContainer().isEmpty()) {
return;
}
protected void writeComponents(final Ops.MapSerializer map, final Item item) {
if (item.dataContainer().isEmpty()) {
return;
}
map.writeMap("components", components -> {
for (final StructuredData<?> data : item.dataContainer().data().values()) {
String key = Key.namespaced(data.key().identifier());
if (data.isEmpty()) {
key = "!" + key;
}
//noinspection unchecked
components.write(key, (Type<StructuredData<?>>) dataType, data);
map.writeMap("components", components -> {
for (final StructuredData<?> data : item.dataContainer().data().values()) {
String key = Key.namespaced(data.key().identifier());
if (data.isEmpty()) {
key = "!" + key;
}
});
//noinspection unchecked
components.write(key, (Type<StructuredData<?>>) dataType, data);
}
});
}
@@ -76,7 +76,7 @@ public class StructuredDataType extends Type<StructuredData<?>> implements Struc
}
<T> StructuredData<T> readData(final ByteBuf buffer, final StructuredDataKey<T> key, final int id) {
return StructuredData.of(key, key.type().read(buffer), id);
return StructuredData.of(key, key.type().readTrackingDepth(buffer), id);
}
public DataFiller filler(final Protocol<?, ?, ?, ?> protocol) {
@@ -26,9 +26,12 @@ import com.viaversion.viaversion.api.minecraft.ProfileKey;
import com.viaversion.viaversion.api.type.OptionalType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.ByteArrayType;
import io.netty.buffer.ByteBuf;
public class ProfileKeyType extends Type<ProfileKey> {
private static final ByteArrayType PUBLIC_KEY_TYPE = ByteArrayType.withMaxLength(512);
private static final ByteArrayType KEY_SIGNATURE_TYPE = ByteArrayType.withMaxLength(4096);
public ProfileKeyType() {
super(ProfileKey.class);
@@ -36,14 +39,14 @@ public class ProfileKeyType extends Type<ProfileKey> {
@Override
public ProfileKey read(final ByteBuf buffer) {
return new ProfileKey(buffer.readLong(), Types.BYTE_ARRAY_PRIMITIVE.read(buffer), Types.BYTE_ARRAY_PRIMITIVE.read(buffer));
return new ProfileKey(buffer.readLong(), PUBLIC_KEY_TYPE.read(buffer), KEY_SIGNATURE_TYPE.read(buffer));
}
@Override
public void write(final ByteBuf buffer, final ProfileKey object) {
buffer.writeLong(object.expiresAt());
Types.BYTE_ARRAY_PRIMITIVE.write(buffer, object.publicKey());
Types.BYTE_ARRAY_PRIMITIVE.write(buffer, object.keySignature());
PUBLIC_KEY_TYPE.write(buffer, object.publicKey());
KEY_SIGNATURE_TYPE.write(buffer, object.keySignature());
}
public static final class OptionalProfileKeyType extends OptionalType<ProfileKey> {
@@ -39,9 +39,7 @@ public interface Copyable {
final int length = Array.getLength(object);
final Object copy = Array.newInstance(componentType, length);
if (componentType.isPrimitive()) {
for (int i = 0; i < length; i++) {
Array.set(copy, i, Array.get(object, i));
}
System.arraycopy(object, 0, copy, 0, length);
} else {
// See if we need to copy elements, too
for (int i = 0; i < length; i++) {
@@ -0,0 +1,53 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2026 ViaVersion and contributors
*
* 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.
*/
package com.viaversion.viaversion.util;
public final class NestingLimiter {
private static final ThreadLocal<int[]> STATE = ThreadLocal.withInitial(() -> new int[]{0, -1}); // Current depth, max depth
/**
* Sets the max depth for reads on the current thread.
*
* @param maxDepth max depth, or -1 for no limit
* @return the previous max depth, to be set again once done
*/
public static int setMaxDepth(final int maxDepth) {
final int[] state = STATE.get();
final int previous = state[1];
state[1] = maxDepth;
return previous;
}
public static void increment() {
final int[] state = STATE.get();
if (state[1] != -1 && state[0] >= state[1]) {
throw new IllegalArgumentException("Data is nested too deeply");
}
state[0]++;
}
public static void decrement() {
STATE.get()[0]--;
}
}
@@ -80,7 +80,7 @@ public interface Rewritable {
static Int2IntFunction soundRewriteFunction(final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
return protocol.getMappingData().getSoundMappings() == null ? Int2IntFunction.identity()
: (clientbound ? protocol.getMappingData().getSoundMappings()::getNewId : protocol.getMappingData()::getOldSoundId);
: (clientbound ? protocol.getMappingData().getSoundMappings()::getNewId : protocol.getMappingData().getSoundMappings().inverse()::getNewId);
}
static Int2IntFunction entityRewriteFunction(final Protocol<?, ?, ?, ?> protocol, final boolean clientbound) {
@@ -44,11 +44,22 @@ public final class UUIDUtil {
}
public static @Nullable UUID parseUUID(final String uuidString) {
try {
return UUID.fromString(uuidString);
} catch (final IllegalArgumentException e) {
// Check the format beforehand instead of catching the exception, exceptions are expensive
if (uuidString.length() != 36) {
return null;
}
for (int i = 0; i < 36; i++) {
final char c = uuidString.charAt(i);
if (i == 8 || i == 13 || i == 18 || i == 23) {
if (c != '-') {
return null;
}
} else if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) {
return null;
}
}
return UUID.fromString(uuidString);
}
/**
@@ -37,6 +37,7 @@ import com.viaversion.viaversion.commands.ViaCommandHandler;
import com.viaversion.viaversion.configuration.ConfigurationProviderImpl;
import com.viaversion.viaversion.connection.ConnectionManagerImpl;
import com.viaversion.viaversion.debug.DebugHandlerImpl;
import com.viaversion.viaversion.platform.UserConnectionViaVersionPlatform;
import com.viaversion.viaversion.protocol.ProtocolManagerImpl;
import com.viaversion.viaversion.protocol.ServerProtocolVersionRange;
import com.viaversion.viaversion.protocol.ServerProtocolVersionSingleton;
@@ -203,6 +204,10 @@ public class ViaManagerImpl implements ViaManager {
// Refresh Versions
protocolManager.refreshVersions();
if (protocolVersion.isKnown() && !(platform instanceof UserConnectionViaVersionPlatform)) {
exploitableVersionsWarning(protocolVersion);
}
for (final Runnable listener : postEnableListeners) {
listener.run();
}
@@ -243,6 +248,28 @@ public class ViaManagerImpl implements ViaManager {
platform.getLogger().info("ViaVersion has been disabled; uninjected the platform and shut down the scheduler.");
}
private void exploitableVersionsWarning(final ServerProtocolVersion serverProtocolVersion) {
if (!platform.isProxy() && isExploitableVersion(serverProtocolVersion.highestSupportedProtocolVersion())) {
platform.getLogger().warning("The Vanilla protocol of Minecraft 1.20.5-1.21.4 is exploitable easily. Please consider updating your server to at least 1.21.5.");
return;
}
final List<String> versions = new ArrayList<>();
for (final ProtocolVersion version : protocolManager.getSupportedVersions()) {
if (isExploitableVersion(version) && !platform.getConf().blockedProtocolVersions().contains(version)) {
versions.add('"' + version.getName() + '"');
}
}
if (!versions.isEmpty()) {
platform.getLogger().warning("The Vanilla protocol of Minecraft 1.20.5-1.21.4 is easily exploitable. "
+ "If you don't need to support these versions, consider adding them to block-versions in the ViaVersion config: " + String.join(", ", versions));
}
}
private static boolean isExploitableVersion(final ProtocolVersion version) {
return version.betweenInclusive(ProtocolVersion.v1_20_5, ProtocolVersion.v1_21_4);
}
private void checkJavaVersion() {
int javaVersion = Runtime.version().feature();
if (javaVersion < 17) {
@@ -233,6 +233,7 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
callOnMappingDataLoaded(getEntityRewriter());
callOnMappingDataLoaded(getItemRewriter());
callOnMappingDataLoaded(getTagRewriter());
callOnMappingDataLoaded(getBlockRewriter());
}
private void callRegister(@Nullable Rewriter<?> rewriter) {
@@ -31,7 +31,6 @@ public record CodecRegistryContext(Protocol<?, ?, ?, ?> protocol, RegistryAccess
// Generally from hardcoded, but highly variable client data
private static final Set<StructuredDataKey<?>> NOT_IMPLEMENTED = new ReferenceOpenHashSet<>(List.of(
StructuredDataKey.DAMAGE_TYPE1_21_11
));
public CodecRegistryContext(final Protocol<?, ?, ?, ?> protocol, final RegistryAccess registryAccess, final boolean mapped) {
@@ -95,6 +95,7 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
private boolean translateOcelotToCat;
private boolean enforceSecureChat;
private boolean handleInvalidItemCount;
private boolean backupNestedItems;
private boolean cancelBlockSounds;
private boolean hideScoreboardNumbers;
private boolean fix1_21PlacementRotation;
@@ -172,6 +173,7 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
translateOcelotToCat = getBoolean("translate-ocelot-to-cat", true);
enforceSecureChat = getBoolean("enforce-secure-chat", false);
handleInvalidItemCount = getBoolean("handle-invalid-item-count", false);
backupNestedItems = getBoolean("backup-nested-items", false);
cancelBlockSounds = getBoolean("cancel-block-sounds", true);
hideScoreboardNumbers = getBoolean("hide-scoreboard-numbers", false);
fix1_21PlacementRotation = getBoolean("fix-1_21-placement-rotation", true);
@@ -622,6 +624,11 @@ public class AbstractViaConfig extends Config implements ViaVersionConfig {
return handleInvalidItemCount;
}
@Override
public boolean backupNestedItems() {
return backupNestedItems;
}
@Override
public boolean cancelBlockSounds() {
return cancelBlockSounds;
@@ -36,15 +36,16 @@ import com.viaversion.viaversion.exception.CancelException;
import com.viaversion.viaversion.exception.InformativeException;
import com.viaversion.viaversion.protocol.packet.PacketWrapperImpl;
import com.viaversion.viaversion.util.ChatColorUtil;
import com.viaversion.viaversion.util.NestingLimiter;
import com.viaversion.viaversion.util.PipelineUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.CodecException;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -58,6 +59,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class UserConnectionImpl implements UserConnection {
private static final int PASSTHROUGH_DATA_BYTES = Long.BYTES + 2;
private static final int MAX_SERVERBOUND_NESTING_DEPTH = 128;
private static final AtomicLong IDS = new AtomicLong();
private final long id = IDS.incrementAndGet();
private final SplittableRandom random = new SplittableRandom();
@@ -225,10 +227,10 @@ public class UserConnectionImpl implements UserConnection {
private void sendRawPacket(final ByteBuf packet, final boolean currentThread) {
if (currentThread) {
sendRawPacketNow(packet);
sendRawPacketFuture(packet);
} else {
try {
channel.eventLoop().execute(() -> sendRawPacketNow(packet));
channel.eventLoop().execute(() -> sendRawPacketFuture(packet));
} catch (Throwable e) {
packet.release(); // Couldn't schedule
e.printStackTrace();
@@ -236,25 +238,22 @@ public class UserConnectionImpl implements UserConnection {
}
}
private void sendRawPacketNow(final ByteBuf buf) {
final ChannelPipeline pipeline = getChannel().pipeline();
final ViaInjector injector = Via.getManager().getInjector();
if (clientSide) {
// We'll just assume that Via decoder isn't wrapping the original decoder
pipeline.context(injector.getDecoderName()).fireChannelRead(buf);
} else {
pipeline.context(injector.getEncoderName()).writeAndFlush(buf);
}
}
@Override
public ChannelFuture sendRawPacketFuture(final ByteBuf packet) {
final ViaInjector injector = Via.getManager().getInjector();
final ChannelHandlerContext context = channel.pipeline().context(clientSide ? injector.getDecoderName() : injector.getEncoderName());
if (context == null) {
// Handlers are removed once the channel is closed
packet.release();
return channel.newFailedFuture(new ClosedChannelException());
}
if (clientSide) {
// Assume that decoder isn't wrapping
getChannel().pipeline().context(Via.getManager().getInjector().getDecoderName()).fireChannelRead(packet);
return getChannel().newSucceededFuture();
context.fireChannelRead(packet);
return channel.newSucceededFuture();
} else {
return channel.pipeline().context(Via.getManager().getInjector().getEncoderName()).writeAndFlush(packet);
return context.writeAndFlush(packet);
}
}
@@ -343,7 +342,7 @@ public class UserConnectionImpl implements UserConnection {
writeAndFlush(packet);
} else {
try {
getChannel().eventLoop().execute(() -> writeAndFlush(packet));
channel.eventLoop().execute(() -> writeAndFlush(packet));
} catch (Throwable e) {
e.printStackTrace();
packet.release(); // Couldn't schedule
@@ -352,7 +351,12 @@ public class UserConnectionImpl implements UserConnection {
}
private void writeAndFlush(final ByteBuf buf) {
getChannel().pipeline().context(Via.getManager().getInjector().getEncoderName()).writeAndFlush(buf);
final ChannelHandlerContext context = channel.pipeline().context(Via.getManager().getInjector().getEncoderName());
if (context == null) {
buf.release();
return;
}
context.writeAndFlush(buf);
}
@Override
@@ -406,10 +410,13 @@ public class UserConnectionImpl implements UserConnection {
final int valuesReaderIndex = buf.readerIndex();
final PacketWrapperImpl wrapper = new PacketWrapperImpl(id, buf, this);
final int previousMaxDepth = NestingLimiter.setMaxDepth(direction == Direction.SERVERBOUND ? MAX_SERVERBOUND_NESTING_DEPTH : -1);
try {
protocolInfo.getPipeline().transform(direction, protocolInfo.getState(direction), wrapper);
} catch (final CancelException ex) {
throw cancelSupplier.apply(ex);
} finally {
NestingLimiter.setMaxDepth(previousMaxDepth);
}
writeToBuffer(wrapper, buf, id, valuesReaderIndex);
@@ -329,7 +329,9 @@ public class ProtocolManagerImpl implements ProtocolManager {
List<ProtocolPathEntry> path = calculateProtocolPath(clientVersion, serverVersion);
if (path == null) {
// Also cache that there is no path
pathCache.put(protocolKey, List.of());
if (clientVersion.isKnown() && serverVersion.isKnown()) {
pathCache.put(protocolKey, List.of());
}
return null;
}
@@ -21,6 +21,7 @@ import com.google.common.base.Preconditions;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.ProtocolInfo;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.Direction;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
@@ -42,6 +43,7 @@ import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class PacketWrapperImpl implements PacketWrapper {
@@ -271,7 +273,7 @@ public class PacketWrapperImpl implements PacketWrapper {
.set("Server Protocol", protocolInfo.serverProtocolVersion().getName())
.set("Index", index)
.set("Type", type.getTypeName())
.set("Data", this.packetValues)
.set("Data", new PacketValuesSummary(this.packetValues))
.set("Packet ID", this.id);
}
@@ -621,6 +623,49 @@ public class PacketWrapperImpl implements PacketWrapper {
'}';
}
private static final class PacketValuesSummary {
private final List<PacketValue<?>> values;
private String s;
private PacketValuesSummary(final List<PacketValue<?>> values) {
this.values = values;
}
@Override
public String toString() {
if (this.s == null) {
this.s = Via.getManager().isDebug() ? this.values.toString() : summarize();
}
return this.s;
}
private String summarize() {
final int maxLength = Math.max(0, Via.getConfig().maxErrorLength());
final StringBuilder builder = new StringBuilder("[");
for (final PacketValue<?> packetValue : this.values) {
if (builder.length() > 1) {
builder.append(", ");
}
builder.append('{').append(packetValue.type()).append(": ").append(stringToLog(packetValue.value())).append('}');
if (builder.length() >= maxLength) {
builder.setLength(maxLength);
return builder.append("...").toString();
}
}
return builder.append(']').toString();
}
private static String stringToLog(@Nullable final Object value) {
if (value == null || value instanceof Number || value instanceof Boolean || value instanceof Character
|| value instanceof CharSequence || value instanceof UUID || value instanceof Enum<?>) {
return String.valueOf(value);
} else if (value instanceof Item item) {
return "Item{id=" + item.identifier() + ", amount=" + item.amount() + "}";
}
return "<" + value.getClass().getSimpleName() + ">";
}
}
public static final class PacketValue<T> {
private final Type<T> type;
private T value;
@@ -154,6 +154,9 @@ public class InitialBaseProtocol extends AbstractProtocol<BaseClientboundPacket,
info.setState(State.STATUS);
} else if (state == LOGIN_INTENT || state == TRANSFER_INTENT) {
info.setState(State.LOGIN);
} else {
// Unknown intent, don't handle further packets as handshakes
wrapper.user().setActive(false);
}
});
}
@@ -48,7 +48,9 @@ public class ServerboundBaseProtocol1_7 extends AbstractProtocol<BaseClientbound
// State tracking
registerServerbound(ServerboundLoginPackets.LOGIN_ACKNOWLEDGED, wrapper -> {
final ProtocolInfo info = wrapper.user().getProtocolInfo();
info.setState(State.CONFIGURATION);
if (info.getUuid() != null) {
info.setState(State.CONFIGURATION);
}
});
// Handle blocked version disconnect
@@ -24,8 +24,6 @@ import java.util.ArrayList;
import java.util.List;
public class WallConnectionHandler extends AbstractFenceConnectionHandler {
private static final BlockFace[] BLOCK_FACES = {BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST};
private static final int[] OPPOSITES = {3, 2, 1, 0};
static List<ConnectionData.ConnectorInitAction> init() {
List<ConnectionData.ConnectorInitAction> actions = new ArrayList<>(2);
@@ -49,7 +47,7 @@ public class WallConnectionHandler extends AbstractFenceConnectionHandler {
@Override
protected byte getStates(UserConnection user, BlockPosition position) {
byte states = super.getStates(user, position);
if (up(user, position)) states |= 16;
if (up(user, position, states)) states |= 16;
return states;
}
@@ -58,28 +56,9 @@ public class WallConnectionHandler extends AbstractFenceConnectionHandler {
return 32;
}
public boolean up(UserConnection user, BlockPosition position) {
if (isWall(getBlockData(user, position.getRelative(BlockFace.BOTTOM))) || isWall(getBlockData(user, position.getRelative(BlockFace.TOP))))
return true;
int blockFaces = getBlockFaces(user, position);
if (blockFaces == 0 || blockFaces == 0xF) return true;
for (int i = 0; i < BLOCK_FACES.length; i++) {
if ((blockFaces & (1 << i)) != 0 && (blockFaces & (1 << OPPOSITES[i])) == 0) return true;
}
return false;
}
private int getBlockFaces(UserConnection user, BlockPosition position) {
int blockFaces = 0;
for (int i = 0; i < BLOCK_FACES.length; i++) {
if (isWall(getBlockData(user, position.getRelative(BLOCK_FACES[i])))) {
blockFaces |= 1 << i;
}
}
return blockFaces;
}
private boolean isWall(int id) {
return getBlockStates().contains(id);
private boolean up(UserConnection user, BlockPosition position, byte states) {
// Only straight walls with air above have no post, see BlockWall#getActualState
boolean straight = states == (2 | 4) || states == (1 | 8);
return !straight || getBlockData(user, position.getRelative(BlockFace.TOP)) != 0;
}
}
@@ -494,9 +494,12 @@ public class ItemPacketRewriter1_13 extends ItemRewriter<ClientboundPackets1_12_
if (tag == null)
item.setTag(tag = new CompoundTag());
if (!tag.contains("EntityTag")) {
CompoundTag entityTag = new CompoundTag();
entityTag.put("id", new StringTag(eggEntityId.get()));
tag.put("EntityTag", entityTag);
tag.put("EntityTag", new CompoundTag());
}
// The id is removed from a remaining EntityTag in handleItemToClient
CompoundTag entityTag = tag.getCompoundTag("EntityTag");
if (entityTag != null && !entityTag.contains("id")) {
entityTag.putString("id", eggEntityId.get());
}
} else {
rawId = IdAndData.getId(oldId) << 16 | oldId & 0xF;
@@ -548,6 +551,10 @@ public class ItemPacketRewriter1_13 extends ItemRewriter<ClientboundPackets1_12_
if (patternsTag != null) {
for (CompoundTag pattern : patternsTag) {
NumberTag colorTag = pattern.getNumberTag("Color");
if (colorTag == null) {
continue;
}
pattern.putInt("Color", 15 - colorTag.asInt()); // Invert color id
}
}
@@ -659,6 +666,11 @@ public class ItemPacketRewriter1_13 extends ItemRewriter<ClientboundPackets1_12_
}
tag.put("CanDestroy", newCanDestroy);
}
// Damage, map and the original id are stored in a tag created in handleItemToClient
if (tag.isEmpty()) {
item.setTag(null);
}
}
return item;
}
@@ -31,6 +31,7 @@ import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ClientboundPacke
import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17;
import com.viaversion.viaversion.rewriter.ItemRewriter;
import com.viaversion.viaversion.rewriter.RecipeRewriter;
import com.viaversion.viaversion.util.Limit;
public final class ItemPacketRewriter1_17 extends ItemRewriter<ClientboundPackets1_16_2, ServerboundPackets1_17, Protocol1_16_4To1_17> {
@@ -55,7 +56,7 @@ public final class ItemPacketRewriter1_17 extends ItemRewriter<ClientboundPacket
handler(wrapper -> {
// Affected items - throw them away!
int length = wrapper.read(Types.VAR_INT);
int length = Limit.max(wrapper.read(Types.VAR_INT), 128);
for (int i = 0; i < length; i++) {
wrapper.read(Types.SHORT); // Slot
wrapper.read(Types.ITEM1_13_2);
@@ -125,6 +126,7 @@ public final class ItemPacketRewriter1_17 extends ItemRewriter<ClientboundPacket
}
if (tag.getNumberTag("map") == null) {
tag.put("map", new IntTag(0));
tag.putBoolean(nbtTagName("map"), true);
}
}
@@ -132,4 +134,20 @@ public final class ItemPacketRewriter1_17 extends ItemRewriter<ClientboundPacket
return item;
}
@Override
public Item handleItemToServer(UserConnection connection, Item item) {
if (item == null) return null;
super.handleItemToServer(connection, item);
CompoundTag tag = item.tag();
if (item.identifier() == 733 && tag != null && tag.remove(nbtTagName("map")) != null) {
tag.remove("map");
if (tag.isEmpty()) {
item.setTag(null);
}
}
return item;
}
}
@@ -55,6 +55,7 @@ import com.viaversion.viaversion.rewriter.SoundRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.util.CipherUtil;
import com.viaversion.viaversion.util.ComponentUtil;
import com.viaversion.viaversion.util.Limit;
import java.util.concurrent.ThreadLocalRandom;
public final class Protocol1_18_2To1_19 extends AbstractProtocol<ClientboundPackets1_18, ClientboundPackets1_19, ServerboundPackets1_17, ServerboundPackets1_19> {
@@ -243,7 +244,7 @@ public final class Protocol1_18_2To1_19 extends AbstractProtocol<ClientboundPack
final String command = wrapper.get(Types.STRING, 0);
wrapper.set(Types.STRING, 0, "/" + command);
final int signatures = wrapper.read(Types.VAR_INT);
final int signatures = Limit.max(wrapper.read(Types.VAR_INT), 8);
for (int i = 0; i < signatures; i++) {
wrapper.read(Types.STRING); // Argument name
wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Signature
@@ -174,6 +174,10 @@ public final class ItemPacketRewriter1_19 extends ItemRewriter<ClientboundPacket
private PacketHandler sequenceHandler() {
return wrapper -> {
final int sequence = wrapper.read(Types.VAR_INT);
if (sequence < 0) {
throw new IllegalArgumentException("Expected packet sequence nr >= 0");
}
final AckSequenceProvider provider = Via.getManager().getProviders().get(AckSequenceProvider.class);
provider.handleSequence(wrapper.user(), sequence);
};
@@ -51,6 +51,7 @@ import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.rewriter.ComponentRew
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.rewriter.EntityPacketRewriter1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.rewriter.ItemPacketRewriter1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.storage.NonceStorage1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.storage.PlayerHealthStorage;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.storage.ReceivedMessagesStorage;
import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ClientboundPackets1_19_1;
import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ServerboundPackets1_19_1;
@@ -59,6 +60,7 @@ import com.viaversion.viaversion.rewriter.CommandRewriter;
import com.viaversion.viaversion.rewriter.ParticleRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.util.ComponentUtil;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.Pair;
import java.security.SignatureException;
import java.util.List;
@@ -220,7 +222,7 @@ public final class Protocol1_19_1To1_19_3 extends AbstractProtocol<ClientboundPa
final ChatSession1_19_1 chatSession = wrapper.user().get(ChatSession1_19_1.class);
final ReceivedMessagesStorage messagesStorage = wrapper.user().get(ReceivedMessagesStorage.class);
final int signatures = wrapper.read(Types.VAR_INT);
final int signatures = Limit.max(wrapper.read(Types.VAR_INT), 8);
for (int i = 0; i < signatures; i++) {
wrapper.read(Types.STRING); // Argument name
wrapper.read(Types.SIGNATURE_BYTES); // Signature
@@ -380,6 +382,7 @@ public final class Protocol1_19_1To1_19_3 extends AbstractProtocol<ClientboundPa
@Override
public void init(final UserConnection user) {
user.put(new ReceivedMessagesStorage());
user.put(new PlayerHealthStorage());
addEntityTracker(user, new EntityTrackerBase(user, EntityTypes1_19_3.PLAYER));
}
@@ -28,6 +28,7 @@ import com.viaversion.viaversion.api.type.types.version.Types1_19;
import com.viaversion.viaversion.api.type.types.version.Types1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.Protocol1_19_1To1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.storage.PlayerHealthStorage;
import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ClientboundPackets1_19_1;
import com.viaversion.viaversion.rewriter.EntityRewriter;
import java.util.BitSet;
@@ -75,6 +76,13 @@ public final class EntityPacketRewriter1_19_3 extends EntityRewriter<Clientbound
}
});
protocol.registerClientbound(ClientboundPackets1_19_1.SET_HEALTH, wrapper -> {
final float health = wrapper.passthrough(Types.FLOAT);
final int food = wrapper.passthrough(Types.VAR_INT);
final float saturation = wrapper.passthrough(Types.FLOAT);
wrapper.user().get(PlayerHealthStorage.class).update(health, food, saturation);
});
protocol.registerClientbound(ClientboundPackets1_19_1.RESPAWN, new PacketHandlers() {
@Override
public void register() {
@@ -94,6 +102,18 @@ public final class EntityPacketRewriter1_19_3 extends EntityRewriter<Clientbound
}
wrapper.write(Types.BYTE, keepDataMask);
});
handler(wrapper -> {
final PlayerHealthStorage healthStorage = wrapper.user().get(PlayerHealthStorage.class);
if (healthStorage.dead()) {
// The client takes over the entity data of the dead player, its health of 0 would instantly re-open the death screen
final PacketWrapper setHealth = wrapper.create(ClientboundPackets1_19_3.SET_HEALTH);
setHealth.write(Types.FLOAT, 20F); // Overwritten by the server right after the respawn
setHealth.write(Types.VAR_INT, healthStorage.food());
setHealth.write(Types.FLOAT, healthStorage.saturation());
setHealth.send(Protocol1_19_1To1_19_3.class);
healthStorage.setDead(false);
}
});
}
});
@@ -0,0 +1,49 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2026 ViaVersion 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 com.viaversion.viaversion.protocols.v1_19_1to1_19_3.storage;
import com.viaversion.viaversion.api.connection.StorableObject;
public final class PlayerHealthStorage implements StorableObject {
private boolean dead;
private int food;
private float saturation;
public void update(final float health, final int food, final float saturation) {
this.dead = health <= 0;
this.food = food;
this.saturation = saturation;
}
public boolean dead() {
return this.dead;
}
public void setDead(final boolean dead) {
this.dead = dead;
}
public int food() {
return this.food;
}
public float saturation() {
return this.saturation;
}
}
@@ -26,6 +26,7 @@ import com.viaversion.nbt.tag.NumberTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature;
import com.viaversion.viaversion.api.minecraft.ProfileKey;
import com.viaversion.viaversion.api.minecraft.signature.SignableCommandArgumentsProvider;
import com.viaversion.viaversion.api.minecraft.signature.model.DecoratableMessage;
@@ -34,7 +35,9 @@ import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19
import com.viaversion.viaversion.api.protocol.AbstractProtocol;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.ArrayType;
import com.viaversion.viaversion.libs.mcstructs.text.Style;
import com.viaversion.viaversion.libs.mcstructs.text.TextComponent;
import com.viaversion.viaversion.libs.mcstructs.text.TextFormatting;
@@ -50,6 +53,7 @@ import com.viaversion.viaversion.protocols.v1_19to1_19_1.packet.ServerboundPacke
import com.viaversion.viaversion.protocols.v1_19to1_19_1.storage.ChatTypeStorage;
import com.viaversion.viaversion.protocols.v1_19to1_19_1.storage.NonceStorage1_19_1;
import com.viaversion.viaversion.util.CipherUtil;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.Pair;
import com.viaversion.viaversion.util.ProtocolLogger;
import com.viaversion.viaversion.util.SerializerVersion;
@@ -64,6 +68,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final class Protocol1_19To1_19_1 extends AbstractProtocol<ClientboundPackets1_19, ClientboundPackets1_19_1, ServerboundPackets1_19, ServerboundPackets1_19_1> {
public static final ProtocolLogger LOGGER = new ProtocolLogger(Protocol1_19To1_19_1.class);
private static final Type<PlayerMessageSignature[]> LAST_SEEN_MESSAGES_TYPE = new ArrayType<>(Types.PLAYER_MESSAGE_SIGNATURE, 5);
public Protocol1_19To1_19_1() {
super(ClientboundPackets1_19.class, ClientboundPackets1_19_1.class, ServerboundPackets1_19.class, ServerboundPackets1_19_1.class);
@@ -139,7 +144,7 @@ public final class Protocol1_19To1_19_1 extends AbstractProtocol<ClientboundPack
wrapper.set(Types.BOOLEAN, 0, decoratableMessage.isDecorated()); // Signed preview
}
});
read(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY); // Last seen messages
read(LAST_SEEN_MESSAGES_TYPE); // Last seen messages
read(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE); // Last received message
}
});
@@ -153,7 +158,7 @@ public final class Protocol1_19To1_19_1 extends AbstractProtocol<ClientboundPack
final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class);
final SignableCommandArgumentsProvider argumentsProvider = Via.getManager().getProviders().get(SignableCommandArgumentsProvider.class);
final int signatures = wrapper.read(Types.VAR_INT);
final int signatures = Limit.max(wrapper.read(Types.VAR_INT), 8);
for (int i = 0; i < signatures; i++) {
wrapper.read(Types.STRING); // Argument name
wrapper.read(Types.BYTE_ARRAY_PRIMITIVE); // Signature
@@ -187,7 +192,7 @@ public final class Protocol1_19To1_19_1 extends AbstractProtocol<ClientboundPack
}
});
map(Types.BOOLEAN); // Signed preview
read(Types.PLAYER_MESSAGE_SIGNATURE_ARRAY); // Last seen messages
read(LAST_SEEN_MESSAGES_TYPE); // Last seen messages
read(Types.OPTIONAL_PLAYER_MESSAGE_SIGNATURE); // Last received message
}
});
@@ -137,7 +137,10 @@ public final class BlockItemPacketRewriter1_20_3 extends ItemRewriter<Clientboun
final CompoundTag filteredPages = tag.getCompoundTag("filtered_pages");
if (filteredPages != null) {
for (final String string : filteredPages.keySet()) {
updatePageTag(filteredPages.getStringTag(string));
final StringTag pageTag = filteredPages.getStringTag(string);
if (pageTag != null) {
updatePageTag(pageTag);
}
}
}
}
@@ -59,6 +59,7 @@ import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundConfi
import com.viaversion.viaversion.rewriter.BlockRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.rewriter.text.JsonNBTComponentRewriter;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.ProtocolLogger;
import java.util.BitSet;
import java.util.HashSet;
@@ -148,7 +149,7 @@ public final class Protocol1_20_3To1_20_5 extends AbstractProtocol<ClientboundPa
final ProtocolStorables1_20_5 storables = wrapper.user().storables(this);
final AcknowledgedMessagesStorage storage = storables.acknowledgedMessages();
final long salt = wrapper.read(Types.LONG);
final int signatures = wrapper.read(Types.VAR_INT);
final int signatures = Limit.max(wrapper.read(Types.VAR_INT), 8);
if (storage.isSecureChatEnforced()) {
wrapper.write(Types.LONG, salt);
wrapper.write(Types.VAR_INT, signatures);
@@ -379,7 +379,10 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
tag.putBoolean(nbtTagName(), true);
structuredItem.dataContainer().set(StructuredDataKey.CUSTOM_DATA, tag);
}
return handleStructuredItemToClient(connection, structuredItem);
}
private Item handleStructuredItemToClient(final UserConnection connection, final Item structuredItem) {
// Add data components to fix issues in older protocols
appendItemDataFixComponents(connection, structuredItem);
@@ -429,6 +432,11 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
}
public Item toStructuredItem(final UserConnection connection, final Item old) {
return toStructuredItem(connection, old, false);
}
// Nested items are converted from a copy made by the root item, no need to copy again
private Item toStructuredItem(final UserConnection connection, final Item old, final boolean copied) {
final CompoundTag tag = old.tag();
final StructuredItem item = new StructuredItem(old.identifier(), (byte) old.amount(), new StructuredDataContainer());
final StructuredDataContainer data = item.dataContainer();
@@ -482,7 +490,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final CompoundTag blockEntityTag = tag.getCompoundTag("BlockEntityTag");
if (blockEntityTag != null) {
final CompoundTag clonedTag = blockEntityTag.copy();
final CompoundTag clonedTag = copied ? blockEntityTag : blockEntityTag.copy();
// Same as above
updateBlockEntityTag(connection, data, clonedTag);
CompoundTag spawnData = clonedTag.getCompoundTag("SpawnData");
@@ -571,9 +579,9 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
updateMobTags(data, tag);
updateItemList(connection, data, tag, "ChargedProjectiles", StructuredDataKey.V1_20_5.chargedProjectiles);
updateItemList(connection, data, tag, "ChargedProjectiles", StructuredDataKey.V1_20_5.chargedProjectiles, copied);
if (old.identifier() == 927) {
updateItemList(connection, data, tag, "Items", StructuredDataKey.V1_20_5.bundleContents);
updateItemList(connection, data, tag, "Items", StructuredDataKey.V1_20_5.bundleContents, copied);
}
updateEnchantments(data, tag, "Enchantments", StructuredDataKey.ENCHANTMENTS1_20_5, (hideFlagsValue & StructuredDataConverter.HIDE_ENCHANTMENTS) == 0);
@@ -692,6 +700,11 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
if (bannerPatterns != null) {
restoreBannerPatternsFromBackup(bannerPatterns, data);
}
final CompoundTag trim = backupTag.getCompoundTag("trim");
if (trim != null) {
restoreArmorTrimFromBackup(trim, data);
}
}
private void restoreInstrumentFromBackup(final CompoundTag instrument, final StructuredDataContainer data) {
@@ -799,6 +812,25 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
data.set(StructuredDataKey.BANNER_PATTERNS, patternLayer.toArray(new BannerPatternLayer[0]));
}
private void restoreArmorTrimFromBackup(final CompoundTag trim, final StructuredDataContainer data) {
final Tag materialTag = trim.get("material");
final Holder<ArmorTrimMaterial1_20_5> materialHolder;
if (materialTag instanceof IntTag materialIntTag) {
materialHolder = Holder.of(materialIntTag.asInt());
} else if (materialTag instanceof CompoundTag materialCompoundTag) {
ArmorTrimMaterial1_20_5 material = readTrimMaterial(materialCompoundTag);
if (material == null) {
return;
}
materialHolder = Holder.of(material);
} else return;
final Holder<ArmorTrimPattern> patternHolder = Holder.of(trim.getInt("pattern"));
final boolean showInTooltip = trim.getBoolean("show_in_tooltip");
data.set(StructuredDataKey.TRIM1_20_5, new ArmorTrim1_20_5(materialHolder, patternHolder, showInTooltip));
}
private AdventureModePredicate updateBlockPredicates(final UserConnection connection, final ListTag<StringTag> tag, final boolean showInTooltip) {
final BlockPredicate[] predicates = tag.stream()
.map(StringTag::getValue)
@@ -969,8 +1001,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
private void updateArmorTrim(final UserConnection connection, final StructuredDataContainer data, final CompoundTag trimTag, final boolean showInTooltip) {
final Tag materialTag = trimTag.get("material");
final Holder<ArmorTrimMaterial1_20_5> materialHolder;
final ProtocolStorables1_20_5 storables = connection.storables(protocol);
final ArmorTrimStorage trimStorage = storables.armorTrims();
final ArmorTrimStorage trimStorage = connection.get(ArmorTrimStorage.class);
if (materialTag instanceof StringTag materialStringTag) {
final int id = trimStorage.trimMaterials().keyToId(materialStringTag.getValue());
if (id == -1) {
@@ -979,39 +1010,11 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
materialHolder = Holder.of(id);
} else if (materialTag instanceof CompoundTag materialCompoundTag) {
final StringTag assetNameTag = materialCompoundTag.getStringTag("asset_name");
final StringTag ingredientTag = materialCompoundTag.getStringTag("ingredient");
if (assetNameTag == null || ingredientTag == null) {
ArmorTrimMaterial1_20_5 material = readTrimMaterial(materialCompoundTag);
if (material == null) {
return;
}
final int ingredientId = StructuredDataConverter.removeItemBackupTag(materialCompoundTag, toMappedItemId(ingredientTag.getValue()));
if (ingredientId == -1) {
return;
}
final NumberTag itemModelIndexTag = materialCompoundTag.getNumberTag("item_model_index");
final CompoundTag overrideArmorMaterialsTag = materialCompoundTag.getCompoundTag("override_armor_materials");
final Tag descriptionTag = materialCompoundTag.get("description");
final Map<String, String> overrideArmorMaterials = new Object2ObjectArrayMap<>();
if (overrideArmorMaterialsTag != null) {
for (final Map.Entry<String, Tag> entry : overrideArmorMaterialsTag.entrySet()) {
if (!(entry.getValue() instanceof StringTag valueTag)) {
continue;
}
overrideArmorMaterials.put(entry.getKey(), valueTag.getValue());
}
}
materialHolder = Holder.of(new ArmorTrimMaterial1_20_5(
assetNameTag.getValue(),
ingredientId,
itemModelIndexTag != null ? itemModelIndexTag.asFloat() : 0,
overrideArmorMaterials,
descriptionTag
));
materialHolder = Holder.of(material);
} else return;
final Tag patternTag = trimTag.get("pattern");
@@ -1048,6 +1051,42 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
data.set(StructuredDataKey.TRIM1_20_5, new ArmorTrim1_20_5(materialHolder, patternHolder, showInTooltip));
}
private ArmorTrimMaterial1_20_5 readTrimMaterial(final CompoundTag tag) {
final StringTag assetNameTag = tag.getStringTag("asset_name");
final StringTag ingredientTag = tag.getStringTag("ingredient");
if (assetNameTag == null || ingredientTag == null) {
return null;
}
final int ingredientId = StructuredDataConverter.removeItemBackupTag(tag, toMappedItemId(ingredientTag.getValue()));
if (ingredientId == -1) {
return null;
}
final NumberTag itemModelIndexTag = tag.getNumberTag("item_model_index");
final CompoundTag overrideArmorMaterialsTag = tag.getCompoundTag("override_armor_materials");
final Tag descriptionTag = tag.get("description");
final Map<String, String> overrideArmorMaterials = new Object2ObjectArrayMap<>();
if (overrideArmorMaterialsTag != null) {
for (final Map.Entry<String, Tag> entry : overrideArmorMaterialsTag.entrySet()) {
if (!(entry.getValue() instanceof StringTag valueTag)) {
continue;
}
overrideArmorMaterials.put(entry.getKey(), valueTag.getValue());
}
}
return new ArmorTrimMaterial1_20_5(
assetNameTag.getValue(),
ingredientId,
itemModelIndexTag != null ? itemModelIndexTag.asFloat() : 0,
overrideArmorMaterials,
descriptionTag
);
}
private void updateMobTags(final StructuredDataContainer data, final CompoundTag tag) {
final CompoundTag bucketEntityData = new CompoundTag();
for (final String mobTagKey : MOB_TAGS) {
@@ -1230,12 +1269,12 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
}
private void updateItemList(final UserConnection connection, final StructuredDataContainer data, final CompoundTag tag,
final String key, final StructuredDataKey<Item[]> dataKey) {
final String key, final StructuredDataKey<Item[]> dataKey, final boolean copied) {
final ListTag<CompoundTag> itemsTag = tag.getListTag(key, CompoundTag.class);
if (itemsTag != null) {
final Item[] items = itemsTag.stream()
.limit(256)
.map(item -> itemFromTag(connection, item))
.map(item -> itemFromTag(connection, copied ? item : item.copy()))
.filter(item -> !item.isEmpty())
.toArray(Item[]::new);
data.set(dataKey, items);
@@ -1255,7 +1294,11 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final byte count = item.getByte("Count", (byte) 1);
final CompoundTag tag = item.getCompoundTag("tag");
return handleItemToClient(connection, new DataItem(itemId, count, tag));
final DataItem dataItem = new DataItem(itemId, count, tag);
if (Via.getConfig().backupNestedItems()) {
return handleItemToClient(connection, dataItem);
}
return handleStructuredItemToClient(connection, toStructuredItem(connection, dataItem, true));
}
private void updateEnchantments(final StructuredDataContainer data, final CompoundTag tag, final String key,
@@ -1494,11 +1537,15 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final ListTag<CompoundTag> itemsTag = tag.getListTag("Items", CompoundTag.class);
if (itemsTag != null) {
// Only convert once, items can contain further containers
final int size = Math.min(itemsTag.size(), 256);
final Item[] items = new Item[size];
int highestSlot = 0;
for (int i = 0, size = Math.min(itemsTag.size(), 256); i < size; i++) {
for (int i = 0; i < size; i++) {
final CompoundTag itemTag = itemsTag.get(i);
final Item item = itemFromTag(connection, itemTag);
items[i] = item;
if (item.isEmpty()) {
continue;
}
@@ -1509,18 +1556,19 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
final Item[] filteredItems = new Item[highestSlot + 1];
Arrays.fill(filteredItems, StructuredItem.empty());
for (final CompoundTag itemTag : itemsTag) {
final Item item = itemFromTag(connection, itemTag);
for (int i = 0; i < size; i++) {
final Item item = items[i];
if (item.isEmpty()) {
continue;
}
final int slot = itemTag.getByte("Slot");
final int slot = itemsTag.get(i).getByte("Slot");
if (slot >= 0 && slot < filteredItems.length) {
filteredItems[slot] = item;
}
}
tag.remove("Items");
data.set(StructuredDataKey.V1_20_5.container, filteredItems);
addBlockEntityId(tag, "shulker_box");
}
@@ -551,10 +551,22 @@ public final class StructuredDataConverter {
} else {
final String oldKey = trimStorage.trimPatterns().idToKey(data.pattern().id());
if (oldKey != null) {
trimTag.putString("pattern", oldKey);
if (!"flow".equals(oldKey) && !"bolt".equals(oldKey)) {
trimTag.putString("pattern", oldKey);
} else if (backupInconvertibleData) {
final CompoundTag backupTrim = trimTag.copy();
if (!data.material().isDirect()) {
backupTrim.putInt("material", data.material().id());
}
backupTrim.putInt("pattern", data.pattern().id());
backupTrim.putBoolean("show_in_tooltip", data.showInTooltip());
getBackupTag(tag).put("trim", backupTrim);
}
}
}
tag.put("Trim", trimTag);
if (trimTag.contains("material") && trimTag.contains("pattern")) {
tag.put("Trim", trimTag);
}
if (!data.showInTooltip()) {
putHideFlag(tag, HIDE_ARMOR_TRIM);
}
@@ -55,6 +55,7 @@ import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.rewriter.text.JsonNBTComponentRewriter;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.UUIDUtil;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.UUID;
@@ -183,7 +184,7 @@ public final class Protocol1_20_5To1_21 extends AbstractProtocol<ClientboundPack
}
uuid = UUIDUtil.parseUUID(Key.stripNamespace(id).toUpperCase(Locale.ROOT));
return uuid != null ? uuid : UUIDUtil.randomUUID();
return uuid != null ? uuid : UUID.nameUUIDFromBytes(id.getBytes(StandardCharsets.UTF_8));
}
@Override
@@ -215,9 +216,10 @@ public final class Protocol1_20_5To1_21 extends AbstractProtocol<ClientboundPack
tagRewriter.addEmptyTags(RegistryType.ENTITY, "minecraft:can_turn_in_boats", "minecraft:deflects_projectiles", "minecraft:immune_to_infested",
"minecraft:immune_to_oozing", "minecraft:no_anger_from_wind_charge");
tagRewriter.addTag(RegistryType.ENCHANTMENT, "minecraft:curse", 10, 41); // Binding and vanishing curse
tagRewriter.addTag(RegistryType.ENCHANTMENT, "minecraft:prevents_decorated_pot_shattering", 21); // Silk touch
// Add other enchantment tags empty
tagRewriter.addEmptyTags(RegistryType.ENCHANTMENT, "double_trade_price", "in_enchanting_table", "non_treasure", "on_mob_spawn_equipment", "on_random_loot",
"on_traded_equipment", "prevents_bee_spawns_when_mining", "prevents_decorated_pot_shattering", "prevents_ice_melting", "prevents_infested_spawns", "smelts_loot",
"on_traded_equipment", "prevents_bee_spawns_when_mining", "prevents_ice_melting", "prevents_infested_spawns", "smelts_loot",
"tooltip_order", "tradeable", "treasure", "exclusive_set/armor", "exclusive_set/boots", "exclusive_set/bow", "exclusive_set/crossbow", "exclusive_set/damage",
"exclusive_set/mining", "exclusive_set/riptide");
}
@@ -151,11 +151,15 @@ public final class Protocol1_20To1_20_2 extends AbstractProtocol<ClientboundPack
registerServerbound(State.LOGIN, ServerboundLoginPackets.LOGIN_ACKNOWLEDGED.getId(), -1, wrapper -> {
wrapper.cancel();
final ProtocolStorables1_20_2 storables = wrapper.user().storables(this);
final ConfigurationState configurationState = storables.configurationState();
if (configurationState.bridgePhase() != BridgePhase.PROFILE_SENT) {
return;
}
// Overwrite the state set in the base protocol to what the server actually keeps sending
wrapper.user().getProtocolInfo().setServerState(State.PLAY);
final ProtocolStorables1_20_2 storables = wrapper.user().storables(this);
final ConfigurationState configurationState = storables.configurationState();
configurationState.setBridgePhase(BridgePhase.CONFIGURATION);
configurationState.sendQueuedPackets(wrapper.user());
});
@@ -33,7 +33,6 @@ import com.viaversion.viaversion.api.minecraft.item.StructuredItem;
import com.viaversion.viaversion.api.minecraft.item.data.BlocksAttacks;
import com.viaversion.viaversion.api.minecraft.item.data.DamageResistant1_21_2;
import com.viaversion.viaversion.api.minecraft.item.data.DamageResistant26_1;
import com.viaversion.viaversion.api.minecraft.item.data.DamageType;
import com.viaversion.viaversion.api.minecraft.item.data.JukeboxPlayable;
import com.viaversion.viaversion.api.minecraft.item.data.ProvidesBannerPatterns;
import com.viaversion.viaversion.api.minecraft.item.data.trim.ProvidesTrimMaterial1_20_5;
@@ -86,7 +85,7 @@ public final class BlockItemPacketRewriter26_1 extends StructuredItemRewriter<Cl
container.replace(StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5, StructuredDataKey.PROVIDES_TRIM_MATERIAL26_1, providesTrimMaterial -> upgradeHolder(protocol, connection, providesTrimMaterial.material(), "trim_material"));
container.replace(StructuredDataKey.CHICKEN_VARIANT1_21_5, StructuredDataKey.CHICKEN_VARIANT26_1, chickenVariant -> upgradeEitherVariant(protocol, connection, chickenVariant, "chicken_variant"));
container.replace(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT26_1, nautilusVariant -> upgradeEitherVariant(protocol, connection, nautilusVariant, "zombie_nautilus_variant"));
container.replace(StructuredDataKey.DAMAGE_TYPE1_21_11, StructuredDataKey.DAMAGE_TYPE26_1, damageType -> upgradeEitherVariant(protocol, connection, damageType.id(), "damage_type"));
container.replace(StructuredDataKey.DAMAGE_TYPE1_21_11, StructuredDataKey.DAMAGE_TYPE26_1, damageType -> upgradeEitherVariant(protocol, connection, damageType, "damage_type"));
container.replace(StructuredDataKey.PROVIDES_BANNER_PATTERNS1_21_5, StructuredDataKey.PROVIDES_BANNER_PATTERNS26_1, key -> new ProvidesBannerPatterns(tagKeyToHolderSet(protocol, connection, "banner_pattern", key.original())));
container.replace(StructuredDataKey.DAMAGE_RESISTANT1_21_2, StructuredDataKey.DAMAGE_RESISTANT26_1, damageResistant -> new DamageResistant26_1(tagKeyToHolderSet(protocol, connection, "damage_type", damageResistant.typesTagKey().original())));
container.replaceKey(StructuredDataKey.BLOCKS_ATTACKS1_21_5, StructuredDataKey.BLOCKS_ATTACKS26_1);
@@ -137,14 +136,14 @@ public final class BlockItemPacketRewriter26_1 extends StructuredItemRewriter<Cl
container.replace(StructuredDataKey.PROVIDES_TRIM_MATERIAL26_1, StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5, providesTrimMaterial -> new ProvidesTrimMaterial1_20_5(EitherHolder.of(providesTrimMaterial)));
container.replace(StructuredDataKey.CHICKEN_VARIANT26_1, StructuredDataKey.CHICKEN_VARIANT1_21_5, Either::left);
container.replace(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT26_1, StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, Either::left);
container.replace(StructuredDataKey.DAMAGE_TYPE26_1, StructuredDataKey.DAMAGE_TYPE1_21_11, damageType -> new DamageType(Either.left(damageType)));
container.replace(StructuredDataKey.DAMAGE_TYPE26_1, StructuredDataKey.DAMAGE_TYPE1_21_11, Either::left);
container.replace(StructuredDataKey.PROVIDES_BANNER_PATTERNS26_1, StructuredDataKey.PROVIDES_BANNER_PATTERNS1_21_5, patterns -> tagOrNull(patterns.patterns()));
container.replace(StructuredDataKey.DAMAGE_RESISTANT26_1, StructuredDataKey.DAMAGE_RESISTANT1_21_2, damageResistant -> {
final Key key = tagOrNull(damageResistant.types());
return key != null ? new DamageResistant1_21_2(key) : null;
});
container.replace(StructuredDataKey.BLOCKS_ATTACKS26_1, StructuredDataKey.BLOCKS_ATTACKS1_21_5, blocksAttacks -> {
if (blocksAttacks.bypassedBy() == null) {
if (blocksAttacks.bypassedBy() == null || blocksAttacks.bypassedBy().hasTagKey()) {
return blocksAttacks;
}
// Remove bypassed by ids
@@ -22,6 +22,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.data.MappingDataBase;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.RegistryType;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_5;
@@ -181,7 +182,7 @@ public final class Protocol1_21_4To1_21_5 extends AbstractProtocol<ClientboundPa
StructuredDataKey.POT_DECORATIONS1_20_5, StructuredDataKey.BLOCK_STATE, StructuredDataKey.BEES1_20_5, StructuredDataKey.LOCK1_21_2,
StructuredDataKey.CONTAINER_LOOT, StructuredDataKey.TOOL1_21_5, StructuredDataKey.ITEM_NAME, StructuredDataKey.OMINOUS_BOTTLE_AMPLIFIER,
StructuredDataKey.FOOD1_21_2, StructuredDataKey.JUKEBOX_PLAYABLE1_21_5, StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_5,
StructuredDataKey.REPAIRABLE, StructuredDataKey.ENCHANTABLE, StructuredDataKey.CONSUMABLE1_21_2,
StructuredDataKey.REPAIRABLE, StructuredDataKey.ENCHANTABLE, StructuredDataKey.CONSUMABLE1_21_5,
StructuredDataKey.USE_COOLDOWN, StructuredDataKey.DAMAGE, StructuredDataKey.EQUIPPABLE1_21_5, StructuredDataKey.ITEM_MODEL,
StructuredDataKey.GLIDER, StructuredDataKey.TOOLTIP_STYLE, StructuredDataKey.DEATH_PROTECTION1_21_2, StructuredDataKey.WEAPON,
StructuredDataKey.POTION_DURATION_SCALE, StructuredDataKey.VILLAGER_VARIANT, StructuredDataKey.WOLF_VARIANT, StructuredDataKey.WOLF_COLLAR,
@@ -191,6 +192,9 @@ public final class Protocol1_21_4To1_21_5 extends AbstractProtocol<ClientboundPa
StructuredDataKey.PAINTING_VARIANT, StructuredDataKey.LLAMA_VARIANT, StructuredDataKey.AXOLOTL_VARIANT, StructuredDataKey.CAT_VARIANT,
StructuredDataKey.CAT_COLLAR, StructuredDataKey.SHEEP_COLOR, StructuredDataKey.SHULKER_COLOR, StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5,
StructuredDataKey.BREAK_SOUND, StructuredDataKey.COW_VARIANT, StructuredDataKey.CHICKEN_VARIANT1_21_5, StructuredDataKey.WOLF_SOUND_VARIANT);
tagRewriter.renameTag(RegistryType.BLOCK, "dead_bush_may_place_on", "dry_vegetation_may_place_on");
super.onMappingDataLoaded();
}
@@ -302,6 +302,7 @@ public final class BlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<
final StructuredDataContainer dataContainer = item.dataContainer();
dataContainer.replaceKey(StructuredDataKey.TOOL1_20_5, StructuredDataKey.TOOL1_21_5);
dataContainer.replaceKey(StructuredDataKey.EQUIPPABLE1_21_2, StructuredDataKey.EQUIPPABLE1_21_5);
dataContainer.replaceKey(StructuredDataKey.CONSUMABLE1_21_2, StructuredDataKey.CONSUMABLE1_21_5);
dataContainer.replace(StructuredDataKey.INSTRUMENT1_21_2, StructuredDataKey.INSTRUMENT1_21_5, EitherHolder::of);
// Collect hidden tooltips
@@ -408,6 +409,7 @@ public final class BlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<
final StructuredDataContainer dataContainer = item.dataContainer();
dataContainer.replaceKey(StructuredDataKey.TOOL1_21_5, StructuredDataKey.TOOL1_20_5);
dataContainer.replaceKey(StructuredDataKey.EQUIPPABLE1_21_5, StructuredDataKey.EQUIPPABLE1_21_2);
dataContainer.replaceKey(StructuredDataKey.CONSUMABLE1_21_5, StructuredDataKey.CONSUMABLE1_21_2);
dataContainer.replace(StructuredDataKey.INSTRUMENT1_21_5, StructuredDataKey.INSTRUMENT1_21_2, instrument -> instrument.hasHolder() ? instrument.holder() : null);
final TooltipDisplay tooltipDisplay = dataContainer.get(StructuredDataKey.TOOLTIP_DISPLAY);
@@ -417,7 +419,8 @@ public final class BlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<
}
final FullMappings mappings = Protocol1_21_4To1_21_5.MAPPINGS.getDataComponentSerializerMappings();
if (tooltipDisplay.hiddenComponents().containsAll(HIDE_ADDITIONAL_KEYS.stream().map(key -> mappings.id(key.identifier())).toList())) {
// Components added in 1.21.5 don't have an id here and have already been dropped from the hidden set
if (tooltipDisplay.hiddenComponents().containsAll(HIDE_ADDITIONAL_KEYS.stream().map(key -> mappings.id(key.identifier())).filter(id -> id != -1).toList())) {
dataContainer.set(StructuredDataKey.HIDE_ADDITIONAL_TOOLTIP);
}
}
@@ -478,7 +481,7 @@ public final class BlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<
final ProtocolVersion serverVersion = connection.getProtocolInfo().serverProtocolVersion();
if (serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_8)) {
if (item.identifier() == 858 || item.identifier() == 863 || item.identifier() == 873 || item.identifier() == 868 || item.identifier() == 878) { // swords
item.dataContainer().remove(StructuredDataKey.CONSUMABLE1_21_2);
item.dataContainer().remove(StructuredDataKey.CONSUMABLE1_21_5);
item.dataContainer().set(StructuredDataKey.BLOCKS_ATTACKS1_21_5,
new BlocksAttacks(
0F,
@@ -31,10 +31,12 @@ import com.viaversion.viaversion.codec.hash.HashOps;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.Protocol1_21_4To1_21_5;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
public class ItemHashStorage1_21_5 implements ItemHasher {
private final Cache<Long, StructuredData<?>> hashToStructuredData = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(512).build();
private final long keyMultiplier = ThreadLocalRandom.current().nextLong() | 1; // Odd to keep keys unique, random to not have predictable buckets
private boolean processingClientboundInventoryPacket;
private final CodecContext context;
@@ -54,7 +56,7 @@ public class ItemHashStorage1_21_5 implements ItemHasher {
return;
}
final long key = (long) structuredData.id() << 32 | hash;
final long key = key(structuredData.id(), hash);
try {
this.hashToStructuredData.get(key, structuredData::copy);
} catch (final ExecutionException e) {
@@ -63,11 +65,16 @@ public class ItemHashStorage1_21_5 implements ItemHasher {
}
public StructuredData<?> dataFromHash(final int dataComponentId, final int hash) {
final long key = (long) dataComponentId << 32 | hash;
final long key = key(dataComponentId, hash);
final StructuredData<?> data = this.hashToStructuredData.getIfPresent(key);
return data != null ? data.copy() : null;
}
private long key(final int dataComponentId, final int hash) {
// Add a key multiplier to avoid collisions from Guava's cache unseeded hashing
return ((long) dataComponentId << 32 | (hash & 0xFFFFFFFFL)) * this.keyMultiplier;
}
private static <T> int hash(final Hasher hasher, final StructuredData<T> data) {
hasher.write(data.key().type(), data.value());
return hasher.hash();
@@ -21,6 +21,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.data.MappingDataBase;
import com.viaversion.viaversion.api.minecraft.GameMode;
import com.viaversion.viaversion.api.minecraft.RegistryType;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_6;
import com.viaversion.viaversion.api.protocol.AbstractProtocol;
@@ -131,7 +132,7 @@ public final class Protocol1_21_5To1_21_6 extends AbstractProtocol<ClientboundPa
StructuredDataKey.POT_DECORATIONS1_20_5, StructuredDataKey.BLOCK_STATE, StructuredDataKey.BEES1_20_5, StructuredDataKey.LOCK1_21_2,
StructuredDataKey.CONTAINER_LOOT, StructuredDataKey.TOOL1_21_5, StructuredDataKey.ITEM_NAME, StructuredDataKey.OMINOUS_BOTTLE_AMPLIFIER,
StructuredDataKey.FOOD1_21_2, StructuredDataKey.JUKEBOX_PLAYABLE1_21_5, StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_6,
StructuredDataKey.REPAIRABLE, StructuredDataKey.ENCHANTABLE, StructuredDataKey.CONSUMABLE1_21_2,
StructuredDataKey.REPAIRABLE, StructuredDataKey.ENCHANTABLE, StructuredDataKey.CONSUMABLE1_21_5,
StructuredDataKey.USE_COOLDOWN, StructuredDataKey.DAMAGE, StructuredDataKey.EQUIPPABLE1_21_6, StructuredDataKey.ITEM_MODEL,
StructuredDataKey.GLIDER, StructuredDataKey.TOOLTIP_STYLE, StructuredDataKey.DEATH_PROTECTION1_21_2, StructuredDataKey.WEAPON,
StructuredDataKey.POTION_DURATION_SCALE, StructuredDataKey.VILLAGER_VARIANT, StructuredDataKey.WOLF_VARIANT, StructuredDataKey.WOLF_COLLAR,
@@ -141,6 +142,9 @@ public final class Protocol1_21_5To1_21_6 extends AbstractProtocol<ClientboundPa
StructuredDataKey.PAINTING_VARIANT, StructuredDataKey.LLAMA_VARIANT, StructuredDataKey.AXOLOTL_VARIANT, StructuredDataKey.CAT_VARIANT,
StructuredDataKey.CAT_COLLAR, StructuredDataKey.SHEEP_COLOR, StructuredDataKey.SHULKER_COLOR, StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5,
StructuredDataKey.BREAK_SOUND, StructuredDataKey.COW_VARIANT, StructuredDataKey.CHICKEN_VARIANT1_21_5, StructuredDataKey.WOLF_SOUND_VARIANT);
tagRewriter.renameTag(RegistryType.BLOCK, "plays_ambient_desert_block_sounds", "triggers_ambient_desert_dry_vegetation_block_sounds");
super.onMappingDataLoaded();
}
@@ -155,7 +155,7 @@ public final class Protocol1_21_7To1_21_9 extends AbstractProtocol<ClientboundPa
StructuredDataKey.POT_DECORATIONS1_20_5, StructuredDataKey.BLOCK_STATE, StructuredDataKey.BEES1_21_9, StructuredDataKey.LOCK1_21_2,
StructuredDataKey.CONTAINER_LOOT, StructuredDataKey.TOOL1_21_5, StructuredDataKey.ITEM_NAME, StructuredDataKey.OMINOUS_BOTTLE_AMPLIFIER,
StructuredDataKey.FOOD1_21_2, StructuredDataKey.JUKEBOX_PLAYABLE1_21_5, StructuredDataKey.ATTRIBUTE_MODIFIERS1_21_6,
StructuredDataKey.REPAIRABLE, StructuredDataKey.ENCHANTABLE, StructuredDataKey.CONSUMABLE1_21_2,
StructuredDataKey.REPAIRABLE, StructuredDataKey.ENCHANTABLE, StructuredDataKey.CONSUMABLE1_21_5,
StructuredDataKey.USE_COOLDOWN, StructuredDataKey.DAMAGE, StructuredDataKey.EQUIPPABLE1_21_6, StructuredDataKey.ITEM_MODEL,
StructuredDataKey.GLIDER, StructuredDataKey.TOOLTIP_STYLE, StructuredDataKey.DEATH_PROTECTION1_21_2, StructuredDataKey.WEAPON,
StructuredDataKey.POTION_DURATION_SCALE, StructuredDataKey.VILLAGER_VARIANT, StructuredDataKey.WOLF_VARIANT, StructuredDataKey.WOLF_COLLAR,
@@ -81,7 +81,13 @@ public final class BlockItemPacketRewriter1_21_9 extends StructuredItemRewriter<
}
public static void downgradeData(final Item item, final StructuredDataContainer container) {
container.replaceKey(StructuredDataKey.BEES1_21_9, StructuredDataKey.BEES1_20_5);
container.replace(StructuredDataKey.BEES1_21_9, StructuredDataKey.BEES1_20_5, bees -> {
for (final Bee bee : bees) {
// Stripped from the tag since 1.21.9 but required to load the entity before
bee.entityData().tag().putString("id", "minecraft:bee");
}
return bees;
});
container.replace(StructuredDataKey.ENTITY_DATA1_21_9, StructuredDataKey.ENTITY_DATA1_20_5, entityData -> {
final String id = Protocol1_21_7To1_21_9.MAPPINGS.getEntityMappings().identifier(entityData.type());
entityData.tag().putString("id", id);
@@ -89,9 +89,11 @@ public final class BlockItemPacketRewriter1_21_11 extends StructuredItemRewriter
}
public static void upgradeData(final Item item, final StructuredDataContainer container) {
container.replaceKey(StructuredDataKey.CONSUMABLE1_21_5, StructuredDataKey.CONSUMABLE1_21_2);
}
public static void downgradeData(final Item item, final StructuredDataContainer container) {
container.replaceKey(StructuredDataKey.CONSUMABLE1_21_2, StructuredDataKey.CONSUMABLE1_21_5);
container.remove(StructuredDataKey.SWING_ANIMATION);
container.remove(StructuredDataKey.KINETIC_WEAPON);
container.remove(StructuredDataKey.PIERCING_WEAPON);
@@ -38,7 +38,7 @@ public final class ComponentRewriter1_21_11 extends NBTComponentRewriter<Clientb
final StringTag sprite = tag.getStringTag("sprite");
if (sprite != null) {
final String strippedSprite = Key.stripNamespace(sprite.getValue());
if (strippedSprite.startsWith("item/")) {
if (strippedSprite.startsWith("item/") || strippedSprite.startsWith("trims/items/")) {
tag.putString("atlas", "items");
} else if (strippedSprite.startsWith("block/")) {
tag.putString("atlas", "blocks");
@@ -502,6 +502,7 @@ public final class BlockItemPacketRewriter1_21_2 extends StructuredItemRewriter<
final IntArrayTag emptyEnchantments = customData.getIntArrayTag(nbtTagName("0_enchants"));
if (emptyEnchantments != null) {
Limit.max(emptyEnchantments.length(), 256);
Enchantments enchantments = dataContainer.get(StructuredDataKey.ENCHANTMENTS1_20_5);
if (enchantments == null) {
enchantments = new Enchantments(true);
@@ -409,7 +409,10 @@ public class ItemPacketRewriter1_9 extends ItemRewriter<ClientboundPackets1_8, S
if (tag == null) {
tag = new CompoundTag();
}
CompoundTag entityTag = new CompoundTag();
CompoundTag entityTag = tag.getCompoundTag("EntityTag");
if (entityTag == null) {
entityTag = new CompoundTag();
}
String entityName = EntityIds1_8.ENTITY_ID_TO_NAME.get((int) item.data());
if (entityName != null) {
StringTag id = new StringTag(entityName);
@@ -493,7 +496,13 @@ public class ItemPacketRewriter1_9 extends ItemRewriter<ClientboundPackets1_8, S
data = EntityIds1_8.ENTITY_NAME_TO_ID.get(id.getValue());
}
}
tag.remove("EntityTag");
entityTag.remove("id");
if (entityTag.isEmpty()) {
tag.remove("EntityTag");
}
}
if (tag != null && tag.isEmpty()) {
tag = null;
}
item.setTag(tag);
item.setData((short) data);
@@ -509,6 +518,9 @@ public class ItemPacketRewriter1_9 extends ItemRewriter<ClientboundPackets1_8, S
}
tag.remove("Potion");
}
if (tag != null && tag.isEmpty()) {
tag = null;
}
item.setTag(tag);
item.setData((short) data);
}
@@ -525,6 +537,9 @@ public class ItemPacketRewriter1_9 extends ItemRewriter<ClientboundPackets1_8, S
}
tag.remove("Potion");
}
if (tag != null && tag.isEmpty()) {
tag = null;
}
item.setTag(tag);
item.setData((short) data);
}
@@ -216,6 +216,11 @@ public class SpawnPacketRewriter1_9 {
int entityID = wrapper.get(Types.VAR_INT, 0);
EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
tracker.handleEntityData(entityID, entityDataList);
// dragon default phase on 1.9+ is 10 (hovering), so we set it to match the only phase 1.8 had, which was 0 (circling)
if (tracker.entityType(entityID) == EntityTypes1_9.EntityType.ENDER_DRAGON) {
entityDataList.add(new EntityData(11, EntityDataTypes1_9.VAR_INT, 0)); // 11 = dragon phase id
}
});
}
});
@@ -21,8 +21,10 @@ import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.HolderSet;
import com.viaversion.viaversion.api.minecraft.RegistryEntry;
import com.viaversion.viaversion.api.minecraft.RegistryType;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys26_2;
import com.viaversion.viaversion.api.minecraft.data.version.StructuredDataKeys26_3;
@@ -38,6 +40,7 @@ import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.misc.ParticleType;
import com.viaversion.viaversion.api.type.types.version.Types1_20_5;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
import com.viaversion.viaversion.connection.ProtocolStorablesBase;
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1;
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPackets26_1;
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ServerboundPacket26_1;
@@ -56,6 +59,7 @@ import com.viaversion.viaversion.protocols.v26_2to26_3.rewriter.ComponentRewrite
import com.viaversion.viaversion.protocols.v26_2to26_3.rewriter.EntityPacketRewriter26_3;
import com.viaversion.viaversion.protocols.v26_2to26_3.rewriter.RecipeDisplayRewriter26_3;
import com.viaversion.viaversion.protocols.v26_2to26_3.rewriter.RegistryDataRewriter26_3;
import com.viaversion.viaversion.protocols.v26_2to26_3.storage.ProtocolStorables26_3;
import com.viaversion.viaversion.rewriter.BlockRewriter;
import com.viaversion.viaversion.rewriter.ParticleRewriter;
import com.viaversion.viaversion.rewriter.RecipeDisplayRewriter;
@@ -69,6 +73,10 @@ import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
public final class Protocol26_2To26_3 extends AbstractProtocol<ClientboundPacket26_1, ClientboundPacket26_3, ServerboundPacket26_1, ServerboundPacket26_3> {
public static final MappingData26_3 MAPPINGS = new MappingData26_3();
private static final int SPLASH_POTION_SOUND = 1053;
private static final int INSTANT_SPLASH_POTION_SOUND = 1054;
private static final int SPLASH_POTION_PARTICLES = 2002;
private static final int INSTANT_SPLASH_POTION_PARTICLES = 2007;
private static final String[] POT_PATTERNS = {
"angler",
"archer",
@@ -172,6 +180,27 @@ public final class Protocol26_2To26_3 extends AbstractProtocol<ClientboundPacket
componentRewriter.processTag(wrapper.user(), wrapper.passthrough(Types.TRUSTED_OPTIONAL_TAG)); // Target Name
});
// 26.3 plays the potion splash sound through its own level event, older clients played it together with the particles
appendClientbound(ClientboundPackets26_1.LEVEL_EVENT, wrapper -> {
final int id = wrapper.get(Types.INT, 0);
final BlockPosition position = wrapper.get(Types.BLOCK_POSITION1_14, 0);
final int soundId;
if (id == SPLASH_POTION_PARTICLES) {
soundId = SPLASH_POTION_SOUND;
} else if (id == INSTANT_SPLASH_POTION_PARTICLES) {
soundId = INSTANT_SPLASH_POTION_SOUND;
} else {
return;
}
final PacketWrapper soundPacket = wrapper.create(ClientboundPackets26_3.LEVEL_EVENT);
soundPacket.write(Types.INT, soundId);
soundPacket.write(Types.BLOCK_POSITION1_14, position);
soundPacket.write(Types.INT, 0); // Data
soundPacket.write(Types.BOOLEAN, false); // Global
soundPacket.scheduleSend(Protocol26_2To26_3.class);
});
// 26.3 reads the items the brewing stand slots accept from the server, older clients have them hardcoded
appendClientbound(ClientboundPackets26_1.UPDATE_RECIPES, wrapper -> {
wrapper.resetReader();
@@ -221,9 +250,17 @@ public final class Protocol26_2To26_3 extends AbstractProtocol<ClientboundPacket
StructuredDataKey.PROVIDES_POTTERY_PATTERN, StructuredDataKey.BLOCK_TRANSFORMER, StructuredDataKey.COMPOSTABLE, StructuredDataKey.VILLAGER_FOOD,
StructuredDataKey.COOKING_FUEL, StructuredDataKey.BREWING_FUEL, StructuredDataKey.MOB_VISIBILITY, StructuredDataKey.SIGN_TEXT_BACK, StructuredDataKey.SIGN_TEXT_FRONT,
StructuredDataKey.WAXED, StructuredDataKey.CUSHION_COLOR, StructuredDataKey.ATTACK_ANIMATION, StructuredDataKey.INTERACT_ANIMATION);
tagRewriter.renameTag(RegistryType.BLOCK, "convertable_to_mud", "convertible_to_mud");
super.onMappingDataLoaded();
}
@Override
public ProtocolStorablesBase createStorables() {
return new ProtocolStorables26_3();
}
@Override
public void init(final UserConnection connection) {
addEntityTracker(connection);
@@ -28,7 +28,8 @@ public final class MappingData26_3 extends MappingDataBase {
private static final String[] BREWING_INPUTS = {
"potion",
"splash_potion",
"lingering_potion"
"lingering_potion",
"glass_bottle"
};
private static final String[] BREWING_REAGENTS = {
"blaze_powder",
@@ -18,6 +18,7 @@
package com.viaversion.viaversion.protocols.v26_2to26_3.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.IntTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.connection.UserConnection;
@@ -36,6 +37,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.trim.ArmorTrim26_3;
import com.viaversion.viaversion.api.minecraft.item.data.trim.ArmorTrimMaterial1_20_5;
import com.viaversion.viaversion.api.minecraft.item.data.trim.ArmorTrimMaterial26_3;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.VersionedTypes;
import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ClientboundPacket26_1;
@@ -201,6 +203,10 @@ public final class BlockItemPacketRewriter26_3 extends StructuredItemRewriter<Cl
@Override
protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) {
upgradeData(container);
// Add data components to fix issues in older protocols
appendItemDataFixComponents(connection, item, container);
super.handleItemDataComponentsToClient(connection, item, container);
}
@@ -210,10 +216,31 @@ public final class BlockItemPacketRewriter26_3 extends StructuredItemRewriter<Cl
super.handleItemDataComponentsToServer(connection, item, container);
}
private void appendItemDataFixComponents(final UserConnection connection, final Item item, final StructuredDataContainer container) {
final ProtocolVersion serverVersion = connection.getProtocolInfo().serverProtocolVersion();
if (serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_12_2)) {
// Axes only started stripping logs in 1.13
if (item.identifier() == 1043 || item.identifier() == 1033 || item.identifier() == 1053 || item.identifier() == 1058 || item.identifier() == 1048 || item.identifier() == 1038 || item.identifier() == 1028) {
container.setEmpty(StructuredDataKey.BLOCK_TRANSFORMER);
}
}
if (serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_8)) {
// Shovels only started creating dirt paths in 1.9
if (item.identifier() == 1036 || item.identifier() == 1046 || item.identifier() == 1041 || item.identifier() == 1026 || item.identifier() == 1056 || item.identifier() == 1031 || item.identifier() == 1051) {
container.setEmpty(StructuredDataKey.BLOCK_TRANSFORMER);
}
}
}
public static void upgradeData(final StructuredDataContainer container) {
container.remove(StructuredDataKey.MAP_COLOR);
container.replaceKey(StructuredDataKey.INSTRUMENT26_1, StructuredDataKey.INSTRUMENT26_3);
if (container.hasEmpty(StructuredDataKey.SWING_ANIMATION)) {
container.remove(StructuredDataKey.SWING_ANIMATION);
container.setEmpty(StructuredDataKey.ATTACK_ANIMATION);
container.setEmpty(StructuredDataKey.INTERACT_ANIMATION);
}
container.replace(StructuredDataKey.SWING_ANIMATION, animation -> {
container.set(StructuredDataKey.ATTACK_ANIMATION, animation);
container.set(StructuredDataKey.INTERACT_ANIMATION, animation);
@@ -272,7 +299,7 @@ public final class BlockItemPacketRewriter26_3 extends StructuredItemRewriter<Cl
for (final Map.Entry<String, Tag> entry : mapDecorations.entrySet()) {
final StringTag typeTag = ((CompoundTag) entry.getValue()).getStringTag("type");
if (NEW_MAP_DECORATION_TYPES.contains(Key.stripMinecraftNamespace(typeTag.getValue()))) {
typeTag.setValue("village_plains");
typeTag.setValue("target_x");
}
}
}
@@ -352,6 +379,11 @@ public final class BlockItemPacketRewriter26_3 extends StructuredItemRewriter<Cl
}
backupTrimMaterial(backupTag, "provides_trim_material", container.get(StructuredDataKey.PROVIDES_TRIM_MATERIAL26_1));
final Integer mapColor = container.get(StructuredDataKey.MAP_COLOR);
if (mapColor != null) {
backupTag.putInt("map_color", mapColor);
}
}
private void backupTrimMaterial(final CompoundTag tag, final String key, @Nullable final Holder<ArmorTrimMaterial1_20_5> materialHolder) {
@@ -386,6 +418,11 @@ public final class BlockItemPacketRewriter26_3 extends StructuredItemRewriter<Cl
if (providesTrimMaterialTag != null) {
container.replace(StructuredDataKey.PROVIDES_TRIM_MATERIAL26_3, StructuredDataKey.PROVIDES_TRIM_MATERIAL26_1, material -> restoreTrimMaterial(material, providesTrimMaterialTag));
}
final IntTag mapColorTag = backupTag.getIntTag("map_color");
if (mapColorTag != null) {
container.set(StructuredDataKey.MAP_COLOR, mapColorTag.asInt());
}
}
private Holder<ArmorTrimMaterial1_20_5> restoreTrimMaterial(final Holder<ArmorTrimMaterial26_3> holder, final CompoundTag tag) {
@@ -17,6 +17,7 @@
*/
package com.viaversion.viaversion.protocols.v26_2to26_3.rewriter;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes26_3;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes26_3;
@@ -30,6 +31,7 @@ import com.viaversion.viaversion.protocols.v1_21_11to26_1.packet.ServerboundPack
import com.viaversion.viaversion.protocols.v26_2to26_3.Protocol26_2To26_3;
import com.viaversion.viaversion.protocols.v26_2to26_3.packet.ClientboundPackets26_3;
import com.viaversion.viaversion.protocols.v26_2to26_3.packet.ServerboundPackets26_3;
import com.viaversion.viaversion.protocols.v26_2to26_3.storage.ProtocolStorables26_3;
import com.viaversion.viaversion.rewriter.EntityRewriter;
public final class EntityPacketRewriter26_3 extends EntityRewriter<ClientboundPacket26_1, Protocol26_2To26_3> {
@@ -40,7 +42,12 @@ public final class EntityPacketRewriter26_3 extends EntityRewriter<ClientboundPa
private static final int INTERPOLATION_STEP_TICKS = 3;
private static final int MAIN_HAND = 0;
private static final int OFF_HAND = 1;
private static final int START_DESTROY_BLOCK_ACTION = 0;
private static final int CHANGE_DESTROY_DIRECTION_ACTION = 1;
private static final int ABORT_DESTROY_BLOCK_ACTION = 2;
private static final int STOP_DESTROY_BLOCK_ACTION = 3;
private static final int DESTROY_PROGRESS_PARTICLES = 2019;
private static final int DESTROY_PROGRESS_PARTICLES_AND_SOUND = 2020;
private static final SwingAnimation DEFAULT_SWING_ANIMATION = new SwingAnimation(EnumTypes.SWING_ANIMATION1_21_11.idFromName("whack"), 6);
public EntityPacketRewriter26_3(final Protocol26_2To26_3 protocol) {
@@ -139,16 +146,45 @@ public final class EntityPacketRewriter26_3 extends EntityRewriter<ClientboundPa
protocol.registerServerbound(ServerboundPackets26_3.PUNCH, ServerboundPackets26_1.SWING, wrapper -> {
wrapper.write(Types.VAR_INT, 0); // Main hand
// Sent every tick while destroying a block
final ProtocolStorables26_3 storables = wrapper.user().storables(protocol);
storables.setPunched();
});
protocol.registerServerbound(ServerboundPackets26_3.CLIENT_TICK_END, wrapper -> {
// Needs to be sent by the server every tick after starting now
final ProtocolStorables26_3 storables = wrapper.user().storables(protocol);
final int ticks = storables.endDestroyTick();
if (ticks <= 0) {
return;
}
final PacketWrapper levelEvent = wrapper.create(ClientboundPackets26_3.LEVEL_EVENT);
levelEvent.write(Types.INT, ticks % 4 == 0 ? DESTROY_PROGRESS_PARTICLES_AND_SOUND : DESTROY_PROGRESS_PARTICLES);
levelEvent.write(Types.BLOCK_POSITION1_14, storables.destroyPosition());
levelEvent.write(Types.INT, storables.destroyDirection());
levelEvent.write(Types.BOOLEAN, false); // Global
levelEvent.scheduleSend(Protocol26_2To26_3.class);
});
protocol.registerServerbound(ServerboundPackets26_3.PLAYER_ACTION, wrapper -> {
final int action = wrapper.read(Types.VAR_INT);
final ProtocolStorables26_3 storables = wrapper.user().storables(protocol);
if (action == CHANGE_DESTROY_DIRECTION_ACTION) {
wrapper.read(Types.BLOCK_POSITION1_14); // Position
storables.setDestroyDirection(wrapper.read(Types.UNSIGNED_BYTE));
wrapper.cancel(); // 26.2 has no equivalent and never sends anything here
return;
}
wrapper.write(Types.VAR_INT, action > CHANGE_DESTROY_DIRECTION_ACTION ? action - 1 : action);
if (action == START_DESTROY_BLOCK_ACTION) {
final BlockPosition position = wrapper.passthrough(Types.BLOCK_POSITION1_14);
storables.startDestroying(position, wrapper.passthrough(Types.UNSIGNED_BYTE));
} else if (action == ABORT_DESTROY_BLOCK_ACTION || action == STOP_DESTROY_BLOCK_ACTION) {
storables.stopDestroying();
}
});
protocol.registerClientbound(ClientboundPackets26_1.ANIMATE, wrapper -> {
@@ -18,12 +18,15 @@
package com.viaversion.viaversion.protocols.v26_2to26_3.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.IntTag;
import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.NumberTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.protocols.v26_2to26_3.Protocol26_2To26_3;
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.util.Key;
import java.util.function.UnaryOperator;
public final class RegistryDataRewriter26_3 extends RegistryDataRewriter {
@@ -36,9 +39,10 @@ public final class RegistryDataRewriter26_3 extends RegistryDataRewriter {
final StringTag condition = term.removeUnchecked("condition");
if (condition != null) {
term.put("type", condition);
updateNumberProviders(term, condition);
}
if (Key.equals(condition.getValue(), "damage_source_properties")) {
if (condition != null && Key.equals(condition.getValue(), "damage_source_properties")) {
final CompoundTag predicate = term.getCompoundTag("predicate");
if (predicate != null) {
final ListTag<CompoundTag> tags = predicate.getListTag("tags", CompoundTag.class);
@@ -51,7 +55,7 @@ public final class RegistryDataRewriter26_3 extends RegistryDataRewriter {
super.updateEnchantmentTerm(term);
// has to run after super, which maps the block id under the old key
if (Key.equals(condition.getValue(), "block_state_property")) {
if (condition != null && Key.equals(condition.getValue(), "block_state_property")) {
condition.setValue("minecraft:match_block");
term.put("blocks", term.remove("block"));
@@ -62,6 +66,117 @@ public final class RegistryDataRewriter26_3 extends RegistryDataRewriter {
}
}
// Number providers have been split into int and float providers
private static void updateNumberProviders(final CompoundTag term, final StringTag condition) {
if (Key.equals(condition.getValue(), "value_check")) {
condition.setValue("int_value_check");
convert(term, "value", "value", RegistryDataRewriter26_3::toIntProvider);
convert(term, "range", "test", RegistryDataRewriter26_3::updateIntRange);
} else if (Key.equals(condition.getValue(), "time_check")) {
convert(term, "value", "value", RegistryDataRewriter26_3::updateIntRange);
} else if (Key.equals(condition.getValue(), "random_chance")) {
convert(term, "chance", "chance", RegistryDataRewriter26_3::toFloatProvider);
}
}
private static Tag updateIntRange(final Tag range) {
if (range instanceof CompoundTag rangeTag) {
convert(rangeTag, "min", "min", RegistryDataRewriter26_3::toIntProvider);
convert(rangeTag, "max", "max", RegistryDataRewriter26_3::toIntProvider);
}
return range;
}
// Old providers were floats, with getInt rounding the value
private static Tag toIntProvider(final Tag tag) {
if (tag instanceof NumberTag numberTag) {
return new IntTag(Math.round(numberTag.asFloat()));
} else if (tag instanceof CompoundTag provider) {
final String type = provider.getString("type");
if (Key.equals(type, "binomial")) {
convert(provider, "n", "n", RegistryDataRewriter26_3::toIntProvider);
convert(provider, "p", "p", RegistryDataRewriter26_3::toFloatProvider);
return provider;
} else if (Key.equals(type, "score") && provider.getFloat("scale", 1) == 1) {
provider.remove("scale");
return provider;
}
}
return unary("from_float", unary("round", toFloatProvider(tag)));
}
private static Tag toFloatProvider(final Tag tag) {
if (!(tag instanceof CompoundTag provider)) {
return tag;
}
// Untyped providers used to fall back to uniform
switch (Key.stripMinecraftNamespace(provider.getString("type", "uniform"))) {
case "uniform" -> {
provider.putString("type", "minecraft:uniform");
convert(provider, "min", "min", RegistryDataRewriter26_3::toFloatProvider);
convert(provider, "max", "max", RegistryDataRewriter26_3::toFloatProvider);
}
case "sum" -> {
provider.putString("type", "minecraft:add");
convert(provider, "summands", "inputs", summands -> {
final ListTag<CompoundTag> inputs = new ListTag<>(CompoundTag.class);
if (summands instanceof ListTag<?> summandsList) {
for (final Tag summand : summandsList) {
inputs.add(toFloatProviderCompound(summand));
}
}
return inputs;
});
}
case "binomial" -> {
return unary("from_int", toIntProvider(provider));
}
case "score" -> {
final Tag scale = provider.remove("scale");
final CompoundTag score = unary("from_int", provider);
if (scale == null) {
return score;
}
final ListTag<CompoundTag> inputs = new ListTag<>(CompoundTag.class);
inputs.add(score);
inputs.add(toFloatProviderCompound(scale));
final CompoundTag product = new CompoundTag();
product.putString("type", "minecraft:mul");
product.put("inputs", inputs);
return product;
}
}
return provider;
}
private static CompoundTag toFloatProviderCompound(final Tag tag) {
final Tag provider = toFloatProvider(tag);
if (provider instanceof CompoundTag compoundTag) {
return compoundTag;
}
final CompoundTag constant = new CompoundTag();
constant.putString("type", "minecraft:constant");
constant.put("value", provider);
return constant;
}
private static CompoundTag unary(final String type, final Tag input) {
final CompoundTag provider = new CompoundTag();
provider.putString("type", "minecraft:" + type);
provider.put("input", input);
return provider;
}
private static void convert(final CompoundTag tag, final String key, final String newKey, final UnaryOperator<Tag> converter) {
final Tag value = tag.remove(key);
if (value != null) {
tag.put(newKey, converter.apply(value));
}
}
@Override
public boolean updateBlockStateProvider(final CompoundTag tag) {
final StringTag typeTag = tag.getStringTag("type");
@@ -0,0 +1,75 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2026 ViaVersion 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 com.viaversion.viaversion.protocols.v26_2to26_3.storage;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.connection.ProtocolStorablesBase;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class ProtocolStorables26_3 extends ProtocolStorablesBase {
private BlockPosition destroyPosition;
private short destroyDirection;
private int destroyTicks;
private boolean punched;
public @Nullable BlockPosition destroyPosition() {
return destroyPosition;
}
public int destroyDirection() {
return destroyDirection;
}
public void setDestroyDirection(final short destroyDirection) {
this.destroyDirection = destroyDirection;
}
public void startDestroying(final BlockPosition position, final short direction) {
this.destroyPosition = position;
this.destroyDirection = direction;
this.destroyTicks = 0;
this.punched = false;
}
public void stopDestroying() {
this.destroyPosition = null;
}
public void setPunched() {
this.punched = true;
}
/**
* Returns the destroy tick that just ended, or -1 if the client did not continue destroying in it.
*
* @return destroy tick that just ended, or -1
*/
public int endDestroyTick() {
if (destroyPosition == null) {
return -1;
}
if (!punched) {
destroyPosition = null;
return -1;
}
punched = false;
return destroyTicks++;
}
}
@@ -35,6 +35,7 @@ import com.viaversion.viaversion.api.minecraft.chunks.PaletteType;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.rewriter.MappingDataListener;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.util.MathUtil;
@@ -44,7 +45,7 @@ import java.util.List;
import java.util.function.BiConsumer;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BlockRewriter<C extends ClientboundPacketType> {
public class BlockRewriter<C extends ClientboundPacketType> implements MappingDataListener {
protected final Protocol<C, ?, ?, ?> protocol;
private final Type<BlockPosition> positionType;
private final Type<CompoundTag> compoundTagType;
@@ -86,8 +86,10 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
switch (key) {
case "enchantment" -> updateEnchantments(connection, entries);
case "trim_material" -> updateTrimMaterials(entries);
case "jukebox_song" -> updateJukeboxSongs(entries);
case "trim_material" -> updateTrimMaterials(connection, entries);
case "jukebox_song" -> updateJukeboxSongs(connection, entries);
case "trim_pattern", "instrument" -> updateTextComponents(connection, entries, "description");
case "painting_variant" -> updateTextComponents(connection, entries, "title", "author");
case "worldgen/biome" -> updateBiomes(entries);
case "worldgen/block_state_provider" -> updateBlockStateProviders(entries);
case "dimension_type" -> updateDimensionTypes(entries);
@@ -291,6 +293,18 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
updateTextComponent(connection, tag, "tooltip");
}
protected void updateTextComponents(final UserConnection connection, final RegistryEntry[] entries, final String... keys) {
for (final RegistryEntry entry : entries) {
if (!(entry.tag() instanceof final CompoundTag tag)) {
continue;
}
for (final String key : keys) {
updateTextComponent(connection, tag, key);
}
}
}
protected void updateTextComponent(final UserConnection connection, @Nullable final CompoundTag tag, final String key) {
if (tag != null && protocol.getComponentRewriter() != null) {
protocol.getComponentRewriter().processTag(connection, tag.get(key));
@@ -333,6 +347,7 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
}
final CompoundTag tag = (CompoundTag) entry.tag();
updateTextComponent(connection, tag, "description");
if (!Mappings.isFullIdentity(protocol.getMappingData().getFullItemMappings())) {
updateItemList(tag.getListTag("supported_items", StringTag.class));
updateItemList(tag.getListTag("primary_items", StringTag.class));
@@ -365,7 +380,8 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
}
}
public void updateTrimMaterials(final RegistryEntry[] entries) {
public void updateTrimMaterials(final UserConnection connection, final RegistryEntry[] entries) {
updateTextComponents(connection, entries, "description");
if (Mappings.isFullIdentity(protocol.getMappingData().getFullItemMappings())) {
return;
}
@@ -384,7 +400,8 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
}
}
public void updateJukeboxSongs(final RegistryEntry[] entries) {
public void updateJukeboxSongs(final UserConnection connection, final RegistryEntry[] entries) {
updateTextComponents(connection, entries, "description");
// can be overridden
}
@@ -454,6 +471,14 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
}
}
}
Tag dripstoneParticle = TagUtil.getNamespacedTag(tag, "visual/default_dripstone_particle");
if (dripstoneParticle instanceof CompoundTag modifierTag && modifierTag.contains("argument")) {
dripstoneParticle = modifierTag.get("argument");
}
if (dripstoneParticle instanceof CompoundTag particle) {
handleParticleData(particle);
}
}
private void updateNestedEffect(final CompoundTag effectsTag) {
@@ -212,13 +212,24 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
return null;
}
final MappingData mappingData = protocol.getMappingData();
final FullMappings dataComponentMappings = mappingData != null ? mappingData.getDataComponentSerializerMappings() : null;
if (dataComponentMappings == null) {
return null;
}
final int dataComponentTypes = Math.max(dataComponentMappings.size(), dataComponentMappings.mappedSize());
final OriginalHashedItem hashedItem = new OriginalHashedItem(idTag.asInt(), item.amount(), backupTagName);
try {
for (final Map.Entry<String, Tag> entry : originalHashes.entrySet()) {
if (entry.getValue() instanceof IntTag hashTag) {
final String key = entry.getKey();
if (!key.equals("id")) {
hashedItem.dataHashesById().put(Integer.parseInt(key), hashTag.asInt());
final int id = Integer.parseInt(key);
if (id < 0 || id >= dataComponentTypes) {
return null;
}
hashedItem.dataHashesById().put(id, hashTag.asInt());
}
}
}
@@ -229,6 +240,9 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
final IntArrayTag removedTag = originalHashes.getIntArrayTag("removed");
if (removedTag != null) {
for (final int id : removedTag.getValue()) {
if (id < 0 || id >= dataComponentTypes) {
return null;
}
hashedItem.removedDataIds().add(id);
}
}
@@ -423,11 +437,11 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
if (customName != null) {
container.set(StructuredDataKey.CUSTOM_NAME, customName);
}
}
final Tag itemName = removeBackupTag(customData, "item_name");
if (itemName != null) {
container.set(StructuredDataKey.ITEM_NAME, itemName);
}
final Tag itemName = removeBackupTag(customData, "item_name");
if (itemName != null) {
container.set(StructuredDataKey.ITEM_NAME, itemName);
}
}
@@ -29,6 +29,11 @@ import com.viaversion.viaversion.rewriter.BlockRewriter;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BlockRewriter1_21_5<C extends ClientboundPacketType> extends BlockRewriter<C> {
private int signId = -1;
private int hangingSignId = -1;
private int shelfId = -1;
private int campfireId = -1;
private int vaultId = -1;
public BlockRewriter1_21_5(final Protocol<C, ?, ?, ?> protocol, final ChunkTypeSupplier chunkTypeSupplier, @Nullable final ChunkTypeSupplier mappedChunkTypeSupplier) {
super(protocol, Types.BLOCK_POSITION1_14, Types.TRUSTED_COMPOUND_TAG, chunkTypeSupplier, mappedChunkTypeSupplier);
@@ -41,17 +46,46 @@ public class BlockRewriter1_21_5<C extends ClientboundPacketType> extends BlockR
@Override
public void handleBlockEntity(final UserConnection connection, final BlockEntity blockEntity) {
final CompoundTag tag = blockEntity.tag();
if (tag == null) {
if (tag == null || protocol.getComponentRewriter() == null) {
return;
}
final FullMappings blockEntityMappings = protocol.getMappingData().getBlockEntityMappings();
if (blockEntityMappings != null && protocol.getComponentRewriter() != null) {
final int typeId = blockEntity.typeId();
if (typeId == signId || typeId == hangingSignId) {
// Update sign text components, relevant as of 1.21.5 given they are properly parsed and should not error on the client
if (blockEntity.typeId() == blockEntityMappings.mappedId("sign") || blockEntity.typeId() == blockEntityMappings.mappedId("hanging_sign")) {
updateSignMessages(connection, tag.getCompoundTag("front_text"));
updateSignMessages(connection, tag.getCompoundTag("back_text"));
updateSignMessages(connection, tag.getCompoundTag("front_text"));
updateSignMessages(connection, tag.getCompoundTag("back_text"));
} else if (typeId == shelfId || typeId == campfireId) {
// Update items that are actually visible to the client
final ListTag<CompoundTag> items = tag.getListTag("Items", CompoundTag.class);
if (items != null) {
for (final CompoundTag item : items) {
updateItem(connection, item);
}
}
} else if (typeId == vaultId) {
final CompoundTag sharedData = tag.getCompoundTag("shared_data");
if (sharedData != null) {
updateItem(connection, sharedData.getCompoundTag("display_item"));
}
}
}
@Override
public void onMappingDataLoaded() {
final FullMappings blockEntityMappings = protocol.getMappingData() != null ? protocol.getMappingData().getBlockEntityMappings() : null;
if (blockEntityMappings != null) {
signId = blockEntityMappings.mappedId("sign");
hangingSignId = blockEntityMappings.mappedId("hanging_sign");
shelfId = blockEntityMappings.mappedId("shelf");
campfireId = blockEntityMappings.mappedId("campfire");
vaultId = blockEntityMappings.mappedId("vault");
}
}
private void updateItem(final UserConnection connection, @Nullable final CompoundTag item) {
if (item != null && item.getStringTag("id") != null) {
protocol.getComponentRewriter().handleShowItem(connection, item);
}
}
@@ -418,6 +418,35 @@ public abstract class ComponentRewriterBase<C extends ClientboundPacketType> imp
// Entity / block entity types
handleTypedEntityData(componentsTag, "entity_data", protocol.getMappingData().getEntityMappings());
handleTypedEntityData(componentsTag, "block_entity_data", protocol.getMappingData().getBlockEntityMappings());
handleTooltipDisplay(componentsTag);
}
private void handleTooltipDisplay(final CompoundTag componentsTag) {
final FullMappings mappings = protocol.getMappingData().getDataComponentSerializerMappings();
if (Mappings.isFullIdentity(mappings)) {
return;
}
final CompoundTag tooltipDisplay = TagUtil.getNamespacedCompoundTag(componentsTag, "tooltip_display");
if (tooltipDisplay == null) {
return;
}
final ListTag<StringTag> hiddenComponents = tooltipDisplay.getListTag("hidden_components", StringTag.class);
if (hiddenComponents == null) {
return;
}
// Remap or remove data component types that have been renamed or removed
hiddenComponents.getValue().removeIf(componentTag -> {
final String mappedId = mappings.mappedIdentifier(componentTag.getValue());
if (mappedId != null) {
componentTag.setValue(mappedId);
return false;
}
return true;
});
}
private void handleSoundEventTag(@Nullable final Tag soundEventTag, final FullMappings soundMappings) {
@@ -194,6 +194,10 @@ enforce-secure-chat: false
# Handles items with invalid count values (higher than max stack size) on 1.20.3 servers.
handle-invalid-item-count: false
#
# Backs up the original data of nested items (e.g. in shulker boxes or bundles) for 1.20.5+ clients on 1.20.4 and older servers.
# This keeps their data intact when taken out individually in creative mode, but greatly increases the size of deeply nested items.
backup-nested-items: false
#
# Hides sidebar scoreboard numbers for 1.20.3+ clients on older server versions.
hide-scoreboard-numbers: false
#
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff. Show more