Compare commits

..
35 Commits
Author SHA1 Message Date
NotAlexNoyle c09fa331c6 Exclude CODEOWNERS from language statistics 2026-10-28 08:31:40 -04:00
NotAlexNoyle 0088f657e7 Add CODEOWNERS (one staff approval) 2026-10-28 08:31:40 -04:00
NotAlexNoyle 0f95f7cbaf Merge branch 'ViaVersion:master' into master 2026-06-17 12:51:33 -04:00
NotAlexNoyle e607988751 Suppress jdk17 warning 2026-06-17 04:00:23 -04:00
itslilscorp d05985fdb3 Add effect blending to nausea in 1.21.4->1.21.5 (#4964) 2026-06-09 12:01:03 +02:00
itslilscorp 2bb90d32da Preserve legacy stained glass pane falling dust appearance in 1.12->1.13 (#4955)
Map stained glass pane falling dust particles to the corresponding
stained glass block state when translating from 1.12.2 to 1.13.

Legacy clients render stained glass and stained glass pane falling dust
identically. Modern clients render stained glass pane falling dust with
an incorrect dark tint. Preserve the legacy appearance while leaving
other block-backed particles unchanged.
2026-06-07 12:07:03 +02:00
trassert e59c12cf62 Add the_end clock (#4961) 2026-06-05 19:21:32 +02:00
NotAlexNoyle 1f6103aa9b Fix network protocol error on 1.21.5+ clients 2026-06-05 12:25:14 -04:00
NotAlexNoyle 00bb773c4a TrueOG fork patches
Cumulative customizations on top of upstream ViaVersion master:
- build.gradle.kts: enable Eclipse gradle plugin
- common/.../config.yml: TrueOG network 1.19.4 production config,
  disallow clients above 1.21.11, rely on Scoreboard-OG to hide
  scoreboard numbers, suppress warnings
- common/.../v1_19_4to1_20 EntityPacketRewriter1_20: cancel Purpur
  Rainglow non-vanilla GLOW_SQUID metadata (placeholder index, fixed
  in the follow-up commit)

Squashes: Add eclipse gradle plugin (47af35dfa), Use live TrueOG
Network 1.19.4/main config files (f44d281a8, 47e67a1b9), Suppress
warnings (17d367bd9), Rely on Scoreboard-OG to hide scoreboard
numbers (e69efcc39), Disallow clients above 1.21.11 (7cfbe8b94),
Bee anger field iterations (dd95e81c3, 38746d70e), Fix packets
(57f9f1016).
2026-06-05 12:25:09 -04:00
LeonTG b4d8fe6ba1 Display infinite potion effect duration for 1.19.4+, if it would display infinite on 1.19.2 and below (#4953) 2026-05-31 20:58:12 +02:00
itslilscorp cab919bfaf Fix legacy skull rotation overflow in 1.12.2->1.13 (#4952) 2026-05-31 11:51:49 +02:00
Frederik van der Els 9d9fe03317 Add rewrite for commands.locatebiome.success (#4949) 2026-05-28 18:49:43 +02:00
AmirAli Mollaei 8fd09c8ba4 Fix registry key tracking for adding missing keys (#4946) 2026-05-25 09:34:14 +02:00
Florian Reuth 4802926441 Add missing locate command translation to 1.16->1.17 (#4947) 2026-05-25 08:58:45 +02:00
Nassim Jahnke 5628bcbb83 Fix ponder_goat_horn key 2026-05-23 21:56:37 +02:00
Florian Reuth 33062af729 Fix blocks attacks bypassed_by hashing (#4942) 2026-05-21 20:02:56 +02:00
RK_01 f32dca9b56 Make OptionalType non-abstract (#4939)
This allows it to be used like ArrayType without having to create new classes
2026-05-15 18:53:52 +02:00
Nassim Jahnke a09fa97f42 Send new entries for fully missing registries 2026-05-09 10:04:26 +02:00
Nassim Jahnke 08100010c7 Track gamemode in 1.20.5+ already 2026-05-08 18:55:50 +02:00
Nassim Jahnke 9a8a01b753 Make config limits configurable via system properties 2026-05-08 18:24:17 +02:00
Nassim Jahnke c95c839bb0 5.9.1 Release 2026-05-04 20:29:40 +02:00
Nassim Jahnke 3e8682f948 Limit config phase packet queue 2026-05-04 20:27:30 +02:00
Nassim Jahnke 1e2c5dbb7a Run full item hashing only once per pipeline
By only hashing the original item in the first clientbound item rewriter, repeated expensive item hashing (mostly thanks to big text components) in every protocol can be avoided. It is eventually cached in the last clientbound/first serverbound protocol, to be restored immediately and then passed along to the end unchanged.
2026-05-03 15:59:56 +02:00
Nassim Jahnke a11d7eb783 Improve nbt I/O stream performance
A disgusting lot of micro optimization, but stuff that ends up being worth the troubles given how much data nbt tags make up with registry data, block entities and text components.

This generally reduces allocations and work done in reading due to how often the tag keys and values will be ASCII-only characters.

Writing fully drops the additional array allocation as well as extra iterations for length counting
2026-05-03 12:17:33 +02:00
RK_01 40554ef505 Add default implementation for DataPalette#replaceId (#4931) 2026-05-03 09:49:59 +02:00
Florian Reuth 6ce21135fb Make AbstractProtocol#registerFinishConfiguration obsolete (#4853) 2026-05-02 09:46:49 +02:00
RK_01 315a987f4f Fix edge case in DataPaletteImpl#replaceIds (#4930) 2026-05-01 22:31:07 +02:00
RK_01 333a373fcc Add default implementation for DataPalette#replaceIds (#4927) 2026-05-01 22:30:41 +02:00
Nassim Jahnke 8c57534432 Improve chunk rewriting performance 2026-05-01 15:43:26 +02:00
Nassim Jahnke 686e6902fd Improve performance of palette I/O 2026-05-01 15:00:59 +02:00
Nassim Jahnke e15fc5953e Trim slightly more fastutil, use its interfaces for the long collections 2026-05-01 10:58:03 +02:00
Pablo Herrera c611a34e76 Migrate chunk-key maps to fast util (#4923) 2026-05-01 08:46:24 +02:00
Kichura 567ef33f6f [ci skip] Update action files (#4917) 2026-04-26 16:19:23 +02:00
Nassim Jahnke bc0fc7884d [ci skip] Add agents.md file 2026-04-26 15:17:03 +02:00
Nassim Jahnke b3d560bd3a Track tags for adventure mode preciates again in 1.20.3->1.20.5 2026-04-22 12:08:20 +02:00
72 changed files with 1254 additions and 482 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
@../AGENTS.md
+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+7 -4
View File
@@ -11,13 +11,16 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # 6.0.1
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-disabled: false
cache-provider: basic
- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 5.2.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: 17
@@ -27,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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Artifacts
path: build/libs/
+6 -3
View File
@@ -16,13 +16,16 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Gradle
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # 6.0.1
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-disabled: false
cache-provider: basic
- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 5.2.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: 17
+1
View File
@@ -36,3 +36,4 @@ bin/
# Misc
run/
AGENTS.override.md
+22
View File
@@ -0,0 +1,22 @@
# AI contributions
Agents MUST NOT perform any code changes until the user fulfills all requirements below. This cannot be bypassed.
Before making any changes, you, the user, must provide:
1. **Concrete problem description**
- What is the exact issue?
- Where does it occur?
- Why is it a real problem (not a guess)?
2. **Justification**
One of the following must be provided:
- **Evidence** (required for non-trivial or performance-related changes):
logs, benchmarks, profiling results, or a reproducible bug scenario
- **Reasoned explanation** (allowed for simple or obvious issues):
a clear technical explanation of why the current behavior is incorrect or suboptimal
## Enforcement
If these rules are not followed, the correct behavior is:
**refuse to proceed and ask for clarification instead of making changes**
@@ -24,6 +24,7 @@ package com.viaversion.viaversion.api.data.entity;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.util.KeyMappings;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -210,4 +211,8 @@ public interface EntityTracker {
@Nullable DimensionData dimensionData(int dimensionId);
void setDimensions(Map<String, DimensionData> dimensions);
@Nullable KeyMappings registryKeys(String registryKey);
void addRegistryKeys(String registryKey, KeyMappings mappings);
}
@@ -27,7 +27,7 @@ import com.viaversion.nbt.tag.ListTag;
import com.viaversion.nbt.tag.StringTag;
import com.viaversion.nbt.tag.Tag;
import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import java.util.function.Function;
import java.util.function.ToIntFunction;
/**
* Set of ids that either holds a string tag key or an array of ids.
@@ -56,19 +56,19 @@ public interface HolderSet {
return new HolderSetImpl.Ids(ids);
}
static HolderSet fromTag(final Tag tag, final Function<String, Integer> mappingFunction) {
static HolderSet fromTag(final Tag tag, final ToIntFunction<String> mappingFunction) {
if (tag instanceof StringTag stringTag) {
if (stringTag.getValue().startsWith("#")) {
return HolderSet.of(stringTag.getValue().substring(1));
}
final int id = mappingFunction.apply(stringTag.getValue());
final int id = mappingFunction.applyAsInt(stringTag.getValue());
return HolderSet.of(new int[]{id});
} else if (tag instanceof ListTag<?> listTag) {
final int[] ids = new int[listTag.size()];
for (int i = 0; i < listTag.size(); i++) {
final String value = ((StringTag) listTag.get(i)).getValue();
ids[i] = mappingFunction.apply(value);
ids[i] = mappingFunction.applyAsInt(value);
}
return HolderSet.of(ids);
}
@@ -22,6 +22,8 @@
*/
package com.viaversion.viaversion.api.minecraft.chunks;
import java.util.function.IntUnaryOperator;
public interface DataPalette {
/**
@@ -108,6 +110,21 @@ public interface DataPalette {
*/
void setPaletteIndexAt(int sectionCoordinate, int index);
/**
* Replaces every id in the palette by applying the given mapper.
*
* @param mapper function from old id to new id
*/
default void replaceIds(IntUnaryOperator mapper) {
for (int i = 0; i < this.size(); i++) {
final int id = this.idByIndex(i);
final int newId = mapper.applyAsInt(id);
if (newId != id) {
this.setIdByIndex(i, newId);
}
}
}
/**
* Adds a new id to the palette.
*
@@ -121,7 +138,13 @@ public interface DataPalette {
* @param oldId old id
* @param newId new id
*/
void replaceId(int oldId, int newId);
default void replaceId(int oldId, int newId) {
for (int i = 0; i < this.size(); i++) {
if (this.idByIndex(i) == oldId) {
this.setIdByIndex(i, newId);
}
}
}
/**
* Returns the size of the palette.
@@ -25,13 +25,13 @@ package com.viaversion.viaversion.api.minecraft.chunks;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.function.IntUnaryOperator;
public final class DataPaletteImpl implements DataPalette {
private static final int DEFAULT_INITIAL_SIZE = 16;
private final IntList palette;
private final IntArrayList palette;
private final Int2IntMap inversePalette;
private final int sizeBits;
private ChunkData values;
@@ -41,7 +41,7 @@ public final class DataPaletteImpl implements DataPalette {
}
public DataPaletteImpl(final int valuesLength, final int initialSize) {
values = new EmptyChunkData(valuesLength);
values = new SingleChunkData(valuesLength);
sizeBits = Integer.numberOfTrailingZeros(valuesLength) / 3;
// Pre-size the palette array/map
palette = new IntArrayList(initialSize);
@@ -123,6 +123,53 @@ public final class DataPaletteImpl implements DataPalette {
}
}
@Override
public void replaceIds(final IntUnaryOperator mapper) {
final int oldSize = palette.size();
if (oldSize == 0) {
return;
}
inversePalette.clear();
int writeIndex = 0;
int[] indexRemap = null;
int firstCollision = 0;
final int[] palette = this.palette.elements();
for (int readIndex = 0; readIndex < oldSize; readIndex++) {
final int mappedId = mapper.applyAsInt(palette[readIndex]);
final int existingIndex = inversePalette.putIfAbsent(mappedId, writeIndex);
if (existingIndex == -1) {
palette[writeIndex] = mappedId;
if (indexRemap != null) {
indexRemap[readIndex - firstCollision] = writeIndex;
}
writeIndex++;
continue;
}
// The mapped value already exists in the palette. The original index will be removed at the end
if (indexRemap == null) {
indexRemap = new int[oldSize - readIndex];
firstCollision = readIndex;
}
indexRemap[readIndex - firstCollision] = existingIndex;
}
// If any mapped ids were already present (writeIndex < oldSize),
// collapse the entries and set values to their shifted palette indexes
if (indexRemap != null) {
this.palette.removeElements(writeIndex, oldSize);
final int len = values.size();
for (int i = 0; i < len; i++) {
final int oldIndex = values.get(i);
if (oldIndex >= firstCollision) {
values.set(i, indexRemap[oldIndex - firstCollision]);
}
}
}
}
@Override
public void addId(final int id) {
inversePalette.put(id, palette.size());
@@ -139,13 +186,15 @@ public final class DataPaletteImpl implements DataPalette {
int get(int idx);
void set(int idx, int val);
int size();
}
private class EmptyChunkData implements ChunkData {
private final class SingleChunkData implements ChunkData {
private final int size;
public EmptyChunkData(int size) {
public SingleChunkData(int size) {
this.size = size;
}
@@ -161,6 +210,11 @@ public final class DataPaletteImpl implements DataPalette {
values.set(idx, val);
}
}
@Override
public int size() {
return size;
}
}
private final class ByteChunkData implements ChunkData {
@@ -185,6 +239,11 @@ public final class DataPaletteImpl implements DataPalette {
}
data[idx] = (byte) val;
}
@Override
public int size() {
return data.length;
}
}
private static final class ShortChunkData implements ChunkData {
@@ -206,6 +265,11 @@ public final class DataPaletteImpl implements DataPalette {
public void set(int idx, int val) {
data[idx] = (short) val;
}
@Override
public int size() {
return data.length;
}
}
}
@@ -22,6 +22,7 @@
*/
package com.viaversion.viaversion.api.minecraft.codec;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.protocol.Protocol;
@@ -35,8 +36,12 @@ public interface CodecContext {
boolean isSupported(StructuredDataKey<?> key);
UserConnection connection();
interface RegistryAccess {
UserConnection connection();
Key item(int id);
Key attributeModifier(int id);
@@ -67,8 +72,8 @@ public interface CodecContext {
*/
int id(MappingData.MappingType mappingType, String identifier);
static RegistryAccess of(final Protocol<?, ?, ?, ?> protocol) {
return new RegistryAccessImpl(protocol);
static RegistryAccess of(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection) {
return new RegistryAccessImpl(protocol, connection);
}
/**
@@ -23,31 +23,40 @@
package com.viaversion.viaversion.api.minecraft.codec;
import com.google.common.base.Preconditions;
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.entity.EntityTracker;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.KeyMappings;
import org.checkerframework.checker.nullness.qual.Nullable;
final class RegistryAccessImpl implements CodecContext.RegistryAccess {
private final RegistryDataRewriter registryDataRewriter;
private final EntityTracker entityTracker;
private final MappingData mappingData;
private final UserConnection connection;
private final boolean mapped;
RegistryAccessImpl(final Protocol<?, ?, ?, ?> protocol) {
this.mappingData = protocol.getMappingData();
this.registryDataRewriter = protocol.getRegistryDataRewriter();
RegistryAccessImpl(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection) {
this.mappingData = Preconditions.checkNotNull(protocol.getMappingData());
this.entityTracker = Preconditions.checkNotNull(protocol.getEntityRewriter().tracker(connection));
this.connection = connection;
this.mapped = false;
}
private RegistryAccessImpl(final MappingData mappingData, final RegistryDataRewriter registryDataRewriter, final boolean mapped) {
private RegistryAccessImpl(final MappingData mappingData, final UserConnection connection, final EntityTracker entityTracker, final boolean mapped) {
this.mappingData = mappingData;
this.registryDataRewriter = registryDataRewriter;
this.connection = connection;
this.entityTracker = entityTracker;
this.mapped = mapped;
}
@Override
public UserConnection connection() {
return connection;
}
@Override
public Key item(final int id) {
return key(mappingData.getFullItemMappings(), id);
@@ -91,8 +100,8 @@ final class RegistryAccessImpl implements CodecContext.RegistryAccess {
@Override
public Key registryKey(final String registry, final int id) {
final KeyMappings mappings = registryDataRewriter.getMappings(registry);
Preconditions.checkNotNull(mappings, "No registry mappings for registry: " + registry);
final KeyMappings mappings = entityTracker.registryKeys(registry);
Preconditions.checkNotNull(mappings, "No registry mappings for registry: %s", registry);
final String identifier = id >= 0 && id < mappings.size() ? mappings.idToKey(id) : null;
return key(identifier, id);
}
@@ -108,6 +117,6 @@ final class RegistryAccessImpl implements CodecContext.RegistryAccess {
@Override
public CodecContext.RegistryAccess withMapped(final boolean mapped) {
return this.mapped == mapped ? this : new RegistryAccessImpl(this.mappingData, this.registryDataRewriter, mapped);
return this.mapped == mapped ? this : new RegistryAccessImpl(this.mappingData, this.connection, this.entityTracker, mapped);
}
}
@@ -122,7 +122,7 @@ public record BlocksAttacks(
.writeOptional("disable_cooldown_scale", Types.FLOAT, value.disableCooldownScale(), 1F)
.writeOptional("damage_reductions", DamageReduction.ARRAY_TYPE, value.damageReductions(), defaultDamageReductions)
.writeOptional("item_damage", ItemDamageFunction.TYPE, value.itemDamage(), defaultItemDamage)
.writeOptional("bypassed_by", Types.HOLDER_SET, value.bypassedBy())
.writeOptional("bypassed_by", new HolderSetType(RegistryKey.of("damage_type")), value.bypassedBy())
.writeOptional("block_sound", Types.SOUND_EVENT, value.blockSound())
.writeOptional("disabled_sound", Types.SOUND_EVENT, value.disableSound()));
}
@@ -74,6 +74,11 @@ public interface PacketTypesProvider<CU extends ClientboundPacketType, CM extend
return map != null ? map.typeByName(typeName) : null;
}
default @Nullable CM mappedClientboundType(final State state, final String typeName) {
PacketTypeMap<CM> map = mappedClientboundPacketTypes().get(state);
return map != null ? map.typeByName(typeName) : null;
}
default @Nullable SU unmappedServerboundType(final State state, final String typeName) {
PacketTypeMap<SU> map = unmappedServerboundPacketTypes().get(state);
return map != null ? map.typeByName(typeName) : null;
@@ -25,19 +25,22 @@ package com.viaversion.viaversion.api.rewriter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.util.KeyMappings;
import org.checkerframework.checker.nullness.qual.Nullable;
public interface RegistryDataRewriter {
void handle(PacketWrapper wrapper);
@Nullable
KeyMappings getMappings(String registryKey);
boolean shouldRemoveRegistry(String registryKey);
boolean hasRegistriesToRemove();
void updateDialog(UserConnection connection, CompoundTag tag);
/**
* Sends registries that had additional entries configured, but weren't sent by the server at all
* by the time finish_configuration was sent to the client.
*
* @param connection the user connection
*/
void sendMissingRegistries(final UserConnection connection);
}
@@ -26,10 +26,10 @@ import com.viaversion.viaversion.api.minecraft.codec.Ops;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class OptionalType<T> extends Type<T> {
public class OptionalType<T> extends Type<T> {
private final Type<T> type;
protected OptionalType(final Type<T> type) {
public OptionalType(final Type<T> type) {
super(type.getOutputClass());
this.type = type;
}
@@ -22,6 +22,7 @@
*/
package com.viaversion.viaversion.api.type.types.misc;
import com.viaversion.nbt.limiter.TagLimiter;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.api.minecraft.codec.Ops;
@@ -49,7 +50,7 @@ public class CompoundTagType extends Type<CompoundTag> {
public CompoundTagType(final boolean limitMaxBytes) {
super(CompoundTag.class);
this.maxBytes = limitMaxBytes ? NamedCompoundTagType.MAX_NBT_BYTES : Integer.MAX_VALUE;
this.maxBytes = limitMaxBytes ? TagLimiter.DEFAULT_MAX_BYTES : Integer.MAX_VALUE;
}
@Override
@@ -28,17 +28,14 @@ import com.viaversion.nbt.tag.Tag;
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.io.FastByteBufInputStream;
import com.viaversion.viaversion.io.FastByteBufOutputStream;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.Nullable;
public class NamedCompoundTagType extends Type<CompoundTag> {
public static final int MAX_NBT_BYTES = 2097152; // 2mb
public static final int MAX_NESTING_LEVEL = 512;
public NamedCompoundTagType() {
super(CompoundTag.class);
}
@@ -46,7 +43,7 @@ public class NamedCompoundTagType extends Type<CompoundTag> {
@Override
public CompoundTag read(final ByteBuf buffer) {
try {
return read(buffer, MAX_NBT_BYTES, true);
return read(buffer, TagLimiter.DEFAULT_MAX_BYTES, true);
} catch (final IOException e) {
throw new RuntimeException(e);
}
@@ -74,8 +71,8 @@ public class NamedCompoundTagType extends Type<CompoundTag> {
buffer.skipBytes(buffer.readUnsignedShort());
}
final TagLimiter tagLimiter = TagLimiter.create(maxBytes, MAX_NESTING_LEVEL);
return CompoundTag.read(new ByteBufInputStream(buffer), tagLimiter, 0);
final TagLimiter tagLimiter = TagLimiter.create(maxBytes, TagLimiter.DEFAULT_MAX_NESTING_LEVEL);
return CompoundTag.read(new FastByteBufInputStream(buffer), tagLimiter, 0);
}
public static void write(final ByteBuf buffer, final Tag tag, final @Nullable String name) throws IOException {
@@ -84,7 +81,7 @@ public class NamedCompoundTagType extends Type<CompoundTag> {
return;
}
final ByteBufOutputStream out = new ByteBufOutputStream(buffer);
final FastByteBufOutputStream out = new FastByteBufOutputStream(buffer);
out.writeByte(tag.getTagId());
if (name != null) {
out.writeUTF(name);
@@ -43,8 +43,8 @@ import com.viaversion.viaversion.api.minecraft.codec.Ops;
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.io.FastByteBufInputStream;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import java.io.IOException;
import java.util.Map;
@@ -58,7 +58,7 @@ public class TagType extends Type<Tag> {
public TagType(final boolean limitBytes) {
super(Tag.class);
this.maxBytes = limitBytes ? NamedCompoundTagType.MAX_NBT_BYTES : Integer.MAX_VALUE;
this.maxBytes = limitBytes ? TagLimiter.DEFAULT_MAX_BYTES : Integer.MAX_VALUE;
}
@Override
@@ -68,9 +68,9 @@ public class TagType extends Type<Tag> {
return null;
}
final TagLimiter tagLimiter = TagLimiter.create(this.maxBytes, NamedCompoundTagType.MAX_NESTING_LEVEL);
final TagLimiter tagLimiter = TagLimiter.create(this.maxBytes, TagLimiter.DEFAULT_MAX_NESTING_LEVEL);
try {
return TagRegistry.read(id, new ByteBufInputStream(buffer), tagLimiter, 0);
return TagRegistry.read(id, new FastByteBufInputStream(buffer), tagLimiter, 0);
} catch (final IOException e) {
if (Via.getManager().isDebug()) {
throw new RuntimeException(e);
@@ -0,0 +1,312 @@
/*
* 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.io;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import java.io.DataInput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;
import java.nio.charset.StandardCharsets;
/**
* {@link ByteBufInputStream} alternative with a more fine-tuned {@link #readUTF()} implementation
* for fast reading of NBT strings.
*/
public final class FastByteBufInputStream extends InputStream implements DataInput {
private static final byte[] EMPTY_BYTES = new byte[0];
private static final char[] EMPTY_CHARS = new char[0];
private byte[] bytearr = EMPTY_BYTES;
private char[] chararr = EMPTY_CHARS;
private final ByteBuf buf;
public FastByteBufInputStream(final ByteBuf buffer) {
Preconditions.checkNotNull(buffer);
this.buf = buffer;
buffer.markReaderIndex();
}
@Override
public String readUTF() throws IOException {
// Note that this is a modified UTF-8 format, so we can't use netty's string reading
final int utflen = readUnsignedShort();
if (utflen == 0) {
return "";
}
final ByteBuf buf = this.buf;
final int readerIdx = buf.readerIndex();
if (buf.readableBytes() < utflen) {
throw new EOFException();
}
final String result;
if (buf.hasArray()) {
// Directly pass in the backing array
result = decodeFromArray(buf.array(), buf.arrayOffset() + readerIdx, utflen);
buf.readerIndex(readerIdx + utflen);
} else {
// We need to construct some array for the String anyway, so reading it into an array is fine;
// share the byte array.
final byte[] data = ensureBytes(utflen);
buf.readBytes(data, 0, utflen);
result = decodeFromArray(data, 0, utflen);
}
return result;
}
private String decodeFromArray(final byte[] data, final int offset, final int utflen) throws UTFDataFormatException {
// Go through ASCII symbols and directly pass through as a latin-1 string if possible
int i = 0;
while (i < utflen && data[offset + i] >= 0) {
i++;
}
if (i == utflen) {
return new String(data, offset, utflen, StandardCharsets.ISO_8859_1);
}
// Continue with a regular UTF-8 char decode
final char[] chars = ensureChars(utflen);
for (int j = 0; j < i; j++) {
chars[j] = (char) data[offset + j];
}
final int charCount = decode(data, offset + i, offset + utflen, chars, i);
return new String(chars, 0, charCount);
}
private static int decode(final byte[] data, int pos, final int end, final char[] chars, int charCount) throws UTFDataFormatException {
// From DataInputStream
while (pos < end) {
final int c = data[pos] & 0xff;
switch (c >> 4) {
case 0, 1, 2, 3, 4, 5, 6, 7 -> {
pos++;
chars[charCount++] = (char) c;
}
case 12, 13 -> {
if (pos + 2 > end) {
throw new UTFDataFormatException("malformed input: partial character at end");
}
final int char2 = data[pos + 1];
if ((char2 & 0xC0) != 0x80) {
throw new UTFDataFormatException("malformed input around byte " + (pos + 2));
}
chars[charCount++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
pos += 2;
}
case 14 -> {
if (pos + 3 > end) {
throw new UTFDataFormatException("malformed input: partial character at end");
}
final int char2 = data[pos + 1];
final int char3 = data[pos + 2];
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException("malformed input around byte " + (pos + 2));
}
chars[charCount++] = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F));
pos += 3;
}
default -> throw new UTFDataFormatException("malformed input around byte " + pos);
}
}
return charCount;
}
private byte[] ensureBytes(final int len) {
if (bytearr.length < len) {
bytearr = new byte[Math.max(len, 64)];
}
return bytearr;
}
private char[] ensureChars(final int len) {
if (chararr.length < len) {
chararr = new char[Math.max(len, 64)];
}
return chararr;
}
@Override
public int available() {
return buf.readableBytes();
}
@Override
public void mark(final int readlimit) {
buf.markReaderIndex();
}
@Override
public boolean markSupported() {
return true;
}
@Override
public int read() throws IOException {
if (!buf.isReadable()) {
return -1;
}
return buf.readByte() & 0xff;
}
@Override
public int read(final byte[] b, final int off, int len) throws IOException {
final int available = buf.readableBytes();
if (available == 0) {
return -1;
}
len = Math.min(available, len);
buf.readBytes(b, off, len);
return len;
}
@Override
public void reset() throws IOException {
buf.resetReaderIndex();
}
@Override
public long skip(final long n) throws IOException {
if (n > Integer.MAX_VALUE) {
return skipBytes(Integer.MAX_VALUE);
} else {
return skipBytes((int) n);
}
}
@Override
public boolean readBoolean() throws IOException {
checkAvailable(1);
return buf.readBoolean();
}
@Override
public byte readByte() throws IOException {
checkAvailable(1);
return buf.readByte();
}
@Override
public char readChar() {
return (char) buf.readShort();
}
@Override
public double readDouble() {
return Double.longBitsToDouble(buf.readLong());
}
@Override
public float readFloat() throws IOException {
return Float.intBitsToFloat(buf.readInt());
}
@Override
public void readFully(final byte[] b) throws IOException {
readFully(b, 0, b.length);
}
@Override
public void readFully(final byte[] b, final int off, final int len) throws IOException {
if (len < 0) {
throw new IndexOutOfBoundsException("fieldSize cannot be a negative number");
}
checkAvailable(len);
buf.readBytes(b, off, len);
}
@Override
public int readInt() throws IOException {
checkAvailable(4);
return buf.readInt();
}
@Override
public String readLine() {
// Don't reuse the StringBuilder here, this is not relevant for packet reading
final StringBuilder lineBuf = new StringBuilder();
int available = available();
if (available == 0) {
return null;
}
loop:
do {
int c = buf.readUnsignedByte();
--available;
switch (c) {
case '\n':
break loop;
case '\r':
if (available > 0 && (char) buf.getUnsignedByte(buf.readerIndex()) == '\n') {
buf.skipBytes(1);
--available;
}
break loop;
default:
lineBuf.append((char) c);
}
} while (available > 0);
return !lineBuf.isEmpty() ? lineBuf.toString() : "";
}
@Override
public long readLong() throws IOException {
checkAvailable(8);
return buf.readLong();
}
@Override
public short readShort() throws IOException {
checkAvailable(2);
return buf.readShort();
}
@Override
public int readUnsignedByte() {
return buf.readByte() & 0xff;
}
@Override
public int readUnsignedShort() {
return buf.readShort() & 0xffff;
}
@Override
public int skipBytes(final int n) {
final int nBytes = Math.min(available(), n);
buf.skipBytes(nBytes);
return nBytes;
}
private void checkAvailable(final int fieldSize) throws IOException {
if (fieldSize > buf.readableBytes()) {
throw new EOFException("fieldSize is too long! Length is " + fieldSize + ", but maximum is " + available());
}
}
}
@@ -0,0 +1,178 @@
/*
* 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.io;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UTFDataFormatException;
/**
* {@link ByteBufOutputStream} alternative with a more fine-tuned {@link #writeUTF(String)} implementation
* for fast writing of NBT strings.
*/
public final class FastByteBufOutputStream extends OutputStream implements DataOutput {
private final ByteBuf buf;
public FastByteBufOutputStream(final ByteBuf buffer) {
Preconditions.checkNotNull(buffer);
this.buf = buffer;
}
@Override
public void writeUTF(final String s) throws IOException {
// This writes the individual characters to the buffer without an intermediary array.
// What's lost from no longer bulk writing is gained from avoiding allocation and multiple
// extra iterations of the full string.
final int len = s.length();
if (len == 0) {
buf.writeShort(0);
return;
}
if (len > 65535) {
throw new UTFDataFormatException("encoded string too long: " + len + " characters");
}
// Reserve space for the length prefix
final int lenIndex = buf.writerIndex();
final int startIndex = lenIndex + Short.BYTES;
buf.ensureWritable(Short.BYTES + len); // even if it ends up being larger, netty does a good job at avoiding constant resizing
buf.writeShort(len);
// Start writing single ASCII bytes
int i = 0;
for (; i < len; i++) {
final char c = s.charAt(i);
if (c >= 0x80 || c == 0) break;
buf.writeByte(c);
}
if (i == len) {
return; // done
}
// Write the rest
for (; i < len; i++) {
final char c = s.charAt(i);
if (c < 0x80 && c != 0) {
buf.writeByte(c);
} else if (c >= 0x800) {
buf.writeByte(0xE0 | ((c >> 12) & 0x0F));
buf.writeByte(0x80 | ((c >> 6) & 0x3F));
buf.writeByte(0x80 | (c & 0x3F));
} else {
buf.writeByte(0xC0 | ((c >> 6) & 0x1F));
buf.writeByte(0x80 | (c & 0x3F));
}
}
// Don't do another round of counting just for the bounds check; check at the end,
// even if that means that in the worst case this might have already written a good bit over the threshold.
final int utflen = buf.writerIndex() - startIndex;
if (utflen > 65535) {
buf.writerIndex(lenIndex);
throw new UTFDataFormatException("encoded string too long: " + utflen + " bytes");
}
// Set the actual length
buf.setShort(lenIndex, utflen);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
buf.writeBytes(b, off, len);
}
@Override
public void write(final byte[] b) throws IOException {
buf.writeBytes(b);
}
@Override
public void write(final int b) throws IOException {
buf.writeByte(b);
}
@Override
public void writeBoolean(final boolean v) throws IOException {
buf.writeBoolean(v);
}
@Override
public void writeByte(final int v) throws IOException {
buf.writeByte(v);
}
@Override
public void writeBytes(final String s) throws IOException {
final int length = s.length();
buf.ensureWritable(length);
final int offset = buf.writerIndex();
for (int i = 0; i < length; i++) {
buf.setByte(offset + i, (byte) s.charAt(i));
}
buf.writerIndex(offset + length);
}
@Override
public void writeChar(final int v) {
buf.writeChar(v);
}
@Override
public void writeChars(final String s) {
final int len = s.length();
for (int i = 0; i < len; i++) {
buf.writeChar(s.charAt(i));
}
}
@Override
public void writeDouble(final double v) throws IOException {
buf.writeDouble(v);
}
@Override
public void writeFloat(final float v) throws IOException {
buf.writeFloat(v);
}
@Override
public void writeInt(final int v) throws IOException {
buf.writeInt(v);
}
@Override
public void writeLong(final long v) throws IOException {
buf.writeLong(v);
}
@Override
public void writeShort(final int v) throws IOException {
buf.writeShort((short) v);
}
}
@@ -23,6 +23,7 @@
package com.viaversion.viaversion.util;
import java.util.function.IntToLongFunction;
import java.util.function.IntUnaryOperator;
public final class CompactArrayUtil {
private static final long[] RECIPROCAL_MULT_AND_ADD = {
@@ -51,40 +52,39 @@ public final class CompactArrayUtil {
throw new UnsupportedOperationException();
}
public static long[] createCompactArrayWithPadding(int bitsPerEntry, int entries, IntToLongFunction valueGetter) {
long maxEntryValue = (1L << bitsPerEntry) - 1;
char valuesPerLong = (char) (64 / bitsPerEntry);
int magicIndex = valuesPerLong - 1;
long divideAdd = RECIPROCAL_MULT_AND_ADD[magicIndex];
long divideMul = divideAdd != 0 ? divideAdd : 0x80000000L; // Special case for the 0x80000000/0x00000000 tuple
int divideShift = RECIPROCAL_RIGHT_SHIFT[magicIndex];
int size = (entries + valuesPerLong - 1) / valuesPerLong;
public static long[] createCompactArrayWithPadding(int bitsPerEntry, int entries, IntUnaryOperator valueGetter) {
final long maxEntryValue = (1L << bitsPerEntry) - 1;
final int valuesPerLong = (char) (64 / bitsPerEntry);
final int size = (entries + valuesPerLong - 1) / valuesPerLong;
final long[] data = new long[size];
long[] data = new long[size];
for (int i = 0; i < entries; i++) {
long value = valueGetter.applyAsLong(i);
int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
data[cellIndex] = data[cellIndex] & ~(maxEntryValue << bitIndex) | (value & maxEntryValue) << bitIndex;
int i = 0;
for (int cellIndex = 0; cellIndex < size; cellIndex++) {
long cell = 0L;
int bitIndex = 0;
final int limit = Math.min(i + valuesPerLong, entries);
while (i < limit) {
cell |= (valueGetter.applyAsInt(i++) & maxEntryValue) << bitIndex;
bitIndex += bitsPerEntry;
}
data[cellIndex] = cell;
}
return data;
}
public static void iterateCompactArrayWithPadding(int bitsPerEntry, int entries, long[] data, BiIntConsumer consumer) {
long maxEntryValue = (1L << bitsPerEntry) - 1;
char valuesPerLong = (char) (64 / bitsPerEntry);
int magicIndex = valuesPerLong - 1;
long divideAdd = RECIPROCAL_MULT_AND_ADD[magicIndex];
long divideMul = divideAdd != 0 ? divideAdd : 0x80000000L;
int divideShift = RECIPROCAL_RIGHT_SHIFT[magicIndex];
final long maxEntryValue = (1L << bitsPerEntry) - 1;
final int valuesPerLong = 64 / bitsPerEntry;
final int size = data.length;
for (int i = 0; i < entries; i++) {
int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
int value = (int) (data[cellIndex] >> bitIndex & maxEntryValue);
consumer.consume(i, value);
int i = 0;
for (int cellIndex = 0; cellIndex < size; cellIndex++) {
long cell = data[cellIndex];
final int limit = Math.min(i + valuesPerLong, entries);
while (i < limit) {
consumer.consume(i++, (int) (cell & maxEntryValue));
cell >>>= bitsPerEntry;
}
}
}
@@ -49,7 +49,7 @@ fun ShadowJar.configureRelocations() {
}
fun ShadowJar.configureExcludes() {
// FastUtil - we only want object, int, and certain reference maps
// FastUtil - we only want object, int, long-to-object, and certain reference maps
// Object types
exclude("it/unimi/dsi/fastutil/*/*2Reference*")
exclude("it/unimi/dsi/fastutil/*/*Reference2Int*")
@@ -58,17 +58,23 @@ fun ShadowJar.configureExcludes() {
exclude("it/unimi/dsi/fastutil/*/*Short*")
exclude("it/unimi/dsi/fastutil/*/*Float*")
exclude("it/unimi/dsi/fastutil/*/*Double*")
exclude("it/unimi/dsi/fastutil/*/*Long*")
exclude("it/unimi/dsi/fastutil/*/*2Long*")
exclude("it/unimi/dsi/fastutil/*/*Char*")
// Map types
exclude("it/unimi/dsi/fastutil/*/*Custom*")
exclude("it/unimi/dsi/fastutil/*/*Tree*")
exclude("it/unimi/dsi/fastutil/*/*Heap*")
exclude("it/unimi/dsi/fastutil/*/*Queue*")
exclude("it/unimi/dsi/fastutil/longs/*2Int*")
exclude("it/unimi/dsi/fastutil/longs/*ArrayMap*")
exclude("it/unimi/dsi/fastutil/longs/*LinkedOpenHashMap*")
exclude("it/unimi/dsi/fastutil/longs/*SortedMap*")
// Crossing fingers
exclude("it/unimi/dsi/fastutil/*Big*")
exclude("it/unimi/dsi/fastutil/*/*Big*")
exclude("it/unimi/dsi/fastutil/*/*Synchronized*")
exclude("it/unimi/dsi/fastutil/*/*Unmodifiable*")
// More
exclude("it/unimi/dsi/fastutil/io/TextIO")
exclude("it/unimi/dsi/fastutil/io/TextIO.class")
exclude("it/unimi/dsi/fastutil/io/BinIO.class")
}
+1
View File
@@ -1,5 +1,6 @@
plugins {
base
eclipse
id("via.build-logic")
}
@@ -267,8 +267,8 @@ public class ViaManagerImpl implements ViaManager {
platform.getLogger().warning("ViaVersion no longer officially supports this version of Java, only offering unsupported compatibility builds.");
platform.getLogger().warning("See https://github.com/ViaVersion/ViaVersion/releases/tag/5.0.0 for more information.");
} else if (version < 21) {
platform.getLogger().warning("Please update your Java runtime to at least Java 21 (your version is " + javaVersion + ").");
platform.getLogger().warning("At some point in the future, ViaVersion will no longer be compatible with this version of Java.");
// platform.getLogger().warning("Please update your Java runtime to at least Java 21 (your version is " + javaVersion + ").");
// platform.getLogger().warning("At some point in the future, ViaVersion will no longer be compatible with this version of Java.");
}
}
@@ -115,7 +115,6 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
applySharedRegistrations();
registerPackets();
registerConfigurationChangeHandlers();
// Register the rest of the ids with no handlers if necessary
if (unmappedClientboundPacketType != null && mappedClientboundPacketType != null
@@ -149,31 +148,6 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
return new ProtocolLogger(getClass());
}
protected void registerConfigurationChangeHandlers() {
// Register handlers for protocol state switching
// Assuming ids will change too often, it is cleaner to register them here instead of the base protocols,
// even if there will be multiple of these handlers
final SU configurationAcknowledgedPacket = configurationAcknowledgedPacket();
if (configurationAcknowledgedPacket != null) {
appendServerbound(configurationAcknowledgedPacket, setClientStateHandler(State.CONFIGURATION));
}
final CU startConfigurationPacket = startConfigurationPacket();
if (startConfigurationPacket != null) {
appendClientbound(startConfigurationPacket, startConfigurationHandler());
}
final SU finishConfigurationPacket = serverboundFinishConfigurationPacket();
if (finishConfigurationPacket != null) {
appendServerbound(finishConfigurationPacket, setClientStateHandler(State.PLAY));
}
final CU clientboundFinishConfigurationPacket = clientboundFinishConfigurationPacket();
if (clientboundFinishConfigurationPacket != null) {
appendClientbound(clientboundFinishConfigurationPacket, setServerStateHandler(State.PLAY));
}
}
@Override
public void appendClientbound(final CU type, final PacketHandler handler) {
final PacketMapping mapping = clientboundMappings.mappedPacket(type.state(), type.getId());
@@ -234,14 +208,6 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
}
}
public void registerFinishConfiguration(final CU packetType, final PacketHandler handler) {
registerClientbound(packetType, wrapper -> {
// TODO Temporary solution to handle the finish configuration packet already having changed our tracked protocol state in a previous handler
wrapper.user().getProtocolInfo().setServerState(State.CONFIGURATION);
handler.handle(wrapper);
});
}
@Override
public final void loadMappingData() {
getMappingData().load();
@@ -316,22 +282,6 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
return PacketMappings.arrayMappings();
}
protected @Nullable SU configurationAcknowledgedPacket() {
return packetTypesProvider.unmappedServerboundType(State.PLAY, "CONFIGURATION_ACKNOWLEDGED");
}
protected @Nullable CU startConfigurationPacket() {
return packetTypesProvider.unmappedClientboundType(State.PLAY, "START_CONFIGURATION");
}
protected @Nullable SU serverboundFinishConfigurationPacket() {
return packetTypesProvider.unmappedServerboundType(State.CONFIGURATION, "FINISH_CONFIGURATION");
}
protected @Nullable CU clientboundFinishConfigurationPacket() {
return packetTypesProvider.unmappedClientboundType(State.CONFIGURATION, "FINISH_CONFIGURATION");
}
/**
* Returns the server protocol version for this protocol,
* or null if not set (e.g. for base protocols).
@@ -563,23 +513,6 @@ public abstract class AbstractProtocol<CU extends ClientboundPacketType, CM exte
}
}
private PacketHandler setClientStateHandler(final State state) {
return wrapper -> wrapper.user().getProtocolInfo().setClientState(state);
}
private PacketHandler setServerStateHandler(final State state) {
return wrapper -> wrapper.user().getProtocolInfo().setServerState(state);
}
private PacketHandler startConfigurationHandler() {
return setServerStateHandler(State.CONFIGURATION).then(wrapper -> {
// Mimic client behaviour
final EntityTracker tracker = wrapper.user().getEntityTracker(getClass());
if (tracker != null) {
tracker.clear();
}
});
}
@Override
public final PacketTypesProvider<CU, CM, SM, SU> getPacketTypesProvider() {
@@ -17,6 +17,7 @@
*/
package com.viaversion.viaversion.codec;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.codec.CodecContext;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.protocol.Protocol;
@@ -48,4 +49,9 @@ public record CodecRegistryContext(Protocol<?, ?, ?, ?> protocol, RegistryAccess
final VersionedTypesHolder types = mapped ? protocol.mappedTypes() : protocol.types();
return (NOT_IMPLEMENTED.isEmpty() || !NOT_IMPLEMENTED.contains(key)) && types.structuredDataKeys().supportsOps(key);
}
@Override
public UserConnection connection() {
return registryAccess.connection();
}
}
@@ -27,14 +27,17 @@ import com.viaversion.viaversion.api.data.entity.StoredEntityData;
import com.viaversion.viaversion.api.data.entity.TrackedEntity;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.util.Key;
import com.viaversion.viaversion.util.KeyMappings;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeListener {
protected final Int2ObjectMap<TrackedEntity> entities = new Int2ObjectOpenHashMap<>();
private final Map<String, KeyMappings> registryKeyMappings = new HashMap<>();
private final UserConnection connection;
private final EntityType playerType;
private Integer clientEntityId;
@@ -115,6 +118,7 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
}
clientEntityId = null;
registryKeyMappings.clear();
}
@Override
@@ -222,4 +226,14 @@ public class EntityTrackerBase implements EntityTracker, ClientEntityIdChangeLis
public void setDimensions(Map<String, DimensionData> dimensions) {
this.dimensions = dimensions;
}
@Override
public @Nullable KeyMappings registryKeys(String registryKey) {
return registryKeyMappings.get(Key.stripMinecraftNamespace(registryKey));
}
@Override
public void addRegistryKeys(String registryKey, KeyMappings mappings) {
registryKeyMappings.put(Key.stripMinecraftNamespace(registryKey), mappings);
}
}
@@ -40,14 +40,14 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class ItemHasherBase implements ItemHasher {
public static int UNKNOWN_HASH = 399825415; // some random-ish number, from hashing Integer.MIN_VALUE+1 with crc32c
private final Map<Integer, HashedItem> hashes = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(1024).<Integer, HashedItem>build().asMap();
private final Map<Integer, OriginalHashedItem> hashes = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(1024).<Integer, OriginalHashedItem>build().asMap();
protected final UserConnection connection;
private boolean processingClientboundInventoryPacket;
private final CodecContext context;
private final CodecContext mappedContext;
public ItemHasherBase(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection) {
final RegistryAccess registryAccess = RegistryAccess.of(protocol);
final RegistryAccess registryAccess = RegistryAccess.of(protocol, connection);
this.context = new CodecRegistryContext(protocol, registryAccess, false);
this.mappedContext = new CodecRegistryContext(protocol, registryAccess, true);
this.connection = connection;
@@ -83,12 +83,13 @@ public class ItemHasherBase implements ItemHasher {
*
* @param customData custom_data tag
* @param originalHashedItem the original (pre-transformed) hashed item
* @param backupTagName protocol-specific backup tag name where the item should become a regular hashed item again
*/
public void trackOriginalHashedItem(final CompoundTag customData, final HashedItem originalHashedItem) {
public void trackOriginalHashedItem(final CompoundTag customData, final HashedItem originalHashedItem, final String backupTagName) {
// 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);
this.hashes.put(customDataHash, new OriginalHashedItem(originalHashedItem, backupTagName));
}
/**
@@ -98,8 +99,8 @@ public class ItemHasherBase implements ItemHasher {
* @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);
public @Nullable OriginalHashedItem originalHashedItem(final int customDataHash, final HashedItem clientItem) {
OriginalHashedItem originalItem = this.hashes.get(customDataHash);
if (originalItem == null) {
return null;
}
@@ -0,0 +1,60 @@
/*
* 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.data.item;
import com.viaversion.viaversion.api.minecraft.item.HashedItem;
import com.viaversion.viaversion.api.minecraft.item.HashedStructuredItem;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
/**
* Marker class to avoid continuous hashing and restoration of items in every protocol.
* Instead, this restores it once and passes the result through to the protocol that originally stored the backup.
*/
public final class OriginalHashedItem extends HashedStructuredItem {
private final String backupTagName;
public OriginalHashedItem(final HashedItem item, final String backupTagName) {
this(item.identifier(), item.amount(), item.dataHashesById(), item.removedDataIds(), backupTagName); // don't copy
}
public OriginalHashedItem(final int identifier, final int amount, final String backupTagName) {
super(identifier, amount);
this.backupTagName = backupTagName;
}
private OriginalHashedItem(final int identifier, final int amount, final Int2IntMap dataHashes, final IntSet removedData, final String backupTagName) {
super(identifier, amount, dataHashes, removedData);
this.backupTagName = backupTagName;
}
public String backupTagName() {
return backupTagName;
}
public HashedItem asRegularItem() {
return new HashedStructuredItem(identifier(), amount(), dataHashesById(), removedDataIds());
}
@Override
public OriginalHashedItem copy() {
return new OriginalHashedItem(identifier(), amount(), new Int2IntOpenHashMap(dataHashesById()), new IntOpenHashSet(removedDataIds()), backupTagName);
}
}
@@ -338,7 +338,10 @@ public class PacketWrapperImpl implements PacketWrapper {
final ProtocolInfo protocolInfo = user().getProtocolInfo();
final List<Protocol> protocols = protocolInfo.getPipeline().pipes(protocolClass, skipCurrentPipeline, direction);
apply(direction, protocolInfo.getState(direction), protocols);
// Use the packet's own state if known to avoid issues with stale tracked connection state
// (e.g. a previous protocol in the pipeline already having changed serverState to PLAY while still processing FINISH_CONFIGURATION)
final State state = this.packetType != null ? this.packetType.state() : protocolInfo.getState(direction);
apply(direction, state, protocols);
final ByteBuf output = allocateOutputBuffer();
try {
writeToBuffer(output);
@@ -109,7 +109,7 @@ public record RegistrationContext<CU extends ClientboundPacketType, SU extends S
return false;
}
private SU serverboundPacketType(final ServerboundPacketType genericPacketType) {
public SU serverboundPacketType(final ServerboundPacketType genericPacketType) {
final SU type = protocol.getPacketTypesProvider().unmappedServerboundType(genericPacketType.state(), genericPacketType.getName());
if (type == null) {
throw new IllegalArgumentException("Could not find serverbound packet type for " + genericPacketType.state() + " " + genericPacketType.getName());
@@ -117,7 +117,7 @@ public record RegistrationContext<CU extends ClientboundPacketType, SU extends S
return type;
}
private CU clientboundPacketType(final ClientboundPacketType genericPacketType) {
public CU clientboundPacketType(final ClientboundPacketType genericPacketType) {
final CU type = protocol.getPacketTypesProvider().unmappedClientboundType(genericPacketType.state(), genericPacketType.getName());
if (type == null) {
throw new IllegalArgumentException("Could not find clientbound packet type for " + genericPacketType.state() + " " + genericPacketType.getName() + " in " + protocol.getClass().getSimpleName());
@@ -19,6 +19,7 @@ package com.viaversion.viaversion.protocol.shared_registration.def;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.protocol.shared_registration.SharedRegistrations;
import com.viaversion.viaversion.protocol.shared_registration.def.base.ConfigurationRegistrations;
public final class DefaultRegistrations {
@@ -101,6 +102,7 @@ public final class DefaultRegistrations {
.since(ProtocolVersion.v1_13, RegistryRegistrations::registerStatistics1_13)
.since(ProtocolVersion.v1_19, RegistryRegistrations::registerCommands1_19)
.since(ProtocolVersion.v1_20_2, ConfigurationRegistrations::registerConfigurationStateSwitching)
.since(ProtocolVersion.v1_21, RegistryRegistrations::registerRegistryData1_21)
.since(ProtocolVersion.v1_21, RegistryRegistrations::registerAttributes1_21)
.since(ProtocolVersion.v1_21_2, RegistryRegistrations::registerRecipePackets1_21_2)
@@ -67,13 +67,11 @@ final class EntityRegistrations {
static <CU extends ClientboundPacketType> void registerEntityPackets1_21_4(final RegistrationContext<CU, ?> ctx, final EntityRewriter<CU, ?> er) {
common1_20_5(ctx, er);
registerGameModeTrackers1_21_4(ctx, er);
ctx.clientbound(ClientboundPackets1_21_2.ADD_ENTITY, er::registerTrackerWithData1_19);
}
static <CU extends ClientboundPacketType> void registerEntityPackets1_21_9(final RegistrationContext<CU, ?> ctx, final EntityRewriter<CU, ?> er) {
common1_20_5(ctx, er);
registerGameModeTrackers1_21_4(ctx, er);
ctx.clientbound(ClientboundPackets1_21_9.ADD_ENTITY, er::registerTrackerWithData1_21_9);
}
@@ -82,11 +80,8 @@ final class EntityRegistrations {
ctx.clientbound(ClientboundPackets1_20_5.REMOVE_ENTITIES, er::registerRemoveEntities);
ctx.clientbound(ClientboundPackets1_20_5.LOGIN, er::registerLogin1_20_5);
ctx.clientbound(ClientboundPackets1_20_5.RESPAWN, er::registerRespawn1_20_5);
}
private static <CU extends ClientboundPacketType> void registerGameModeTrackers1_21_4(final RegistrationContext<CU, ?> ctx, final EntityRewriter<CU, ?> er) {
ctx.clientbound(ClientboundPackets1_21_2.GAME_EVENT, er::registerGameEvent);
ctx.clientbound(ClientboundPackets1_21_2.PLAYER_ABILITIES, er::registerPlayerAbilities);
ctx.clientbound(ClientboundPackets1_20_5.GAME_EVENT, er::registerGameEvent);
ctx.clientbound(ClientboundPackets1_20_5.PLAYER_ABILITIES, er::registerPlayerAbilities);
}
@@ -135,7 +135,7 @@ final class ItemRegistrations {
ctx.clientbound(ClientboundPackets1_21.UPDATE_ADVANCEMENTS, ir::registerAdvancements1_20_3);
ctx.clientbound(ClientboundPackets1_21.COOLDOWN, ir::registerCooldown);
ctx.serverbound(ServerboundPackets1_20_5.CONTAINER_CLICK, ir::registerContainerClick1_17_1);
ctx.serverbound(ServerboundPackets1_20_5.SET_CREATIVE_MODE_SLOT, ir::registerSetCreativeModeSlot);
ctx.serverbound(ServerboundPackets1_20_5.SET_CREATIVE_MODE_SLOT, ir::registerSetCreativeModeSlot1_20_5);
ctx.clientbound(ClientboundPackets1_21.SET_EQUIPMENT, ir::registerSetEquipment);
ctx.clientbound(ClientboundPackets1_21.OPEN_SCREEN, ir::registerOpenScreen);
ctx.clientbound(ClientboundPackets1_21.MERCHANT_OFFERS, ir::registerMerchantOffers1_20_5);
@@ -147,7 +147,7 @@ final class ItemRegistrations {
ctx.clientbound(ClientboundPackets1_21_2.UPDATE_ADVANCEMENTS, ir::registerAdvancements1_20_3);
ctx.clientbound(ClientboundPackets1_21_2.COOLDOWN, ir::registerCooldown1_21_2);
ctx.serverbound(ServerboundPackets1_21_2.CONTAINER_CLICK, ir::registerContainerClick1_21_2);
ctx.serverbound(ServerboundPackets1_21_2.SET_CREATIVE_MODE_SLOT, ir::registerSetCreativeModeSlot);
ctx.serverbound(ServerboundPackets1_21_2.SET_CREATIVE_MODE_SLOT, ir::registerSetCreativeModeSlot1_20_5);
ctx.clientbound(ClientboundPackets1_21_2.SET_EQUIPMENT, ir::registerSetEquipment);
ctx.clientbound(ClientboundPackets1_21_2.OPEN_SCREEN, ir::registerOpenScreen);
ctx.clientbound(ClientboundPackets1_21_2.MERCHANT_OFFERS, ir::registerMerchantOffers1_20_5);
@@ -0,0 +1,66 @@
/*
* 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.protocol.shared_registration.def.base;
import com.viaversion.viaversion.api.data.entity.EntityTracker;
import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType;
import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.protocol.shared_registration.PacketBound;
import com.viaversion.viaversion.protocol.shared_registration.RegistrationContext;
import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundConfigurationPackets1_20_2;
import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ClientboundPackets1_20_2;
import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundConfigurationPackets1_20_2;
import com.viaversion.viaversion.protocols.v1_20to1_20_2.packet.ServerboundPackets1_20_2;
public final class ConfigurationRegistrations {
public static <CU extends ClientboundPacketType, SU extends ServerboundPacketType> void registerConfigurationStateSwitching(final RegistrationContext<CU, SU> ctx) {
ctx.serverbound(ServerboundPackets1_20_2.CONFIGURATION_ACKNOWLEDGED,
packetType -> ctx.protocol().registerServerbound(packetType, wrapper -> wrapper.user().getProtocolInfo().setClientState(State.CONFIGURATION)),
PacketBound.ADDED_AT_MIN
);
ctx.clientbound(ClientboundPackets1_20_2.START_CONFIGURATION, packetType -> ctx.protocol().registerClientbound(packetType, wrapper -> {
wrapper.user().getProtocolInfo().setServerState(State.CONFIGURATION);
// Mimic client behaviour by resetting tracked entities when configuration starts.
final EntityTracker tracker = wrapper.user().getEntityTracker(ctx.protocol().getClass());
if (tracker != null) {
tracker.clear();
}
}), PacketBound.ADDED_AT_MIN);
ctx.serverbound(ServerboundConfigurationPackets1_20_2.FINISH_CONFIGURATION,
packetType -> ctx.protocol().registerServerbound(packetType, wrapper -> wrapper.user().getProtocolInfo().setClientState(State.PLAY)),
PacketBound.ADDED_AT_MIN
);
ctx.clientbound(ClientboundConfigurationPackets1_20_2.FINISH_CONFIGURATION, packetType -> ctx.protocol().registerClientbound(packetType, wrapper -> {
final RegistryDataRewriter registryDataRewriter = ctx.protocol().getRegistryDataRewriter();
if (registryDataRewriter != null) {
registryDataRewriter.sendMissingRegistries(wrapper.user());
}
wrapper.user().getProtocolInfo().setServerState(State.PLAY);
}),
PacketBound.ADDED_AT_MIN
);
}
}
@@ -46,7 +46,7 @@ import java.util.List;
/**
* Initial base protocol which is kept always in the pipeline.
* <p>
* State tracking for configuration state is done via {@link AbstractProtocol#registerConfigurationChangeHandlers()}
* State tracking for configuration state is done via shared registrations.
*/
public class InitialBaseProtocol extends AbstractProtocol<BaseClientboundPacket, BaseClientboundPacket, BaseServerboundPacket, BaseServerboundPacket> {
@@ -87,7 +87,7 @@ public class ParticleIdMappings1_13 {
add(16); // (43->16) endrod -> minecraft:end_rod
add(7); // (44->7) damageindicator -> minecraft:damage_indicator
add(40); // (45->40) sweepattack -> minecraft:sweep_attack
add(20, blockHandler()); // (46->20) fallingdust -> minecraft:falling_dust
add(20, blockHandler(160, 95)); // (46->20) fallingdust -> minecraft:falling_dust
// BlockState VarInt The ID of the block state.
add(41); // (47->41) totem -> minecraft:totem_of_undying
add(38); // (48->38) spit -> minecraft:spit
@@ -148,8 +148,15 @@ public class ParticleIdMappings1_13 {
// Handle (id+(data<<12)) encoded blocks
private static ParticleDataHandler blockHandler() {
return blockHandler(-1, -1);
}
private static ParticleDataHandler blockHandler(int fromBlockId, int toBlockId) {
return (particle, data) -> {
int value = data[0];
if ((value & 4095) == fromBlockId) {
value = value & ~4095 | toBlockId;
}
int combined = (((value & 4095) << 4) | (value >> 12 & 15));
int newId = WorldPacketRewriter1_13.toNewId(combined);
@@ -41,15 +41,15 @@ public class SkullHandler implements BlockEntityProvider.BlockEntityHandler {
int id = storage.get(position).getOriginal();
if (id >= SKULL_WALL_START && id <= SKULL_END) {
NumberTag rot = tag.getNumberTag("Rot");
if (rot != null) {
id += rot.asInt() & 0xF;
}
NumberTag skullType = tag.getNumberTag("SkullType");
if (skullType != null) {
id += skullType.asInt() * 20;
}
NumberTag rot = tag.getNumberTag("Rot");
if (rot != null) {
id += rot.asInt();
}
} else {
Protocol1_12_2To1_13.LOGGER.warning("Why does this block have the skull block entity? " + tag);
return -1;
@@ -18,20 +18,15 @@
package com.viaversion.viaversion.protocols.v1_12_2to1_13.storage;
import com.google.common.collect.EvictingQueue;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import java.util.Queue;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BlockConnectionStorage implements StorableObject {
private static Constructor<?> fastUtilLongObjectHashMap;
private final Map<Long, SectionData> blockStorage = createLongObjectMap();
private final Long2ObjectMap<SectionData> blockStorage = new Long2ObjectOpenHashMap<>();
@SuppressWarnings("UnstableApiUsage")
private final Queue<BlockPosition> modified = EvictingQueue.create(5);
@@ -39,16 +34,6 @@ public class BlockConnectionStorage implements StorableObject {
private long lastIndex = -1;
private SectionData lastSection;
static {
try {
//noinspection StringBufferReplaceableByString - prevent relocation
String className = new StringBuilder("it").append(".unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap").toString();
fastUtilLongObjectHashMap = Class.forName(className).getConstructor();
Via.getPlatform().getLogger().info("Using FastUtil Long2ObjectOpenHashMap for block connections");
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
}
}
public static void init() {
}
@@ -146,18 +131,6 @@ public class BlockConnectionStorage implements StorableObject {
return (((x >> 4) & 0x3FFFFFFL) << 38) | (((y >> 4) & 0xFFFL) << 26) | ((z >> 4) & 0x3FFFFFFL);
}
private <T> Map<Long, T> createLongObjectMap() {
if (fastUtilLongObjectHashMap != null) {
try {
//noinspection unchecked
return (Map<Long, T>) fastUtilLongObjectHashMap.newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
return new HashMap<>();
}
private static final class SectionData {
private final short[] blockStates = new short[4096];
private short nonEmptyBlocks;
@@ -22,12 +22,14 @@ import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.ChunkPosition;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
public class BlockStorage implements StorableObject {
private static final IntSet WHITELIST = new IntOpenHashSet(46);
private final Map<Long, Map<BlockPosition, ReplacementData>> blocks = new HashMap<>();
private final Long2ObjectMap<Map<BlockPosition, ReplacementData>> blocks = new Long2ObjectOpenHashMap<>();
static {
// Flower pots
@@ -38,6 +38,8 @@ public final class ComponentRewriter1_17 extends JsonNBTComponentRewriter<Client
mappings.put("commands.replaceitem.block.success", "commands.item.block.set.success");
mappings.put("commands.debug.reportSaved", "Created debug report in %s");
mappings.put("commands.debug.reportFailed", "Failed to create debug report");
mappings.put("commands.locate.success", "The nearest %s is at %s (%s blocks away)");
mappings.put("commands.locatebiome.success", "The nearest %s is at %s (%s blocks away)");
}
@Override
@@ -19,16 +19,16 @@ package com.viaversion.viaversion.protocols.v1_17_1to1_18.storage;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.minecraft.ChunkPosition;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class ChunkLightStorage implements StorableObject {
private final Map<Long, ChunkLight> lightPackets = new HashMap<>();
private final Set<Long> loadedChunks = new HashSet<>();
private final Long2ObjectMap<ChunkLight> lightPackets = new Long2ObjectOpenHashMap<>();
private final LongSet loadedChunks = new LongOpenHashSet();
public void storeLight(final int x, final int z, final ChunkLight chunkLight) {
lightPackets.put(ChunkPosition.chunkKey(x, z), chunkLight);
@@ -73,6 +73,20 @@ public final class Protocol1_19_3To1_19_4 extends AbstractProtocol<ClientboundPa
};
replaceClientbound(ClientboundPackets1_19_3.COMMANDS, commandRewriter::handle1_19);
registerClientbound(ClientboundPackets1_19_3.UPDATE_MOB_EFFECT, wrapper -> {
wrapper.passthrough(Types.VAR_INT); // entity id
wrapper.passthrough(Types.VAR_INT); // effect id
wrapper.passthrough(Types.BYTE); // amplifier
int duration = wrapper.read(Types.VAR_INT);
if (duration >= Short.MAX_VALUE) { // Anything at or above this would display as infinite on 1.19.2 and below, and on 1.19.4+, infinite is -1
wrapper.write(Types.VAR_INT, -1);
} else {
wrapper.write(Types.VAR_INT, duration);
}
});
registerClientbound(ClientboundPackets1_19_3.SERVER_DATA, wrapper -> {
JsonElement element = wrapper.read(Types.OPTIONAL_COMPONENT);
if (element != null) {
@@ -142,6 +142,14 @@ public final class EntityPacketRewriter1_20 extends EntityRewriter<ClientboundPa
registerEntityDataTypeHandler(Types1_20.ENTITY_DATA_TYPES.itemType, Types1_20.ENTITY_DATA_TYPES.blockStateType, Types1_20.ENTITY_DATA_TYPES.optionalBlockStateType, Types1_20.ENTITY_DATA_TYPES.particleType, null);
registerBlockStateHandler(EntityTypes1_19_4.ABSTRACT_MINECART, 11);
// Purpur's Rainglow patch adds a non-vanilla SQUID_COLOR (String) accessor on GlowSquid,
// declared right after DATA_DARK_TICKS_REMAINING, so the server emits it at source index 17.
// AGEABLE_WATER_CREATURE.addIndex(16) in v1_21To1_21_2 later shifts it to index 18, where
// 1.21.11+ clients only allocate 18 slots (max valid index 17) and crash with
// ArrayIndexOutOfBoundsException: Index 18 out of bounds for length 18. Drop at source.
// Vanilla 1.19.4 GlowSquid has no slot 17, so this is safe for non-Purpur servers.
filter().type(EntityTypes1_19_4.GLOW_SQUID).cancel(17);
// Rotate item display by 180 degrees around the Y axis
filter().type(EntityTypes1_19_4.ITEM_DISPLAY).handler((event, data) -> {
if (event.trackedEntity().hasSentEntityData() || event.hasExtraData()) {
@@ -77,7 +77,14 @@ public final class Protocol1_20_3To1_20_5 extends AbstractProtocol<ClientboundPa
private final EntityPacketRewriter1_20_5 entityRewriter = new EntityPacketRewriter1_20_5(this);
private final BlockItemPacketRewriter1_20_5 itemRewriter = new BlockItemPacketRewriter1_20_5(this);
private final ParticleRewriter1_20_5 particleRewriter = new ParticleRewriter1_20_5(this);
private final TagRewriter<ClientboundPacket1_20_3> tagRewriter = new TagRewriter<>(this);
private final TagRewriter<ClientboundPacket1_20_3> tagRewriter = new TagRewriter<>(this) {
@Override
public void handleGeneric(final PacketWrapper wrapper) {
super.handleGeneric(wrapper);
wrapper.resetReader();
wrapper.user().put(new TagKeys(wrapper));
}
};
private final ComponentRewriter1_20_5<ClientboundPacket1_20_3> componentRewriter = new ComponentRewriter1_20_5<>(this, VersionedTypes.V1_20_5.structuredData);
private final BlockRewriter<ClientboundPacket1_20_3> blockRewriter = BlockRewriter.for1_20_2(this, ChunkType1_20_2::new);
@@ -190,7 +197,7 @@ public final class Protocol1_20_3To1_20_5 extends AbstractProtocol<ClientboundPa
wrapper.cancel();
});
registerClientbound(ClientboundPackets1_20_3.START_CONFIGURATION, wrapper -> wrapper.user().put(new AcknowledgedMessagesStorage()));
appendClientbound(ClientboundPackets1_20_3.START_CONFIGURATION, wrapper -> wrapper.user().put(new AcknowledgedMessagesStorage()));
registerClientbound(State.LOGIN, ClientboundLoginPackets.LOGIN_FINISHED, wrapper -> {
wrapper.passthrough(Types.UUID); // UUID
@@ -253,12 +260,6 @@ public final class Protocol1_20_3To1_20_5 extends AbstractProtocol<ClientboundPa
cancelServerbound(ServerboundPackets1_20_5.DEBUG_SAMPLE_SUBSCRIPTION);
}
private void updateTags(final PacketWrapper wrapper) {
tagRewriter.handleGeneric(wrapper);
wrapper.resetReader();
wrapper.user().put(new TagKeys(wrapper));
}
private void fixChatAck(final PacketWrapper wrapper, final AcknowledgedMessagesStorage storage) {
final int offset = wrapper.read(Types.VAR_INT);
final BitSet acknowledged = wrapper.read(Types.ACKNOWLEDGED_BIT_SET);
@@ -344,6 +344,15 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
recipeRewriter.handleRecipeType(wrapper, type);
}
});
protocol.replaceServerbound(ServerboundPackets1_20_5.SET_CREATIVE_MODE_SLOT, wrapper -> {
if (!protocol.getEntityRewriter().tracker(wrapper.user()).canInstaBuild()) {
wrapper.cancel();
return;
}
wrapper.passthrough(Types.SHORT); // Slot
wrapper.write(itemType(), handleItemToServer(wrapper.user(), wrapper.read(mappedItemType())));
});
}
public Item handleNonEmptyItemToClient(final UserConnection connection, @Nullable Item item) {
@@ -1076,7 +1085,7 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
}
private void updateEffects(final ListTag<CompoundTag> effects, final StructuredDataContainer data) {
final SuspiciousStewEffect[] suspiciousStewEffects = new SuspiciousStewEffect[effects.size()];
final List<SuspiciousStewEffect> suspiciousStewEffects = new ArrayList<>();
for (int i = 0; i < effects.size(); i++) {
final CompoundTag effect = effects.get(i);
final String effectIdString = effect.getString("id", "luck");
@@ -1087,10 +1096,10 @@ public final class BlockItemPacketRewriter1_20_5 extends ItemRewriter<Clientboun
effectId,
duration
);
suspiciousStewEffects[i] = stewEffect;
suspiciousStewEffects.add(stewEffect);
}
}
data.set(StructuredDataKey.SUSPICIOUS_STEW_EFFECTS, suspiciousStewEffects);
data.set(StructuredDataKey.SUSPICIOUS_STEW_EFFECTS, suspiciousStewEffects.toArray(SuspiciousStewEffect[]::new));
}
private void updateLodestoneTracker(final boolean tracked, final CompoundTag lodestonePosTag, final String lodestoneDimension, final StructuredDataContainer data) {
@@ -278,7 +278,9 @@ public final class EntityPacketRewriter1_20_5 extends EntityRewriter<Clientbound
storage.clear();
final byte gamemode = wrapper.get(Types.BYTE, 0);
sendRangeAttributes(wrapper.user(), gamemode == GameMode.CREATIVE.id());
final boolean creativeMode = gamemode == GameMode.CREATIVE.id();
sendRangeAttributes(wrapper.user(), creativeMode);
tracker(wrapper.user()).setInstaBuild(creativeMode);
});
}
});
@@ -293,7 +295,9 @@ public final class EntityPacketRewriter1_20_5 extends EntityRewriter<Clientbound
wrapper.passthrough(Types.LONG); // Seed
final byte gamemode = wrapper.passthrough(Types.BYTE);
sendRangeAttributes(wrapper.user(), gamemode == GameMode.CREATIVE.id());
final boolean creativeMode = gamemode == GameMode.CREATIVE.id();
sendRangeAttributes(wrapper.user(), creativeMode);
tracker(wrapper.user()).setInstaBuild(creativeMode);
wrapper.user().put(new ScoreboardTeamStorage());
});
@@ -338,7 +342,14 @@ public final class EntityPacketRewriter1_20_5 extends EntityRewriter<Clientbound
// Resend attributes either with their original list or with the creative range modifier added
final int value = (int) Math.floor(wrapper.passthrough(Types.FLOAT) + 0.5F);
sendRangeAttributes(wrapper.user(), value == GameMode.CREATIVE.id());
final boolean creativeMode = value == GameMode.CREATIVE.id();
sendRangeAttributes(wrapper.user(), creativeMode);
tracker(wrapper.user()).setInstaBuild(creativeMode);
});
protocol.registerClientbound(ClientboundPackets1_20_3.PLAYER_ABILITIES, wrapper -> {
final byte flags = wrapper.passthrough(Types.BYTE);
tracker(wrapper.user()).setInstaBuild((flags & 1 << 3) != 0);
});
}
@@ -67,7 +67,7 @@ public final class EntityPacketRewriter1_21 extends EntityRewriter<ClientboundPa
}
});
protocol.registerFinishConfiguration(ClientboundConfigurationPackets1_20_5.FINISH_CONFIGURATION, wrapper -> {
protocol.appendClientbound(ClientboundConfigurationPackets1_20_5.FINISH_CONFIGURATION, wrapper -> {
// Add new registries
final PacketWrapper paintingRegistryPacket = wrapper.create(ClientboundConfigurationPackets1_20_5.REGISTRY_DATA);
paintingRegistryPacket.write(Types.STRING, "minecraft:painting_variant");
@@ -272,7 +272,7 @@ public final class Protocol1_20To1_20_2 extends AbstractProtocol<ClientboundPack
}
// Queue packets sent by the server while we wait for the client to transition to the configuration state
configurationBridge.addPacketToQueue(packetWrapper, true);
configurationBridge.addClientboundPacketToQueue(packetWrapper);
throw CancelException.generate();
}
@@ -295,7 +295,7 @@ public final class Protocol1_20To1_20_2 extends AbstractProtocol<ClientboundPack
}
}
configurationBridge.addPacketToQueue(packetWrapper, true);
configurationBridge.addClientboundPacketToQueue(packetWrapper);
throw CancelException.generate();
}
@@ -315,7 +315,7 @@ public final class Protocol1_20To1_20_2 extends AbstractProtocol<ClientboundPack
} else {
// Not a packet that can be mapped to the configuration protocol
// Includes resource pack packets to make sure it is not applied sooner than the server expects
configurationBridge.addPacketToQueue(packetWrapper, true);
configurationBridge.addClientboundPacketToQueue(packetWrapper);
throw CancelException.generate();
}
return;
@@ -375,7 +375,7 @@ public final class Protocol1_20To1_20_2 extends AbstractProtocol<ClientboundPack
private PacketHandler queueServerboundPacket(final ServerboundPackets1_20_2 packetType) {
return wrapper -> {
wrapper.setPacketType(packetType);
wrapper.user().get(ConfigurationState.class).addPacketToQueue(wrapper, false);
wrapper.user().get(ConfigurationState.class).addServerboundPacketToQueue(wrapper);
wrapper.cancel();
};
}
@@ -385,11 +385,6 @@ public final class Protocol1_20To1_20_2 extends AbstractProtocol<ClientboundPack
return MAPPINGS;
}
@Override
protected void registerConfigurationChangeHandlers() {
// Don't handle it in the transitioning protocol
}
@Override
public void init(final UserConnection user) {
user.put(new ConfigurationState());
@@ -34,6 +34,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class ConfigurationState implements StorableObject {
private static final int MAX_SERVERBOUND_QUEUED_PACKETS = Integer.getInteger("viaversion.maxQueuedServerboundConfigPackets", 1000);
private static final int MAX_SERVERBOUND_QUEUED_BYTES = Integer.getInteger("viaversion.maxQueuedServerboundConfigPacketBytes", 1048576);
private static final QueuedPacket[] EMPTY_PACKET_ARRAY = new QueuedPacket[0];
private final List<QueuedPacket> packetQueue = new ArrayList<>();
private BridgePhase bridgePhase = BridgePhase.NONE;
@@ -41,6 +43,8 @@ public class ConfigurationState implements StorableObject {
private boolean queuedJoinGame;
private CompoundTag lastDimensionRegistry;
private ClientInformation clientInformation;
private int queuedServerboundBytes;
private int queuedServerboundPackets;
public BridgePhase bridgePhase() {
return bridgePhase;
@@ -70,8 +74,20 @@ public class ConfigurationState implements StorableObject {
this.clientInformation = clientInformation;
}
public void addPacketToQueue(final PacketWrapper wrapper, final boolean clientbound) {
packetQueue.add(toQueuedPacket(wrapper, clientbound, false));
public void addClientboundPacketToQueue(final PacketWrapper wrapper) {
packetQueue.add(toQueuedPacket(wrapper, true, false));
}
public void addServerboundPacketToQueue(final PacketWrapper wrapper) {
final QueuedPacket queued = toQueuedPacket(wrapper, false, false);
final int bytes = queued.buf().readableBytes();
if (queuedServerboundPackets >= MAX_SERVERBOUND_QUEUED_PACKETS || queuedServerboundBytes + bytes > MAX_SERVERBOUND_QUEUED_BYTES) {
wrapper.user().disconnect("Sent too many packets during config phase");
return;
}
queuedServerboundPackets++;
queuedServerboundBytes += bytes;
packetQueue.add(queued);
}
private QueuedPacket toQueuedPacket(final PacketWrapper wrapper, final boolean clientbound, final boolean skipCurrentPipeline) {
@@ -141,6 +157,7 @@ public class ConfigurationState implements StorableObject {
packetQueue.clear();
bridgePhase = BridgePhase.NONE;
queuedJoinGame = false;
queuedServerboundBytes = 0;
}
public boolean queuedOrSentJoinGame() {
@@ -90,10 +90,13 @@ public final class Protocol1_21_11To26_1 extends AbstractProtocol<ClientboundPac
protected void registerPackets() {
super.registerPackets();
registerFinishConfiguration(ClientboundConfigurationPackets1_21_9.FINISH_CONFIGURATION, wrapper -> {
appendClientbound(ClientboundConfigurationPackets1_21_9.FINISH_CONFIGURATION, wrapper -> {
final PacketWrapper clocksPacket = wrapper.create(ClientboundConfigurationPackets1_21_9.REGISTRY_DATA);
clocksPacket.write(Types.STRING, "world_clock");
clocksPacket.write(Types.REGISTRY_ENTRY_ARRAY, new RegistryEntry[]{new RegistryEntry("minecraft:overworld", new CompoundTag())});
clocksPacket.write(Types.REGISTRY_ENTRY_ARRAY, new RegistryEntry[]{
new RegistryEntry("minecraft:overworld", new CompoundTag()),
new RegistryEntry("minecraft:the_end", new CompoundTag())
});
clocksPacket.send(Protocol1_21_11To26_1.class);
sendSoundVariants(wrapper, "cat_sound_variant", MAPPINGS.catSoundVariants());
@@ -198,6 +201,9 @@ public final class Protocol1_21_11To26_1 extends AbstractProtocol<ClientboundPac
spearDamageType.putString("scaling", "when_caused_by_living_non_player");
registryDataRewriter.addEntries("damage_type", new RegistryEntry("spear", spearDamageType));
final CompoundTag temperateChicken = new CompoundTag();
temperateChicken.putString("asset_id", "entity/chicken/chicken_temperate");
temperateChicken.putString("baby_asset_id", "entity/chicken/chicken_temperate_baby");
final CompoundTag coldChicken = new CompoundTag();
coldChicken.putString("asset_id", "entity/chicken/chicken_cold");
coldChicken.putString("baby_asset_id", "entity/chicken/chicken_cold_baby");
@@ -205,7 +211,7 @@ public final class Protocol1_21_11To26_1 extends AbstractProtocol<ClientboundPac
final CompoundTag warmChicken = new CompoundTag();
warmChicken.putString("asset_id", "entity/chicken/chicken_warm");
warmChicken.putString("baby_asset_id", "entity/chicken/chicken_warm_baby");
registryDataRewriter.addEntries("chicken_variant", new RegistryEntry("cold", coldChicken), new RegistryEntry("warm", warmChicken));
registryDataRewriter.addEntries("chicken_variant", new RegistryEntry("temperate", temperateChicken), new RegistryEntry("cold", coldChicken), new RegistryEntry("warm", warmChicken));
final CompoundTag ponderGoatHornInstrument = new CompoundTag();
final CompoundTag ponderGoatHornDescription = new CompoundTag();
@@ -214,7 +220,7 @@ public final class Protocol1_21_11To26_1 extends AbstractProtocol<ClientboundPac
ponderGoatHornInstrument.putString("sound_event", "item.goat_horn.sound.0");
ponderGoatHornInstrument.putFloat("use_duration", 7F);
ponderGoatHornInstrument.putFloat("range", 256F);
registryDataRewriter.addEntries("instrument", new RegistryEntry("spear", ponderGoatHornInstrument));
registryDataRewriter.addEntries("instrument", new RegistryEntry("ponder_goat_horn", ponderGoatHornInstrument));
addJukeboxPlayables("11", "13", "5", "blocks", "cat", "chirp", "far", "mall", "mellohi", "otherside", "pigstep", "relic", "stal", "strad", "wait", "ward", "creator", "creator_music_box", "lava_chicken", "tears", "precipice");
addTrimMaterials("quartz", "iron", "netherite", "redstone", "copper", "gold", "emerald", "diamond", "lapis", "amethyst", "resin");
@@ -77,17 +77,17 @@ public final class BlockItemPacketRewriter26_1 extends StructuredItemRewriter<Cl
@Override
protected void handleItemDataComponentsToClient(final UserConnection connection, final Item item, final StructuredDataContainer container) {
upgradeData(protocol, protocol.types().structuredDataKeys(), container);
upgradeData(protocol, connection, protocol.types().structuredDataKeys(), container);
super.handleItemDataComponentsToClient(connection, item, container);
}
public static void upgradeData(final Protocol<?, ?, ?, ?> protocol, final StructuredDataKeys1_21_11 dataKeys, final StructuredDataContainer container) {
container.replace(StructuredDataKey.JUKEBOX_PLAYABLE1_21_5, StructuredDataKey.JUKEBOX_PLAYABLE26_1, jukeboxPlayable -> upgradeHolder(protocol, jukeboxPlayable.song(), "jukebox_song"));
container.replace(StructuredDataKey.INSTRUMENT1_21_5, StructuredDataKey.INSTRUMENT26_1, instrument -> upgradeHolder(protocol, instrument, "instrument"));
container.replace(StructuredDataKey.PROVIDES_TRIM_MATERIAL1_21_5, StructuredDataKey.PROVIDES_TRIM_MATERIAL26_1, providesTrimMaterial -> upgradeHolder(protocol, providesTrimMaterial.material(), "trim_material"));
container.replace(StructuredDataKey.CHICKEN_VARIANT1_21_5, StructuredDataKey.CHICKEN_VARIANT26_1, chickenVariant -> upgradeEitherVariant(protocol, chickenVariant, "chicken_variant"));
container.replace(StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT1_21_11, StructuredDataKey.ZOMBIE_NAUTILUS_VARIANT26_1, nautilusVariant -> upgradeEitherVariant(protocol, nautilusVariant, "zombie_nautilus_variant"));
container.replace(StructuredDataKey.DAMAGE_TYPE1_21_11, StructuredDataKey.DAMAGE_TYPE26_1, damageType -> upgradeEitherVariant(protocol, damageType.id(), "damage_type"));
public static void upgradeData(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection, final StructuredDataKeys1_21_11 dataKeys, final StructuredDataContainer container) {
container.replace(StructuredDataKey.JUKEBOX_PLAYABLE1_21_5, StructuredDataKey.JUKEBOX_PLAYABLE26_1, jukeboxPlayable -> upgradeHolder(protocol, connection, jukeboxPlayable.song(), "jukebox_song"));
container.replace(StructuredDataKey.INSTRUMENT1_21_5, StructuredDataKey.INSTRUMENT26_1, instrument -> upgradeHolder(protocol, connection, instrument, "instrument"));
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.PROVIDES_BANNER_PATTERNS1_21_5, StructuredDataKey.PROVIDES_BANNER_PATTERNS26_1, key -> new ProvidesBannerPatterns(HolderSet.of(key.original())));
container.replace(StructuredDataKey.DAMAGE_RESISTANT1_21_2, StructuredDataKey.DAMAGE_RESISTANT26_1, damageResistant -> new DamageResistant26_1(HolderSet.of(damageResistant.typesTagKey().original())));
container.replaceKey(StructuredDataKey.BLOCKS_ATTACKS1_21_5, StructuredDataKey.BLOCKS_ATTACKS26_1);
@@ -103,20 +103,20 @@ public final class BlockItemPacketRewriter26_1 extends StructuredItemRewriter<Cl
}
}
private static @Nullable Integer upgradeEitherVariant(final Protocol<?, ?, ?, ?> protocol, final Either<Integer, String> eitherHolder, final String registry) {
return eitherHolder.isLeft() ? eitherHolder.left() : registryIdOrNull(protocol, registry, eitherHolder.right());
private static @Nullable Integer upgradeEitherVariant(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection, final Either<Integer, String> eitherHolder, final String registry) {
return eitherHolder.isLeft() ? eitherHolder.left() : registryIdOrNull(protocol, connection, registry, eitherHolder.right());
}
private static <T> @Nullable Holder<T> upgradeHolder(final Protocol<?, ?, ?, ?> protocol, final EitherHolder<T> eitherHolder, final String registry) {
private static <T> @Nullable Holder<T> upgradeHolder(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection, final EitherHolder<T> eitherHolder, final String registry) {
if (eitherHolder.hasHolder()) {
return eitherHolder.holder();
}
final Integer id = registryIdOrNull(protocol, registry, eitherHolder.key());
final Integer id = registryIdOrNull(protocol, connection, registry, eitherHolder.key());
return id != null ? Holder.of((int) id) : null;
}
private static @Nullable Integer registryIdOrNull(final Protocol<?, ?, ?, ?> protocol, final String registry, final String key) {
final int id = protocol.getRegistryDataRewriter().getMappings(registry).keyToId(key);
private static @Nullable Integer registryIdOrNull(final Protocol<?, ?, ?, ?> protocol, final UserConnection connection, final String registry, final String key) {
final int id = protocol.getEntityRewriter().tracker(connection).registryKeys(registry).keyToId(key);
return id != -1 ? id : null;
}
@@ -197,7 +197,7 @@ 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));
connection.addItemHasher(this.getClass(), new ItemHashStorage1_21_5(this));
connection.addItemHasher(this.getClass(), new ItemHashStorage1_21_5(this, connection));
connection.put(new MessageIndexStorage());
}
@@ -68,7 +68,7 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
wrapper.write(Types.SHORT, (short) 0);
});
protocol.registerFinishConfiguration(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> {
protocol.appendClientbound(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> {
// Old registries, but now also modifiable
sendEntityVariants(wrapper.user(), "minecraft:frog_variant", "frog", true, "temperate", "warm", "cold");
sendEntityVariants(wrapper.user(), "minecraft:cat_variant", "cat", false, "tabby", "black", "red", "siamese", "british_shorthair", "calico", "persian", "ragdoll", "white", "jellie", "all_black");
@@ -88,6 +88,19 @@ public final class EntityPacketRewriter1_21_5 extends EntityRewriter<Clientbound
wrapper.user().get(MessageIndexStorage.class).setIndex(0);
});
protocol.registerClientbound(ClientboundPackets1_21_2.UPDATE_MOB_EFFECT, wrapper -> {
// Add blending to nausea effect
wrapper.passthrough(Types.VAR_INT); // Entity ID
final int effectId = wrapper.passthrough(Types.VAR_INT); // Effect ID
wrapper.passthrough(Types.VAR_INT); // Amplifier
wrapper.passthrough(Types.VAR_INT); // Duration
byte flags = wrapper.read(Types.BYTE); // Flags
if (effectId == 8) { // Nausea
flags |= 0x08; // blend
}
wrapper.write(Types.BYTE, flags);
});
protocol.replaceClientbound(ClientboundPackets1_21_2.SET_PLAYER_TEAM, wrapper -> {
wrapper.passthrough(Types.STRING); // Team Name
final byte action = wrapper.passthrough(Types.BYTE); // Mode
@@ -19,6 +19,7 @@ package com.viaversion.viaversion.protocols.v1_21_4to1_21_5.storage;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.codec.CodecContext;
import com.viaversion.viaversion.api.minecraft.codec.CodecContext.RegistryAccess;
@@ -37,8 +38,8 @@ public class ItemHashStorage1_21_5 implements ItemHasher {
private boolean processingClientboundInventoryPacket;
private final CodecContext context;
public ItemHashStorage1_21_5(final Protocol1_21_4To1_21_5 protocol) {
final RegistryAccess registryAccess = RegistryAccess.of(protocol);
public ItemHashStorage1_21_5(final Protocol1_21_4To1_21_5 protocol, final UserConnection connection) {
final RegistryAccess registryAccess = RegistryAccess.of(protocol, connection);
this.context = new CodecRegistryContext(protocol, registryAccess, true); // always using 1.21.5 items as input
}
@@ -42,7 +42,7 @@ public final class EntityPacketRewriter1_21_6 extends EntityRewriter<Clientbound
@Override
public void registerPackets() {
protocol.registerFinishConfiguration(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> {
protocol.appendClientbound(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> {
// send server links dialog as vanilla doesn't show server links by default otherwise
final PacketWrapper dialogsPacket = PacketWrapper.create(ClientboundConfigurationPackets1_21.REGISTRY_DATA, wrapper.user());
dialogsPacket.write(Types.STRING, "minecraft:dialog");
@@ -95,7 +95,7 @@ public final class Protocol1_21_9To1_21_11 extends AbstractProtocol<ClientboundP
protected void registerPackets() {
super.registerPackets();
registerFinishConfiguration(ClientboundConfigurationPackets1_21_9.FINISH_CONFIGURATION, wrapper -> {
appendClientbound(ClientboundConfigurationPackets1_21_9.FINISH_CONFIGURATION, wrapper -> {
final PacketWrapper zombieNautilusVariantsPacket = wrapper.create(ClientboundConfigurationPackets1_21_9.REGISTRY_DATA);
zombieNautilusVariantsPacket.write(Types.STRING, "zombie_nautilus_variant");
final CompoundTag temperateZombieNautilus = new CompoundTag();
@@ -97,7 +97,7 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter<Clientbound
entity.setRotation(yaw, pitch);
});
protocol.registerFinishConfiguration(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> {
protocol.appendClientbound(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> {
final PacketWrapper instrumentsPacket = wrapper.create(ClientboundConfigurationPackets1_21.REGISTRY_DATA);
instrumentsPacket.write(Types.STRING, "minecraft:instrument");
final RegistryEntry[] entries = new RegistryEntry[GOAT_HORN_INSTRUMENTS.length];
@@ -19,12 +19,12 @@ package com.viaversion.viaversion.protocols.v1_21to1_21_2.storage;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.minecraft.ChunkPosition;
import java.util.HashSet;
import java.util.Set;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
public class ChunkLoadTracker implements StorableObject {
private final Set<Long> loadedChunks = new HashSet<>();
private final LongSet loadedChunks = new LongOpenHashSet();
public void addChunk(final int x, final int z) {
this.loadedChunks.add(ChunkPosition.chunkKey(x, z));
@@ -18,12 +18,13 @@
package com.viaversion.viaversion.protocols.v1_8to1_9.storage;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.HashSet;
import java.util.Set;
public class ClientWorld1_9 extends ClientWorld {
private final Set<Long> loadedChunks = new HashSet<>();
private final Set<Long> loadedChunks = new LongOpenHashSet();
public Set<Long> getLoadedChunks() {
return loadedChunks;
@@ -22,12 +22,13 @@ import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.ChunkPosition;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class CommandBlockStorage implements StorableObject {
private final Map<Long, Map<BlockPosition, CompoundTag>> storedCommandBlocks = new HashMap<>();
private final Map<Long, Map<BlockPosition, CompoundTag>> storedCommandBlocks = new Long2ObjectOpenHashMap<>();
private boolean permissions;
public void unloadChunk(int x, int z) {
@@ -217,10 +217,7 @@ public class BlockRewriter<C extends ClientboundPacketType> {
}
DataPalette palette = section.palette(PaletteType.BLOCKS);
for (int i = 0; i < palette.size(); i++) {
int mappedBlockStateId = protocol.getMappingData().getNewBlockStateId(palette.idByIndex(i));
palette.setIdByIndex(i, mappedBlockStateId);
}
palette.replaceIds(protocol.getMappingData()::getNewBlockStateId);
}
}
@@ -284,10 +281,7 @@ public class BlockRewriter<C extends ClientboundPacketType> {
for (final ChunkSection section : chunk.getSections()) {
final DataPalette blockPalette = section.palette(PaletteType.BLOCKS);
for (int i = 0; i < blockPalette.size(); i++) {
final int id = blockPalette.idByIndex(i);
blockPalette.setIdByIndex(i, protocol.getMappingData().getNewBlockStateId(id));
}
blockPalette.replaceIds(protocol.getMappingData()::getNewBlockStateId);
}
return chunk;
}
@@ -37,6 +37,7 @@ import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.data.item.OriginalHashedItem;
import com.viaversion.viaversion.util.Limit;
import com.viaversion.viaversion.util.Rewritable;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -110,6 +111,12 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
@Override
public HashedItem handleHashedItem(final UserConnection connection, final HashedItem item) {
if (item instanceof OriginalHashedItem restoredItem) {
// Pass through until we're at the original backup protocol.
// This means it won't be correct every step of the way, but it will be once it reaches the target protocol
return restoredItem.backupTagName().equals(nbtTagName()) ? restoredItem.asRegularItem() : item;
}
final MappingData mappingData = protocol.getMappingData();
if (mappingData == null) {
return item;
@@ -126,9 +133,9 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
// 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);
final OriginalHashedItem originalHashedItem = itemHasher.originalHashedItem(customDataHash, item);
if (originalHashedItem != null) {
return originalHashedItem;
return originalHashedItem.backupTagName().equals(nbtTagName()) ? originalHashedItem.asRegularItem() : originalHashedItem;
}
}
}
@@ -249,6 +256,17 @@ public class ItemRewriter<C extends ClientboundPacketType, S extends Serverbound
});
}
public void registerSetCreativeModeSlot1_20_5(S packetType) {
protocol.registerServerbound(packetType, wrapper -> {
if (protocol.getEntityRewriter() != null && !protocol.getEntityRewriter().tracker(wrapper.user()).canInstaBuild()) {
wrapper.cancel();
return;
}
wrapper.passthrough(Types.SHORT); // Slot
wrapper.write(itemType, handleItemToServer(wrapper.user(), wrapper.read(mappedItemType)));
});
}
public void registerContainerClick(S packetType) {
protocol.registerServerbound(packetType, wrapper -> {
wrapper.passthrough(Types.BYTE); // Container ID
@@ -26,9 +26,12 @@ 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.entity.DimensionData;
import com.viaversion.viaversion.api.data.entity.EntityTracker;
import com.viaversion.viaversion.api.minecraft.RegistryEntry;
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.protocol.packet.State;
import com.viaversion.viaversion.api.rewriter.ComponentRewriter;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.data.entity.DimensionDataImpl;
@@ -52,7 +55,6 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
private final Map<String, Consumer<CompoundTag>> enchantmentEffectHandlers = new Object2ObjectArrayMap<>(); // for nested enchantment data
private final Map<String, List<RegistryEntry>> toAdd = new Object2ObjectArrayMap<>();
private final Set<String> toRemove = new HashSet<>();
protected final Map<String, KeyMappings> registryKeyMappings = new HashMap<>();
protected final Protocol<?, ?, ?, ?> protocol;
public RegistryDataRewriter(final Protocol<?, ?, ?, ?> protocol) {
@@ -78,7 +80,8 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
for (int i = 0; i < entries.length; i++) {
keys[i] = Key.stripMinecraftNamespace(entries[i].key());
}
this.registryKeyMappings.put(key, new KeyMappings(keys));
protocol.getEntityRewriter().tracker(connection).addRegistryKeys(key, new KeyMappings(keys));
switch (key) {
case "enchantment" -> updateEnchantments(connection, entries);
@@ -141,6 +144,28 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
registryEntryHandlers.put(Key.stripMinecraftNamespace(registryKey), handler);
}
@Override
public void sendMissingRegistries(final UserConnection connection) {
final EntityTracker entityTracker = protocol.getEntityRewriter().tracker(connection);
for (final Map.Entry<String, List<RegistryEntry>> entry : this.toAdd.entrySet()) {
if (entityTracker.registryKeys(entry.getKey()) != null) {
continue;
}
final List<RegistryEntry> toAdd = entry.getValue();
final RegistryEntry[] entries = new RegistryEntry[toAdd.size()];
for (int i = 0; i < toAdd.size(); i++) {
entries[i] = toAdd.get(i).copy();
}
final ClientboundPacketType packetType = protocol.getPacketTypesProvider().mappedClientboundType(State.CONFIGURATION, "REGISTRY_DATA");
final PacketWrapper registryData = PacketWrapper.create(packetType, connection);
registryData.write(Types.STRING, entry.getKey());
registryData.write(Types.REGISTRY_ENTRY_ARRAY, entries);
registryData.send(protocol.getClass());
}
}
public void addEnchantmentEffectRewriter(final String key, final Consumer<CompoundTag> rewriter) {
enchantmentEffectHandlers.put(Key.stripMinecraftNamespace(key), rewriter);
}
@@ -495,15 +520,6 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
return entries;
}
@Override
public @Nullable KeyMappings getMappings(final String registryKey) {
return this.registryKeyMappings.get(Key.stripMinecraftNamespace(registryKey));
}
public Map<String, KeyMappings> registryKeyMappings() {
return registryKeyMappings;
}
@Override
public boolean shouldRemoveRegistry(final String registryKey) {
return this.toRemove.contains(Key.stripMinecraftNamespace(registryKey));
@@ -20,10 +20,12 @@ package com.viaversion.viaversion.rewriter;
import com.google.common.base.Preconditions;
import com.viaversion.nbt.tag.CompoundTag;
import com.viaversion.nbt.tag.IntArrayTag;
import com.viaversion.nbt.tag.IntTag;
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.Mappings;
import com.viaversion.viaversion.api.data.item.ItemHasher;
import com.viaversion.viaversion.api.minecraft.EitherHolder;
import com.viaversion.viaversion.api.minecraft.Holder;
@@ -41,6 +43,7 @@ import com.viaversion.viaversion.api.protocol.packet.ServerboundPacketType;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.VersionedTypesHolder;
import com.viaversion.viaversion.data.item.ItemHasherBase;
import com.viaversion.viaversion.data.item.OriginalHashedItem;
import com.viaversion.viaversion.util.Rewritable;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import java.util.List;
@@ -51,6 +54,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
T extends Protocol<C, ?, ?, S>> extends ItemRewriter<C, S, T> {
public static final String MARKER_KEY = "VV|custom_data";
private static final String ORIGINAL_HASHES_KEY = "VV|original_hashes";
public StructuredItemRewriter(T protocol) {
super(protocol);
@@ -72,7 +76,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
}
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);
final HashedItem originalHashedItem = hashItemIfNeeded(connection, item, itemHasher); // non-null if tracking is actually necessary here
final StructuredDataContainer dataContainer = item.dataContainer();
updateItemDataComponentTypeIds(dataContainer, true);
@@ -89,25 +93,71 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
item.setIdentifier(mappingData.getNewItemId(item.identifier()));
}
handleRewritablesToClient(connection, dataContainer, originalHashedItem != null ? itemHasher : null);
handleRewritablesToClient(connection, dataContainer, itemHasher);
handleItemDataComponentsToClient(connection, item, dataContainer);
storeOriginalHashedItem(item, itemHasher, originalHashedItem); // has to be called AFTER all modifications - override handleItemDataComponentsToClient instead of this method if needed
if (originalHashedItem != null) {
storeOriginalHashedItem(connection, item, itemHasher, originalHashedItem); // has to be called AFTER all modifications - override handleItemDataComponentsToClient instead of this method if needed
}
return item;
}
protected @Nullable HashedItem hashItem(final Item item, @Nullable final ItemHasherBase hasher) {
/**
* Hashes the given item if the hasher is not null, the item doesn't already have the original hashes.
* If the original hashes are already present, it will only retreive them during the last clientbound / first clientbound protocol,
* where it is restored during serverbound item packets before being passed along to the end.
*
* @param connection connection
* @param item pre-modiciation item
* @param hasher hasher
* @return hashed item if the hasher is not null and the item has to be hashed for tracking in this specific protocol
*/
protected @Nullable HashedItem hashItemIfNeeded(final UserConnection connection, 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, false);
if (hasher == null || !hasher.isProcessingClientboundInventoryPacket()) {
return null;
}
final CompoundTag customData = item.dataContainer().get(StructuredDataKey.CUSTOM_DATA);
final CompoundTag originalHashes;
if (customData != null && (originalHashes = customData.getCompoundTag(ORIGINAL_HASHES_KEY)) != null) {
if (isFirstServerbound(connection)) {
// Get the item that was originally saved when there was the first hash change to cache in this protocol
return backedUpOriginalHashes(originalHashes, item);
}
// If the original has already been saved and this isn't the final clientbound protocol, no need to do anything else
return null;
}
return hasher.toHashedItem(item, false);
}
protected void storeOriginalHashedItem(final Item item, final ItemHasherBase hasher, @Nullable final HashedItem originalHashedItem) {
if (originalHashedItem == null || item.dataContainer().isEmpty()) {
/**
* Stores the hashes of the original item in the custom_data of the transformed item.
* <p>
* During the first clientbound item handling, {@code original_hashes} is saved.
* Only in the final protocol before reaching the client is the resulting custom_data hash cached,
* to then be retrieved in the first serverbound protocol and passed through the rest.
*
* @param connection connection
* @param item modified item to store the hash of
* @param hasher hasher
* @param originalHashedItem pre-modification hashed item
*/
protected void storeOriginalHashedItem(final UserConnection connection, final Item item, final ItemHasherBase hasher, final HashedItem originalHashedItem) {
if (originalHashedItem == null || (originalHashedItem.dataHashesById().isEmpty() && originalHashedItem.removedDataIds().isEmpty())) {
return;
}
// Check if the hashed data is the same, this will also prevent unnecessary backups due to missing converters
if (originalHashedItem instanceof OriginalHashedItem restoredHashedItem) {
// This is the final clientbound/first serverbound protocol; cache the previously stored original hashed item
hasher.trackOriginalHashedItem(item.dataContainer().get(StructuredDataKey.CUSTOM_DATA), originalHashedItem, restoredHashedItem.backupTagName());
return;
}
// Check if the hashed data is the same, this will also prevent unnecessary backups due to missing converters.
// Map ids back so equality only checks for value changes, or type changes that aren't uniform
final HashedItem hashedItem = hasher.toHashedItem(item, true);
normalizeHashedItemToServer(hashedItem);
if (hashedItem.dataHashesById().equals(originalHashedItem.dataHashesById()) && hashedItem.removedDataIds().equals(originalHashedItem.removedDataIds())) {
return;
}
@@ -116,15 +166,67 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
// 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();
originalHashes.putInt("id", originalHashedItem.identifier());
originalHashes.putString("backup_tag", nbtTagName());
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");
customTag.put(ORIGINAL_HASHES_KEY, originalHashes);
hasher.trackOriginalHashedItem(customTag, originalHashedItem);
if (isFirstServerbound(connection)) {
hasher.trackOriginalHashedItem(customTag, originalHashedItem, nbtTagName());
}
}
private void normalizeHashedItemToServer(final HashedItem item) {
final MappingData mappingData = protocol.getMappingData();
if (mappingData == null || Mappings.isIntIdIdentity(mappingData.getDataComponentSerializerMappings())) {
return;
}
updateHashedItemDataComponentIds(item, mappingData.getDataComponentSerializerMappings().inverse());
}
private boolean isFirstServerbound(final UserConnection connection) {
// Only actually cache the original item once in the final clientbound/first serverbound protocol
for (final Protocol<?, ?, ?, ?> protocol : connection.getProtocolInfo().getPipeline().pipes()) {
if (connection.getItemHasher(protocol.getClass()) instanceof ItemHasherBase) {
return protocol.getClass() == this.protocol.getClass();
}
}
return false;
}
protected @Nullable OriginalHashedItem backedUpOriginalHashes(final CompoundTag originalHashes, final Item item) {
final IntTag idTag = originalHashes.getIntTag("id");
final String backupTagName = originalHashes.getString("backup_tag");
if (idTag == null || backupTagName == null) {
return null;
}
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());
}
}
}
} catch (final NumberFormatException e) {
return null;
}
final IntArrayTag removedTag = originalHashes.getIntArrayTag("removed");
if (removedTag != null) {
for (final int id : removedTag.getValue()) {
hashedItem.removedDataIds().add(id);
}
}
return hashedItem;
}
@Override
@@ -203,7 +305,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
}
protected void handleRewritablesToClient(final UserConnection connection, final StructuredDataContainer container, @Nullable final ItemHasher itemHasher) {
if (itemHasher == null) {
if (itemHasher == null || !itemHasher.isProcessingClientboundInventoryPacket()) {
handleRewritables(connection, true, container, this::handleItemToClient);
return;
}
@@ -304,7 +406,7 @@ public class StructuredItemRewriter<C extends ClientboundPacketType, S extends S
* @param customData custom data tag
*/
protected void restoreBackupData(final Item item, final StructuredDataContainer container, final CompoundTag customData) {
customData.remove(nbtTagName("original_hashes"));
customData.remove(ORIGINAL_HASHES_KEY);
// Remove custom name
if (removeBackupTag(customData, "added_custom_name") != null) {
@@ -9,240 +9,100 @@
#----------------------------------------------------------#
#
# Should ViaVersion check for updates?
check-for-updates: true
check-for-updates: false
# Send the supported versions with the Status (Ping) response packet
send-supported-versions: false
send-supported-versions: true
# Easier to configure alternative to 'block-protocols'. Uses readable version strings with possible '<' and '>' prefixes.
# An example to block 1.16.4, everything below 1.16, as well as everything above 1.17.1 would be: ["<1.16", "1.16.4", ">1.17.1"]
# You can use both this and the block-protocols option at the same time as well.
block-versions: []
block-versions: [">1.21.11"]
# Block specific Minecraft protocol version numbers.
# List of all Minecraft protocol versions: https://minecraft.wiki/w/Protocol_version, or use a generator: https://via.krusic22.com
block-protocols: []
# Change the blocked disconnect message
block-disconnect-msg: "You are using an unsupported Minecraft version!"
# If you use ProtocolLib, we can't reload without kicking the players.
# (We don't suggest using reload either, use a plugin manager)
# You can customize the message we kick people with if you use ProtocolLib here.
reload-disconnect-msg: "Server reload, please rejoin!"
# Settings for logging behavior. When debugging issues, we may ask you to change some of these.
# Most of these are disabled by default to prevent log spam from bad user input.
block-disconnect-msg: |
§7[§a§lTrue§c§lOG §e§lNetwork§7] §l§cUnsupported client! §e:(
§4♥ §6Please re-log using MCJE 1.8-1.21.11 §4♥
reload-disconnect-msg: |
§7[§a§lTrue§c§lOG §e§lNetwork§7] §l§6Server is restarting... §4♥
logging:
# Whether to log more info about kicks due to blocked protocol versions to the console.
log-blocked-joins: false
# Whether to log errors during entity data conversion.
log-entity-data-errors: true
# Whether to log errors during text component conversion.
log-text-component-conversion-errors: false
# Whether to warn for other issues when converting items, blocks, or other data between server and client.
log-text-component-conversion-errors: true
log-other-conversion-warnings: false
# Maximum length of error messages in the console.
max-error-length: 1500
#
# Used for auto updating config files. Do not touch.
config-version: 1
init-config-version: 1
# Whether to update config values that were left on their default to newly changed defaults. Recommended to keep enabled.
migrate-default-config-changes: true
# If enabled, ViaVersion will send the native player version to the server on connect via a plugin message.
# See the wiki for more info: https://github.com/ViaVersion/ViaVersion/wiki/Server-and-Player-Details-Protocol
migrate-default-config-changes: false
send-player-details: true
# If enabled, ViaVersion will send the native server version to a player on connect via a plugin message.
# See the wiki above for more info.
send-server-details: true
#----------------------------------------------------------#
# VELOCITY OPTIONS #
#----------------------------------------------------------#
#
# Velocity allows you to have different server versions inside.
# Instead of you entering all the versions of these servers, we can ping them.
#
# What interval would you like us to ping at? (in seconds)
# Use -1 to disable.
velocity-ping-interval: 60
# If the above is enabled, should we save the info to the config (in the section below)
velocity-ping-save: true
# To get a server's protocol, ViaVersion will do the following:
# Look for the server in the following section, then look for the last ping if velocity-ping is enabled
# otherwise use default.
#
# The format for the following is:
# servername: protocolversion
# You can find protocol ids on https://minecraft.wiki/w/Protocol_version
# It will fall back to the default option if none found.
velocity-servers: {}
#
#----------------------------------------------------------#
# GLOBAL PACKET LIMITER #
#----------------------------------------------------------#
# Packets Per Second (PPS) and kilobyte packet size limiters.
# Setting 'max-per-second' or 'sustained-max-per-second' to -1 will disable the individual limit.
# Kick messages may include the %pps or %bps placeholders respectively, though they are more intended for debugging purposes.
#
# Limits the amount of packets sent per second.
# Clients by default send around 20-90 packets per second.
packet-limiter:
enabled: true
# The maximum packets a client can send per second.
max-per-second: 800
max-per-second-kick-message: "You are sending too many packets!"
#
# We can also kick them if they repeatedly send too many packets over a longer period of time.
# E.g. if a player sends over 200 packets per second for 4 out of 7 seconds, we kick them.
# The maximum packets per second a client can send over a longer period of time.
max-per-second-kick-message: Your client is sending too many packets.
sustained-max-per-second: 200
# Period to track in seconds.
sustained-period-seconds: 7
# The number of seconds within the interval that the client can exceed the limit before being kicked.
sustained-threshold: 4
sustained-kick-message: "You are sending too many packets, :("
# Limits the size of packets in kilobytes sent per second.
sustained-kick-message: Your client is sending way too many packets!
packet-size-limiter:
enabled: false
enabled: true
max-per-second: -1
max-per-second-kick-message: "You are sending too large packets!"
max-per-second-kick-message: Your client is sending too many packets per second.
sustained-max-per-second: -1
sustained-period-seconds: 5
sustained-threshold: 3
sustained-kick-message: "You are sending too large packets, :("
#
#----------------------------------------------------------#
# MULTIPLE VERSIONS OPTIONS #
#----------------------------------------------------------#
#
# Should we enable our hologram patch?
# If they're in the wrong place, enable this
hologram-patch: false
# This is the offset, should work as default when enabled.
sustained-kick-message: Your client is sending packets which are larger than the limit!
hologram-patch: true
hologram-y: -0.96
# Whether to make sure ViaVersion's UserConnection object is already available in the PlayerJoinEvent.
# You may disable this for faster startup/join time if you are 100% sure no plugin requires this.
register-userconnections-on-join: true
# Should we disable piston animation for 1.11/1.11.1 clients?
# In some cases, when firing lots of pistons, it crashes them.
piston-animation-patch: false
# Experimental - Should we fix shift quick move action for 1.12 clients (causes shift + double click not to work when moving items) (only works on 1.8-1.11.2 bukkit based servers)
quick-move-action-fix: false
# Should we use prefix for team color on 1.13 and above clients?
piston-animation-patch: true
quick-move-action-fix: true
team-colour-fix: true
# 1.13 introduced new auto complete which can trigger "Kicked for spamming" for servers older than 1.13, the following option will disable it completely.
disable-1_13-auto-complete: false
# The following option will delay the tab complete request in x ticks if greater than 0, if other tab-complete is received, the previous is cancelled
1_13-tab-complete-delay: 0
# For 1.13 clients the smallest (1 layer) snow doesn't have collisions, this will send these as 2 snowlayers for 1.13+ clients to prevent them bugging through them
fix-low-snow-collision: false
# Infested blocks are instantly breakable for 1.13+ clients, resulting in them being unable to break them on sub 1.13 servers. This remaps them to their normal stone variants
fix-infested-block-breaking: true
# In 1.14 the client page limit has been upped to 100 (from 50). Some anti-exploit plugins ban when clients go higher than 50. This option cuts edited books to 50 pages.
1_13-tab-complete-delay: 2
fix-low-snow-collision: true
fix-infested-block-breaking: false
truncate-1_14-books: false
# This prevents clients using 1.9-1.13 on 1.8 servers from receiving no knockback/having velocity bugs whilst sneaking under a block.
change-1_9-hitbox: false
# Similar to the above, but for 1.14+ players on 1.8-1.13 servers.
# WARNING: This gives 1.14+ players the ability to sneak under blocks, that players under that version cannot (sneaking in places that are only 1.5 blocks high)!
# Another thing to remember is that those players might be missed by projectiles and other hits directed at the very top of their head whilst sneaking.
change-1_9-hitbox: true
change-1_14-hitbox: false
# Fixes 1.14+ clients on sub 1.14 servers having a light value of 0 for non-full blocks.
fix-non-full-blocklight: true
# Fixes walk animation not shown when health is set to Float.NaN
fix-1_14-health-nan: true
# Should 1.15+ clients respawn instantly / without showing a death screen?
use-1_15-instant-respawn: false
#
# Enable serverside block-connections for 1.13+ clients - all the options in this section are built around this option
serverside-blockconnections: true
# Sets the method for the block connections (world for highly experimental (USE AT OWN RISK) world-level or packet for packet-level)
blockconnection-method: packet
# When activated, only the most important blocks are stored in the blockstorage. (fences, glass panes etc. won't connect to solid blocks)
reduce-blockstorage-memory: false
# When activated with serverside-blockconnections, flower parts with blocks above will be sent as stems
# Useful for lobbyservers where users can't build and those stems are used decoratively
flowerstem-when-block-above: false
# Vines that are not connected to blocks will be mapped to air, else 1.13+ would still be able to climb up on them.
vine-climb-fix: false
#
# Ignores incoming plugin channel messages of 1.16+ clients with channel names longer than 32 characters.
# CraftBukkit had this limit hardcoded until 1.16, so we have to assume any server/proxy might have this arbitrary check present.
flowerstem-when-block-above: true
vine-climb-fix: true
ignore-long-1_16-channel-names: true
#
# Force 1.17+ client to accept the server resource pack; they will automatically disconnect if they decline.
forced-use-1_17-resource-pack: false
# The message to be displayed at the prompt when the 1.17+ client receives the server resource pack.
resource-pack-1_17-prompt: ''
#
# Caches light until chunks are unloaded to allow later chunk update packets as opposed to instantly uncaching when the first chunk data is sent.
# Only disable this if you know what you are doing.
cache-1_17-light: true
#
# Force-update 1.19.4+ player's inventory when they try to swap armor in a pre-occupied slot.
armor-toggle-fix: true
#
# Get the world names which should be returned for each vanilla dimension
map-1_16-world-names:
overworld: "minecraft:overworld"
nether: "minecraft:the_nether"
end: "minecraft:the_end"
#
# If disabled, tamed cats will be displayed as ocelots to 1.14+ clients on 1.13 servers. Otherwise, ocelots (tamed and untamed) will be displayed as cats.
overworld: minecraft:overworld
nether: minecraft:the_nether
end: minecraft:the_end
translate-ocelot-to-cat: false
#
# Determines the value sent to 1.19+ clients on join if currently not accessible by ViaVersion.
# It is not recommended to fake this value if your server is running 1.19 or later, as 1.20.5 have stricter chat handling and may get kicked otherwise.
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
#
# Hides scoreboard numbers for 1.20.3+ clients on older server versions.
hide-scoreboard-numbers: false
#
# Fixes 1.21+ clients on 1.20.5 servers placing water/lava buckets at the wrong location when moving fast, NOTE: This may cause issues with anti-cheat plugins.
fix-1_21-placement-rotation: true
#
# If enabled, cancel swing packets sent while having an inventory opened on 1.15.2 and below servers. This can cause false positives with anti-cheat plugins.
fix-1_21-placement-rotation: false
cancel-swing-in-inventory: true
#
#----------------------------------------------------------#
# 1.9+ CLIENTS ON 1.8 SERVERS OPTIONS #
#----------------------------------------------------------#
#
# No collide options, these allow you to configure how collision works.
# Do you want us to prevent collision?
prevent-collision: true
# If the above is true, should we automatically team players until you do?
auto-team: true
# When enabled, 1.9+ will be able to block by using shields
shield-blocking: true
# If this setting is active, the main hand is used instead of the off-hand to trigger the blocking of the player.
# With the main hand, the blocking starts way faster.
# (Requires "show-shield-when-sword-in-hand" to be disabled)
no-delay-shield-blocking: false
# If this setting is active, the shield will appear immediately for 1.9+ when you hold a sword in your main hand.
# The shield disappears when you switch to another item.
# (Requires "shield-blocking" to be enabled)
no-delay-shield-blocking: true
show-shield-when-sword-in-hand: false
# Enable player tick simulation, this fixes eating, drinking, nether portals.
simulate-pt: true
# Should we use nms player to simulate packets, (may fix anti-cheat issues)
nms-player-ticking: true
# Should we patch boss bars so they work? (Default: true, disable if you're having issues)
bossbar-patch: true
# If your boss bar flickers on 1.9+, set this to 'true'. It will keep all boss bars on 100% (not recommended)
bossbar-anti-flicker: false
# This will show the new effect indicator in the top-right corner for 1.9+ players.
use-new-effect-indicator: true
# Show the new death messages for 1.9+ on the death screen
use-new-effect-indicator: false
use-new-deathmessages: true
# Should we cache our items, this will prevent the server from being lagged out, however, the cost is a constant task caching items
item-cache: true
# Should we replace extended pistons to fix 1.10.1 (Only on chunk loading)?
replace-pistons: false
# What id should we replace with, default is air. (careful of players getting stuck standing on them)
replacement-piston-id: 0
# Fix 1.9+ clients not rendering the far away chunks and improve chunk rendering when moving fast (Increases network usage and decreases client fps slightly)
chunk-border-fix: false
# Allows 1.9+ left-handedness (main hand) on 1.8 servers
left-handed-handling: true
# Tries to cancel block break/place sounds sent by 1.8 servers to 1.9+ clients to prevent them from playing twice
cancel-block-sounds: true
# If enabled, 1.21.11+ clients on 1.8 servers will get wider entity hitboxes when attacking with items
use-1_8-hitbox-margin: true
@@ -44,8 +44,8 @@ import com.viaversion.viaversion.codec.CodecRegistryContext;
import com.viaversion.viaversion.codec.hash.HashFunction;
import com.viaversion.viaversion.codec.hash.HashOps;
import com.viaversion.viaversion.common.PlatformTestBase;
import com.viaversion.viaversion.connection.UserConnectionImpl;
import com.viaversion.viaversion.protocols.v1_21_6to1_21_7.Protocol1_21_6To1_21_7;
import com.viaversion.viaversion.rewriter.RegistryDataRewriter;
import com.viaversion.viaversion.util.KeyMappings;
import com.viaversion.viaversion.util.UUIDUtil;
import org.junit.jupiter.api.Assertions;
@@ -61,8 +61,10 @@ public class ItemHashTest extends PlatformTestBase {
@BeforeAll
static void loadContext() {
final UserConnectionImpl dummyConnection = new UserConnectionImpl(null);
protocol = Via.getManager().getProtocolManager().getProtocol(Protocol1_21_6To1_21_7.class);
context = new CodecRegistryContext(null, CodecContext.RegistryAccess.of(protocol), false);
protocol.init(dummyConnection);
context = new CodecRegistryContext(null, CodecContext.RegistryAccess.of(protocol, dummyConnection), false);
hasher = new HashOps(context, HashFunction.CRC32C);
}
@@ -71,12 +73,6 @@ public class ItemHashTest extends PlatformTestBase {
hasher.reset();
}
private HashOps hasher(final Class<? extends Protocol<?, ?, ?, ?>> protocolClass) {
final Protocol<?, ?, ?, ?> protocol = Via.getManager().getProtocolManager().getProtocol(protocolClass);
final CodecRegistryContext context = new CodecRegistryContext(null, CodecContext.RegistryAccess.of(protocol), false);
return new HashOps(context, HashFunction.CRC32C);
}
@Test
void testNumberAndKey() {
final UseCooldown useCooldown = new UseCooldown(0.5f, ":stick");
@@ -138,7 +134,7 @@ public class ItemHashTest extends PlatformTestBase {
@Test
void testRegistryHolderId() {
((RegistryDataRewriter) protocol.getRegistryDataRewriter()).registryKeyMappings().put("banner_pattern", new KeyMappings("base"));
protocol.getEntityRewriter().tracker(context.connection()).addRegistryKeys("banner_pattern", new KeyMappings("base"));
hasher.write(BannerPatternLayer.ARRAY_TYPE, new BannerPatternLayer[]{new BannerPatternLayer(Holder.of(0), 0)});
Assertions.assertEquals(606382024, hasher.hash(), "banner_pattern hash mismatch");
}
+1 -1
View File
@@ -1,5 +1,5 @@
# Project properties - we put these here so they can be modified without causing a recompile of the build scripts
projectVersion=5.9.0
projectVersion=5.9.2-SNAPSHOT
# Smile emoji (note that modrinth may not have added the version on release yet)
mcVersions=26.1.2, 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2, 1.18.1, 1.18, 1.17.1, 1.17, 1.16.5, 1.16.4, 1.16.3, 1.16.2, 1.16.1, 1.16, 1.15.2, 1.15.1, 1.15, 1.14.4, 1.14.3, 1.14.2, 1.14.1, 1.14, 1.13.2, 1.13.1, 1.13, 1.12.2, 1.12.1, 1.12, 1.11.2, 1.11.1, 1.11, 1.10.2, 1.10.1, 1.10, 1.9.4, 1.9.3, 1.9.2, 1.9.1, 1.9, 1.8.9
+1 -1
View File
@@ -5,7 +5,7 @@ metadata.format.version = "1.1"
gson = "2.13.2"
snakeYaml = "2.6"
fastutil = "8.5.15"
vianbt = "5.2.0"
vianbt = "5.2.1"
mcstructs = "5-3.3.1-SNAPSHOT"
# Common provided
+2
View File
@@ -38,6 +38,8 @@ setupViaSubproject("velocity")
setupViaSubproject("sponge")
setupViaSubproject("fabric")
//include("benchmark")
setupSubproject("viaversion") {
projectDir = file("universal")
}