Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public ShortArray3d getBlocks() {
}

public NibbleArray3d getBlockLight() {
// Create block light array if needed, as block light is not optional in 1.8.9
if (this.blocklight == null) {
this.blocklight = new NibbleArray3d(4096);
}
return this.blocklight;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk;
import com.github.retrooper.packetevents.protocol.world.chunk.NetworkChunkData;
import com.github.retrooper.packetevents.protocol.world.chunk.NibbleArray3d;
import com.github.retrooper.packetevents.protocol.world.chunk.ShortArray3d;
import com.github.retrooper.packetevents.protocol.world.chunk.impl.v1_8.Chunk_v1_8;
import com.github.retrooper.packetevents.protocol.world.chunk.reader.ChunkReader;
import com.github.retrooper.packetevents.protocol.world.dimension.DimensionType;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
Expand All @@ -35,128 +35,203 @@

public class ChunkReader_v1_8 implements ChunkReader {

private static final int SECTION_COUNT = 16;
private static final int BLOCK_COUNT = 4096;
private static final int BLOCK_DATA_LENGTH = BLOCK_COUNT * Short.BYTES;
private static final int LIGHT_DATA_LENGTH = BLOCK_COUNT / 2;
private static final int BIOME_DATA_LENGTH = 16 * 16;

@Override
public BaseChunk[] read(
DimensionType dimensionType, BitSet chunkMask, BitSet secondaryChunkMask, boolean fullChunk,
boolean hasBlockLight, boolean hasSkyLight, int chunkSize, int arrayLength, PacketWrapper<?> wrapper
boolean forceSkyLight, boolean checkForSkyLight, int chunkSize, int arrayLength, PacketWrapper<?> wrapper
) {
byte[] data = wrapper.readByteArrayOfSize(arrayLength);
int populatedSections = 0;
for (int index = 0; index < SECTION_COUNT; index++) {
if (chunkMask.get(index)) {
populatedSections++;
}
}

Chunk_v1_8[] chunks = new Chunk_v1_8[16];
int pos = 0;
int expected = fullChunk ? 256 : 0; // 256 if full chunk for the biome data, always sent if full chunk
boolean sky = false;

ShortBuffer buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();

// 0 = Calculate expected length and determine if the packet has skylight.
// 1 = Create chunks from mask and get blocks.
// 2 = Get block light.
// 3 = Get sky light.
for (int pass = 0; pass < 4; pass++) {
for (int ind = 0; ind < 16; ind++) {
if (chunkMask.get(ind)) {
if (pass == 0) {
// Block length + Blocklight length
expected += (4096 * 2) + 2048;
}

if (pass == 1) {
chunks[ind] = new Chunk_v1_8(sky || hasBlockLight);
ShortArray3d blocks = chunks[ind].getBlocks();
buf.position(pos / 2);
buf.get(blocks.getData(), 0, blocks.getData().length);
pos += blocks.getData().length * 2;
}

if (pass == 2) {
NibbleArray3d blocklight = chunks[ind].getBlockLight();
System.arraycopy(data, pos, blocklight.getData(), 0, blocklight.getData().length);
pos += blocklight.getData().length;
}

if (pass == 3 && (sky || hasBlockLight)) {
NibbleArray3d skylight = chunks[ind].getSkyLight();
System.arraycopy(data, pos, skylight.getData(), 0, skylight.getData().length);
pos += skylight.getData().length;
}
}
int sectionDataLength = populatedSections * (BLOCK_DATA_LENGTH + LIGHT_DATA_LENGTH);
int expectedWithoutSkyLight = sectionDataLength + (fullChunk ? BIOME_DATA_LENGTH : 0);
// Bulk packets specify skylight; regular packets infer it from the payload length.
boolean skyLight = forceSkyLight || arrayLength > expectedWithoutSkyLight && checkForSkyLight;
int requiredSectionDataLength = sectionDataLength
+ (skyLight ? populatedSections * LIGHT_DATA_LENGTH : 0);
if (arrayLength < requiredSectionDataLength) {
throw new IllegalArgumentException("1.8 chunk data is shorter than its section mask requires ("
+ arrayLength + " < " + requiredSectionDataLength + ")");
}

Chunk_v1_8[] chunks = new Chunk_v1_8[SECTION_COUNT];
// Reuse one buffer for little-endian block data.
byte[] encodedBlocks = populatedSections == 0 ? null : new byte[BLOCK_DATA_LENGTH];
ShortBuffer decodedBlocks = encodedBlocks == null ? null
: ByteBuffer.wrap(encodedBlocks).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();

for (int index = 0; index < SECTION_COUNT; index++) {
if (chunkMask.get(index)) {
Chunk_v1_8 chunk = new Chunk_v1_8(skyLight);
chunks[index] = chunk;
ByteBufHelper.readBytes(wrapper.buffer, encodedBlocks);
short[] blocks = chunk.getBlocks().getData();
decodedBlocks.position(0);
decodedBlocks.get(blocks, 0, blocks.length);
}
}

if (pass == 0 && data.length > expected) {
// If we have more data than blocks and blocklight combined, there must be skylight data as well.
sky = hasSkyLight;
for (int index = 0; index < SECTION_COUNT; index++) {
Chunk_v1_8 chunk = chunks[index];
if (chunk != null) {
ByteBufHelper.readBytes(wrapper.buffer, chunk.getBlockLight().getData());
}
}

// reset reader index of buffer to end of data, we still need to read biome data
int ri = ByteBufHelper.readerIndex(wrapper.buffer);
ByteBufHelper.readerIndex(wrapper.buffer, ri - (arrayLength - pos));
if (skyLight) {
for (int index = 0; index < SECTION_COUNT; index++) {
Chunk_v1_8 chunk = chunks[index];
if (chunk != null) {
ByteBufHelper.readBytes(wrapper.buffer, chunk.getSkyLight().getData());
}
}
}

return chunks;
}

@Deprecated
public static NetworkChunkData chunksToData(Chunk_v1_8[] chunks, byte[] biomes) {
int chunkMask = 0;
boolean fullChunk = biomes != null;
boolean sky = false;
int length = fullChunk ? biomes.length : 0;
byte[] data = null;
int chunkMask = calculateChunkMask(chunks, fullChunk);
boolean sky = hasSkyLight(chunks, chunkMask);
byte[] data = new byte[calculateDataLength(chunks, biomes, chunkMask)];
int pos = 0;
ShortBuffer buf = null;

// 0 = Determine length and masks.
// 1 = Add blocks.
// 2 = Add block light.
// 3 = Add sky light.
for (int pass = 0; pass < 4; pass++) {
for (int ind = 0; ind < chunks.length; ++ind) {
Chunk_v1_8 chunk = chunks[ind];
if (chunk != null && (!fullChunk || !chunk.isEmpty())) {
if (pass == 0) {
chunkMask |= 1 << ind;
length += chunk.getBlocks().getData().length * 2;
length += chunk.getBlockLight().getData().length;
if (chunk.getSkyLight() != null) {
length += chunk.getSkyLight().getData().length;
}
}

if (pass == 1) {
short blocks[] = chunk.getBlocks().getData();
buf.position(pos / 2);
buf.put(blocks, 0, blocks.length);
pos += blocks.length * 2;
}

if (pass == 2) {
byte blocklight[] = chunk.getBlockLight().getData();
System.arraycopy(blocklight, 0, data, pos, blocklight.length);
pos += blocklight.length;
}

if (pass == 3 && chunk.getSkyLight() != null) {
byte skylight[] = chunk.getSkyLight().getData();
System.arraycopy(skylight, 0, data, pos, skylight.length);
pos += skylight.length;
sky = true;
}
}
ShortBuffer blockData = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();

for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
short[] blocks = chunks[index].getBlocks().getData();
blockData.position(pos / Short.BYTES);
blockData.put(blocks, 0, blocks.length);
pos += blocks.length * Short.BYTES;
}
}

if (pass == 0) {
data = new byte[length];
buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
byte[] blockLight = chunks[index].getBlockLight().getData();
System.arraycopy(blockLight, 0, data, pos, blockLight.length);
pos += blockLight.length;
}
}

for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
NibbleArray3d skyLight = chunks[index].getSkyLight();
if (skyLight != null) {
byte[] skyLightData = skyLight.getData();
System.arraycopy(skyLightData, 0, data, pos, skyLightData.length);
pos += skyLightData.length;
}
}
}

// Add biomes.
if (fullChunk) {
System.arraycopy(biomes, 0, data, pos, biomes.length);
pos += biomes.length;
}

return new NetworkChunkData(chunkMask, fullChunk, sky, data);
}
}

@ApiStatus.Internal
public static int calculateChunkMask(Chunk_v1_8[] chunks, boolean fullChunk) {
int chunkMask = 0;
for (int index = 0; index < chunks.length; index++) {
Chunk_v1_8 chunk = chunks[index];
if (chunk != null && (!fullChunk || !chunk.isEmpty())) {
chunkMask |= 1 << index;
}
}
return chunkMask;
}

@ApiStatus.Internal
public static int calculateDataLength(Chunk_v1_8[] chunks, byte[] biomes, int chunkMask) {
int length = biomes == null ? 0 : biomes.length;
for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
Chunk_v1_8 chunk = chunks[index];
length += chunk.getBlocks().getData().length * Short.BYTES;
length += chunk.getBlockLight().getData().length;
if (chunk.getSkyLight() != null) {
length += chunk.getSkyLight().getData().length;
}
}
}
return length;
}

