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 2b5c3c5a9..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-2025 ViaVersion and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -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 679538ab1..f14ee56d8 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 @@ -17,11 +17,13 @@ */ package com.viaversion.viarewind.api.type.chunk; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor.CompressorUtil; 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 io.netty.buffer.Unpooled; + +import java.util.zip.DataFormatException; import java.util.zip.Deflater; public class BulkChunkType1_7_6 extends Type { @@ -38,40 +40,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 +97,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 83577f2ea..636627a69 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,16 @@ */ package com.viaversion.viarewind.api.type.chunk; -import com.viaversion.viarewind.api.minecraft.ExtendedBlockStorage; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor.CompressorUtil; 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 io.netty.buffer.Unpooled; + +import java.util.zip.DataFormatException; import java.util.zip.Deflater; import static com.viaversion.viaversion.api.minecraft.chunks.ChunkSection.SIZE; @@ -38,143 +40,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); + + 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); + } - 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; + 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/Protocol1_8To1_7_6_10.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/Protocol1_8To1_7_6_10.java index cb6fe94a4..1ce4d13f8 100644 --- a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/Protocol1_8To1_7_6_10.java +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/Protocol1_8To1_7_6_10.java @@ -54,7 +54,6 @@ import java.util.concurrent.TimeUnit; public class Protocol1_8To1_7_6_10 extends BackwardsProtocol { - public static final RewindMappingData MAPPINGS = new RewindMappingData("1.8", "1.7.10"); private final BlockItemPacketRewriter1_8 itemRewriter = new BlockItemPacketRewriter1_8(this); 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 7a81b717e..91e25971f 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 @@ -17,18 +17,15 @@ */ package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor.CompressorUtil; import com.viaversion.viaversion.api.type.Types; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; -import java.util.zip.Inflater; public class CompressionDecoder extends MessageToMessageDecoder { - private final Inflater inflater = new Inflater(); - private int threshold; public CompressionDecoder(final int threshold) { @@ -55,21 +52,12 @@ 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); - } else { - in.retain(); - } - ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer(outLength, outLength); + ByteBuf output = ctx.alloc().buffer(outLength); try { - this.inflater.setInput(temp.array(), temp.arrayOffset() + temp.readerIndex(), temp.readableBytes()); - output.writerIndex(output.writerIndex() + this.inflater.inflate(output.array(), output.arrayOffset(), outLength)); + CompressorUtil.getCompressor().inflate(in, output, outLength); out.add(output.retain()); } finally { output.release(); - temp.release(); - 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 a44a60a65..ee9c0f57b 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 @@ -17,16 +17,13 @@ */ package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression; +import com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor.CompressorUtil; import com.viaversion.viaversion.api.type.Types; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import java.util.zip.Deflater; public class CompressionEncoder extends MessageToByteEncoder { - private final Deflater deflater = new Deflater(); - private int threshold; public CompressionEncoder(final int threshold) { @@ -48,26 +45,6 @@ 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); - } else { - in.retain(); - } - ByteBuf output = ByteBufAllocator.DEFAULT.heapBuffer(); - try { - this.deflater.setInput(temp.array(), temp.arrayOffset() + temp.readerIndex(), temp.readableBytes()); - deflater.finish(); - - while (!deflater.finished()) { - output.ensureWritable(4096); - output.writerIndex(output.writerIndex() + this.deflater.deflate(output.array(), output.arrayOffset() + output.writerIndex(), output.writableBytes())); - } - out.writeBytes(output); - } finally { - output.release(); - temp.release(); - this.deflater.reset(); - } + CompressorUtil.getCompressor().deflate(in, out); } } diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/Compressor.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/Compressor.java new file mode 100644 index 000000000..1c7116620 --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/Compressor.java @@ -0,0 +1,28 @@ +/* + * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind + * Copyright (C) 2018-2025 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor; + +import io.netty.buffer.ByteBuf; + +import java.util.zip.DataFormatException; + +public interface Compressor { + void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) throws DataFormatException; + + void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; +} diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/CompressorUtil.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/CompressorUtil.java new file mode 100644 index 000000000..ff34dc4e1 --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/CompressorUtil.java @@ -0,0 +1,37 @@ +/* + * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind + * Copyright (C) 2018-2025 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor; + +public class CompressorUtil { + public static boolean VELOCITY_COMPRESSION_PRESENT; + + static { + try { + Class.forName("com.velocitypowered.natives.compression.VelocityCompressor"); + VELOCITY_COMPRESSION_PRESENT = true; + } catch (ClassNotFoundException e) { + + } + } + + public static ThreadLocal COMPRESSOR = ThreadLocal.withInitial(() -> VELOCITY_COMPRESSION_PRESENT ? new VelocityCompressor() : new JavaCompressor()); + + public static Compressor getCompressor() { + return COMPRESSOR.get(); + } +} diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/JavaCompressor.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/JavaCompressor.java new file mode 100644 index 000000000..cf8835e16 --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/JavaCompressor.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018-2023 Velocity 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 . + */ + +// Based on: https://github.com/PaperMC/Velocity/blob/dev/3.0.0/native/src/main/java/com/velocitypowered/natives/compression/JavaVelocityCompressor.java +// This fallback exists so that velocity-natives is not a shaded dependency + +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor; + +import static com.google.common.base.Preconditions.checkArgument; + +import io.netty.buffer.ByteBuf; +import java.nio.ByteBuffer; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * Implements deflate compression by wrapping {@link Deflater} and {@link Inflater}. + */ +public class JavaCompressor implements Compressor { + static final int ZLIB_BUFFER_SIZE = 8192; + + private final Deflater deflater; + private final Inflater inflater; + + public JavaCompressor() { + this.deflater = new Deflater(); + this.inflater = new Inflater(); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) + throws DataFormatException { + // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. + checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers"); + checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers"); + + final int origIdx = source.readerIndex(); + inflater.setInput(source.nioBuffer()); + + try { + final int readable = source.readableBytes(); + while (!inflater.finished() && inflater.getBytesRead() < readable) { + if (!destination.isWritable()) { + destination.ensureWritable(ZLIB_BUFFER_SIZE); + } + + ByteBuffer destNioBuf = destination.nioBuffer(destination.writerIndex(), + destination.writableBytes()); + int produced = inflater.inflate(destNioBuf); + destination.writerIndex(destination.writerIndex() + produced); + } + + if (!inflater.finished()) { + throw new DataFormatException("Received a deflate stream that was too large, wanted " + + uncompressedSize); + } + source.readerIndex(origIdx + inflater.getTotalIn()); + } finally { + inflater.reset(); + } + } + + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. + checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers"); + checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers"); + + final int origIdx = source.readerIndex(); + deflater.setInput(source.nioBuffer()); + deflater.finish(); + + while (!deflater.finished()) { + if (!destination.isWritable()) { + destination.ensureWritable(ZLIB_BUFFER_SIZE); + } + + ByteBuffer destNioBuf = destination.nioBuffer(destination.writerIndex(), + destination.writableBytes()); + int produced = deflater.deflate(destNioBuf); + destination.writerIndex(destination.writerIndex() + produced); + } + + source.readerIndex(origIdx + deflater.getTotalIn()); + deflater.reset(); + } +} diff --git a/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/VelocityCompressor.java b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/VelocityCompressor.java new file mode 100644 index 000000000..f3afe9d84 --- /dev/null +++ b/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/provider/compression/compressor/VelocityCompressor.java @@ -0,0 +1,37 @@ +/* + * This file is part of ViaRewind - https://github.com/ViaVersion/ViaRewind + * Copyright (C) 2018-2025 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viarewind.protocol.v1_8to1_7_6_10.provider.compression.compressor; + +import com.velocitypowered.natives.util.Natives; +import io.netty.buffer.ByteBuf; + +import java.util.zip.DataFormatException; + +public class VelocityCompressor implements Compressor { + private final com.velocitypowered.natives.compression.VelocityCompressor velocityCompressor = Natives.compress.get().create(-1); + + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + velocityCompressor.deflate(source, destination); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) throws DataFormatException { + velocityCompressor.inflate(source, destination, uncompressedSize); + } +}