diff --git a/common/build.gradle.kts b/common/build.gradle.kts index ec8defa44..42d6cd84c 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -9,6 +9,7 @@ val viaProxy: Configuration by configurations.creating dependencies { compileOnly("io.netty:netty-all:4.0.20.Final") compileOnly("com.google.guava:guava:17.0") + compileOnly("com.velocitypowered:velocity-native:3.4.0-SNAPSHOT") viaProxy("net.raphimc:ViaProxy:[3.0.0,4.0.0)") { isTransitive = false diff --git a/common/src/main/java/com/viaversion/viarewind/api/minecraft/ExtendedBlockStorage.java b/common/src/main/java/com/viaversion/viarewind/api/minecraft/ExtendedBlockStorage.java deleted file mode 100644 index 47f053df1..000000000 --- a/common/src/main/java/com/viaversion/viarewind/api/minecraft/ExtendedBlockStorage.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind - * Copyright (C) 2018-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 . - */ -package com.viaversion.viarewind.api.minecraft; - -import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; -import com.viaversion.viaversion.api.minecraft.chunks.NibbleArray; - -public class ExtendedBlockStorage { - private final byte[] blockLSBArray = new byte[ChunkSection.SIZE]; - - private final NibbleArray blockMetadataArray = new NibbleArray(this.blockLSBArray.length); - private final NibbleArray blockLightArray = new NibbleArray(this.blockLSBArray.length); - - private NibbleArray blockMSBArray; - private NibbleArray skyLightArray; - - public ExtendedBlockStorage(final boolean skylight) { - if (skylight) { - this.skyLightArray = new NibbleArray(this.blockLSBArray.length); - } - } - - public void setBlockId(final int x, final int y, final int z, final int value) { - this.blockLSBArray[ChunkSection.index(x, y, z)] = (byte) (value & 255); - if (value > 255) { - this.getOrCreateBlockMSBArray().set(x, y, z, (value & 0xF00) >> 8); - } else if (this.blockMSBArray != null) { - this.blockMSBArray.set(x, y, z, 0); - } - } - - public void setBlockMetadata(final int x, final int y, final int z, final int value) { - this.blockMetadataArray.set(x, y, z, value); - } - - public boolean hasBlockMSBArray() { - return this.blockMSBArray != null; - } - - public byte[] getBlockLSBArray() { - return this.blockLSBArray; - } - - public NibbleArray getOrCreateBlockMSBArray() { - if (this.blockMSBArray == null) { - return this.blockMSBArray = new NibbleArray(this.blockLSBArray.length); - } - return this.blockMSBArray; - } - - public NibbleArray getBlockMetadataArray() { - return this.blockMetadataArray; - } - - public NibbleArray getBlockLightArray() { - return this.blockLightArray; - } - - public NibbleArray getSkyLightArray() { - return this.skyLightArray; - } -} diff --git a/common/src/main/java/com/viaversion/viarewind/api/type/chunk/BulkChunkType1_7_6.java b/common/src/main/java/com/viaversion/viarewind/api/type/chunk/BulkChunkType1_7_6.java index 4a752788b..9b5de5c95 100644 --- a/common/src/main/java/com/viaversion/viarewind/api/type/chunk/BulkChunkType1_7_6.java +++ b/common/src/main/java/com/viaversion/viarewind/api/type/chunk/BulkChunkType1_7_6.java @@ -18,10 +18,9 @@ package com.viaversion.viarewind.api.type.chunk; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; -import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.type.Type; -import com.viaversion.viaversion.util.Pair; import io.netty.buffer.ByteBuf; + import java.util.zip.Deflater; public class BulkChunkType1_7_6 extends Type { @@ -38,40 +37,51 @@ public Chunk[] read(ByteBuf byteBuf) { } @Override - public void write(ByteBuf byteBuf, Chunk[] chunks) { + public void write(ByteBuf buffer, Chunk[] chunks) { final int chunkCount = chunks.length; - final int[] chunkX = new int[chunkCount]; - final int[] chunkZ = new int[chunkCount]; - final short[] primaryBitMask = new short[chunkCount]; - final short[] additionalBitMask = new short[chunkCount]; + final int[] addBitMasks = new int[chunkCount]; + int totalSize = 0; + boolean anySkyLight = false; - final byte[][] dataArrays = new byte[chunkCount][]; - int dataSize = 0; + for (Chunk chunk : chunks) { + if (ChunkType1_7_6.hasSkyLight(chunk)) { + anySkyLight = true; + break; + } + } for (int i = 0; i < chunkCount; i++) { - final Chunk chunk = chunks[i]; - Pair chunkData; - try { - chunkData = ChunkType1_7_6.serialize(chunk); - final byte[] data = chunkData.key(); - dataArrays[i] = data; - dataSize += data.length; - } catch (Exception e) { - throw new RuntimeException("Unable to serialize chunk", e); - } - chunkX[i] = chunk.getX(); - chunkZ[i] = chunk.getZ(); - primaryBitMask[i] = (short) chunk.getBitmask(); - additionalBitMask[i] = chunkData.value(); + Chunk chunk = chunks[i]; + addBitMasks[i] = ChunkType1_7_6.getAddBitMask(chunk); + boolean biomes = chunk.isFullChunk() && chunk.getBiomeData() != null; + + totalSize += ChunkType1_7_6.calcSize( + chunk.getBitmask(), + addBitMasks[i], + anySkyLight, + biomes + ); } - final byte[] data = new byte[dataSize]; - int destPos = 0; - for (final byte[] array : dataArrays) { - System.arraycopy(array, 0, data, destPos, array.length); - destPos += array.length; + final byte[] data = new byte[totalSize]; + int offset = 0; + + for (int i = 0; i < chunkCount; i++) { + Chunk chunk = chunks[i]; + boolean biomes = chunk.isFullChunk() && chunk.getBiomeData() != null; + + offset = ChunkType1_7_6.serialize( + chunk, + data, + offset, + addBitMasks[i], + anySkyLight, + biomes + ); } + buffer.writeShort(chunkCount); + final Deflater deflater = new Deflater(); byte[] compressedData; int compressedSize; @@ -84,27 +94,16 @@ public void write(ByteBuf byteBuf, Chunk[] chunks) { deflater.end(); } - byteBuf.writeShort(chunkCount); - byteBuf.writeInt(compressedSize); - - boolean skyLight = false; - for (Chunk chunk : chunks) { - for (ChunkSection section : chunk.getSections()) { - if (section != null && section.getLight().hasSkyLight()) { - skyLight = true; - break; - } - } - } - - byteBuf.writeBoolean(skyLight); // hasSkyLight - byteBuf.writeBytes(compressedData, 0, compressedSize); + buffer.writeInt(compressedSize); + buffer.writeBoolean(anySkyLight); + buffer.writeBytes(compressedData, 0, compressedSize); for (int i = 0; i < chunkCount; i++) { - byteBuf.writeInt(chunkX[i]); - byteBuf.writeInt(chunkZ[i]); - byteBuf.writeShort(primaryBitMask[i]); - byteBuf.writeShort(additionalBitMask[i]); + Chunk chunk = chunks[i]; + buffer.writeInt(chunk.getX()); + buffer.writeInt(chunk.getZ()); + buffer.writeShort(chunk.getBitmask()); + buffer.writeShort(addBitMasks[i]); } } } diff --git a/common/src/main/java/com/viaversion/viarewind/api/type/chunk/ChunkType1_7_6.java b/common/src/main/java/com/viaversion/viarewind/api/type/chunk/ChunkType1_7_6.java index 38727bd69..3d30b133e 100644 --- a/common/src/main/java/com/viaversion/viarewind/api/type/chunk/ChunkType1_7_6.java +++ b/common/src/main/java/com/viaversion/viarewind/api/type/chunk/ChunkType1_7_6.java @@ -17,14 +17,12 @@ */ package com.viaversion.viarewind.api.type.chunk; -import com.viaversion.viarewind.api.minecraft.ExtendedBlockStorage; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; +import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.type.Type; -import com.viaversion.viaversion.util.Pair; import io.netty.buffer.ByteBuf; -import java.io.IOException; import java.util.zip.Deflater; import static com.viaversion.viaversion.api.minecraft.chunks.ChunkSection.SIZE; @@ -38,143 +36,163 @@ public ChunkType1_7_6() { super(Chunk.class); } - public static Pair serialize(final Chunk chunk) throws IOException { - final ExtendedBlockStorage[] storageArrays = new ExtendedBlockStorage[16]; - for (int i = 0; i < storageArrays.length; i++) { - final ChunkSection section = chunk.getSections()[i]; - if (section != null) { - final ExtendedBlockStorage storage = storageArrays[i] = new ExtendedBlockStorage(section.getLight().hasSkyLight()); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - for (int y = 0; y < 16; y++) { - final int flatBlock = section.palette(PaletteType.BLOCKS).idAt(x, y, z); - storage.setBlockId(x, y, z, flatBlock >> 4); - storage.setBlockMetadata(x, y, z, flatBlock & 15); - } - } - } - storage.getBlockLightArray().setHandle(section.getLight().getBlockLight()); - if (section.getLight().hasSkyLight()) { - storage.getSkyLightArray().setHandle(section.getLight().getSkyLight()); - } - } - } + @Override + public Chunk read(ByteBuf byteBuf) { + throw new UnsupportedOperationException(); // Not needed, see https://github.com/ViaVersion/ViaLegacy/blob/main/src/main/java/net/raphimc/vialegacy/protocols/release/protocol1_8to1_7_6_10/types/Chunk1_7_6Type.java + } + @Override + public void write(ByteBuf buffer, Chunk chunk) { + final int bitmask = chunk.getBitmask(); + final int addBitmask = getAddBitMask(chunk); + final boolean hasSkyLight = hasSkyLight(chunk); final boolean biomes = chunk.isFullChunk() && chunk.getBiomeData() != null; - final int totalSize = calculateSize(storageArrays, chunk.getBitmask(), biomes); - final byte[] output = new byte[totalSize]; - int index = 0; + final int size = calcSize(bitmask, addBitmask, hasSkyLight, biomes); + final byte[] data = new byte[size]; + + serialize(chunk, data, 0, addBitmask, hasSkyLight, biomes); + + buffer.writeInt(chunk.getX()); + buffer.writeInt(chunk.getZ()); + buffer.writeBoolean(chunk.isFullChunk()); + buffer.writeShort(bitmask); + buffer.writeShort(addBitmask); - for (int i = 0; i < storageArrays.length; i++) { - if ((chunk.getBitmask() & 1 << i) != 0) { - final byte[] blockLSBArray = storageArrays[i].getBlockLSBArray(); - System.arraycopy(blockLSBArray, 0, output, index, blockLSBArray.length); - index += blockLSBArray.length; + final Deflater deflater = new Deflater(); + byte[] compressedData; + int compressedSize; + try { + deflater.setInput(data, 0, data.length); + deflater.finish(); + compressedData = new byte[data.length]; + compressedSize = deflater.deflate(compressedData); + } finally { + deflater.end(); + } + + buffer.writeInt(compressedSize); + buffer.writeBytes(compressedData, 0, compressedSize); + } + + public static int serialize(Chunk chunk, byte[] output, int offset, int addBitmask, boolean writeSkyLight, boolean biomes) { + final ChunkSection[] sections = chunk.getSections(); + final int bitmask = chunk.getBitmask(); + + for (int i = 0; i < 16; i++) { + if ((bitmask & (1 << i)) != 0) { + final ChunkSection section = sections[i]; + final DataPalette palette = section.palette(PaletteType.BLOCKS); + for (int j = 0; j < SIZE; j++) { + final int block = palette.idAt(j); + output[offset++] = (byte) ((block >> 4) & 0xFF); + } } } - for (int i = 0; i < storageArrays.length; i++) { - if ((chunk.getBitmask() & 1 << i) != 0) { - final byte[] blockMetadataArray = storageArrays[i].getBlockMetadataArray().getHandle(); - System.arraycopy(blockMetadataArray, 0, output, index, blockMetadataArray.length); - index += blockMetadataArray.length; + for (int i = 0; i < 16; i++) { + if ((bitmask & (1 << i)) != 0) { + final ChunkSection section = sections[i]; + final DataPalette palette = section.palette(PaletteType.BLOCKS); + for (int j = 0; j < ChunkSection.SIZE; j += 2) { + final int meta1 = palette.idAt(j) & 0xF; + final int meta2 = palette.idAt(j + 1) & 0xF; + output[offset++] = (byte) (meta1 | (meta2 << 4)); + } } } - for (int i = 0; i < storageArrays.length; i++) { - if ((chunk.getBitmask() & 1 << i) != 0) { - final byte[] blockLightArray = storageArrays[i].getBlockLightArray().getHandle(); - System.arraycopy(blockLightArray, 0, output, index, blockLightArray.length); - index += blockLightArray.length; + for (int i = 0; i < 16; i++) { + if ((bitmask & (1 << i)) != 0) { + final byte[] blockLight = sections[i].getLight().getBlockLight(); + System.arraycopy(blockLight, 0, output, offset, LIGHT_LENGTH); + offset += LIGHT_LENGTH; } } - for (int i = 0; i < storageArrays.length; i++) { - if ((chunk.getBitmask() & 1 << i) != 0 && storageArrays[i].getSkyLightArray() != null) { - final byte[] skyLightArray = storageArrays[i].getSkyLightArray().getHandle(); - System.arraycopy(skyLightArray, 0, output, index, skyLightArray.length); - index += skyLightArray.length; + if (writeSkyLight) { + for (int i = 0; i < 16; i++) { + if ((bitmask & (1 << i)) != 0) { + if (sections[i].getLight().hasSkyLight()) { + final byte[] skyLight = sections[i].getLight().getSkyLight(); + System.arraycopy(skyLight, 0, output, offset, LIGHT_LENGTH); + } + offset += LIGHT_LENGTH; + } } } - short additionalBitMask = 0; - for (int i = 0; i < storageArrays.length; i++) { - if ((chunk.getBitmask() & 1 << i) != 0 && storageArrays[i].hasBlockMSBArray()) { - additionalBitMask |= (short) (1 << i); - final byte[] blockMSBArray = storageArrays[i].getOrCreateBlockMSBArray().getHandle(); - System.arraycopy(blockMSBArray, 0, output, index, blockMSBArray.length); - index += blockMSBArray.length; + if (addBitmask != 0) { + for (int i = 0; i < 16; i++) { + if ((bitmask & (1 << i)) != 0 && (addBitmask & (1 << i)) != 0) { + final ChunkSection section = sections[i]; + final DataPalette palette = section.palette(PaletteType.BLOCKS); + for (int j = 0; j < SIZE; j += 2) { + final int add1 = (palette.idAt(j) >> 12) & 0xF; + final int add2 = (palette.idAt(j + 1) >> 12) & 0xF; + output[offset++] = (byte) (add1 | (add2 << 4)); + } + } } } - if (biomes) { - for (int biome : chunk.getBiomeData()) { - output[index++] = (byte) biome; + if (biomes && chunk.getBiomeData() != null) { + final int[] biomeData = chunk.getBiomeData(); + for (int biome : biomeData) { + output[offset++] = (byte) biome; } } - return new Pair<>(output, additionalBitMask); + return offset; } - private static int calculateSize(final ExtendedBlockStorage[] storageArrays, final int bitmask, final boolean biomes) { - int totalSize = 0; - for (int i = 0; i < storageArrays.length; i++) { - if ((bitmask & 1 << i) != 0) { - totalSize += SIZE; // Block lsb array - totalSize += SIZE / 2; // Block metadata array - totalSize += LIGHT_LENGTH; // Block light array + public static int calcSize(int bitmask, int addBitmask, boolean hasSkyLight, boolean biomes) { + int size = 0; + int sections = Integer.bitCount(bitmask); - if (storageArrays[i].getSkyLightArray() != null) { - totalSize += LIGHT_LENGTH; - } + size += sections * SIZE; + size += sections * (SIZE / 2); + size += sections * LIGHT_LENGTH; - if (storageArrays[i].hasBlockMSBArray()) { - totalSize += SIZE / 2; // Block msb array - } - } + if (hasSkyLight) { + size += sections * LIGHT_LENGTH; + } + + if (addBitmask != 0) { + size += Integer.bitCount(addBitmask) * (SIZE / 2); } + if (biomes) { - totalSize += 256; + size += 256; } - return totalSize; - } - @Override - public Chunk read(ByteBuf byteBuf) { - throw new UnsupportedOperationException(); // Not needed, see https://github.com/ViaVersion/ViaLegacy/blob/main/src/main/java/net/raphimc/vialegacy/protocols/release/protocol1_8to1_7_6_10/types/Chunk1_7_6Type.java + return size; } - @Override - public void write(ByteBuf output, Chunk chunk) { - Pair chunkData; - try { - chunkData = serialize(chunk); - } catch (IOException e) { - throw new RuntimeException("Unable to serialize chunk", e); + public static int getAddBitMask(Chunk chunk) { + int addBitMask = 0; + for (int i = 0; i < 16; i++) { + if ((chunk.getBitmask() & (1 << i)) != 0) { + final ChunkSection section = chunk.getSections()[i]; + final DataPalette palette = section.palette(PaletteType.BLOCKS); + for (int j = 0; j < SIZE; j++) { + final int id = palette.idAt(j); + if ((id >> 12) != 0) { + addBitMask |= (1 << i); + break; + } + } + } } - final byte[] data = chunkData.key(); - final short additionalBitMask = chunkData.value(); + return addBitMask; + } - final Deflater deflater = new Deflater(); - byte[] compressedData; - int compressedSize; - try { - deflater.setInput(data, 0, data.length); - deflater.finish(); - compressedData = new byte[data.length]; - compressedSize = deflater.deflate(compressedData); - } finally { - deflater.end(); + public static boolean hasSkyLight(Chunk chunk) { + for (ChunkSection section : chunk.getSections()) { + if (section != null && section.getLight().hasSkyLight()) { + return true; + } } - - output.writeInt(chunk.getX()); - output.writeInt(chunk.getZ()); - output.writeBoolean(chunk.isFullChunk()); - output.writeShort(chunk.getBitmask()); - output.writeShort(additionalBitMask); - output.writeInt(compressedSize); - output.writeBytes(compressedData, 0, compressedSize); + return false; } - } diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/CompressionHandlerProvider.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/CompressionHandlerProvider.java index 4d93e8670..ed33cb105 100644 --- a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/CompressionHandlerProvider.java +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/CompressionHandlerProvider.java @@ -28,9 +28,23 @@ public abstract class CompressionHandlerProvider implements Provider { public abstract void onTransformPacket(UserConnection user); - public abstract ChannelHandler getEncoder(int threshold); - - public abstract ChannelHandler getDecoder(int threshold); + /** + * Creates the compression encoder + * + * @param compressor A nullable object that may present a native backend for compression (if available) + * @param threshold The compression threshold + * @return The encoder + */ + public abstract ChannelHandler getEncoder(Object compressor, int threshold); + + /** + * Creates the compression decoder + * + * @param compressor A nullable object that may present a native backend for compression (if available) + * @param threshold The compression threshold + * @return The decoder + */ + public abstract ChannelHandler getDecoder(Object compressor, int threshold); public boolean isRemoveCompression(UserConnection user) { return user.get(CompressionStatusTracker.class).removeCompression; diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionDecoder.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionDecoder.java index 39dffc05b..4e280a928 100644 --- a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionDecoder.java +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionDecoder.java @@ -24,6 +24,7 @@ import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; +import java.util.zip.DataFormatException; import java.util.zip.Inflater; public class CompressionDecoder extends MessageToMessageDecoder { @@ -41,7 +42,9 @@ public void setThreshold(final int threshold) { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - if (!in.isReadable()) return; + if (!in.isReadable()) { + return; + } int outLength = Types.VAR_INT.readPrimitive(in); if (outLength == 0) { @@ -55,11 +58,15 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t throw new DecoderException("Badly compressed packet - size of " + outLength + " is larger than protocol maximum of " + 2097152); } - ByteBuf temp = in; - if (!in.hasArray()) { - temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(in); + inflate(ctx, in, outLength, out); + } + + protected void inflate(final ChannelHandlerContext ctx, final ByteBuf source, final int outLength, final List out) throws DataFormatException { + ByteBuf temp = source; + if (!source.hasArray()) { + temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(source); } else { - in.retain(); + source.retain(); } ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer(outLength, outLength); try { @@ -72,4 +79,5 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t this.inflater.reset(); } } + } diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionEncoder.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionEncoder.java index f5a32868a..9320bf59b 100644 --- a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionEncoder.java +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/CompressionEncoder.java @@ -22,6 +22,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; +import java.util.zip.DataFormatException; import java.util.zip.Deflater; public class CompressionEncoder extends MessageToByteEncoder { @@ -48,11 +49,15 @@ protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Types.VAR_INT.writePrimitive(out, frameLength); - ByteBuf temp = in; - if (!in.hasArray()) { - temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(in); + deflate(in, out); + } + + protected void deflate(final ByteBuf source, final ByteBuf destination) throws DataFormatException { + ByteBuf temp = source; + if (!source.hasArray()) { + temp = ByteBufAllocator.DEFAULT.heapBuffer().writeBytes(source); } else { - in.retain(); + source.retain(); } ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer(); try { @@ -63,11 +68,12 @@ protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws output.ensureWritable(4096); output.writerIndex(output.writerIndex() + this.deflater.deflate(output.array(), output.arrayOffset() + output.writerIndex(), output.writableBytes())); } - out.writeBytes(output); + destination.writeBytes(output); } finally { output.release(); temp.release(); this.deflater.reset(); } } + } diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/TrackingCompressionHandlerProvider.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/TrackingCompressionHandlerProvider.java index 9df530d0c..7514fa3c7 100644 --- a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/TrackingCompressionHandlerProvider.java +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/TrackingCompressionHandlerProvider.java @@ -21,6 +21,8 @@ import com.viaversion.viarewind.api.minecraft.netty.EmptyChannelHandler; import com.viaversion.viarewind.api.minecraft.netty.ForwardMessageToByteEncoder; import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.CompressionHandlerProvider; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity.VelocityCompressionDecoder; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity.VelocityCompressionEncoder; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import io.netty.channel.ChannelHandler; @@ -28,6 +30,19 @@ public class TrackingCompressionHandlerProvider extends CompressionHandlerProvider { + // Use Velocity's compression backend if ViaRewind is loaded on a Velocity server or a custom server software + // is shipping them in the classpath. + private static boolean velocityNatives; + + static { + try { + Class.forName("com.velocitypowered.natives.compression.VelocityCompressor"); + velocityNatives = true; + } catch (final ClassNotFoundException ignored) { + velocityNatives = false; + } + } + @Override public void setCompressionThreshold(UserConnection user, int threshold) { final ChannelPipeline pipeline = user.getChannel().pipeline(); @@ -36,12 +51,17 @@ public void setCompressionThreshold(UserConnection user, int threshold) { return; } + Object compressor = null; + if (velocityNatives) { + compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); + } + final String compressHandlerName = ViaRewind.getPlatform().compressHandlerName(); final CompressionEncoder encoder = (CompressionEncoder) pipeline.get(compressHandlerName); if (encoder != null) { encoder.setThreshold(threshold); } else { - pipeline.addBefore(Via.getManager().getInjector().getEncoderName(), compressHandlerName, getEncoder(threshold)); + pipeline.addBefore(Via.getManager().getInjector().getEncoderName(), compressHandlerName, getEncoder(compressor, threshold)); } final String decompressHandlerName = ViaRewind.getPlatform().decompressHandlerName(); @@ -49,7 +69,7 @@ public void setCompressionThreshold(UserConnection user, int threshold) { if (decoder != null) { decoder.setThreshold(threshold); } else { - pipeline.addBefore(Via.getManager().getInjector().getDecoderName(), decompressHandlerName, getDecoder(threshold)); + pipeline.addBefore(Via.getManager().getInjector().getDecoderName(), decompressHandlerName, getDecoder(compressor, threshold)); } } @@ -77,12 +97,21 @@ public void onTransformPacket(UserConnection user) { } @Override - public ChannelHandler getEncoder(int threshold) { - return new CompressionEncoder(threshold); + public ChannelHandler getEncoder(final Object compressor, int threshold) { + if (velocityNatives) { + return new VelocityCompressionEncoder(threshold, (com.velocitypowered.natives.compression.VelocityCompressor) compressor); + } else { + return new CompressionEncoder(threshold); + } } @Override - public ChannelHandler getDecoder(int threshold) { - return new CompressionDecoder(threshold); + public ChannelHandler getDecoder(final Object compressor, int threshold) { + if (velocityNatives) { + return new VelocityCompressionDecoder(threshold, (com.velocitypowered.natives.compression.VelocityCompressor) compressor); + } else { + return new CompressionDecoder(threshold); + } } + } diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/velocity/VelocityCompressionDecoder.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/velocity/VelocityCompressionDecoder.java new file mode 100644 index 000000000..eb7c0a6be --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/velocity/VelocityCompressionDecoder.java @@ -0,0 +1,47 @@ +/* + * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind + * Copyright (C) 2018-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 . + */ +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity; + +import com.velocitypowered.natives.compression.VelocityCompressor; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.CompressionDecoder; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import java.util.List; +import java.util.zip.DataFormatException; + +public final class VelocityCompressionDecoder extends CompressionDecoder { + + private final VelocityCompressor compressor; + + public VelocityCompressionDecoder(final int threshold, final VelocityCompressor compressor) { + super(threshold); + this.compressor = compressor; + } + + @Override + protected void inflate(final ChannelHandlerContext ctx, final ByteBuf source, final int outLength, final List out) throws DataFormatException { + ByteBuf output = ctx.alloc().buffer(outLength); + try { + this.compressor.inflate(source, output, outLength); + out.add(output.retain()); + } finally { + output.release(); + } + } + +} diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/velocity/VelocityCompressionEncoder.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/velocity/VelocityCompressionEncoder.java new file mode 100644 index 000000000..064f6aba7 --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/velocity/VelocityCompressionEncoder.java @@ -0,0 +1,39 @@ +/* + * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind + * Copyright (C) 2018-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 . + */ +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.velocity; + +import com.velocitypowered.natives.compression.VelocityCompressor; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.CompressionEncoder; +import io.netty.buffer.ByteBuf; +import java.util.zip.DataFormatException; + +public final class VelocityCompressionEncoder extends CompressionEncoder { + + private final VelocityCompressor compressor; + + public VelocityCompressionEncoder(final int threshold, final VelocityCompressor compressor) { + super(threshold); + this.compressor = compressor; + } + + @Override + protected void deflate(final ByteBuf source, final ByteBuf destination) throws DataFormatException { + this.compressor.deflate(source, destination); + } + +}