@ApiStatus.Internal
public static boolean hasSkyLight(Chunk_v1_8[] chunks, int chunkMask) {
for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0 && chunks[index].getSkyLight() != null) {
return true;
}
}
return false;
}

@ApiStatus.Internal
public static void writeChunkData(
PacketWrapper<?> wrapper, Chunk_v1_8[] chunks, byte[] biomes, int chunkMask, int dataLength
) {
ensureWritable(wrapper, dataLength);

int largestBlockDataLength = 0;
for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
largestBlockDataLength = Math.max(largestBlockDataLength,
chunks[index].getBlocks().getData().length * Short.BYTES);
}
}

byte[] encodedBlocks = largestBlockDataLength == 0 ? null : new byte[largestBlockDataLength];
ShortBuffer blockData = encodedBlocks == null ? null
: ByteBuffer.wrap(encodedBlocks).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
short[] blocks = chunks[index].getBlocks().getData();
blockData.position(0);
blockData.put(blocks, 0, blocks.length);
ByteBufHelper.writeBytes(wrapper.buffer, encodedBlocks, 0, blocks.length * Short.BYTES);
}
}

for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0) {
ByteBufHelper.writeBytes(wrapper.buffer, chunks[index].getBlockLight().getData());
}
}

for (int index = 0; index < chunks.length; index++) {
if ((chunkMask & 1 << index) != 0 && chunks[index].getSkyLight() != null) {
ByteBufHelper.writeBytes(wrapper.buffer, chunks[index].getSkyLight().getData());
}
}

