Prepare for hashed item rewriting

This commit is contained in:
Nassim Jahnke committed 2025-05-11 23:10:20 +02:00
1 parent a6d58fc02b
commit 3554db9e3a
27 files changed
+452 -106

No files matched your search

+5 -6
View File
@@ -22,19 +22,18 @@ dependencies {
api(libs.gson) {
exclude("com.google.errorprone", "error_prone_annotations")
}
api(libs.guava) {
exclude("com.google.errorprone", "error_prone_annotations")
exclude("org.jspecify", "jspecify")
exclude("com.google.j2objc", "j2objc-annotations")
}
implementation(rootProject.libs.text) {
exclude("com.google.code.gson", "gson")
exclude("com.google.guava", "guava")
exclude("com.viaversion", "nbt")
}
api(libs.snakeYaml)
compileOnlyApi(libs.netty)
compileOnlyApi(libs.guava) {
exclude("com.google.errorprone", "error_prone_annotations")
exclude("org.jspecify", "jspecify")
exclude("com.google.j2objc", "j2objc-annotations")
}
compileOnlyApi(libs.checkerQual)
}
@@ -24,6 +24,7 @@ package com.viaversion.viaversion.api.connection;
import com.viaversion.viaversion.api.configuration.ViaVersionConfig;
import com.viaversion.viaversion.api.data.entity.EntityTracker;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketTracker;
@@ -99,6 +100,23 @@ public interface UserConnection {
*/
void addEntityTracker(Class<? extends Protocol> protocolClass, EntityTracker tracker);
/**
* Adds an item hasher to the user connection.
*
* @param protocolClass protocol class
* @param itemHasher item hasher
*/
void addItemHasher(Class<? extends Protocol> protocolClass, ItemHasher itemHasher);
/**
* Returns the item hasher by the given protocol class if present.
*
* @param protocolClass protocol class
* @param <T> item hasher type
* @return item hasher if present
*/
@Nullable <T extends ItemHasher> T getItemHasher(Class<? extends Protocol> protocolClass);
/**
* Returns the client world by the given protocol class if present.
*
@@ -0,0 +1,51 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2025 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.data.item;
import java.util.List;
/**
* Used for converting items to hashed items and caching them for later use during serverbound packets.
* <p>
* Most of this stays in the internal module given non-permanent, imperfect caching as well as missing converters.
*/
public interface ItemHasher {
/**
* Returns whether this connection is currently processing a clientbound player inventory packet.
* <p>
* Used for checking when it is necessary to track/process item data hashes.
*
* @return true if processing a clientbound inventory packet, false otherwise
*/
boolean isProcessingClientboundInventoryPacket();
void setProcessingClientboundInventoryPacket(boolean processingClientboundInventoryPacket);
/**
* Sets the enchantment registry for this connection.
*
* @param enchantments list of enchantment identifiers
*/
void setEnchantments(List<String> enchantments);
}
@@ -284,6 +284,10 @@ public final class StructuredDataContainer implements Copyable {
return data;
}
public boolean isEmpty() {
return data.isEmpty();
}
@Override
public String toString() {
return "StructuredDataContainer{" +
@@ -39,4 +39,7 @@ public interface HashedItem extends ItemBase {
Int2IntMap dataHashesById();
IntSet removedDataIds();
@Override
HashedItem copy();
}
@@ -79,6 +79,11 @@ public class HashedStructuredItem implements HashedItem {
return this.removedData;
}
@Override
public HashedItem copy() {
return new HashedStructuredItem(identifier, amount, new Int2IntOpenHashMap(dataHashes), new IntOpenHashSet(removedData));
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
@@ -27,7 +27,7 @@ import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.util.Copyable;
import org.checkerframework.checker.nullness.qual.Nullable;
public interface Item extends ItemBase, Copyable {
public interface Item extends ItemBase {
/**
* Returns the item data. Always 0 for 1.13+ items.
@@ -22,11 +22,13 @@
*/
package com.viaversion.viaversion.api.minecraft.item;
import com.viaversion.viaversion.util.Copyable;
/**
* @see Item
* @see HashedItem
*/
public interface ItemBase {
public interface ItemBase extends Copyable {
/**
* Returns the item identifier.
@@ -64,4 +66,7 @@ public interface ItemBase {
default boolean isEmpty() {
return identifier() == 0 || amount() <= 0;
}
@Override
ItemBase copy();
}
@@ -26,6 +26,7 @@ import com.google.common.base.Preconditions;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.entity.EntityTracker;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType;
import com.viaversion.viaversion.api.protocol.packet.Direction;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
@@ -247,6 +248,10 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
connection.addEntityTracker(this.getClass(), tracker);
}
protected void addItemHasher(UserConnection connection, ItemHasher hasher) {
connection.addItemHasher(this.getClass(), hasher);
}
protected PacketTypesProvider<CU, CM, SM, SU> createPacketTypesProvider() {
return new SimplePacketTypesProvider<>(
packetTypeMap(unmappedClientboundPacketType, unmappedClientboundPacketType),
@@ -23,6 +23,7 @@
package com.viaversion.viaversion.api.rewriter;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.type.Type;
@@ -50,6 +51,17 @@ public interface ItemRewriter<T extends Protocol<?, ?, ?, ?>> extends Rewriter<T
@Nullable
Item handleItemToServer(UserConnection connection, @Nullable Item item);
/**
* Returns the rewritten item, which may or may not be the same given HashedItem instance.
* <p>
* Used starting with 1.22.
*
* @param connection user connection
* @param item hashed item
* @return rewritten hashed item
*/
HashedItem handleHashedItem(UserConnection connection, HashedItem item);
/**
* Returns the item type of the current protocol.
*
@@ -43,7 +43,6 @@ publishing {
fun ShadowJar.configureRelocations() {
relocate("com.google.gson", "com.viaversion.viaversion.libs.gson")
relocate("com.google.common", "com.viaversion.viaversion.libs.guava")
relocate("it.unimi.dsi.fastutil", "com.viaversion.viaversion.libs.fastutil")
relocate("net.lenni0451.mcstructs", "com.viaversion.viaversion.libs.mcstructs")
relocate("org.yaml.snakeyaml", "com.viaversion.viaversion.libs.snakeyaml")
-1
View File
@@ -2,7 +2,6 @@ dependencies {
api(projects.viaversionApi)
api(rootProject.libs.bundles.mcstructs) {
exclude("com.google.code.gson", "gson")
exclude("com.google.guava", "guava")
exclude("com.viaversion", "nbt")
}
@@ -32,7 +32,6 @@ public class ProtocolInfoImpl implements ProtocolInfo {
private String username;
private UUID uuid;
private ProtocolPipeline pipeline;
private boolean processingClientboundInventoryPacket;
@Override
public State getClientState() {
@@ -110,21 +109,6 @@ public class ProtocolInfoImpl implements ProtocolInfo {
this.pipeline = pipeline;
}
/**
* Returns whether this connection is currently processing a clientbound player inventory packet.
* <p>
* Used for checking when it is necessary to track/process item data hashes.
*
* @return true if processing a clientbound inventory packet, false otherwise
*/
public boolean isProcessingClientboundInventoryPacket() {
return processingClientboundInventoryPacket;
}
public void setProcessingClientboundInventoryPacket(final boolean processingClientboundInventoryPacket) {
this.processingClientboundInventoryPacket = processingClientboundInventoryPacket;
}
@Override
public String toString() {
return "ProtocolInfo{" +
@@ -23,6 +23,7 @@ import com.viaversion.viaversion.api.connection.ProtocolInfo;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.entity.EntityTracker;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.platform.ViaInjector;
import com.viaversion.viaversion.api.protocol.Protocol;
@@ -60,6 +61,7 @@ public class UserConnectionImpl implements UserConnection {
private final long id = IDS.incrementAndGet();
private final Map<Class<?>, StorableObject> storedObjects = new ConcurrentHashMap<>();
private final Map<Class<? extends Protocol>, EntityTracker> entityTrackers = new HashMap<>();
private final Map<Class<? extends Protocol>, ItemHasher> itemHashers = new HashMap<>();
private final Map<Class<? extends Protocol>, ClientWorld> clientWorlds = new HashMap<>();
private final PacketTracker packetTracker = new PacketTracker(this);
private final Set<UUID> passthroughTokens = Collections.newSetFromMap(CacheBuilder.newBuilder()
@@ -131,6 +133,16 @@ public class UserConnectionImpl implements UserConnection {
entityTrackers.putIfAbsent(protocolClass, tracker);
}
@Override
public void addItemHasher(final Class<? extends Protocol> protocolClass, final ItemHasher itemHasher) {
itemHashers.putIfAbsent(protocolClass, itemHasher);
}
@Override
public @Nullable <T extends ItemHasher> T getItemHasher(Class<? extends Protocol> protocolClass) {
return (T) itemHashers.get(protocolClass);
}
@Override
public @Nullable <T extends ClientWorld> T getClientWorld(final Class<? extends Protocol> protocolClass) {
return (T) clientWorlds.get(protocolClass);
@@ -160,6 +172,7 @@ public class UserConnectionImpl implements UserConnection {
}
storedObjects.clear();
entityTrackers.clear();
itemHashers.clear();
clientWorlds.clear();
}
}
@@ -0,0 +1,147 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2025 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.data.item;
import com.google.common.cache.CacheBuilder;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.HashedStructuredItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.rewriter.item.ItemDataComponentConverter;
import com.viaversion.viaversion.rewriter.item.ItemDataComponentConverter.RegistryAccess;
import com.viaversion.viaversion.util.SerializerVersion;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.lenni0451.mcstructs.converter.impl.v1_21_5.HashConverter_v1_21_5;
import net.lenni0451.mcstructs.itemcomponents.ItemComponentRegistry;
import net.lenni0451.mcstructs.itemcomponents.impl.v1_21_5.ItemComponents_v1_21_5;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ItemHasherBase implements ItemHasher {
public static final int UNKNOWN_HASH = Integer.MIN_VALUE;
private final Map<Integer, HashedItem> hashes = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(1024).<Integer, HashedItem>build().asMap();
private final List<String> enchantments = new ArrayList<>();
private final ItemDataComponentConverter itemComponentConverter;
protected final UserConnection connection;
private boolean processingClientboundInventoryPacket;
public ItemHasherBase(final UserConnection connection, final SerializerVersion serializerVersion, final MappingData mappingData) {
final RegistryAccess registryAccess = RegistryAccess.of(
this.enchantments,
ItemComponentRegistry.V1_21_5.getRegistries(), // Unused outside of file deserialization
mappingData
);
this.itemComponentConverter = new ItemDataComponentConverter(serializerVersion, registryAccess);
this.connection = connection;
}
@Override
public void setEnchantments(final List<String> enchantments) {
this.enchantments.clear();
this.enchantments.addAll(enchantments);
}
/**
* Returns the hashed item for the given item. Not all data may be successfully hashed.
*
* @param item the item to hash
* @return the hashed item
*/
public HashedItem toHashedItem(final Item item) {
return toHashedItem(this.itemComponentConverter, item);
}
public static HashedItem toHashedItem(final ItemDataComponentConverter converter, final Item item) {
final HashedItem hashedItem = new HashedStructuredItem(item.identifier(), item.amount());
for (final StructuredData<?> data : item.dataContainer().data().values()) {
if (data.isEmpty()) {
hashedItem.removedDataIds().add(data.id());
continue;
}
final ItemDataComponentConverter.Result<?> result = converter.viaToMcStructs(data);
final int hash = result != null ? hash(result) : UNKNOWN_HASH;
hashedItem.dataHashesById().put(data.id(), hash);
}
return hashedItem;
}
/**
* Tracks the data for the given data container, storing the original hashes via the now transformed data hashes.
*
* @param customData custom_data tag
* @param originalHashedItem the original (pre-transformed) hashed item
*/
public void trackOriginalHashedItem(final CompoundTag customData, final HashedItem originalHashedItem) {
// Store them via the custom_data hash, which includes the original data hashes.
// Not perfect (as opposed to storing it by the full hashed item), but good enough. In the rare occasion there is a collision, we ignore it without issues.
final int customDataHash = hashTag(customData);
this.hashes.put(customDataHash, originalHashedItem);
}
/**
* Returns a copy of the original hashed item for a given custom_data hash.
*
* @param customDataHash custom_data hash
* @param clientItem the hashed item from the client that the original should be retrieved for
* @return the original hashed item, or null if not found
*/
public @Nullable HashedItem originalHashedItem(final int customDataHash, final HashedItem clientItem) {
HashedItem originalItem = this.hashes.get(customDataHash);
if (originalItem == null) {
return null;
}
originalItem = originalItem.copy();
// Take hashes for data we couldn't calculate the hash of from the client item, more likely than not it's the same
for (final Int2IntMap.Entry entry : originalItem.dataHashesById().int2IntEntrySet()) {
final int typeId = entry.getIntKey();
if (entry.getIntValue() == UNKNOWN_HASH && clientItem.dataHashesById().containsKey(typeId)) {
final int clientProvidedHash = clientItem.dataHashesById().get(typeId);
originalItem.dataHashesById().put(typeId, clientProvidedHash);
}
}
return originalItem;
}
@Override
public boolean isProcessingClientboundInventoryPacket() {
return processingClientboundInventoryPacket;
}
@Override
public void setProcessingClientboundInventoryPacket(final boolean processingClientboundInventoryPacket) {
this.processingClientboundInventoryPacket = processingClientboundInventoryPacket;
}
private static <T> int hash(final ItemDataComponentConverter.Result<T> result) {
return result.type().getCodec().serialize(HashConverter_v1_21_5.CRC32C, result.value()).getOrThrow().asInt();
}
private int hashTag(final CompoundTag tag) {
return ItemComponents_v1_21_5.V1_21_5.CUSTOM_DATA.getCodec().serialize(HashConverter_v1_21_5.CRC32C, tag).getOrThrow().asInt();
}
}
@@ -17,9 +17,13 @@
*/
package com.viaversion.viaversion.protocols.template;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5;
import com.viaversion.viaversion.api.type.types.version.Types1_21_4;
import com.viaversion.viaversion.api.type.types.version.Types1_21_5;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4;
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.rewriter.RecipeDisplayRewriter1_21_5;
@@ -77,4 +81,19 @@ final class BlockItemPacketRewriter1_99 extends StructuredItemRewriter<Clientbou
// OR do this if serialization of recipes changed and override the relevant method
// Add new serializers to RecipeDisplayRewriter, or extend the last one for changes
}
@Override
public Item handleItemToClient(final UserConnection connection, final Item item) {
if (item.isEmpty()) {
return item;
}
final ItemHasherBase itemHasher = itemHasher(connection); // get the original hashed item and store it later if there are any changes that could affect the data hashes
final HashedItem originalHashedItem = hashItem(item, itemHasher);
super.handleItemToClient(connection, item);
storeOriginalHashedItem(item, itemHasher, originalHashedItem); // has to be called AFTER all modifications
return item;
}
}
@@ -26,6 +26,7 @@ import com.viaversion.viaversion.api.protocol.packet.provider.PacketTypesProvide
import com.viaversion.viaversion.api.protocol.packet.provider.SimplePacketTypesProvider;
import com.viaversion.viaversion.api.type.types.version.Types1_21_4;
import com.viaversion.viaversion.data.entity.EntityTrackerBase;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5;
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21;
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4;
@@ -38,6 +39,7 @@ import com.viaversion.viaversion.rewriter.ParticleRewriter;
import com.viaversion.viaversion.rewriter.SoundRewriter;
import com.viaversion.viaversion.rewriter.StatisticsRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter;
import com.viaversion.viaversion.util.SerializerVersion;
import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
@@ -137,6 +139,7 @@ final class Protocol1_99To_98 extends AbstractProtocol<ClientboundPacket1_21_2,
public void init(final UserConnection connection) {
// Register the entity tracker - used for entity id/entity data rewriting AND for tracking world data sent to the client (then used for chunk data rewriting)
addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_4.PLAYER));
addItemHasher(connection, new ItemHasherBase(connection, SerializerVersion.V1_21_5, MAPPINGS));
}
// Overriding these four methods is important as they are relied on various rewriter classes
@@ -211,8 +211,8 @@ public final class Protocol1_21_4To1_21_5 extends AbstractProtocol<ClientboundPa
@Override
public void init(final UserConnection connection) {
addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_4.PLAYER));
addItemHasher(connection, new ItemHashStorage1_21_5(MAPPINGS));
connection.put(new MessageIndexStorage());
connection.put(new ItemHashStorage1_21_5(connection, MAPPINGS));
}
@Override
@@ -63,7 +63,6 @@ import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2;
import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_21_5;
import com.viaversion.viaversion.api.type.types.version.Types1_21_4;
import com.viaversion.viaversion.api.type.types.version.Types1_21_5;
import com.viaversion.viaversion.connection.ProtocolInfoImpl;
import com.viaversion.viaversion.protocol.packet.PacketWrapperImpl;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.Protocol1_21_4To1_21_5;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ServerboundPacket1_21_5;
@@ -345,10 +344,10 @@ public final class BlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<
appendItemDataFixComponents(connection, item);
// Store the data components if necessary for the server, the client only sends data hashes now
if (((ProtocolInfoImpl) connection.getProtocolInfo()).isProcessingClientboundInventoryPacket()) {
final ItemHashStorage1_21_5 hasher = connection.get(ItemHashStorage1_21_5.class);
final ItemHashStorage1_21_5 itemHasher = itemHasher(connection);
if (itemHasher.isProcessingClientboundInventoryPacket()) {
for (final StructuredData<?> data : dataContainer.data().values()) {
hasher.trackStructuredData(data);
itemHasher.trackStructuredData(data);
}
}
@@ -495,7 +494,7 @@ public final class BlockItemPacketRewriter1_21_5 extends StructuredItemRewriter<
private StructuredItem convertHashedItemToStructuredItem(final UserConnection connection, final HashedItem hashedItem) {
final StructuredItem item = new StructuredItem(hashedItem.identifier(), hashedItem.amount());
final ItemHashStorage1_21_5 hasher = connection.get(ItemHashStorage1_21_5.class);
final ItemHashStorage1_21_5 hasher = itemHasher(connection);
final Map<StructuredDataKey<?>, StructuredData<?>> structuredDataMap = item.dataContainer().data();
for (final Int2IntMap.Entry hashEntry : hashedItem.dataHashesById().int2IntEntrySet()) {
final StructuredData<?> structuredData = hasher.dataFromHash(hashEntry.getIntKey(), hashEntry.getIntValue());
@@ -32,18 +32,14 @@ import com.viaversion.viaversion.api.type.types.version.Types1_21_5;
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.Protocol1_21_4To1_21_5;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.packet.ClientboundPackets1_21_5;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.storage.ItemHashStorage1_21_5;
import com.viaversion.viaversion.protocols.v1_21_4to1_21_5.storage.MessageIndexStorage;
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2;
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
import com.viaversion.viaversion.rewriter.EntityRewriter;
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.rewriter.entitydata.EntityDataHandlerEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import net.lenni0451.mcstructs.core.Identifier;
public final class EntityPacketRewriter1_21_5 extends EntityRewriter<ClientboundPacket1_21_2, Protocol1_21_4To1_21_5> {
private static final int ATTACK_BLOCKED_ENTITY_EVENT = 29;
@@ -83,14 +79,6 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
final RegistryDataRewriter registryDataRewriter = new RegistryDataRewriter(protocol) {
@Override
public RegistryEntry[] handle(final UserConnection connection, final String key, final RegistryEntry[] entries) {
if (key.equals("enchantment")) {
final List<Identifier> identifiers = new ArrayList<>();
for (final RegistryEntry entry : entries) {
identifiers.add(Identifier.of(entry.key()));
}
connection.get(ItemHashStorage1_21_5.class).setEnchantmentRegistry(identifiers);
}
if (!key.equals("wolf_variant")) {
return super.handle(connection, key, entries);
}
@@ -18,34 +18,34 @@
package com.viaversion.viaversion.protocols.v1_21_4to1_21_5.storage;
import com.google.common.cache.CacheBuilder;
import com.viaversion.viaversion.api.connection.StoredObject;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.rewriter.item.ItemDataComponentConverter;
import com.viaversion.viaversion.rewriter.item.ItemDataComponentConverter.RegistryAccess;
import com.viaversion.viaversion.util.SerializerVersion;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.lenni0451.mcstructs.converter.impl.v1_21_5.HashConverter_v1_21_5;
import net.lenni0451.mcstructs.core.Identifier;
import net.lenni0451.mcstructs.itemcomponents.ItemComponentRegistry;
public class ItemHashStorage1_21_5 extends StoredObject {
public class ItemHashStorage1_21_5 implements ItemHasher {
private final Map<Long, StructuredData<?>> hashToStructuredData = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(512).<Long, StructuredData<?>>build().asMap();
private final List<Identifier> enchantmentRegistry = new ArrayList<>();
private final List<String> enchantmentRegistry = new ArrayList<>();
private final ItemDataComponentConverter itemComponentConverter;
private boolean processingClientboundInventoryPacket;
public ItemHashStorage1_21_5(final UserConnection connection, final MappingData mappingData) {
super(connection);
public ItemHashStorage1_21_5(final MappingData mappingData) {
final RegistryAccess registryAccess = RegistryAccess.of(this.enchantmentRegistry, ItemComponentRegistry.V1_21_5.getRegistries(), mappingData);
this.itemComponentConverter = new ItemDataComponentConverter(registryAccess);
this.itemComponentConverter = new ItemDataComponentConverter(SerializerVersion.V1_21_5, registryAccess);
}
public void setEnchantmentRegistry(final List<Identifier> enchantmentRegistry) {
@Override
public void setEnchantments(final List<String> enchantments) {
this.enchantmentRegistry.clear();
this.enchantmentRegistry.addAll(enchantmentRegistry);
this.enchantmentRegistry.addAll(enchantments);
}
public void trackStructuredData(final StructuredData<?> structuredData) {
@@ -71,4 +71,14 @@ public class ItemHashStorage1_21_5 extends StoredObject {
private static <T> int hash(final ItemDataComponentConverter.Result<T> result) {
return result.type().getCodec().serialize(HashConverter_v1_21_5.CRC32C, result.value()).getOrThrow().asInt();
}
@Override
public boolean isProcessingClientboundInventoryPacket() {
return this.processingClientboundInventoryPacket;
}
@Override
public void setProcessingClientboundInventoryPacket(final boolean processingClientboundInventoryPacket) {
this.processingClientboundInventoryPacket = processingClientboundInventoryPacket;
}
}
@@ -23,6 +23,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.FullMappings;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.data.Mappings;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.Protocol;
@@ -34,7 +35,7 @@ import com.viaversion.viaversion.api.rewriter.ComponentRewriter;
import com.viaversion.viaversion.api.rewriter.RewriterBase;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.connection.ProtocolInfoImpl;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.Rewritable;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -94,6 +95,35 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
return item;
}
@Override
public HashedItem handleHashedItem(final UserConnection connection, final HashedItem item) {
final MappingData mappingData = protocol.getMappingData();
if (mappingData == null) {
return item;
}
final FullMappings dataComponentMappings = mappingData.getDataComponentSerializerMappings();
if (dataComponentMappings != null) {
updateHashedItemDataComponentIds(item, dataComponentMappings.inverse());
final int customDataId = dataComponentMappings.id("custom_data");
if (item.dataHashesById().containsKey(customDataId)) {
// Use the original hashed item if we can find it in the cache
final int customDataHash = item.dataHashesById().get(customDataId);
final ItemHasherBase itemHasher = itemHasher(connection);
final HashedItem originalHashedItem = itemHasher.originalHashedItem(customDataHash, item);
if (originalHashedItem != null) {
return originalHashedItem;
}
}
}
if (mappingData.getItemMappings() != null) {
item.setIdentifier(mappingData.getOldItemId(item.identifier()));
}
return item;
}
public void registerSetContent(C packetType) {
protocol.registerClientbound(packetType, wrapper -> {
wrapper.passthrough(Types.UNSIGNED_BYTE); // Container id
@@ -499,22 +529,31 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
}
protected @Nullable Item handleItemToClientAndTrackHash(final UserConnection connection, @Nullable Item item) {
final ProtocolInfoImpl protocolInfo = (ProtocolInfoImpl) connection.getProtocolInfo();
protocolInfo.setProcessingClientboundInventoryPacket(true);
final ItemHasher itemHasher = itemHasher(connection);
if (itemHasher == null) {
return handleItemToClient(connection, item);
}
itemHasher.setProcessingClientboundInventoryPacket(true);
try {
return this.handleItemToClient(connection, item);
} finally {
protocolInfo.setProcessingClientboundInventoryPacket(false);
itemHasher.setProcessingClientboundInventoryPacket(false);
}
}
protected void passthroughClientboundItemAndTrackHash(final PacketWrapper wrapper) {
final ProtocolInfoImpl protocolInfo = (ProtocolInfoImpl) wrapper.user().getProtocolInfo();
protocolInfo.setProcessingClientboundInventoryPacket(true);
final ItemHasher itemHasher = itemHasher(wrapper.user());
if (itemHasher == null) {
passthroughClientboundItem(wrapper);
return;
}
itemHasher.setProcessingClientboundInventoryPacket(true);
try {
this.passthroughClientboundItem(wrapper);
} finally {
protocolInfo.setProcessingClientboundInventoryPacket(false);
itemHasher.setProcessingClientboundInventoryPacket(false);
}
}
@@ -524,20 +563,12 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
}
protected void passthroughHashedItem(final PacketWrapper wrapper) {
final HashedItem item = wrapper.passthrough(Types.HASHED_ITEM);
final MappingData mappingData = protocol.getMappingData();
if (mappingData == null) {
return;
}
final HashedItem item = handleHashedItem(wrapper.user(), wrapper.read(Types.HASHED_ITEM));
wrapper.write(Types.HASHED_ITEM, item);
}
if (mappingData.getItemMappings() != null) {
item.setIdentifier(mappingData.getOldItemId(item.identifier()));
}
final FullMappings dataComponentMappings = protocol.getMappingData().getDataComponentSerializerMappings();
if (dataComponentMappings != null) {
updateHashedItemDataComponentIds(item, dataComponentMappings.inverse());
}
protected <T extends ItemHasher> @Nullable T itemHasher(final UserConnection connection) {
return connection.getItemHasher(protocol.getClass());
}
protected void updateHashedItemDataComponentIds(final HashedItem item, final FullMappings mappings) {
@@ -549,9 +580,10 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
continue;
}
// Let's hope the hash didn't change...
final int hash = addedData.remove(id);
addedData.put(mappedId, hash);
if (mappedId != -1) {
addedData.put(mappedId, hash);
}
}
}
@@ -564,7 +596,9 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
}
removedData.remove(id);
removedData.add(mappedId);
if (mappedId != -1) {
removedData.add(mappedId);
}
}
}
}
@@ -24,6 +24,7 @@ import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.FullMappings;
import com.viaversion.viaversion.api.data.entity.DimensionData;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.RegistryEntry;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
@@ -60,7 +61,7 @@ public class RegistryDataRewriter {
public RegistryEntry[] handle(final UserConnection connection, String key, RegistryEntry[] entries) {
key = Key.stripMinecraftNamespace(key);
if (key.equals("enchantment")) {
updateEnchantments(entries);
updateEnchantments(connection, entries);
} else if (key.equals("trim_material")) {
updateTrimMaterials(entries);
}
@@ -119,8 +120,10 @@ public class RegistryDataRewriter {
}
}
public void updateEnchantments(final RegistryEntry[] entries) {
public void updateEnchantments(final UserConnection connection, final RegistryEntry[] entries) {
final List<String> identifiers = new ArrayList<>(entries.length);
for (final RegistryEntry entry : entries) {
identifiers.add(entry.key());
if (entry.tag() == null) {
continue;
}
@@ -149,6 +152,11 @@ public class RegistryDataRewriter {
updateAttributesFields(effects);
}
final ItemHasher itemHasher = connection.getItemHasher(protocol.getClass());
if (itemHasher != null) {
itemHasher.setEnchantments(identifiers);
}
}
public void updateTrimMaterials(final RegistryEntry[] entries) {
@@ -18,15 +18,18 @@
package com.viaversion.viaversion.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.IntArrayTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.FullMappings;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.EitherHolder;
import com.viaversion.viaversion.api.minecraft.Holder;
import com.viaversion.viaversion.api.minecraft.data.StructuredData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.minecraft.item.data.FilterableComponent;
import com.viaversion.viaversion.api.minecraft.item.data.WrittenBook;
@@ -34,8 +37,9 @@ import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType;
import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.connection.ProtocolInfoImpl;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.util.Rewritable;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -76,6 +80,36 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
return item;
}
protected @Nullable HashedItem hashItem(final Item item, @Nullable final ItemHasherBase hasher) {
// Hash the original item from open inventory data to be able to get it back out of serverbound hashed items
return hasher == null || !hasher.isProcessingClientboundInventoryPacket() ? null : hasher.toHashedItem(item);
}
protected void storeOriginalHashedItem(final Item item, final ItemHasherBase hasher, @Nullable final HashedItem originalHashedItem) {
if (originalHashedItem == null || item.dataContainer().isEmpty()) {
return;
}
// Check if the hashed data is the same, this will also prevent unnecessary backups due to missing converters
final HashedItem hashedItem = hasher.toHashedItem(item);
if (hashedItem.dataHashesById().equals(originalHashedItem.dataHashesById()) && hashedItem.removedDataIds().equals(originalHashedItem.removedDataIds())) {
return;
}
// Always has to be AFTER any modification - Use the custom_data hash as a key to the original hashes.
// This is much easier/cheaper than tracking via the full hashed item, as collisions are both acceptable and still unlikely.
final CompoundTag originalHashes = new CompoundTag();
for (final Int2IntMap.Entry entry : originalHashedItem.dataHashesById().int2IntEntrySet()) {
originalHashes.putInt(Integer.toString(entry.getIntKey()), entry.getIntValue());
}
originalHashes.put("removed", new IntArrayTag(originalHashedItem.removedDataIds().toIntArray()));
final CompoundTag customTag = createCustomTag(item);
saveTag(customTag, originalHashes, "original_hashes");
hasher.trackOriginalHashedItem(customTag, originalHashedItem);
}
@Override
public Item handleItemToServer(UserConnection connection, Item item) {
if (item.isEmpty()) {
@@ -89,7 +123,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
updateItemDataComponentTypeIds(item.dataContainer(), false);
updateItemDataComponents(connection, item, false);
restoreTextComponents(item);
restoreBackupData(item);
return item;
}
@@ -136,14 +170,18 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
}
}
final ProtocolInfoImpl protocolInfo = ((ProtocolInfoImpl) connection.getProtocolInfo());
final boolean processingClientboundInventoryPacket = protocolInfo.isProcessingClientboundInventoryPacket();
protocolInfo.setProcessingClientboundInventoryPacket(false); // Don't track items inside items
final ItemHasher itemHasher = itemHasher(connection);
final ItemHandler itemHandler = clientbound ? this::handleItemToClient : this::handleItemToServer;
try {
if (itemHasher != null && itemHasher.isProcessingClientboundInventoryPacket()) {
// Don't track items inside items
itemHasher.setProcessingClientboundInventoryPacket(false);
try {
updateItemDataComponents(connection, clientbound, container, itemHandler);
} finally {
itemHasher.setProcessingClientboundInventoryPacket(true);
}
} else {
updateItemDataComponents(connection, clientbound, container, itemHandler);
} finally {
protocolInfo.setProcessingClientboundInventoryPacket(processingClientboundInventoryPacket);
}
}
@@ -203,30 +241,31 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
}
}
protected void restoreTextComponents(final Item item) {
protected void restoreBackupData(final Item item) {
final StructuredDataContainer data = item.dataContainer();
final CompoundTag customData = data.get(StructuredDataKey.CUSTOM_DATA);
if (customData == null) {
return;
}
customData.remove(nbtTagName("original_hashes"));
// Remove custom name
if (customData.remove(nbtTagName("added_custom_name")) != null) {
data.remove(StructuredDataKey.CUSTOM_NAME);
removeCustomTag(data, customData);
} else {
final Tag customName = removeBackupTag(customData, "custom_name");
if (customName != null) {
data.set(StructuredDataKey.CUSTOM_NAME, customName);
removeCustomTag(data, customData);
}
final Tag itemName = removeBackupTag(customData, "item_name");
if (itemName != null) {
data.set(StructuredDataKey.ITEM_NAME, itemName);
removeCustomTag(data, customData);
}
}
removeCustomTag(data, customData);
}
protected CompoundTag createCustomTag(final Item item) {
@@ -28,6 +28,7 @@ import com.viaversion.viaversion.api.minecraft.item.data.Enchantments;
import com.viaversion.viaversion.api.minecraft.item.data.FilterableComponent;
import com.viaversion.viaversion.api.minecraft.item.data.FilterableString;
import com.viaversion.viaversion.api.minecraft.item.data.FireworkExplosion;
import com.viaversion.viaversion.util.SerializerVersion;
import com.viaversion.viaversion.util.Unit;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
@@ -50,15 +51,16 @@ import net.lenni0451.mcstructs.itemcomponents.registry.Registry;
import net.lenni0451.mcstructs.itemcomponents.registry.RegistryEntry;
import net.lenni0451.mcstructs.itemcomponents.registry.RegistryTag;
import net.lenni0451.mcstructs.text.TextComponent;
import net.lenni0451.mcstructs.text.serializer.TextComponentCodec;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class ItemDataComponentConverter {
private final Map<StructuredDataKey<?>, Converter<?, ?>> converters = new Reference2ObjectOpenHashMap<>();
private final SerializerVersion serializerVersion;
private final RegistryAccess registryAccess;
public ItemDataComponentConverter(final RegistryAccess registryAccess) {
public ItemDataComponentConverter(final SerializerVersion serializerVersion, final RegistryAccess registryAccess) {
this.serializerVersion = serializerVersion;
this.registryAccess = registryAccess;
this.direct(StructuredDataKey.CUSTOM_DATA, ItemComponentRegistry.V1_21_5.CUSTOM_DATA);
this.direct(StructuredDataKey.MAX_STACK_SIZE, ItemComponentRegistry.V1_21_5.MAX_STACK_SIZE);
@@ -84,10 +86,10 @@ public final class ItemDataComponentConverter {
booleans.add(b);
}
return new Result<>(ItemComponentRegistry.V1_21_5.CUSTOM_MODEL_DATA, new Types_v1_21_4.CustomModelData(
floats,
booleans,
Arrays.asList(customModelData.strings()),
intArrayToIntList(customModelData.colors())
floats,
booleans,
Arrays.asList(customModelData.strings()),
intArrayToIntList(customModelData.colors())
));
});
this.notImplemented(StructuredDataKey.TOOLTIP_DISPLAY);
@@ -287,7 +289,7 @@ public final class ItemDataComponentConverter {
this.register(key, value -> new Result<>(result, new RegistryTag(registry, Identifier.of(value))));
}
private static Converter<Tag[], List<TextComponent>> stringArrayToTextComponentArray(final ItemComponent<List<TextComponent>> itemComponent) {
private Converter<Tag[], List<TextComponent>> stringArrayToTextComponentArray(final ItemComponent<List<TextComponent>> itemComponent) {
return tags -> {
final List<TextComponent> textComponents = new ArrayList<>(tags.length);
for (final Tag tag : tags) {
@@ -306,8 +308,8 @@ public final class ItemDataComponentConverter {
return new Types_v1_20_5.FireworkExplosions(explosionShape, intArrayToIntList(explosion.colors()), intArrayToIntList(explosion.fadeColors()), explosion.hasTrail(), explosion.hasTwinkle());
}
private static TextComponent convertTextComponent(final Tag tag) {
return tag != null ? TextComponentCodec.V1_21_5.deserialize(tag) : null;
private TextComponent convertTextComponent(final Tag tag) {
return tag != null ? this.serializerVersion.toComponent(tag) : null;
}
private static List<Integer> intArrayToIntList(final int[] array) {
@@ -392,7 +394,7 @@ public final class ItemDataComponentConverter {
RegistryEntry getEnchantment(final int networkId);
static RegistryAccess of(final List<Identifier> enchantments, final Registries registries, final MappingData mappingData) {
static RegistryAccess of(final List<String> enchantments, final Registries registries, final MappingData mappingData) {
return new RegistryAccessImpl(enchantments, registries, mappingData);
}
}
@@ -25,11 +25,11 @@ import net.lenni0451.mcstructs.itemcomponents.registry.Registry;
import net.lenni0451.mcstructs.itemcomponents.registry.RegistryEntry;
final class RegistryAccessImpl implements ItemDataComponentConverter.RegistryAccess {
private final List<Identifier> enchantments;
private final List<String> enchantments;
private final Registries registries;
private final MappingData mappingData;
RegistryAccessImpl(final List<Identifier> enchantments, final Registries registries, final MappingData mappingData) {
RegistryAccessImpl(final List<String> enchantments, final Registries registries, final MappingData mappingData) {
this.enchantments = enchantments;
this.registries = registries;
this.mappingData = mappingData;
@@ -52,6 +52,6 @@ final class RegistryAccessImpl implements ItemDataComponentConverter.RegistryAcc
if (networkId < 0 || networkId >= this.enchantments.size()) {
return new RegistryEntry(registry, Identifier.of("viaversion", "unknown/enchantment/" + networkId));
}
return new RegistryEntry(registry, this.enchantments.get(networkId));
return new RegistryEntry(registry, Identifier.of(this.enchantments.get(networkId)));
}
}
+2 -2
View File
@@ -3,13 +3,13 @@ metadata.format.version = "1.1"
[versions]
gson = "2.12.1"
guava = "33.4.8-jre" # 17.0 can be assumed by platforms, but we require newer API
fastutil = "8.5.15"
vianbt = "5.1.0"
mcstructs = "5-3.1.0-SNAPSHOT"
# Common provided
netty = "4.0.20.Final"
guava = "17.0"
snakeYaml = "2.2"
junit = "5.11.4"
@@ -25,7 +25,6 @@ viaProxy = "3.4.2-SNAPSHOT"
[libraries]
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil" }
vianbt = { group = "com.viaversion", name = "nbt", version.ref = "vianbt" }
# Custom version that uses ViaNBT instead of its own inbuilt NBT library
@@ -33,6 +32,7 @@ text = { group = "com.viaversion.mcstructs", name = "text", version.ref = "mcstr
itemDataComponents = { group = "com.viaversion.mcstructs", name = "itemcomponents", version.ref = "mcstructs" }
netty = { group = "io.netty", name = "netty-all", version.ref = "netty" }
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
snakeYaml = { group = "org.yaml", name = "snakeyaml", version.ref = "snakeYaml" }
jupiterApi = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }