mirror of
https://github.com/ViaVersion/ViaVersion.git
synced 2024-11-26 12:05:41 +01:00
Merge pull request #200 from lenis0012/chunks
Implement new chunk transformer
This commit is contained in:
commit
7c66507960
@ -7,6 +7,7 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import us.myles.ViaVersion.chunks.ChunkManager;
|
||||||
import us.myles.ViaVersion.packets.State;
|
import us.myles.ViaVersion.packets.State;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -16,6 +17,7 @@ public class ConnectionInfo {
|
|||||||
private static final long IDLE_PACKET_LIMIT = 20; // Max 20 ticks behind
|
private static final long IDLE_PACKET_LIMIT = 20; // Max 20 ticks behind
|
||||||
|
|
||||||
private final SocketChannel channel;
|
private final SocketChannel channel;
|
||||||
|
private final ChunkManager chunkManager;
|
||||||
private Object lastPacket;
|
private Object lastPacket;
|
||||||
private java.util.UUID UUID;
|
private java.util.UUID UUID;
|
||||||
private State state = State.HANDSHAKE;
|
private State state = State.HANDSHAKE;
|
||||||
@ -29,6 +31,7 @@ public class ConnectionInfo {
|
|||||||
|
|
||||||
public ConnectionInfo(SocketChannel socketChannel) {
|
public ConnectionInfo(SocketChannel socketChannel) {
|
||||||
this.channel = socketChannel;
|
this.channel = socketChannel;
|
||||||
|
this.chunkManager = new ChunkManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
|
32
src/main/java/us/myles/ViaVersion/chunks/Chunk.java
Normal file
32
src/main/java/us/myles/ViaVersion/chunks/Chunk.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package us.myles.ViaVersion.chunks;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@Getter
|
||||||
|
public class Chunk {
|
||||||
|
private final int x;
|
||||||
|
private final int z;
|
||||||
|
private final boolean groundUp;
|
||||||
|
private final int primaryBitmask;
|
||||||
|
private final ChunkSection[] sections;
|
||||||
|
private final byte[] biomeData;
|
||||||
|
private boolean unloadPacket = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunk unload.
|
||||||
|
*
|
||||||
|
* @param x coord
|
||||||
|
* @param z coord
|
||||||
|
*/
|
||||||
|
protected Chunk(int x, int z) {
|
||||||
|
this(x, z, true, 0, new ChunkSection[16], null);
|
||||||
|
this.unloadPacket = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBiomeData() {
|
||||||
|
return biomeData != null && groundUp;
|
||||||
|
}
|
||||||
|
}
|
224
src/main/java/us/myles/ViaVersion/chunks/ChunkManager.java
Normal file
224
src/main/java/us/myles/ViaVersion/chunks/ChunkManager.java
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package us.myles.ViaVersion.chunks;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import us.myles.ViaVersion.ConnectionInfo;
|
||||||
|
import us.myles.ViaVersion.util.PacketUtil;
|
||||||
|
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||||
|
import us.myles.ViaVersion.util.ReflectionUtil.ClassReflection;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public class ChunkManager {
|
||||||
|
/**
|
||||||
|
* Amount of sections in a chunk.
|
||||||
|
*/
|
||||||
|
private static final int SECTION_COUNT = 16;
|
||||||
|
/**
|
||||||
|
* size of each chunk section (16x16x16).
|
||||||
|
*/
|
||||||
|
private static final int SECTION_SIZE = 16;
|
||||||
|
/**
|
||||||
|
* Length of biome data.
|
||||||
|
*/
|
||||||
|
private static final int BIOME_DATA_LENGTH = 256;
|
||||||
|
|
||||||
|
private final ConnectionInfo info;
|
||||||
|
private final Set<Long> loadedChunks = Sets.newConcurrentHashSet();
|
||||||
|
private final Set<Long> bulkChunks = Sets.newConcurrentHashSet();
|
||||||
|
|
||||||
|
// Reflection
|
||||||
|
private static ClassReflection mapChunkBulkRef;
|
||||||
|
private static ClassReflection mapChunkRef;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
mapChunkBulkRef = new ClassReflection(ReflectionUtil.nms("PacketPlayOutMapChunkBulk"));
|
||||||
|
mapChunkRef = new ClassReflection(ReflectionUtil.nms("PacketPlayOutMapChunk"));
|
||||||
|
} catch(Exception e) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "Failed to initialise chunk reflection", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkManager(ConnectionInfo info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a map chunk bulk in to separate map chunk packets.
|
||||||
|
* These packets are registered so that they will never be seen as unload packets.
|
||||||
|
*
|
||||||
|
* @param packet to transform
|
||||||
|
* @return List of chunk data packets
|
||||||
|
*/
|
||||||
|
public List<Object> transformMapChunkBulk(Object packet) {
|
||||||
|
List<Object> list = Lists.newArrayList();
|
||||||
|
try {
|
||||||
|
int[] xcoords = mapChunkBulkRef.getFieldValue("a", packet, int[].class);
|
||||||
|
int[] zcoords = mapChunkBulkRef.getFieldValue("b", packet, int[].class);
|
||||||
|
Object[] chunkMaps = mapChunkBulkRef.getFieldValue("c", packet, Object[].class);
|
||||||
|
for(int i = 0; i < chunkMaps.length; i++) {
|
||||||
|
int x = xcoords[i];
|
||||||
|
int z = zcoords[i];
|
||||||
|
Object chunkMap = chunkMaps[i];
|
||||||
|
Object chunkPacket = mapChunkRef.newInstance();
|
||||||
|
mapChunkRef.setFieldValue("a", chunkPacket, x);
|
||||||
|
mapChunkRef.setFieldValue("b", chunkPacket, z);
|
||||||
|
mapChunkRef.setFieldValue("c", chunkPacket, chunkMap);
|
||||||
|
mapChunkRef.setFieldValue("d", chunkPacket, true); // Chunk bulk chunks are always ground-up
|
||||||
|
bulkChunks.add(toLong(x, z)); // Store for later
|
||||||
|
list.add(chunkPacket);
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "Failed to transform chunk bulk", e);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read chunk from 1.8 chunk data.
|
||||||
|
*
|
||||||
|
* @param input data
|
||||||
|
* @return Chunk
|
||||||
|
*/
|
||||||
|
public Chunk readChunk(ByteBuf input) {
|
||||||
|
// Primary data
|
||||||
|
int chunkX = input.readInt();
|
||||||
|
int chunkZ = input.readInt();
|
||||||
|
long chunkHash = toLong(chunkX, chunkZ);
|
||||||
|
boolean groundUp = input.readByte() != 0;
|
||||||
|
int bitmask = input.readUnsignedShort();
|
||||||
|
int dataLength = PacketUtil.readVarInt(input);
|
||||||
|
|
||||||
|
// Data to be read
|
||||||
|
BitSet usedSections = new BitSet(16);
|
||||||
|
ChunkSection[] sections = new ChunkSection[16];
|
||||||
|
byte[] biomeData = null;
|
||||||
|
|
||||||
|
// Calculate section count from bitmask
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if((bitmask & (1 << i)) != 0) {
|
||||||
|
usedSections.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int sectionCount = usedSections.cardinality(); // the amount of sections set
|
||||||
|
|
||||||
|
// If the chunk is from a chunk bulk, it is never an unload packet
|
||||||
|
// Other wise, if it has no data, it is :)
|
||||||
|
boolean isBulkPacket = bulkChunks.remove(chunkHash);
|
||||||
|
if(sectionCount == 0 && groundUp && !isBulkPacket && loadedChunks.contains(chunkHash)) {
|
||||||
|
// This is a chunk unload packet
|
||||||
|
loadedChunks.remove(chunkHash);
|
||||||
|
return new Chunk(chunkX, chunkZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = input.readerIndex();
|
||||||
|
loadedChunks.add(chunkHash); // mark chunk as loaded
|
||||||
|
|
||||||
|
// Read blocks
|
||||||
|
for(int i = 0; i < SECTION_COUNT; i++) {
|
||||||
|
if(!usedSections.get(i)) continue; // Section not set
|
||||||
|
ChunkSection section = new ChunkSection();
|
||||||
|
sections[i] = section;
|
||||||
|
|
||||||
|
// Read block data and convert to short buffer
|
||||||
|
byte[] blockData = new byte[ChunkSection.SIZE * 2];
|
||||||
|
input.readBytes(blockData);
|
||||||
|
ShortBuffer blockBuf = ByteBuffer.wrap(blockData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||||
|
|
||||||
|
for(int j = 0; j < ChunkSection.SIZE; j++) {
|
||||||
|
int mask = blockBuf.get();
|
||||||
|
int type = mask >> 4;
|
||||||
|
int data = mask & 0xF;
|
||||||
|
section.setBlock(j, type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read block light
|
||||||
|
for(int i = 0; i < SECTION_COUNT; i++) {
|
||||||
|
if(!usedSections.get(i)) continue; // Section not set, has no light
|
||||||
|
byte[] blockLightArray = new byte[ChunkSection.LIGHT_LENGTH];
|
||||||
|
input.readBytes(blockLightArray);
|
||||||
|
sections[i].setBlockLight(blockLightArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read sky light
|
||||||
|
int bytesLeft = dataLength - (input.readerIndex() - startIndex);
|
||||||
|
if(bytesLeft >= ChunkSection.LIGHT_LENGTH) {
|
||||||
|
for(int i = 0; i < SECTION_COUNT; i++) {
|
||||||
|
if(!usedSections.get(i)) continue; // Section not set, has no light
|
||||||
|
byte[] skyLightArray = new byte[ChunkSection.LIGHT_LENGTH];
|
||||||
|
input.readBytes(skyLightArray);
|
||||||
|
sections[i].setSkyLight(skyLightArray);
|
||||||
|
bytesLeft -= ChunkSection.LIGHT_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read biome data
|
||||||
|
if(bytesLeft >= BIOME_DATA_LENGTH) {
|
||||||
|
biomeData = new byte[BIOME_DATA_LENGTH];
|
||||||
|
input.readBytes(biomeData);
|
||||||
|
bytesLeft -= BIOME_DATA_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check remaining bytes
|
||||||
|
if(bytesLeft > 0) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, bytesLeft + " Bytes left after reading chunk! (" + groundUp + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return chunk
|
||||||
|
return new Chunk(chunkX, chunkZ, groundUp, bitmask, sections, biomeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write chunk over 1.9 protocol.
|
||||||
|
*
|
||||||
|
* @param chunk chunk
|
||||||
|
* @param output output
|
||||||
|
*/
|
||||||
|
public void writeChunk(Chunk chunk, ByteBuf output) {
|
||||||
|
if(chunk.isUnloadPacket()) {
|
||||||
|
output.clear();
|
||||||
|
PacketUtil.writeVarInt(0x1D, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write primary info
|
||||||
|
output.writeInt(chunk.getX());
|
||||||
|
output.writeInt(chunk.getZ());
|
||||||
|
if(chunk.isUnloadPacket()) return;
|
||||||
|
output.writeByte(chunk.isGroundUp() ? 0x01 : 0x00);
|
||||||
|
PacketUtil.writeVarInt(chunk.getPrimaryBitmask(), output);
|
||||||
|
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
for(int i = 0; i < SECTION_COUNT; i++) {
|
||||||
|
ChunkSection section = chunk.getSections()[i];
|
||||||
|
if(section == null) continue; // Section not set
|
||||||
|
section.writeBlocks(buf);
|
||||||
|
section.writeBlockLight(buf);
|
||||||
|
if(!section.hasSkyLight()) continue; // No sky light, we're done here.
|
||||||
|
section.writeSkyLight(buf);
|
||||||
|
}
|
||||||
|
buf.readerIndex(0);
|
||||||
|
PacketUtil.writeVarInt(buf.readableBytes() + (chunk.hasBiomeData() ? 256 : 0), output);
|
||||||
|
output.writeBytes(buf);
|
||||||
|
buf.release(); // release buffer
|
||||||
|
|
||||||
|
// Write biome data
|
||||||
|
if(chunk.hasBiomeData()) {
|
||||||
|
output.writeBytes(chunk.getBiomeData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long toLong(int msw, int lsw) {
|
||||||
|
return ((long) msw << 32) + lsw - -2147483648L;
|
||||||
|
}
|
||||||
|
}
|
143
src/main/java/us/myles/ViaVersion/chunks/ChunkSection.java
Normal file
143
src/main/java/us/myles/ViaVersion/chunks/ChunkSection.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package us.myles.ViaVersion.chunks;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import us.myles.ViaVersion.util.PacketUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChunkSection {
|
||||||
|
/**
|
||||||
|
* Size (dimensions) of blocks in a chunk section.
|
||||||
|
*/
|
||||||
|
public static final int SIZE = 16 * 16 * 16; // width * depth * height
|
||||||
|
/**
|
||||||
|
* Length of the sky and block light nibble arrays.
|
||||||
|
*/
|
||||||
|
public static final int LIGHT_LENGTH = 16 * 16 * 16 / 2; // size * size * size / 2 (nibble bit count)
|
||||||
|
/**
|
||||||
|
* Length of the block data array.
|
||||||
|
*/
|
||||||
|
// public static final int BLOCK_LENGTH = 16 * 16 * 16 * 2; // size * size * size * 2 (char bit count)
|
||||||
|
|
||||||
|
private final List<Integer> palette = Lists.newArrayList();
|
||||||
|
private final int[] blocks;
|
||||||
|
private final NibbleArray blockLight;
|
||||||
|
private NibbleArray skyLight;
|
||||||
|
|
||||||
|
public ChunkSection() {
|
||||||
|
this.blocks = new int[SIZE];
|
||||||
|
this.blockLight = new NibbleArray(SIZE);
|
||||||
|
palette.add(0); // AIR
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlock(int x, int y, int z, int type, int data) {
|
||||||
|
setBlock(index(x, y, z), type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlock(int idx, int type, int data) {
|
||||||
|
int hash = type << 4 | (data & 0xF);
|
||||||
|
int index = palette.indexOf(hash);
|
||||||
|
if(index == -1) {
|
||||||
|
index = palette.size();
|
||||||
|
palette.add(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[idx] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockLight(byte[] data) {
|
||||||
|
blockLight.setHandle(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSkyLight(byte[] data) {
|
||||||
|
if(data.length != LIGHT_LENGTH) throw new IllegalArgumentException("Data length != " + LIGHT_LENGTH);
|
||||||
|
this.skyLight = new NibbleArray(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int index(int x, int y, int z) {
|
||||||
|
return z << 8 | y << 4 | x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeBlocks(ByteBuf output) {
|
||||||
|
// Write bits per block
|
||||||
|
int bitsPerBlock = 4;
|
||||||
|
while(palette.size() > 1 << bitsPerBlock) {
|
||||||
|
bitsPerBlock += 1;
|
||||||
|
}
|
||||||
|
long maxEntryValue = (1L << bitsPerBlock) - 1;
|
||||||
|
output.writeByte(bitsPerBlock);
|
||||||
|
|
||||||
|
// Write pallet (or not)
|
||||||
|
PacketUtil.writeVarInt(palette.size(), output);
|
||||||
|
for(int mappedId : palette) {
|
||||||
|
PacketUtil.writeVarInt(mappedId, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = (int) Math.ceil(SIZE * bitsPerBlock / 64.0);
|
||||||
|
PacketUtil.writeVarInt(length, output);
|
||||||
|
long[] data = new long[length];
|
||||||
|
for(int index = 0; index < blocks.length; index++) {
|
||||||
|
int value = blocks[index];
|
||||||
|
int bitIndex = index * bitsPerBlock;
|
||||||
|
int startIndex = bitIndex / 64;
|
||||||
|
int endIndex = ((index + 1) * bitsPerBlock - 1) / 64;
|
||||||
|
int startBitSubIndex = bitIndex % 64;
|
||||||
|
data[startIndex] = data[startIndex] & ~(maxEntryValue << startBitSubIndex) | ((long) value & maxEntryValue) << startBitSubIndex;
|
||||||
|
if(startIndex != endIndex) {
|
||||||
|
int endBitSubIndex = 64 - startBitSubIndex;
|
||||||
|
data[endIndex] = data[endIndex] >>> endBitSubIndex << endBitSubIndex | ((long) value & maxEntryValue) >> endBitSubIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PacketUtil.writeLongs(data, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeBlockLight(ByteBuf output) {
|
||||||
|
output.writeBytes(blockLight.getHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeSkyLight(ByteBuf output) {
|
||||||
|
output.writeBytes(skyLight.getHandle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSkyLight() {
|
||||||
|
return skyLight != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get expected size of this chunk section.
|
||||||
|
*
|
||||||
|
* @return Amount of bytes sent by this section
|
||||||
|
*/
|
||||||
|
public int getExpectedSize() {
|
||||||
|
int bitsPerBlock = palette.size() > 255 ? 16 : 8;
|
||||||
|
int bytes = 1; // bits per block
|
||||||
|
bytes += paletteBytes(); // palette
|
||||||
|
bytes += countBytes(bitsPerBlock == 16 ? SIZE * 2 : SIZE); // block data length
|
||||||
|
bytes += (palette.size() > 255 ? 2 : 1) * SIZE; // block data
|
||||||
|
bytes += LIGHT_LENGTH; // block light
|
||||||
|
bytes += hasSkyLight() ? LIGHT_LENGTH : 0; // sky light
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int paletteBytes() {
|
||||||
|
// Count bytes used by pallet
|
||||||
|
int bytes = countBytes(palette.size());
|
||||||
|
for(int mappedId : palette) {
|
||||||
|
bytes += countBytes(mappedId);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countBytes(int value) {
|
||||||
|
// Count amount of bytes that would be sent if the value were sent as a VarInt
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
PacketUtil.writeVarInt(value, buf);
|
||||||
|
buf.readerIndex(0);
|
||||||
|
int bitCount = buf.readableBytes();
|
||||||
|
buf.release();
|
||||||
|
return bitCount;
|
||||||
|
}
|
||||||
|
}
|
74
src/main/java/us/myles/ViaVersion/chunks/NibbleArray.java
Normal file
74
src/main/java/us/myles/ViaVersion/chunks/NibbleArray.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package us.myles.ViaVersion.chunks;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class NibbleArray {
|
||||||
|
private final byte[] handle;
|
||||||
|
|
||||||
|
public NibbleArray(int length) {
|
||||||
|
if(length == 0 || length % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("Length of nibble array must be a positive number dividable by 2!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handle = new byte[length / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray(byte[] handle) {
|
||||||
|
if(handle.length == 0 || handle.length % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("Length of nibble array must be a positive number dividable by 2!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte get(int x, int y, int z) {
|
||||||
|
return get(y << 8 | z << 4 | x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte get(int index) {
|
||||||
|
byte value = handle[index / 2];
|
||||||
|
if(index % 2 == 0) {
|
||||||
|
return (byte) (value & 0xF);
|
||||||
|
} else {
|
||||||
|
return (byte) ((value >> 4) & 0xF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int x, int y, int z, int value) {
|
||||||
|
set(y << 8 | z << 4 | x, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, int value) {
|
||||||
|
index /= 2;
|
||||||
|
if(index % 2 == 0) {
|
||||||
|
handle[index] = (byte) (handle[index] & 0xF0 | value & 0xF);
|
||||||
|
} else {
|
||||||
|
handle[index] = (byte) (handle[index] & 0xF | (value & 0xF) << 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return handle.length * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int actualSize() {
|
||||||
|
return handle.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(byte value) {
|
||||||
|
value &= 0xF; // Max nibble size (= 16)
|
||||||
|
Arrays.fill(handle, (byte) ((value << 4) | value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandle(byte[] handle) {
|
||||||
|
if(handle.length != this.handle.length) {
|
||||||
|
throw new IllegalArgumentException("Length of handle must equal to size of nibble array!");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(handle, 0, this.handle, 0, handle.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getHandle() {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
@ -26,24 +26,7 @@ public class ViaChunkHandler extends MessageToMessageEncoder {
|
|||||||
info.setLastPacket(o);
|
info.setLastPacket(o);
|
||||||
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
||||||
if(o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
if(o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
||||||
final int[] locX = ReflectionUtil.get(o, "a", int[].class);
|
list.addAll(info.getChunkManager().transformMapChunkBulk(o));
|
||||||
final int[] locZ = ReflectionUtil.get(o, "b", int[].class);
|
|
||||||
final Object world = ReflectionUtil.get(o, "world", ReflectionUtil.nms("World"));
|
|
||||||
Class<?> mapChunk = ReflectionUtil.nms("PacketPlayOutMapChunk");
|
|
||||||
final Constructor constructor = mapChunk.getDeclaredConstructor(ReflectionUtil.nms("Chunk"), boolean.class, int.class);
|
|
||||||
for(int i = 0; i < locX.length; i++) {
|
|
||||||
int x = locX[i];
|
|
||||||
int z = locZ[i];
|
|
||||||
// world invoke function
|
|
||||||
try {
|
|
||||||
Object chunk = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class).invoke(world, x, z);
|
|
||||||
Object packet = constructor.newInstance(chunk, true, 65535);
|
|
||||||
list.add(packet);
|
|
||||||
} catch(InstantiationException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ import us.myles.ViaVersion.api.ViaVersion;
|
|||||||
import us.myles.ViaVersion.api.boss.BossBar;
|
import us.myles.ViaVersion.api.boss.BossBar;
|
||||||
import us.myles.ViaVersion.api.boss.BossColor;
|
import us.myles.ViaVersion.api.boss.BossColor;
|
||||||
import us.myles.ViaVersion.api.boss.BossStyle;
|
import us.myles.ViaVersion.api.boss.BossStyle;
|
||||||
|
import us.myles.ViaVersion.chunks.Chunk;
|
||||||
|
import us.myles.ViaVersion.chunks.ChunkManager;
|
||||||
import us.myles.ViaVersion.metadata.MetaIndex;
|
import us.myles.ViaVersion.metadata.MetaIndex;
|
||||||
import us.myles.ViaVersion.metadata.MetadataRewriter;
|
import us.myles.ViaVersion.metadata.MetadataRewriter;
|
||||||
import us.myles.ViaVersion.metadata.MetadataRewriter.Entry;
|
import us.myles.ViaVersion.metadata.MetadataRewriter.Entry;
|
||||||
@ -776,54 +778,15 @@ public class OutgoingTransformer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (packet == PacketType.PLAY_CHUNK_DATA) {
|
if (packet == PacketType.PLAY_CHUNK_DATA) {
|
||||||
// We need to catch unloading chunk packets as defined by wiki.vg
|
// Read chunk
|
||||||
// To unload chunks, send this packet with Ground-Up Continuous=true and no 16^3 chunks (eg. Primary Bit Mask=0)
|
ChunkManager chunkManager = info.getChunkManager();
|
||||||
int chunkX = input.readInt();
|
Chunk chunk = chunkManager.readChunk(input);
|
||||||
int chunkZ = input.readInt();
|
if(chunk == null) {
|
||||||
output.writeInt(chunkX);
|
|
||||||
output.writeInt(chunkZ);
|
|
||||||
|
|
||||||
|
|
||||||
boolean groundUp = input.readBoolean();
|
|
||||||
output.writeBoolean(groundUp);
|
|
||||||
|
|
||||||
int bitMask = input.readUnsignedShort();
|
|
||||||
int size = PacketUtil.readVarInt(input);
|
|
||||||
byte[] data = new byte[size];
|
|
||||||
input.readBytes(data);
|
|
||||||
// if (bitMask == 0 && groundUp) {
|
|
||||||
// // if 256
|
|
||||||
// output.clear();
|
|
||||||
// PacketUtil.writeVarInt(PacketType.PLAY_UNLOAD_CHUNK.getNewPacketID(), output);
|
|
||||||
// output.writeInt(chunkX);
|
|
||||||
// output.writeInt(chunkZ);
|
|
||||||
// System.out.println("Sending unload chunk " + chunkX + " " + chunkZ + " - " + size + " bulk: " + bulk);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
boolean sk = false;
|
|
||||||
if (info.getLastPacket().getClass().getName().endsWith("PacketPlayOutMapChunkBulk")) {
|
|
||||||
try {
|
|
||||||
sk = ReflectionUtil.get(info.getLastPacket(), "d", boolean.class);
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column read = NetUtil.readOldChunkData(chunkX, chunkZ, groundUp, bitMask, data, true, sk);
|
|
||||||
if (read == null) {
|
|
||||||
throw new CancelException();
|
throw new CancelException();
|
||||||
}
|
}
|
||||||
// Write chunk section array :((
|
|
||||||
ByteBuf temp = output.alloc().buffer();
|
// Write chunk
|
||||||
try {
|
chunkManager.writeChunk(chunk, output);
|
||||||
int bitmask = NetUtil.writeNewColumn(temp, read, groundUp, sk);
|
|
||||||
PacketUtil.writeVarInt(bitmask, output);
|
|
||||||
PacketUtil.writeVarInt(temp.readableBytes(), output);
|
|
||||||
output.writeBytes(temp);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
output.writeBytes(input);
|
output.writeBytes(input);
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package us.myles.ViaVersion.util;
|
package us.myles.ViaVersion.util;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ReflectionUtil {
|
public class ReflectionUtil {
|
||||||
private static String BASE = Bukkit.getServer().getClass().getPackage().getName();
|
private static String BASE = Bukkit.getServer().getClass().getPackage().getName();
|
||||||
@ -50,4 +54,74 @@ public class ReflectionUtil {
|
|||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
field.set(o, value);
|
field.set(o, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class ClassReflection {
|
||||||
|
private final Class<?> handle;
|
||||||
|
private final Map<String, Field> fields = Maps.newConcurrentMap();
|
||||||
|
private final Map<String, Method> methods = Maps.newConcurrentMap();
|
||||||
|
|
||||||
|
public ClassReflection(Class<?> handle) {
|
||||||
|
this(handle, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClassReflection(Class<?> handle, boolean recursive) {
|
||||||
|
this.handle = handle;
|
||||||
|
scanFields(handle, recursive);
|
||||||
|
scanMethods(handle, recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanFields(Class<?> host, boolean recursive) {
|
||||||
|
if(host.getSuperclass() != null && recursive) {
|
||||||
|
scanFields(host.getSuperclass(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Field field : host.getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
fields.put(field.getName(), field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanMethods(Class<?> host, boolean recursive) {
|
||||||
|
if(host.getSuperclass() != null && recursive) {
|
||||||
|
scanMethods(host.getSuperclass(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Method method : host.getDeclaredMethods()) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
methods.put(method.getName(), method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object newInstance() throws IllegalAccessException, InstantiationException {
|
||||||
|
return handle.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Field getField(String name) {
|
||||||
|
return fields.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFieldValue(String fieldName, Object instance, Object value) throws IllegalAccessException {
|
||||||
|
getField(fieldName).set(instance, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getFieldValue(String fieldName, Object instance, Class<T> type) throws IllegalAccessException {
|
||||||
|
return type.cast(getField(fieldName).get(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T invokeMethod(Class<T> type, String methodName, Object instance, Object... args) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
return type.cast(getMethod(methodName).invoke(instance, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Method getMethod(String name) {
|
||||||
|
return methods.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Field> getFields() {
|
||||||
|
return Collections.unmodifiableCollection(fields.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Method> getMethods() {
|
||||||
|
return Collections.unmodifiableCollection(methods.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user