if (biomes != null) {
ByteBufHelper.writeBytes(wrapper.buffer, biomes);
}
}

@ApiStatus.Internal
public static void ensureWritable(PacketWrapper<?> wrapper, int additionalBytes) {
int writerIndex = ByteBufHelper.writerIndex(wrapper.buffer);
int requiredCapacity = Math.addExact(writerIndex, additionalBytes);
if (requiredCapacity > ByteBufHelper.capacity(wrapper.buffer)) {
ByteBufHelper.capacity(wrapper.buffer, requiredCapacity);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,13 @@ public void write() {
ByteBufHelper.writerIndex(dataBuffer, newWriterIndex);
}
} else if (v1_8) {
NetworkChunkData data = ChunkReader_v1_8.chunksToData((Chunk_v1_8[]) chunks, column.getBiomeDataBytes());
writeShort(data.getMask());
writeByteArray(data.getData());
Chunk_v1_8[] legacyChunks = (Chunk_v1_8[]) chunks;
byte[] biomes = column.getBiomeDataBytes();
int mask = ChunkReader_v1_8.calculateChunkMask(legacyChunks, biomes != null);
int dataLength = ChunkReader_v1_8.calculateDataLength(legacyChunks, biomes, mask);
writeShort(mask);
writeVarInt(dataLength);
ChunkReader_v1_8.writeChunkData(this, legacyChunks, biomes, mask, dataLength);
return;
} else {
NetworkChunkData data = ChunkReader_v1_7.chunksToData((Chunk_v1_7[]) chunks, column.getBiomeDataBytes());
Expand Down
Loading
Loading