mirror of
https://github.com/ViaVersion/ViaVersion.git
synced 2024-11-23 02:25:19 +01:00
commit
4e1625b017
@ -1,4 +1,4 @@
|
||||
# ViaVersion 0.6.3
|
||||
# ViaVersion 0.6.7
|
||||
[![Build Status](https://travis-ci.org/MylesIsCool/ViaVersion.svg?branch=master)](https://travis-ci.org/MylesIsCool/ViaVersion)
|
||||
**Allows the connection of 1.9 clients to 1.8**
|
||||
|
||||
|
16
pom.xml
16
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>us.myles</groupId>
|
||||
<artifactId>viaversion</artifactId>
|
||||
<version>0.6.4-SNAPSHOT</version>
|
||||
<version>0.6.8-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ViaVersion</name>
|
||||
@ -101,6 +101,10 @@
|
||||
<pattern>org.spacehq.opennbt</pattern>
|
||||
<shadedPattern>us.myles.viaversion.libs.opennbt</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.google.gson</pattern>
|
||||
<shadedPattern>us.myles.viaversion.libs.gson</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
@ -162,6 +166,15 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- GSON (JSON Library) -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.6.2</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Netty (Network Library) -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
@ -170,6 +183,7 @@
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
@ -1,126 +0,0 @@
|
||||
package org.spacehq.mc.protocol.data.game.chunk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import us.myles.ViaVersion.util.PacketUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BlockStorage {
|
||||
private int bitsPerEntry;
|
||||
|
||||
private List<Integer> states;
|
||||
private FlexibleStorage storage;
|
||||
|
||||
public BlockStorage() {
|
||||
this.bitsPerEntry = 4;
|
||||
|
||||
this.states = new ArrayList<>();
|
||||
this.states.add(0);
|
||||
|
||||
this.storage = new FlexibleStorage(this.bitsPerEntry, 4096);
|
||||
}
|
||||
|
||||
public BlockStorage(ByteBuf in) throws IOException {
|
||||
this.bitsPerEntry = in.readUnsignedByte();
|
||||
|
||||
this.states = new ArrayList<>();
|
||||
int stateCount = PacketUtil.readVarInt(in);
|
||||
for (int i = 0; i < stateCount; i++) {
|
||||
this.states.add(PacketUtil.readVarInt(in));
|
||||
}
|
||||
|
||||
this.storage = new FlexibleStorage(this.bitsPerEntry, PacketUtil.readLongs(PacketUtil.readVarInt(in), in));
|
||||
}
|
||||
|
||||
private static int index(int x, int y, int z) {
|
||||
return y << 8 | z << 4 | x;
|
||||
}
|
||||
|
||||
public void write(ByteBuf out) throws IOException {
|
||||
out.writeByte(this.bitsPerEntry);
|
||||
|
||||
PacketUtil.writeVarInt(this.states.size(), out);
|
||||
for (int state : this.states) {
|
||||
PacketUtil.writeVarInt(state, out);
|
||||
}
|
||||
|
||||
long[] data = this.storage.getData();
|
||||
PacketUtil.writeVarInt(data.length, out);
|
||||
PacketUtil.writeLongs(data, out);
|
||||
}
|
||||
|
||||
public int getBitsPerEntry() {
|
||||
return this.bitsPerEntry;
|
||||
}
|
||||
|
||||
public List<Integer> getStates() {
|
||||
return Collections.unmodifiableList(this.states);
|
||||
}
|
||||
|
||||
public FlexibleStorage getStorage() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
public int get(int x, int y, int z) {
|
||||
int id = this.storage.get(index(x, y, z));
|
||||
return this.bitsPerEntry <= 8 ? (id >= 0 && id < this.states.size() ? this.states.get(id) : 0) : id;
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, int state) {
|
||||
set(index(x, y, z), state);
|
||||
}
|
||||
|
||||
public void set(int ind, int state) {
|
||||
int id = this.bitsPerEntry <= 8 ? this.states.indexOf(state) : state;
|
||||
if (id == -1) {
|
||||
this.states.add(state);
|
||||
if (this.states.size() > 1 << this.bitsPerEntry) {
|
||||
this.bitsPerEntry++;
|
||||
|
||||
List<Integer> oldStates = this.states;
|
||||
if (this.bitsPerEntry > 8) {
|
||||
oldStates = new ArrayList<>(this.states);
|
||||
this.states.clear();
|
||||
this.bitsPerEntry = 13;
|
||||
}
|
||||
|
||||
FlexibleStorage oldStorage = this.storage;
|
||||
this.storage = new FlexibleStorage(this.bitsPerEntry, this.storage.getSize());
|
||||
for (int index = 0; index < this.storage.getSize(); index++) {
|
||||
int value = oldStorage.get(index);
|
||||
this.storage.set(index, this.bitsPerEntry <= 8 ? value : oldStates.get(value));
|
||||
}
|
||||
}
|
||||
|
||||
id = this.bitsPerEntry <= 8 ? this.states.indexOf(state) : state;
|
||||
}
|
||||
|
||||
this.storage.set(ind, id);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
for (int index = 0; index < this.storage.getSize(); index++) {
|
||||
if (this.storage.get(index) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || (o instanceof BlockStorage && this.bitsPerEntry == ((BlockStorage) o).bitsPerEntry && this.states.equals(((BlockStorage) o).states) && this.storage.equals(((BlockStorage) o).storage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.bitsPerEntry;
|
||||
result = 31 * result + this.states.hashCode();
|
||||
result = 31 * result + this.storage.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package org.spacehq.mc.protocol.data.game.chunk;
|
||||
|
||||
public class Chunk {
|
||||
private BlockStorage blocks;
|
||||
private NibbleArray3d blocklight;
|
||||
private NibbleArray3d skylight;
|
||||
|
||||
public Chunk(boolean skylight) {
|
||||
this(new BlockStorage(), new NibbleArray3d(2048), skylight ? new NibbleArray3d(2048) : null);
|
||||
}
|
||||
|
||||
public Chunk(BlockStorage blocks, NibbleArray3d blocklight, NibbleArray3d skylight) {
|
||||
this.blocks = blocks;
|
||||
this.blocklight = blocklight;
|
||||
this.skylight = skylight;
|
||||
}
|
||||
|
||||
public BlockStorage getBlocks() {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
public NibbleArray3d getBlockLight() {
|
||||
return this.blocklight;
|
||||
}
|
||||
|
||||
public NibbleArray3d getSkyLight() {
|
||||
return this.skylight;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.blocks.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || (o instanceof Chunk && this.blocks.equals(((Chunk) o).blocks) && this.blocklight.equals(((Chunk) o).blocklight) && ((this.skylight == null && (((Chunk) o).skylight == null)) || (this.skylight != null && this.skylight.equals(((Chunk) o).skylight))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.blocks.hashCode();
|
||||
result = 31 * result + this.blocklight.hashCode();
|
||||
result = 31 * result + (this.skylight != null ? this.skylight.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package org.spacehq.mc.protocol.data.game.chunk;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Column {
|
||||
private int x;
|
||||
private int z;
|
||||
private Chunk chunks[];
|
||||
private byte biomeData[];
|
||||
|
||||
private boolean skylight;
|
||||
|
||||
public Column(int x, int z, Chunk chunks[]) {
|
||||
this(x, z, chunks, null);
|
||||
}
|
||||
|
||||
public Column(int x, int z, Chunk chunks[], byte biomeData[]) {
|
||||
if(chunks.length != 16) {
|
||||
throw new IllegalArgumentException("Chunk array length must be 16.");
|
||||
}
|
||||
|
||||
if(biomeData != null && biomeData.length != 256) {
|
||||
throw new IllegalArgumentException("Biome data array length must be 256.");
|
||||
}
|
||||
|
||||
this.skylight = false;
|
||||
boolean noSkylight = false;
|
||||
for (Chunk chunk : chunks) {
|
||||
if (chunk != null) {
|
||||
if (chunk.getSkyLight() == null) {
|
||||
noSkylight = true;
|
||||
} else {
|
||||
this.skylight = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(noSkylight && this.skylight) {
|
||||
throw new IllegalArgumentException("Either all chunks must have skylight values or none must have them.");
|
||||
}
|
||||
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
this.chunks = chunks;
|
||||
this.biomeData = biomeData;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasBiomeData() {
|
||||
return this.biomeData != null;
|
||||
}
|
||||
|
||||
public boolean hasSkylight() {
|
||||
return this.skylight;
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
package org.spacehq.mc.protocol.data.game.chunk;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class FlexibleStorage {
|
||||
private final long[] data;
|
||||
private final int bitsPerEntry;
|
||||
private final int size;
|
||||
private final long maxEntryValue;
|
||||
|
||||
public FlexibleStorage(int bitsPerEntry, int size) {
|
||||
this(bitsPerEntry, new long[roundToNearest(size * bitsPerEntry, 64) / 64]);
|
||||
}
|
||||
|
||||
public FlexibleStorage(int bitsPerEntry, long[] data) {
|
||||
if(bitsPerEntry < 1 || bitsPerEntry > 32) {
|
||||
throw new IllegalArgumentException("BitsPerEntry cannot be outside of accepted range.");
|
||||
}
|
||||
|
||||
this.bitsPerEntry = bitsPerEntry;
|
||||
this.data = data;
|
||||
|
||||
this.size = this.data.length * 64 / this.bitsPerEntry;
|
||||
this.maxEntryValue = (1L << this.bitsPerEntry) - 1;
|
||||
}
|
||||
|
||||
public long[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public int getBitsPerEntry() {
|
||||
return this.bitsPerEntry;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public int get(int index) {
|
||||
if(index < 0 || index > this.size - 1) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int bitIndex = index * this.bitsPerEntry;
|
||||
int startIndex = bitIndex / 64;
|
||||
int endIndex = ((index + 1) * this.bitsPerEntry - 1) / 64;
|
||||
int startBitSubIndex = bitIndex % 64;
|
||||
if(startIndex == endIndex) {
|
||||
return (int) (this.data[startIndex] >>> startBitSubIndex & this.maxEntryValue);
|
||||
} else {
|
||||
int endBitSubIndex = 64 - startBitSubIndex;
|
||||
return (int) ((this.data[startIndex] >>> startBitSubIndex | this.data[endIndex] << endBitSubIndex) & this.maxEntryValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int index, int value) {
|
||||
if(index < 0 || index > this.size - 1) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
if(value < 0 || value > this.maxEntryValue) {
|
||||
throw new IllegalArgumentException("Value cannot be outside of accepted range.");
|
||||
}
|
||||
|
||||
int bitIndex = index * this.bitsPerEntry;
|
||||
int startIndex = bitIndex / 64;
|
||||
int endIndex = ((index + 1) * this.bitsPerEntry - 1) / 64;
|
||||
int startBitSubIndex = bitIndex % 64;
|
||||
this.data[startIndex] = this.data[startIndex] & ~(this.maxEntryValue << startBitSubIndex) | ((long) value & this.maxEntryValue) << startBitSubIndex;
|
||||
if(startIndex != endIndex) {
|
||||
int endBitSubIndex = 64 - startBitSubIndex;
|
||||
this.data[endIndex] = this.data[endIndex] >>> endBitSubIndex << endBitSubIndex | ((long) value & this.maxEntryValue) >> endBitSubIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private static int roundToNearest(int value, int roundTo) {
|
||||
if(roundTo == 0) {
|
||||
return 0;
|
||||
} else if(value == 0) {
|
||||
return roundTo;
|
||||
} else {
|
||||
if(value < 0) {
|
||||
roundTo *= -1;
|
||||
}
|
||||
|
||||
int remainder = value % roundTo;
|
||||
return remainder != 0 ? value + roundTo - remainder : value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || (o instanceof FlexibleStorage && Arrays.equals(this.data, ((FlexibleStorage) o).data) && this.bitsPerEntry == ((FlexibleStorage) o).bitsPerEntry && this.size == ((FlexibleStorage) o).size && this.maxEntryValue == ((FlexibleStorage) o).maxEntryValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(this.data);
|
||||
result = 31 * result + this.bitsPerEntry;
|
||||
result = 31 * result + this.size;
|
||||
result = 31 * result + (int) this.maxEntryValue;
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package org.spacehq.mc.protocol.data.game.chunk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class NibbleArray3d {
|
||||
private byte[] data;
|
||||
|
||||
public NibbleArray3d(int size) {
|
||||
this.data = new byte[size];
|
||||
}
|
||||
|
||||
public NibbleArray3d(byte[] array) {
|
||||
this.data = array;
|
||||
}
|
||||
|
||||
public NibbleArray3d(ByteBuf in, int size) throws IOException {
|
||||
this.data = new byte[size];
|
||||
in.readBytes(this.data);
|
||||
}
|
||||
|
||||
public void write(ByteBuf out) throws IOException {
|
||||
out.writeBytes(this.data);
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public int get(int x, int y, int z) {
|
||||
int key = y << 8 | z << 4 | x;
|
||||
int index = key >> 1;
|
||||
int part = key & 1;
|
||||
return part == 0 ? this.data[index] & 15 : this.data[index] >> 4 & 15;
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, int val) {
|
||||
int key = y << 8 | z << 4 | x;
|
||||
int index = key >> 1;
|
||||
int part = key & 1;
|
||||
if(part == 0) {
|
||||
this.data[index] = (byte) (this.data[index] & 240 | val & 15);
|
||||
} else {
|
||||
this.data[index] = (byte) (this.data[index] & 15 | (val & 15) << 4);
|
||||
}
|
||||
}
|
||||
|
||||
public void fill(int val) {
|
||||
for(int index = 0; index < this.data.length << 1; index++) {
|
||||
int ind = index >> 1;
|
||||
int part = index & 1;
|
||||
if(part == 0) {
|
||||
this.data[ind] = (byte) (this.data[ind] & 240 | val & 15);
|
||||
} else {
|
||||
this.data[ind] = (byte) (this.data[ind] & 15 | (val & 15) << 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || (o instanceof NibbleArray3d && Arrays.equals(this.data, ((NibbleArray3d) o).data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this.data);
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package org.spacehq.mc.protocol.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.spacehq.mc.protocol.data.game.chunk.Chunk;
|
||||
import org.spacehq.mc.protocol.data.game.chunk.Column;
|
||||
import org.spacehq.mc.protocol.data.game.chunk.NibbleArray3d;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/* From https://github.com/Steveice10/MCProtocolLib/ */
|
||||
/* No credit taken for writing this code, and used accordance to it's license
|
||||
Original by Steveice10, modified to suit this plugin.
|
||||
*/
|
||||
public class NetUtil {
|
||||
public static int writeNewColumn(ByteBuf out, Column column, boolean fullChunk, boolean hasSkylight) throws IOException {
|
||||
int mask = 0;
|
||||
Chunk chunks[] = column.getChunks();
|
||||
for (int index = 0; index < chunks.length; index++) {
|
||||
Chunk chunk = chunks[index];
|
||||
if (chunk != null && (!fullChunk || !chunk.isEmpty())) {
|
||||
mask |= 1 << index;
|
||||
chunk.getBlocks().write(out);
|
||||
chunk.getBlockLight().write(out);
|
||||
if (hasSkylight || column.hasSkylight()) {
|
||||
chunk.getSkyLight().write(out); // TODO: Make a PR to original lib to correct this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fullChunk && column.getBiomeData() != null) {
|
||||
out.writeBytes(column.getBiomeData());
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
public static Column readOldChunkData(int x, int z, boolean isFullChunk, int bitmask, byte[] input, boolean checkForSky, boolean hasSkyLight) {
|
||||
int pos = 0;
|
||||
int expected = isFullChunk ? 256 : 0;
|
||||
boolean sky = false;
|
||||
ShortBuffer buf = ByteBuffer.wrap(input).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.
|
||||
Chunk[] chunks = new Chunk[16];
|
||||
int chunkCount = 0;
|
||||
for (int pass = 0; pass < 4; pass++) {
|
||||
if(pass == 1){
|
||||
if(chunkCount == 0) return null;
|
||||
}
|
||||
for (int ind = 0; ind < 16; ind++) {
|
||||
if ((bitmask & 1 << ind) != 0) {
|
||||
if (pass == 0) {
|
||||
chunkCount++;
|
||||
// Block length + Blocklight length
|
||||
expected += (4096 * 2) + 2048;
|
||||
}
|
||||
|
||||
if (pass == 1) {
|
||||
chunks[ind] = new Chunk(sky || hasSkyLight);
|
||||
buf.position(pos / 2);
|
||||
int buffPos = buf.position();
|
||||
// convert short array to new one
|
||||
|
||||
for (int index = 0; index < 4096; index++) {
|
||||
short ss = buf.get(buffPos + index);
|
||||
// s is 16 bits, 12 bits id and 4 bits data
|
||||
int data = ss & 0xF;
|
||||
int id = (ss >> 4) << 4 | data;
|
||||
|
||||
int newCombined = id; // test
|
||||
|
||||
chunks[ind].getBlocks().set(index, newCombined);
|
||||
}
|
||||
pos += 4096 * 2;
|
||||
|
||||
}
|
||||
|
||||
if (pass == 2) {
|
||||
NibbleArray3d blocklight = chunks[ind].getBlockLight();
|
||||
System.arraycopy(input, pos, blocklight.getData(), 0, blocklight.getData().length);
|
||||
pos += blocklight.getData().length;
|
||||
}
|
||||
|
||||
if (pass == 3) {
|
||||
if (sky) {
|
||||
NibbleArray3d skylight = chunks[ind].getSkyLight();
|
||||
System.arraycopy(input, pos, skylight.getData(), 0, skylight.getData().length);
|
||||
pos += skylight.getData().length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 0) {
|
||||
// If we have more data than blocks and blocklight combined, there must be skylight data as well.
|
||||
if (input.length > expected) {
|
||||
sky = checkForSky;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte biomeData[] = null;
|
||||
if (isFullChunk && (pos + 256 <= input.length)) {
|
||||
|
||||
biomeData = new byte[256];
|
||||
System.arraycopy(input, pos, biomeData, 0, biomeData.length);
|
||||
}
|
||||
|
||||
Column column = new Column(x, z, chunks, biomeData);
|
||||
return column;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import us.myles.ViaVersion.chunks.ChunkManager;
|
||||
import us.myles.ViaVersion.packets.State;
|
||||
|
||||
@Getter
|
||||
@ -16,6 +17,7 @@ public class ConnectionInfo {
|
||||
private static final long IDLE_PACKET_LIMIT = 20; // Max 20 ticks behind
|
||||
|
||||
private final SocketChannel channel;
|
||||
private final ChunkManager chunkManager;
|
||||
private Object lastPacket;
|
||||
private java.util.UUID UUID;
|
||||
private State state = State.HANDSHAKE;
|
||||
@ -29,6 +31,7 @@ public class ConnectionInfo {
|
||||
|
||||
public ConnectionInfo(SocketChannel socketChannel) {
|
||||
this.channel = socketChannel;
|
||||
this.chunkManager = new ChunkManager(this);
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
|
@ -5,6 +5,7 @@ import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import lombok.NonNull;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@ -29,7 +30,6 @@ import us.myles.ViaVersion.util.ListWrapper;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
@ -39,7 +39,6 @@ import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
|
||||
@ -99,7 +98,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
|
||||
public void generateConfig() {
|
||||
File file = new File(getDataFolder(), "config.yml");
|
||||
if(file.exists()) {
|
||||
if (file.exists()) {
|
||||
// Update config options
|
||||
Configuration oldConfig = new Configuration(file);
|
||||
oldConfig.reload(false); // Load current options from config
|
||||
@ -107,9 +106,9 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
saveDefaultConfig(); // Generate new config
|
||||
Configuration newConfig = new Configuration(file);
|
||||
newConfig.reload(true); // Load default options
|
||||
for(String key : oldConfig.getKeys(false)) {
|
||||
for (String key : oldConfig.getKeys(false)) {
|
||||
// Set option in new config if exists
|
||||
if(newConfig.contains(key)) {
|
||||
if (newConfig.contains(key)) {
|
||||
newConfig.set(key, oldConfig.get(key));
|
||||
}
|
||||
}
|
||||
@ -123,51 +122,45 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
try {
|
||||
Class<?> serverClazz = ReflectionUtil.nms("MinecraftServer");
|
||||
Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer");
|
||||
Object connection = serverClazz.getDeclaredMethod("getServerConnection").invoke(server);
|
||||
if (connection == null) {
|
||||
System.out.println("connection is null!!");
|
||||
//try others
|
||||
for (Method m : serverClazz.getDeclaredMethods()) {
|
||||
if (m.getReturnType() != null && !m.getName().equals("getServerConnection")) {
|
||||
if (m.getReturnType().getSimpleName().equals("ServerConnection")) {
|
||||
if (m.getParameterTypes().length == 0) {
|
||||
connection = m.invoke(server);
|
||||
}
|
||||
Object connection = null;
|
||||
for (Method m : serverClazz.getDeclaredMethods()) {
|
||||
if (m.getReturnType() != null) {
|
||||
if (m.getReturnType().getSimpleName().equals("ServerConnection")) {
|
||||
if (m.getParameterTypes().length == 0) {
|
||||
connection = m.invoke(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (connection == null) {
|
||||
getLogger().warning("We failed to find the ServerConnection? :(");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (connection != null) {
|
||||
for (Field field : connection.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
final Object value = field.get(connection);
|
||||
if (value instanceof List) {
|
||||
// Inject the list
|
||||
List wrapper = new ListWrapper((List) value) {
|
||||
@Override
|
||||
public synchronized void handleAdd(Object o) {
|
||||
synchronized (this) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
field.set(connection, wrapper);
|
||||
// Iterate through current list
|
||||
synchronized (wrapper) {
|
||||
for (Object o : (List) value) {
|
||||
if (connection == null) {
|
||||
getLogger().warning("We failed to find the ServerConnection? :( What server are you running?");
|
||||
return;
|
||||
}
|
||||
for (Field field : connection.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
final Object value = field.get(connection);
|
||||
if (value instanceof List) {
|
||||
// Inject the list
|
||||
List wrapper = new ListWrapper((List) value) {
|
||||
@Override
|
||||
public synchronized void handleAdd(Object o) {
|
||||
synchronized (this) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
} else {
|
||||
break; // not the right list.
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
field.set(connection, wrapper);
|
||||
// Iterate through current list
|
||||
synchronized (wrapper) {
|
||||
for (Object o : (List) value) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
} else {
|
||||
break; // not the right list.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,6 +194,13 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
return isPorted(player.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPlayerVersion(@NonNull Player player) {
|
||||
if (!isPorted(player))
|
||||
return 47;
|
||||
return portedPlayers.get(player.getUniqueId()).getProtocol();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPorted(UUID playerUUID) {
|
||||
return portedPlayers.containsKey(playerUUID);
|
||||
@ -245,6 +245,10 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
return getConfig().getBoolean("prevent-collision", true);
|
||||
}
|
||||
|
||||
public boolean isNewEffectIndicator(){
|
||||
return getConfig().getBoolean("use-new-effect-indicator",true);
|
||||
}
|
||||
|
||||
public boolean isSuppressMetadataErrors() {
|
||||
return getConfig().getBoolean("suppress-metadata-errors", false);
|
||||
}
|
||||
|
@ -10,13 +10,20 @@ import java.util.UUID;
|
||||
|
||||
public interface ViaVersionAPI {
|
||||
/**
|
||||
* Is player using 1.9?
|
||||
* Is the player connection modified by ViaVersion?
|
||||
*
|
||||
* @param player
|
||||
* @return True if the client is on 1.9
|
||||
* @param player Bukkit player object
|
||||
* @return True if the client is modified (At the moment it also means version 1.9 and higher)
|
||||
*/
|
||||
boolean isPorted(Player player);
|
||||
|
||||
/**
|
||||
* Get protocol number from a player
|
||||
* @param player Bukkit player object
|
||||
* @return Protocol ID, For example (47=1.8-1.8.8, 107=1.9, 108=1.9.1)
|
||||
*/
|
||||
int getPlayerVersion(Player player);
|
||||
|
||||
/**
|
||||
* Is player using 1.9?
|
||||
*
|
||||
|
@ -11,8 +11,10 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.inventory.CraftingInventory;
|
||||
import us.myles.ViaVersion.ViaVersionPlugin;
|
||||
import us.myles.ViaVersion.api.ViaVersion;
|
||||
@ -89,6 +91,18 @@ public class ArmorListener implements Listener {
|
||||
sendDelayedArmorUpdate(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
if (ViaVersion.getInstance().isPorted(e.getPlayer()))
|
||||
sendDelayedArmorUpdate(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
if (ViaVersion.getInstance().isPorted(e.getPlayer()))
|
||||
sendArmorUpdate(e.getPlayer());
|
||||
}
|
||||
|
||||
public void sendDelayedArmorUpdate(final Player player) {
|
||||
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
|
||||
@Override
|
||||
|
@ -15,10 +15,7 @@ import us.myles.ViaVersion.packets.PacketType;
|
||||
import us.myles.ViaVersion.transformers.OutgoingTransformer;
|
||||
import us.myles.ViaVersion.util.PacketUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
@Getter
|
||||
public class ViaBossBar implements BossBar {
|
||||
@ -142,7 +139,7 @@ public class ViaBossBar implements BossBar {
|
||||
|
||||
private void sendPacket(UpdateAction action) {
|
||||
ByteBuf buf = getPacket(action);
|
||||
for (UUID uuid : players)
|
||||
for (UUID uuid : new ArrayList<>(players))
|
||||
sendPacket(uuid, buf);
|
||||
}
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package us.myles.ViaVersion.commands;
|
||||
|
||||
import io.netty.util.ResourceLeakDetector;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
@ -42,6 +43,13 @@ public class ViaVersionCommand implements CommandExecutor {
|
||||
} else if (args[0].equalsIgnoreCase("debug")) {
|
||||
plugin.setDebug(!plugin.isDebug());
|
||||
sender.sendMessage(color("&6Debug mode is now " + (plugin.isDebug() ? "&aenabled" : "&cdisabled")));
|
||||
} else if (args[0].equalsIgnoreCase("displayleaks")) {
|
||||
if (ResourceLeakDetector.getLevel() != ResourceLeakDetector.Level.ADVANCED) {
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
|
||||
} else {
|
||||
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
|
||||
}
|
||||
sender.sendMessage(color("&6Leak detector is now " + (ResourceLeakDetector.getLevel() == ResourceLeakDetector.Level.ADVANCED ? "&aenabled" : "&cdisabled")));
|
||||
} else if (args[0].equalsIgnoreCase("dontbugme")) {
|
||||
boolean newValue = !plugin.getConfig().getBoolean("checkforupdates", true);
|
||||
plugin.getConfig().set("checkforupdates", newValue);
|
||||
@ -62,7 +70,7 @@ public class ViaVersionCommand implements CommandExecutor {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sendHelp(CommandSender sender){
|
||||
public void sendHelp(CommandSender sender) {
|
||||
sender.sendMessage(color("&aViaVersion &c" + ViaVersion.getInstance().getVersion()));
|
||||
sender.sendMessage(color("&6Commands:"));
|
||||
sender.sendMessage(color("&2/viaversion list &7- &6Shows lists of all 1.9 clients and 1.8 clients."));
|
||||
|
@ -26,24 +26,7 @@ public class ViaChunkHandler extends MessageToMessageEncoder {
|
||||
info.setLastPacket(o);
|
||||
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
||||
if(o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
||||
final int[] locX = ReflectionUtil.get(o, "a", int[].class);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
list.addAll(info.getChunkManager().transformMapChunkBulk(o));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import us.myles.ViaVersion.ConnectionInfo;
|
||||
import us.myles.ViaVersion.transformers.IncomingTransformer;
|
||||
import us.myles.ViaVersion.util.PacketUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
public class ViaDecodeHandler extends ByteToMessageDecoder {
|
||||
@ -32,15 +33,27 @@ public class ViaDecodeHandler extends ByteToMessageDecoder {
|
||||
ByteBuf newPacket = ctx.alloc().buffer();
|
||||
try {
|
||||
incomingTransformer.transform(id, bytebuf, newPacket);
|
||||
bytebuf.clear();
|
||||
bytebuf = newPacket;
|
||||
} catch (Exception e) {
|
||||
// Clear Buffer
|
||||
bytebuf.clear();
|
||||
// Release Packet, be free!
|
||||
newPacket.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// call minecraft decoder
|
||||
list.addAll(PacketUtil.callDecode(this.minecraftDecoder, ctx, bytebuf));
|
||||
try {
|
||||
list.addAll(PacketUtil.callDecode(this.minecraftDecoder, ctx, bytebuf));
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof Exception) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
} finally {
|
||||
if (info.isActive()) {
|
||||
bytebuf.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,7 @@ import us.myles.ViaVersion.CancelException;
|
||||
import us.myles.ViaVersion.ConnectionInfo;
|
||||
import us.myles.ViaVersion.transformers.OutgoingTransformer;
|
||||
import us.myles.ViaVersion.util.PacketUtil;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
public class ViaEncodeHandler extends MessageToByteEncoder {
|
||||
@ -29,7 +27,13 @@ public class ViaEncodeHandler extends MessageToByteEncoder {
|
||||
// handle the packet type
|
||||
if (!(o instanceof ByteBuf)) {
|
||||
// call minecraft encoder
|
||||
PacketUtil.callEncode(this.minecraftEncoder, ctx, o, bytebuf);
|
||||
try {
|
||||
PacketUtil.callEncode(this.minecraftEncoder, ctx, o, bytebuf);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof Exception) {
|
||||
throw (Exception) e.getCause();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytebuf.readableBytes() == 0) {
|
||||
throw new CancelException();
|
||||
|
@ -1,8 +1,10 @@
|
||||
package us.myles.ViaVersion.listeners;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.CommandBlock;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -10,14 +12,19 @@ import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.spacehq.opennbt.tag.builtin.ByteTag;
|
||||
import org.spacehq.opennbt.tag.builtin.CompoundTag;
|
||||
import us.myles.ViaVersion.ViaVersionPlugin;
|
||||
import us.myles.ViaVersion.packets.PacketType;
|
||||
import us.myles.ViaVersion.util.PacketUtil;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@ -26,14 +33,23 @@ public class CommandBlockListener implements Listener {
|
||||
private final ViaVersionPlugin plugin;
|
||||
|
||||
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
|
||||
public void onJoin(final PlayerJoinEvent e) {
|
||||
if (e.getPlayer().isOp() && plugin.isPorted(e.getPlayer())) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
PacketUtil.writeVarInt(PacketType.PLAY_ENTITY_STATUS.getNewPacketID(), buf);
|
||||
buf.writeInt(e.getPlayer().getEntityId());
|
||||
buf.writeByte(26);
|
||||
plugin.sendRawPacket(e.getPlayer(), buf);
|
||||
}
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
sendOp(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onRespawn(final PlayerRespawnEvent e) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendOp(e.getPlayer());
|
||||
}
|
||||
}, 1L);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
sendOp(e.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
@ -41,22 +57,68 @@ public class CommandBlockListener implements Listener {
|
||||
if (e.getAction() == Action.RIGHT_CLICK_BLOCK && plugin.isPorted(e.getPlayer()) && e.getPlayer().isOp()) {
|
||||
try {
|
||||
sendCommandBlockPacket(e.getClickedBlock(), e.getPlayer());
|
||||
} catch (NoSuchFieldException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e1) {
|
||||
e1.printStackTrace();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCommandBlockPacket(Block b, Player player) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, ClassNotFoundException {
|
||||
private void sendOp(Player p) {
|
||||
if (p.isOp() && plugin.isPorted(p)) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
PacketUtil.writeVarInt(PacketType.PLAY_ENTITY_STATUS.getNewPacketID(), buf);
|
||||
buf.writeInt(p.getEntityId());
|
||||
buf.writeByte(26);
|
||||
plugin.sendRawPacket(p, buf);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCommandBlockPacket(Block b, Player player) throws Exception {
|
||||
if (!(b.getState() instanceof CommandBlock))
|
||||
return;
|
||||
CommandBlock cmd = (CommandBlock) b.getState();
|
||||
|
||||
Object tileEntityCommand = ReflectionUtil.get(cmd, "commandBlock", ReflectionUtil.nms("TileEntityCommand"));
|
||||
Object updatePacket = ReflectionUtil.invoke(tileEntityCommand, "getUpdatePacket");
|
||||
Object nmsPlayer = ReflectionUtil.invoke(player, "getHandle");
|
||||
Object playerConnection = ReflectionUtil.get(nmsPlayer, "playerConnection", ReflectionUtil.nms("PlayerConnection"));
|
||||
Method sendPacket = playerConnection.getClass().getMethod("sendPacket", ReflectionUtil.nms("Packet"));
|
||||
sendPacket.invoke(playerConnection, updatePacket); //Let the transformer do the work
|
||||
ByteBuf buf = packetToByteBuf(updatePacket);
|
||||
plugin.sendRawPacket(player, buf);
|
||||
}
|
||||
|
||||
private ByteBuf packetToByteBuf(Object updatePacket) throws Exception {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
PacketUtil.writeVarInt(PacketType.PLAY_UPDATE_BLOCK_ENTITY.getNewPacketID(), buf); //Packet ID
|
||||
long[] pos = getPosition(ReflectionUtil.get(updatePacket, "a", ReflectionUtil.nms("BlockPosition")));
|
||||
PacketUtil.writeBlockPosition(buf, pos[0], pos[1], pos[2]); //Block position
|
||||
buf.writeByte(2); //Action id always 2
|
||||
CompoundTag nbt = getNBT(ReflectionUtil.get(updatePacket, "c", ReflectionUtil.nms("NBTTagCompound")));
|
||||
if (nbt == null) {
|
||||
buf.writeByte(0); //If nbt is null. Use 0 as nbt
|
||||
return buf;
|
||||
}
|
||||
nbt.put(new ByteTag("powered", (byte) 0));
|
||||
nbt.put(new ByteTag("auto", (byte) 0));
|
||||
nbt.put(new ByteTag("conditionMet", (byte) 0));
|
||||
PacketUtil.writeNBT(buf, nbt); //NBT tag
|
||||
return buf;
|
||||
}
|
||||
|
||||
private long[] getPosition(Object obj) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
|
||||
return new long[]{
|
||||
(long) ReflectionUtil.getSuper(obj, "a", int.class), //X
|
||||
(long) ReflectionUtil.getSuper(obj, "c", int.class), //Y
|
||||
(long) ReflectionUtil.getSuper(obj, "d", int.class) //Z
|
||||
};
|
||||
}
|
||||
|
||||
private CompoundTag getNBT(Object obj) throws Exception {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
Method m = ReflectionUtil.nms("NBTCompressedStreamTools").getMethod("a", ReflectionUtil.nms("NBTTagCompound"), DataOutput.class);
|
||||
m.invoke(null, obj, new DataOutputStream(new ByteBufOutputStream(buf)));
|
||||
try {
|
||||
return PacketUtil.readNBT(buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ public enum MetaIndex {
|
||||
PLAYER_ADDITIONAL_HEARTS(HumanEntity.class, 17, Type.Float, 10, NewType.Float),
|
||||
PLAYER_SCORE(HumanEntity.class, 18, Type.Int, 11, NewType.VarInt),
|
||||
PLAYER_HAND(HumanEntity.class, -1, Type.NonExistent, 5, NewType.Byte), // new in 1.9
|
||||
SOMETHING_ANTICHEAT_PLUGINS_FOR_SOME_REASON_USE(HumanEntity.class, 11, Type.Byte, NewType.Discontinued), //For what we know, This doesn't exists. If you think it exists and knows what it does. Please tell us.
|
||||
// horse
|
||||
HORSE_INFO(Horse.class, 16, Type.Int, 12, NewType.Byte),
|
||||
HORSE_TYPE(Horse.class, 19, Type.Byte, 13, NewType.VarInt),
|
||||
|
@ -45,7 +45,7 @@ public class IncomingTransformer {
|
||||
}
|
||||
// Handle movement increment
|
||||
// Update idle status (player, position, look, positionandlook)
|
||||
if(packet == PacketType.PLAY_PLAYER || packet == PacketType.PLAY_PLAYER_POSITION_REQUEST || packet == PacketType.PLAY_PLAYER_LOOK_REQUEST || packet == PacketType.PLAY_PLAYER_POSITION_LOOK_REQUEST) {
|
||||
if (packet == PacketType.PLAY_PLAYER || packet == PacketType.PLAY_PLAYER_POSITION_REQUEST || packet == PacketType.PLAY_PLAYER_LOOK_REQUEST || packet == PacketType.PLAY_PLAYER_POSITION_LOOK_REQUEST) {
|
||||
info.incrementIdlePacket();
|
||||
}
|
||||
PacketUtil.writeVarInt(packetID, output);
|
||||
@ -163,6 +163,18 @@ public class IncomingTransformer {
|
||||
if (packet == PacketType.PLAY_CLOSE_WINDOW_REQUEST) {
|
||||
info.closeWindow();
|
||||
}
|
||||
if (packet == PacketType.PLAY_CLIENT_STATUS) {
|
||||
int action = PacketUtil.readVarInt(input);
|
||||
PacketUtil.writeVarInt(action, input);
|
||||
|
||||
if (action == 2) {
|
||||
// cancel any blocking >.>
|
||||
if (startedBlocking) {
|
||||
sendSecondHandItem(null);
|
||||
startedBlocking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (packet == PacketType.PLAY_CLIENT_SETTINGS) {
|
||||
String locale = PacketUtil.readString(input);
|
||||
PacketUtil.writeString(locale, output);
|
||||
@ -281,13 +293,13 @@ public class IncomingTransformer {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
short curX = input.readUnsignedByte();
|
||||
output.writeByte(curX);
|
||||
short curY = input.readUnsignedByte();
|
||||
output.writeByte(curY);
|
||||
short curZ = input.readUnsignedByte();
|
||||
output.writeByte(curZ);
|
||||
return;
|
||||
short curX = input.readUnsignedByte();
|
||||
output.writeByte(curX);
|
||||
short curY = input.readUnsignedByte();
|
||||
output.writeByte(curY);
|
||||
short curZ = input.readUnsignedByte();
|
||||
output.writeByte(curZ);
|
||||
return;
|
||||
}
|
||||
if (packet == PacketType.PLAY_USE_ITEM) {
|
||||
int hand = PacketUtil.readVarInt(input);
|
||||
@ -329,8 +341,17 @@ public class IncomingTransformer {
|
||||
}
|
||||
if (packet == PacketType.PLAY_CREATIVE_INVENTORY_ACTION) {
|
||||
short slot = input.readShort();
|
||||
if (slot == 45) {
|
||||
ByteBuf buf = info.getChannel().alloc().buffer();
|
||||
PacketUtil.writeVarInt(PacketType.PLAY_SET_SLOT.getNewPacketID(), buf);
|
||||
buf.writeByte(0);
|
||||
buf.writeShort(slot);
|
||||
buf.writeShort(-1); // empty
|
||||
info.sendRawPacket(buf);
|
||||
// Continue the packet simulating throw
|
||||
slot = -999;
|
||||
}
|
||||
output.writeShort(slot);
|
||||
|
||||
ItemSlotRewriter.rewrite1_9To1_8(input, output);
|
||||
}
|
||||
output.writeBytes(input);
|
||||
|
@ -1,14 +1,11 @@
|
||||
package us.myles.ViaVersion.transformers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.spacehq.mc.protocol.data.game.chunk.Column;
|
||||
import org.spacehq.mc.protocol.util.NetUtil;
|
||||
import org.spacehq.opennbt.tag.builtin.ByteTag;
|
||||
import org.spacehq.opennbt.tag.builtin.CompoundTag;
|
||||
import org.spacehq.opennbt.tag.builtin.StringTag;
|
||||
import us.myles.ViaVersion.CancelException;
|
||||
@ -18,6 +15,8 @@ import us.myles.ViaVersion.api.ViaVersion;
|
||||
import us.myles.ViaVersion.api.boss.BossBar;
|
||||
import us.myles.ViaVersion.api.boss.BossColor;
|
||||
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.MetadataRewriter;
|
||||
import us.myles.ViaVersion.metadata.MetadataRewriter.Entry;
|
||||
@ -27,7 +26,6 @@ import us.myles.ViaVersion.slot.ItemSlotRewriter;
|
||||
import us.myles.ViaVersion.sounds.SoundEffect;
|
||||
import us.myles.ViaVersion.util.EntityUtil;
|
||||
import us.myles.ViaVersion.util.PacketUtil;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
@ -36,6 +34,7 @@ import static us.myles.ViaVersion.util.PacketUtil.*;
|
||||
|
||||
|
||||
public class OutgoingTransformer {
|
||||
private static Gson gson = new GsonBuilder().create();
|
||||
|
||||
private final ViaVersionPlugin plugin = (ViaVersionPlugin) ViaVersion.getInstance();
|
||||
|
||||
@ -58,16 +57,16 @@ public class OutgoingTransformer {
|
||||
line = "{\"text\":\"\"}";
|
||||
} else {
|
||||
if ((!line.startsWith("\"") || !line.endsWith("\"")) && (!line.startsWith("{") || !line.endsWith("}"))) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("text", line);
|
||||
return obj.toJSONString();
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("text", line);
|
||||
return gson.toJson(jsonObject);
|
||||
}
|
||||
if (line.startsWith("\"") && line.endsWith("\"")) {
|
||||
line = "{\"text\":" + line + "}";
|
||||
}
|
||||
}
|
||||
try {
|
||||
new JSONParser().parse(line);
|
||||
gson.fromJson(line, JsonObject.class);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Invalid JSON String: \"" + line + "\" Please report this issue to the ViaVersion Github: " + e.getMessage());
|
||||
return "{\"text\":\"\"}";
|
||||
@ -299,11 +298,12 @@ public class OutgoingTransformer {
|
||||
if (packet == PacketType.STATUS_RESPONSE) {
|
||||
String originalStatus = PacketUtil.readString(input);
|
||||
try {
|
||||
JSONObject json = (JSONObject) new JSONParser().parse(originalStatus);
|
||||
JSONObject version = (JSONObject) json.get("version");
|
||||
version.put("protocol", info.getProtocol());
|
||||
PacketUtil.writeString(json.toJSONString(), output);
|
||||
} catch (ParseException e) {
|
||||
JsonObject jsonObject = gson.fromJson(originalStatus, JsonObject.class);
|
||||
JsonObject version = jsonObject.get("version").getAsJsonObject();
|
||||
if (version.get("protocol").getAsInt() != 9999) //Fix ServerListPlus custom outdated message
|
||||
version.addProperty("protocol", info.getProtocol());
|
||||
PacketUtil.writeString(gson.toJson(jsonObject), output);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return;
|
||||
@ -680,7 +680,7 @@ public class OutgoingTransformer {
|
||||
PacketUtil.writeVarInt(duration, output);
|
||||
// we need to write as a byte instead of boolean
|
||||
boolean hideParticles = input.readBoolean();
|
||||
output.writeByte(hideParticles ? 1 : 0);
|
||||
output.writeByte(hideParticles ? plugin.isNewEffectIndicator() ? 2 : 1 : 0);
|
||||
return;
|
||||
}
|
||||
if (packet == PacketType.PLAY_TEAM) {
|
||||
@ -757,73 +757,21 @@ public class OutgoingTransformer {
|
||||
return;
|
||||
}
|
||||
if (action == 2) { //Update commandblock
|
||||
try {
|
||||
CompoundTag nbt = readNBT(input);
|
||||
if (nbt == null)
|
||||
throw new CancelException();
|
||||
//Thanks http://www.minecraftforum.net/forums/minecraft-discussion/redstone-discussion-and/command-blocks/2488148-1-9-nbt-changes-and-additions#TileAllCommandBlocks
|
||||
nbt.put(new ByteTag("powered", (byte) 0));
|
||||
nbt.put(new ByteTag("auto", (byte) 0));
|
||||
nbt.put(new ByteTag("conditionMet", (byte) 0));
|
||||
writeNBT(output, nbt);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new CancelException();
|
||||
}
|
||||
throw new CancelException(); //Only update if player interact with commandblock (The commandblock window will update every time this packet is sent, this would prevent you from change things that update every tick)
|
||||
}
|
||||
output.writeBytes(input, input.readableBytes());
|
||||
return;
|
||||
}
|
||||
if (packet == PacketType.PLAY_CHUNK_DATA) {
|
||||
// We need to catch unloading chunk packets as defined by wiki.vg
|
||||
// To unload chunks, send this packet with Ground-Up Continuous=true and no 16^3 chunks (eg. Primary Bit Mask=0)
|
||||
int chunkX = input.readInt();
|
||||
int chunkZ = input.readInt();
|
||||
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) {
|
||||
// Read chunk
|
||||
ChunkManager chunkManager = info.getChunkManager();
|
||||
Chunk chunk = chunkManager.readChunk(input);
|
||||
if (chunk == null) {
|
||||
throw new CancelException();
|
||||
}
|
||||
// Write chunk section array :((
|
||||
ByteBuf temp = output.alloc().buffer();
|
||||
try {
|
||||
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();
|
||||
}
|
||||
|
||||
// Write chunk
|
||||
chunkManager.writeChunk(chunk, output);
|
||||
return;
|
||||
}
|
||||
output.writeBytes(input);
|
||||
|
@ -42,6 +42,9 @@ public class PacketUtil {
|
||||
}
|
||||
|
||||
public static CompoundTag readNBT(ByteBuf input) throws IOException {
|
||||
// Default client is limited to 2097152 bytes. (2.09mb)
|
||||
Preconditions.checkArgument(input.readableBytes() <= 2097152, "Cannot read NBT (got %s bytes)", input.readableBytes());
|
||||
|
||||
int readerIndex = input.readerIndex();
|
||||
byte b = input.readByte();
|
||||
if (b == 0) {
|
||||
@ -71,37 +74,24 @@ public class PacketUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Object> callDecode(ByteToMessageDecoder decoder, ChannelHandlerContext ctx, Object input) {
|
||||
public static List<Object> callDecode(ByteToMessageDecoder decoder, ChannelHandlerContext ctx, Object input) throws InvocationTargetException {
|
||||
List<Object> output = new ArrayList<>();
|
||||
try {
|
||||
PacketUtil.DECODE_METHOD.invoke(decoder, ctx, input, output);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void callEncode(MessageToByteEncoder encoder, ChannelHandlerContext ctx, Object msg, ByteBuf output) {
|
||||
public static void callEncode(MessageToByteEncoder encoder, ChannelHandlerContext ctx, Object msg, ByteBuf output) throws InvocationTargetException {
|
||||
try {
|
||||
PacketUtil.ENCODE_METHOD.invoke(encoder, ctx, msg, output);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteBuf decompress(ChannelHandlerContext ctx, ByteBuf msg) {
|
||||
ByteToMessageDecoder x = (ByteToMessageDecoder) ctx.pipeline().get("decompress");
|
||||
List<Object> output = callDecode(x, ctx, msg);
|
||||
return output.size() == 0 ? null : (ByteBuf) output.get(0);
|
||||
}
|
||||
|
||||
public static ByteBuf compress(ChannelHandlerContext ctx, ByteBuf msg) {
|
||||
MessageToByteEncoder x = (MessageToByteEncoder) ctx.pipeline().get("compress");
|
||||
ByteBuf output = ctx.alloc().buffer();
|
||||
callEncode(x, ctx, msg, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
/* I take no credit, these are taken from BungeeCord */
|
||||
// https://github.com/SpigotMC/BungeeCord/blob/master/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java
|
||||
public static void writeString(String s, ByteBuf buf) {
|
||||
|
@ -1,10 +1,14 @@
|
||||
package us.myles.ViaVersion.util;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReflectionUtil {
|
||||
private static String BASE = Bukkit.getServer().getClass().getPackage().getName();
|
||||
@ -33,6 +37,12 @@ public class ReflectionUtil {
|
||||
return (T) field.get(null);
|
||||
}
|
||||
|
||||
public static <T> T getSuper(Object o, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field = o.getClass().getSuperclass().getDeclaredField(f);
|
||||
field.setAccessible(true);
|
||||
return (T) field.get(o);
|
||||
}
|
||||
|
||||
public static <T> T get(Object instance, Class<?> clazz, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
|
||||
Field field = clazz.getDeclaredField(f);
|
||||
field.setAccessible(true);
|
||||
@ -50,4 +60,74 @@ public class ReflectionUtil {
|
||||
field.setAccessible(true);
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,4 +19,6 @@ simulate-pt: true
|
||||
# Should we patch boss bars so they work? (Default: true, disable if you're having issues)
|
||||
bossbar-patch: true
|
||||
# If your boss bar flickers on 1.9, set this to 'true'. It will keep all boss bars on 100% (not recommended)
|
||||
bossbar-anti-flicker: false
|
||||
bossbar-anti-flicker: false
|
||||
# This will show the new effect indicator in the top-right corner for 1.9 players.
|
||||
use-new-effect-indicator: true
|
Loading…
Reference in New Issue
Block a user