Compare commits

...
53 Commits
Author SHA1 Message Date
Myles b1b35fe025 Bump version 2016-03-18 21:59:13 +00:00
Myles 11f3844d63 Use GSON instead, should fix memory leak issue with JSON. Also make sure that we use a copy of players for the bossbar. 2016-03-18 18:14:58 +00:00
Mats d40c5851e9 Merge pull request #239 from Matsv/master
Add api method to get player's protocol id
2016-03-17 12:38:51 +01:00
Mats d0e573613b Add api method to get player's protocol id, 2016-03-16 21:01:04 +01:00
Myles 0d3ba67233 Remove compress / decompress, rethrow errors if they occur from another handler, Fixes #233 2016-03-16 13:42:17 +00:00
Myles 9d57521b29 Bump Version 2016-03-15 21:57:38 +00:00
Myles d98d4ff466 Merge pull request #236 from Matsv/meta11
Discountinue player metadata 11.
2016-03-15 21:06:37 +00:00
Myles 71e27b4799 Merge pull request #232 from Matsv/dropcreative
Throw items when you try to put things in your second hand in creative.
2016-03-15 21:06:04 +00:00
Myles f1354edf20 Merge pull request #231 from Matsv/effect
Make it possible to use the new effect indicators in 1.9
2016-03-15 21:05:55 +00:00
Mats 4d82112286 Discountinue metadata 11. 2016-03-15 21:42:40 +01:00
Mats ba5a44d2d2 Remove debug message 2016-03-15 20:40:26 +01:00
Mats 597ddc7f82 Throw items when you try to put things in your second hand in creative. 2016-03-15 20:37:45 +01:00
Mats 6a7bd209a3 Make it possible to use the new effect indicators in 1.9 2016-03-15 20:20:39 +01:00
Myles 722f28f086 Merge pull request #228 from Matsv/slp
Make compatible with ServerListPlus custom outdated message. Fix #227
2016-03-15 17:51:56 +00:00
Mats daed15d61f Make compatible with ServerListPlus custom outdated message. Fix #227 2016-03-15 14:14:10 +01:00
Myles 7cfba0b602 Merge pull request #210 from Matsv/fixarmortjes
Fix #209 woops my fault
2016-03-13 17:02:07 +00:00
Mats 78ab7c840a Fix #209 woops my fault 2016-03-13 16:48:07 +01:00
Myles 669ec3f453 Merge pull request #208 from Matsv/drop
Drop SpaceHQ mcprotocol
2016-03-13 15:17:20 +00:00
Mats dd32b46055 Remove unused imports 2016-03-13 16:13:45 +01:00
Mats 7daa43146e Drop spacehq MC protocol since it's no longer used 2016-03-13 16:12:31 +01:00
Myles 89d5f2cc4f Merge pull request #207 from Matsv/armorattfix
Fix armor not showing on Respawn (With keepinventory true) & world change
2016-03-13 14:30:56 +00:00
Mats 53fb753284 Fix armor not showing on Respawn (With keepinventory true) & world change 2016-03-13 15:22:23 +01:00
Myles 469d81df72 Merge pull request #206 from Matsv/cmdfix
Fix unable to open commandblock after respawn/world change
2016-03-13 13:18:22 +00:00
Mats 7d7f5a0bda Fix unable to open commandblock after respawn/world change 2016-03-13 14:02:10 +01:00
Myles 9954195a8f Merge pull request #205 from Matsv/master
Fix commandblocks not able to change command on update
2016-03-13 12:26:40 +00:00
Mats 62d210de68 Fix commandblocks not able to change command on every-tick commandblock update 2016-03-13 11:06:18 +01:00
Myles 8d2189cb8e Bump versions 2016-03-12 20:52:46 +00:00
Myles 44a044848f Fix ServerConnection #198 2016-03-12 20:48:04 +00:00
Myles 4f133366e1 Add limitation to NBT data, we'll stick with this for now. I don't see this easily being beaten. 2016-03-12 20:41:04 +00:00
Myles 7c66507960 Merge pull request #200 from lenis0012/chunks
Implement new chunk transformer
2016-03-12 20:27:44 +00:00
Lennart ten Wolde e436988303 transform chunk bulk packets 2016-03-12 15:47:35 +01:00
Lennart ten Wolde 8b65efc4bd Implement new chunk transformer 2016-03-12 13:58:10 +01:00
Myles ae2592f3d1 Update to 1.9.1 PRE 2 :) 2016-03-11 16:20:15 +00:00
Myles c009827622 Merge pull request #192 from HugoDaBosss/optimize2
Optimize packet searching
2016-03-11 12:55:15 +00:00
HugoDaBosss afe18379e0 Optimize packet searching 2016-03-11 11:48:42 +01:00
Myles 5ac61fa71d Must of missed this ;) 0.6.3 in desc 2016-03-10 22:52:29 +00:00
HugoDaBosss 3511f9a7b2 Optimized packet searching 2016-03-10 23:13:23 +01:00
Myles e3f7493616 Merge pull request #191 from Matsv/master
Fix Bungeecord "Team viaversion already exists in this scoreboard" and might fix #172
2016-03-10 19:47:43 +00:00
Mats a769599448 Fix Bungeecord "Team viaversion already exists in this scoreboard" and might fix #86 2016-03-10 20:33:32 +01:00
Myles 5df8a1e8e7 Bump version 2016-03-10 16:47:52 +00:00
Myles a3d124b552 Merge pull request #190 from lenis0012/master
Add config wrapper to update config
2016-03-10 16:44:20 +00:00
Lennart ten Wolde 6d95af3963 Add config wrapper to update config
Configuration reads old config and moves it's values over to a new
config copied from the resources directory.

normally, when you save it would delete the comments, but they are
written back in to the new config by this wrapper

now people their config files will actually update
2016-03-10 17:40:40 +01:00
Myles c08a38ff21 Merge pull request #189 from lenis0012/master
fix metadata type mismatch
2016-03-10 16:21:02 +00:00
Lennart ten Wolde 4a804bae7c fix metadata type mismatch & add anti-flicker 2016-03-10 16:52:31 +01:00
Myles fd42c7eba6 Merge pull request #188 from lenis0012/master
fixed default boss title
2016-03-10 14:15:30 +00:00
Lennart ten Wolde 1af86d1354 fixed default boss title 2016-03-10 15:14:27 +01:00
Myles 1b77e1c89f Fix BarAPI, (adds new metaindex, and change colour to be right and remove debug) 2016-03-10 14:07:27 +00:00
Myles c42b9351b8 Remove sync-chunks 2016-03-10 13:25:49 +00:00
Myles ab8c8cd7c8 Merge pull request #187 from lenis0012/chunks
Add chunk handler to netty pipeline. Fixes #48
2016-03-10 13:22:48 +00:00
Myles 83518b92fe Make bossbar patch a configurable option 2016-03-10 13:22:32 +00:00
Myles 1bf1e8981c Merge pull request #186 from lenis0012/master
Add support for 1.8 boss bars. Fixes #41
2016-03-10 13:13:55 +00:00
Lennart ten Wolde b1f076936d Add chunk handler to netty pipeline. FIxes #48 2016-03-10 12:47:09 +01:00
Lennart ten Wolde 319e793cd6 Add support for 1.8 boss bars
Automatically converts legacy boss bars to 1.9 ones using ViaVersions's
boss API.

I suggest you add a config option
Note: also fixed crash on wither/dragon spawn
2016-03-10 03:14:30 +01:00
31 changed files with 1402 additions and 929 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
# ViaVersion 0.6.2
# ViaVersion 0.6.6
[![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**
+15 -1
View File
@@ -6,7 +6,7 @@
<groupId>us.myles</groupId>
<artifactId>viaversion</artifactId>
<version>0.6.3-SNAPSHOT</version>
<version>0.6.7-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;
@@ -24,9 +25,11 @@ import us.myles.ViaVersion.handlers.ViaVersionInitializer;
import us.myles.ViaVersion.listeners.CommandBlockListener;
import us.myles.ViaVersion.update.UpdateListener;
import us.myles.ViaVersion.update.UpdateUtil;
import us.myles.ViaVersion.util.Configuration;
import us.myles.ViaVersion.util.ListWrapper;
import us.myles.ViaVersion.util.ReflectionUtil;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
@@ -64,7 +67,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
@Override
public void onEnable() {
ViaVersion.setInstance(this);
saveDefaultConfig();
generateConfig();
if (System.getProperty("ViaVersion") != null) {
getLogger().severe("ViaVersion is already loaded, we don't support reloads. Please reboot if you wish to update.");
getLogger().severe("Some features may not work.");
@@ -93,27 +96,45 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
getCommand("viaversion").setExecutor(new ViaVersionCommand(this));
}
public void generateConfig() {
File file = new File(getDataFolder(), "config.yml");
if (file.exists()) {
// Update config options
Configuration oldConfig = new Configuration(file);
oldConfig.reload(false); // Load current options from config
file.delete(); // Delete old config
saveDefaultConfig(); // Generate new config
Configuration newConfig = new Configuration(file);
newConfig.reload(true); // Load default options
for (String key : oldConfig.getKeys(false)) {
// Set option in new config if exists
if (newConfig.contains(key)) {
newConfig.set(key, oldConfig.get(key));
}
}
newConfig.save();
} else {
saveDefaultConfig();
}
}
public void injectPacketHandler() {
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) {
getLogger().warning("We failed to find the ServerConnection? :( What server are you running?");
return;
}
if (connection != null) {
for (Field field : connection.getClass().getDeclaredFields()) {
@@ -175,6 +196,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);
@@ -215,15 +243,14 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
this.debug = value;
}
@Override
public boolean isSyncedChunks() {
return getConfig().getBoolean("sync-chunks", true);
}
public boolean isPreventCollision() {
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);
}
@@ -236,6 +263,14 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
return getConfig().getBoolean("hologram-patch", false);
}
public boolean isBossbarPatch() {
return getConfig().getBoolean("bossbar-patch", true);
}
public boolean isBossbarAntiflicker() {
return getConfig().getBoolean("bossbar-anti-flicker", false);
}
public double getHologramYOffset() {
return getConfig().getDouble("hologram-y", -1D);
}
@@ -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?
*
@@ -77,11 +84,4 @@ public interface ViaVersionAPI {
* @return true if debug is enabled
*/
boolean isDebug();
/**
* Obtains if syncing chunks is on
*
* @return true if it is
*/
boolean isSyncedChunks();
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -0,0 +1,36 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.util.ReflectionUtil;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
public class ViaChunkHandler extends MessageToMessageEncoder {
private final ConnectionInfo info;
public ViaChunkHandler(ConnectionInfo info) {
this.info = info;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object o, List list) throws Exception {
// Split chunk bulk packet up in to single chunk packets before it reached the encoder.
// This will prevent issues with several plugins and other protocol handlers due to the chunk being sent twice.
// It also sends the chunk in the right order possible resolving some issues with added chunk/block/entity data.
if (!(o instanceof ByteBuf)) {
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()) {
list.addAll(info.getChunkManager().transformMapChunkBulk(o));
return;
}
}
list.add(o);
}
}
@@ -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 {
@@ -40,7 +41,13 @@ public class ViaDecodeHandler extends ByteToMessageDecoder {
}
}
// 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();
}
}
}
}
@@ -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 {
@@ -28,40 +26,14 @@ public class ViaEncodeHandler extends MessageToByteEncoder {
protected void encode(final ChannelHandlerContext ctx, Object o, final ByteBuf bytebuf) throws Exception {
// handle the packet type
if (!(o instanceof ByteBuf)) {
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);
Runnable chunks = new Runnable() {
@Override
public void run() {
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);
ctx.pipeline().writeAndFlush(packet);
} catch (InstantiationException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
};
chunks.run();
bytebuf.clear();
throw new CancelException();
}
// 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();
@@ -32,8 +32,10 @@ public class ViaVersionInitializer extends ChannelInitializer<SocketChannel> {
// Add our transformers
ViaEncodeHandler encoder = new ViaEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder"));
ViaDecodeHandler decoder = new ViaDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder"));
ViaChunkHandler chunkHandler = new ViaChunkHandler(info);
socketChannel.pipeline().replace("encoder", "encoder", encoder);
socketChannel.pipeline().replace("decoder", "decoder", decoder);
socketChannel.pipeline().addAfter("packet_handler", "viaversion_chunk_handler", chunkHandler);
}
}
@@ -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),
@@ -91,6 +92,8 @@ public enum MetaIndex {
WITHER_TARGET2(Wither.class, 18, Type.Int, 12, NewType.VarInt),
WITHER_TARGET3(Wither.class, 19, Type.Int, 13, NewType.VarInt),
WITHER_INVULN_TIME(Wither.class, 20, Type.Int, 14, NewType.VarInt),
WITHER_PROPERTIES(Wither.class, 10, Type.Byte, NewType.Byte),
WITHER_UNKNOWN(Wither.class, 11, Type.Byte, NewType.Discontinued),
// wither skull
WITHERSKULL_INVULN(WitherSkull.class, 10, Type.Byte, 5, NewType.Boolean),
// guardian
@@ -123,7 +126,13 @@ public enum MetaIndex {
ITEMFRAME_ITEM(ItemFrame.class, 8, Type.Slot, 5, NewType.Slot),
ITEMFRAME_ROTATION(ItemFrame.class, 9, Type.Byte, 6, NewType.VarInt),
// ender crystal
ENDERCRYSTAL_HEALTH(EnderCrystal.class, 8, Type.Int, NewType.Discontinued),;
ENDERCRYSTAL_HEALTH(EnderCrystal.class, 8, Type.Int, NewType.Discontinued),
// Ender dragon boss bar issues
ENDERDRAGON_UNKNOWN(EnderDragon.class, 5, Type.Byte, NewType.Discontinued),
ENDERDRAGON_NAME(EnderDragon.class, 10, Type.String, NewType.Discontinued),
// Normal Ender dragon
ENDERDRAGON_FLAG(EnderDragon.class, 15, Type.Byte, NewType.Discontinued),
ENDERDRAGON_PHASE(EnderDragon.class, 11, Type.Byte, NewType.VarInt);
private Class<?> clazz;
private int newIndex;
@@ -207,7 +207,7 @@ public class MetadataRewriter {
private MetaIndex index;
private Object value;
private Entry(MetaIndex index, Object value, int id) {
public Entry(MetaIndex index, Object value, int id) {
this.index = index;
this.value = value;
this.oldID = id;
@@ -1,200 +1,203 @@
package us.myles.ViaVersion.packets;
public enum PacketType {
/* Handshake serverbound */
HANDSHAKE(State.HANDSHAKE, Direction.INCOMING, 0x00),
/* Login serverbound */
LOGIN_START(State.LOGIN, Direction.INCOMING, 0x00),
LOGIN_ENCRYPTION_RESPONSE(State.LOGIN, Direction.INCOMING, 0x01),
/* Login clientbound */
LOGIN_DISCONNECT(State.LOGIN, Direction.OUTGOING, 0x00),
LOGIN_ENCRYPTION_REQUEST(State.LOGIN, Direction.OUTGOING, 0x01),
LOGIN_SUCCESS(State.LOGIN, Direction.OUTGOING, 0x02),
LOGIN_SETCOMPRESSION(State.LOGIN, Direction.OUTGOING, 0x03),
/* Status serverbound */
STATUS_REQUEST(State.STATUS, Direction.INCOMING, 0x00),
STATUS_PING(State.STATUS, Direction.INCOMING, 0x01),
/* Status clientbound */
STATUS_RESPONSE(State.STATUS, Direction.OUTGOING, 0x00),
STATUS_PONG(State.STATUS, Direction.OUTGOING, 0x01),
/* Play serverbound */
PLAY_TP_CONFIRM(State.PLAY, Direction.INCOMING, -1, 0x00),
PLAY_TAB_COMPLETE_REQUEST(State.PLAY, Direction.INCOMING, 0x14, 0x01),
PLAY_CHAT_MESSAGE_CLIENT(State.PLAY, Direction.INCOMING, 0x01, 0x02),
PLAY_CLIENT_STATUS(State.PLAY, Direction.INCOMING, 0x16, 0x03),
PLAY_CLIENT_SETTINGS(State.PLAY, Direction.INCOMING, 0x15, 0x04),
PLAY_CONFIRM_TRANS(State.PLAY, Direction.INCOMING, 0x0F, 0x05),
PLAY_ENCHANT_ITEM(State.PLAY, Direction.INCOMING, 0x11, 0x06),
PLAY_CLICK_WINDOW(State.PLAY, Direction.INCOMING, 0x0E, 0x07),
PLAY_CLOSE_WINDOW_REQUEST(State.PLAY, Direction.INCOMING, 0x0D, 0x08),
PLAY_PLUGIN_MESSAGE_REQUEST(State.PLAY, Direction.INCOMING, 0x17, 0x09),
PLAY_USE_ENTITY(State.PLAY, Direction.INCOMING, 0x02, 0x0A),
PLAY_KEEP_ALIVE_REQUEST(State.PLAY, Direction.INCOMING, 0x00, 0x0B),
PLAY_PLAYER_POSITION_REQUEST(State.PLAY, Direction.INCOMING, 0x04, 0x0C),
PLAY_PLAYER_POSITION_LOOK_REQUEST(State.PLAY, Direction.INCOMING, 0x06, 0x0D),
PLAY_PLAYER_LOOK_REQUEST(State.PLAY, Direction.INCOMING, 0x05, 0x0E),
PLAY_PLAYER(State.PLAY, Direction.INCOMING, 0x03, 0x0F),
PLAY_VEHICLE_MOVE_REQUEST(State.PLAY, Direction.INCOMING, -1, 0x10),
PLAY_STEER_BOAT(State.PLAY, Direction.INCOMING, -1, 0x11),
PLAY_PLAYER_ABILITIES_REQUEST(State.PLAY, Direction.INCOMING, 0x13, 0x12),
PLAY_PLAYER_DIGGING(State.PLAY, Direction.INCOMING, 0x07, 0x13),
PLAY_ENTITY_ACTION(State.PLAY, Direction.INCOMING, 0x0B, 0x14),
PLAY_STEER_VEHICLE(State.PLAY, Direction.INCOMING, 0x0C, 0x15),
PLAY_RESOURCE_PACK_STATUS(State.PLAY, Direction.INCOMING, 0x19, 0x16),
PLAY_HELD_ITEM_CHANGE_REQUEST(State.PLAY, Direction.INCOMING, 0x09, 0x17),
PLAY_CREATIVE_INVENTORY_ACTION(State.PLAY, Direction.INCOMING, 0x10, 0x18),
PLAY_UPDATE_SIGN_REQUEST(State.PLAY, Direction.INCOMING, 0x12, 0x19),
PLAY_ANIMATION_REQUEST(State.PLAY, Direction.INCOMING, 0x0A, 0x1A),
PLAY_SPECTATE(State.PLAY, Direction.INCOMING, 0x18, 0x1B),
PLAY_PLAYER_BLOCK_PLACEMENT(State.PLAY, Direction.INCOMING, 0x08, 0x1C),
PLAY_USE_ITEM(State.PLAY, Direction.INCOMING, -1, 0x1D),
/* Play clientbound */
PLAY_SPAWN_OBJECT(State.PLAY, Direction.OUTGOING, 0x0E, 0x00),
PLAY_SPAWN_XP_ORB(State.PLAY, Direction.OUTGOING, 0x11, 0x01),
PLAY_SPAWN_GLOBAL_ENTITY(State.PLAY, Direction.OUTGOING, 0x2C, 0x02),
PLAY_SPAWN_MOB(State.PLAY, Direction.OUTGOING, 0x0F, 0x03),
PLAY_SPAWN_PAINTING(State.PLAY, Direction.OUTGOING, 0x10, 0x04),
PLAY_SPAWN_PLAYER(State.PLAY, Direction.OUTGOING, 0x0C, 0x05),
PLAY_ANIMATION(State.PLAY, Direction.OUTGOING, 0x0B, 0x06),
PLAY_STATS(State.PLAY, Direction.OUTGOING, 0x37, 0x07),
PLAY_BLOCK_BREAK_ANIMATION(State.PLAY, Direction.OUTGOING, 0x25, 0x08),
PLAY_UPDATE_BLOCK_ENTITY(State.PLAY, Direction.OUTGOING, 0x35, 0x09),
PLAY_BLOCK_ACTION(State.PLAY, Direction.OUTGOING, 0x24, 0x0A),
PLAY_BLOCK_CHANGE(State.PLAY, Direction.OUTGOING, 0x23, 0x0B),
PLAY_BOSS_BAR(State.PLAY, Direction.OUTGOING, -1, 0x0C),
PLAY_SERVER_DIFFICULTY(State.PLAY, Direction.OUTGOING, 0x41, 0x0D),
PLAY_TAB_COMPLETE(State.PLAY, Direction.OUTGOING, 0x3A, 0x0E),
PLAY_CHAT_MESSAGE(State.PLAY, Direction.OUTGOING, 0x02, 0x0F),
PLAY_MULTI_BLOCK_CHANGE(State.PLAY, Direction.OUTGOING, 0x22, 0x10),
PLAY_CONFIRM_TRANSACTION(State.PLAY, Direction.OUTGOING, 0x32, 0x11),
PLAY_CLOSE_WINDOW(State.PLAY, Direction.OUTGOING, 0x2E, 0x12),
PLAY_OPEN_WINDOW(State.PLAY, Direction.OUTGOING, 0x2D, 0x13),
PLAY_WINDOW_ITEMS(State.PLAY, Direction.OUTGOING, 0x30, 0x14),
PLAY_WINDOW_PROPERTY(State.PLAY, Direction.OUTGOING, 0x31, 0x15),
PLAY_SET_SLOT(State.PLAY, Direction.OUTGOING, 0x2F, 0x16),
PLAY_SET_COOLDOWN(State.PLAY, Direction.OUTGOING, -1, 0x17),
PLAY_PLUGIN_MESSAGE(State.PLAY, Direction.OUTGOING, 0x3F, 0x18),
PLAY_NAMED_SOUND_EFFECT(State.PLAY, Direction.OUTGOING, 0x29, 0x19),
PLAY_DISCONNECT(State.PLAY, Direction.OUTGOING, 0x40, 0x1A),
PLAY_ENTITY_STATUS(State.PLAY, Direction.OUTGOING, 0x1A, 0x1B),
PLAY_EXPLOSION(State.PLAY, Direction.OUTGOING, 0x27, 0x1C),
PLAY_UNLOAD_CHUNK(State.PLAY, Direction.OUTGOING, -1, 0x1D),
PLAY_CHANGE_GAME_STATE(State.PLAY, Direction.OUTGOING, 0x2B, 0x1E),
PLAY_KEEP_ALIVE(State.PLAY, Direction.OUTGOING, 0x00, 0x1F),
PLAY_CHUNK_DATA(State.PLAY, Direction.OUTGOING, 0x21, 0x20),
PLAY_EFFECT(State.PLAY, Direction.OUTGOING, 0x28, 0x21),
PLAY_PARTICLE(State.PLAY, Direction.OUTGOING, 0x2A, 0x22),
PLAY_JOIN_GAME(State.PLAY, Direction.OUTGOING, 0x01, 0x23),
PLAY_MAP(State.PLAY, Direction.OUTGOING, 0x34, 0x24),
PLAY_ENTITY_RELATIVE_MOVE(State.PLAY, Direction.OUTGOING, 0x15, 0x25),
PLAY_ENTITY_LOOK_MOVE(State.PLAY, Direction.OUTGOING, 0x17, 0x26),
PLAY_ENTITY_LOOK(State.PLAY, Direction.OUTGOING, 0x16, 0x27),
PLAY_ENTITY(State.PLAY, Direction.OUTGOING, 0x14, 0x28),
PLAY_VEHICLE_MOVE(State.PLAY, Direction.OUTGOING, -1, 0x29),
PLAY_OPEN_SIGN_EDITOR(State.PLAY, Direction.OUTGOING, 0x36, 0x2A),
PLAY_PLAYER_ABILITIES(State.PLAY, Direction.OUTGOING, 0x39, 0x2B),
PLAY_COMBAT_EVENT(State.PLAY, Direction.OUTGOING, 0x42, 0x2C),
PLAY_PLAYER_LIST_ITEM(State.PLAY, Direction.OUTGOING, 0x38, 0x2D),
PLAY_PLAYER_POSITION_LOOK(State.PLAY, Direction.OUTGOING, 0x08, 0x2E),
PLAY_USE_BED(State.PLAY, Direction.OUTGOING, 0x0A, 0x2F),
PLAY_DESTROY_ENTITIES(State.PLAY, Direction.OUTGOING, 0x13, 0x30),
PLAY_REMOVE_ENTITY_EFFECT(State.PLAY, Direction.OUTGOING, 0x1E, 0x31),
PLAY_RESOURCE_PACK_SEND(State.PLAY, Direction.OUTGOING, 0x48, 0x32),
PLAY_RESPAWN(State.PLAY, Direction.OUTGOING, 0x07, 0x33),
PLAY_ENTITY_HEAD_LOOK(State.PLAY, Direction.OUTGOING, 0x19, 0x34),
PLAY_WORLD_BORDER(State.PLAY, Direction.OUTGOING, 0x44, 0x35),
PLAY_CAMERA(State.PLAY, Direction.OUTGOING, 0x43, 0x36),
PLAY_HELD_ITEM_CHANGE(State.PLAY, Direction.OUTGOING, 0x09, 0x37),
PLAY_DISPLAY_SCOREBOARD(State.PLAY, Direction.OUTGOING, 0x3D, 0x38),
PLAY_ENTITY_METADATA(State.PLAY, Direction.OUTGOING, 0x1C, 0x39),
PLAY_ATTACH_ENTITY(State.PLAY, Direction.OUTGOING, 0x1B, 0x3A),
PLAY_ENTITY_VELOCITY(State.PLAY, Direction.OUTGOING, 0x12, 0x3B),
PLAY_ENTITY_EQUIPMENT(State.PLAY, Direction.OUTGOING, 0x04, 0x3C),
PLAY_SET_XP(State.PLAY, Direction.OUTGOING, 0x1F, 0x3D),
PLAY_UPDATE_HEALTH(State.PLAY, Direction.OUTGOING, 0x06, 0x3E),
PLAY_SCOREBOARD_OBJ(State.PLAY, Direction.OUTGOING, 0x3B, 0x3F),
PLAY_SET_PASSENGERS(State.PLAY, Direction.OUTGOING, -1, 0x40),
PLAY_TEAM(State.PLAY, Direction.OUTGOING, 0x3E, 0x41),
PLAY_UPDATE_SCORE(State.PLAY, Direction.OUTGOING, 0x3C, 0x42),
PLAY_SPAWN_POSITION(State.PLAY, Direction.OUTGOING, 0x05, 0x43),
PLAY_TIME_UPDATE(State.PLAY, Direction.OUTGOING, 0x03, 0x44),
PLAY_TITLE(State.PLAY, Direction.OUTGOING, 0x45, 0x45),
PLAY_UPDATE_SIGN(State.PLAY, Direction.OUTGOING, 0x33, 0x46),
PLAY_SOUND_EFFECT(State.PLAY, Direction.OUTGOING, -1, 0x47),
PLAY_PLAYER_LIST_HEADER_FOOTER(State.PLAY, Direction.OUTGOING, 0x47, 0x48),
PLAY_COLLECT_ITEM(State.PLAY, Direction.OUTGOING, 0x0D, 0x49),
PLAY_ENTITY_TELEPORT(State.PLAY, Direction.OUTGOING, 0x18, 0x4A),
PLAY_ENTITY_PROPERTIES(State.PLAY, Direction.OUTGOING, 0x20, 0x4B),
PLAY_ENTITY_EFFECT(State.PLAY, Direction.OUTGOING, 0x1D, 0x4C),
PLAY_MAP_CHUNK_BULK(State.PLAY, Direction.OUTGOING, 0x26, -1),
PLAY_SET_COMPRESSION(State.PLAY, Direction.OUTGOING, 0x46, -1),
PLAY_UPDATE_ENTITY_NBT(State.PLAY, Direction.OUTGOING, 0x49, -1),;
private State state;
private Direction direction;
private int packetID;
private int newPacketID = -1;
PacketType(State state, Direction direction, int packetID) {
this.state = state;
this.direction = direction;
this.packetID = packetID;
this.newPacketID = packetID;
}
PacketType(State state, Direction direction, int packetID, int newPacketID) {
this.state = state;
this.direction = direction;
this.packetID = packetID;
this.newPacketID = newPacketID;
}
public State getState() {
return state;
}
public Direction getDirection() {
return direction;
}
public int getPacketID() {
return packetID;
}
public int getNewPacketID() {
return newPacketID;
}
public static PacketType findNewPacket(State state, Direction direction, int id) {
if(id == -1)
return null;
for (PacketType pt : values()) {
if (pt.getNewPacketID() == id
&& pt.getState() == state
&& pt.getDirection() == direction)
return pt;
}
return null;
}
public static PacketType findOldPacket(State state, Direction direction, int id) {
if(id == -1)
return null;
for (PacketType pt : values()) {
if (pt.getPacketID() == id
&& pt.getState() == state
&& pt.getDirection() == direction)
return pt;
}
return null;
}
public static PacketType getIncomingPacket(State state, int id) {
return findNewPacket(state, Direction.INCOMING, id);
}
public static PacketType getOutgoingPacket(State state, int id) {
return findOldPacket(state, Direction.OUTGOING, id);
}
}
package us.myles.ViaVersion.packets;
import java.util.HashMap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
public enum PacketType {
/* Handshake serverbound */
HANDSHAKE(State.HANDSHAKE, Direction.INCOMING, 0x00),
/* Login serverbound */
LOGIN_START(State.LOGIN, Direction.INCOMING, 0x00),
LOGIN_ENCRYPTION_RESPONSE(State.LOGIN, Direction.INCOMING, 0x01),
/* Login clientbound */
LOGIN_DISCONNECT(State.LOGIN, Direction.OUTGOING, 0x00),
LOGIN_ENCRYPTION_REQUEST(State.LOGIN, Direction.OUTGOING, 0x01),
LOGIN_SUCCESS(State.LOGIN, Direction.OUTGOING, 0x02),
LOGIN_SETCOMPRESSION(State.LOGIN, Direction.OUTGOING, 0x03),
/* Status serverbound */
STATUS_REQUEST(State.STATUS, Direction.INCOMING, 0x00),
STATUS_PING(State.STATUS, Direction.INCOMING, 0x01),
/* Status clientbound */
STATUS_RESPONSE(State.STATUS, Direction.OUTGOING, 0x00),
STATUS_PONG(State.STATUS, Direction.OUTGOING, 0x01),
/* Play serverbound */
PLAY_TP_CONFIRM(State.PLAY, Direction.INCOMING, -1, 0x00),
PLAY_TAB_COMPLETE_REQUEST(State.PLAY, Direction.INCOMING, 0x14, 0x01),
PLAY_CHAT_MESSAGE_CLIENT(State.PLAY, Direction.INCOMING, 0x01, 0x02),
PLAY_CLIENT_STATUS(State.PLAY, Direction.INCOMING, 0x16, 0x03),
PLAY_CLIENT_SETTINGS(State.PLAY, Direction.INCOMING, 0x15, 0x04),
PLAY_CONFIRM_TRANS(State.PLAY, Direction.INCOMING, 0x0F, 0x05),
PLAY_ENCHANT_ITEM(State.PLAY, Direction.INCOMING, 0x11, 0x06),
PLAY_CLICK_WINDOW(State.PLAY, Direction.INCOMING, 0x0E, 0x07),
PLAY_CLOSE_WINDOW_REQUEST(State.PLAY, Direction.INCOMING, 0x0D, 0x08),
PLAY_PLUGIN_MESSAGE_REQUEST(State.PLAY, Direction.INCOMING, 0x17, 0x09),
PLAY_USE_ENTITY(State.PLAY, Direction.INCOMING, 0x02, 0x0A),
PLAY_KEEP_ALIVE_REQUEST(State.PLAY, Direction.INCOMING, 0x00, 0x0B),
PLAY_PLAYER_POSITION_REQUEST(State.PLAY, Direction.INCOMING, 0x04, 0x0C),
PLAY_PLAYER_POSITION_LOOK_REQUEST(State.PLAY, Direction.INCOMING, 0x06, 0x0D),
PLAY_PLAYER_LOOK_REQUEST(State.PLAY, Direction.INCOMING, 0x05, 0x0E),
PLAY_PLAYER(State.PLAY, Direction.INCOMING, 0x03, 0x0F),
PLAY_VEHICLE_MOVE_REQUEST(State.PLAY, Direction.INCOMING, -1, 0x10),
PLAY_STEER_BOAT(State.PLAY, Direction.INCOMING, -1, 0x11),
PLAY_PLAYER_ABILITIES_REQUEST(State.PLAY, Direction.INCOMING, 0x13, 0x12),
PLAY_PLAYER_DIGGING(State.PLAY, Direction.INCOMING, 0x07, 0x13),
PLAY_ENTITY_ACTION(State.PLAY, Direction.INCOMING, 0x0B, 0x14),
PLAY_STEER_VEHICLE(State.PLAY, Direction.INCOMING, 0x0C, 0x15),
PLAY_RESOURCE_PACK_STATUS(State.PLAY, Direction.INCOMING, 0x19, 0x16),
PLAY_HELD_ITEM_CHANGE_REQUEST(State.PLAY, Direction.INCOMING, 0x09, 0x17),
PLAY_CREATIVE_INVENTORY_ACTION(State.PLAY, Direction.INCOMING, 0x10, 0x18),
PLAY_UPDATE_SIGN_REQUEST(State.PLAY, Direction.INCOMING, 0x12, 0x19),
PLAY_ANIMATION_REQUEST(State.PLAY, Direction.INCOMING, 0x0A, 0x1A),
PLAY_SPECTATE(State.PLAY, Direction.INCOMING, 0x18, 0x1B),
PLAY_PLAYER_BLOCK_PLACEMENT(State.PLAY, Direction.INCOMING, 0x08, 0x1C),
PLAY_USE_ITEM(State.PLAY, Direction.INCOMING, -1, 0x1D),
/* Play clientbound */
PLAY_SPAWN_OBJECT(State.PLAY, Direction.OUTGOING, 0x0E, 0x00),
PLAY_SPAWN_XP_ORB(State.PLAY, Direction.OUTGOING, 0x11, 0x01),
PLAY_SPAWN_GLOBAL_ENTITY(State.PLAY, Direction.OUTGOING, 0x2C, 0x02),
PLAY_SPAWN_MOB(State.PLAY, Direction.OUTGOING, 0x0F, 0x03),
PLAY_SPAWN_PAINTING(State.PLAY, Direction.OUTGOING, 0x10, 0x04),
PLAY_SPAWN_PLAYER(State.PLAY, Direction.OUTGOING, 0x0C, 0x05),
PLAY_ANIMATION(State.PLAY, Direction.OUTGOING, 0x0B, 0x06),
PLAY_STATS(State.PLAY, Direction.OUTGOING, 0x37, 0x07),
PLAY_BLOCK_BREAK_ANIMATION(State.PLAY, Direction.OUTGOING, 0x25, 0x08),
PLAY_UPDATE_BLOCK_ENTITY(State.PLAY, Direction.OUTGOING, 0x35, 0x09),
PLAY_BLOCK_ACTION(State.PLAY, Direction.OUTGOING, 0x24, 0x0A),
PLAY_BLOCK_CHANGE(State.PLAY, Direction.OUTGOING, 0x23, 0x0B),
PLAY_BOSS_BAR(State.PLAY, Direction.OUTGOING, -1, 0x0C),
PLAY_SERVER_DIFFICULTY(State.PLAY, Direction.OUTGOING, 0x41, 0x0D),
PLAY_TAB_COMPLETE(State.PLAY, Direction.OUTGOING, 0x3A, 0x0E),
PLAY_CHAT_MESSAGE(State.PLAY, Direction.OUTGOING, 0x02, 0x0F),
PLAY_MULTI_BLOCK_CHANGE(State.PLAY, Direction.OUTGOING, 0x22, 0x10),
PLAY_CONFIRM_TRANSACTION(State.PLAY, Direction.OUTGOING, 0x32, 0x11),
PLAY_CLOSE_WINDOW(State.PLAY, Direction.OUTGOING, 0x2E, 0x12),
PLAY_OPEN_WINDOW(State.PLAY, Direction.OUTGOING, 0x2D, 0x13),
PLAY_WINDOW_ITEMS(State.PLAY, Direction.OUTGOING, 0x30, 0x14),
PLAY_WINDOW_PROPERTY(State.PLAY, Direction.OUTGOING, 0x31, 0x15),
PLAY_SET_SLOT(State.PLAY, Direction.OUTGOING, 0x2F, 0x16),
PLAY_SET_COOLDOWN(State.PLAY, Direction.OUTGOING, -1, 0x17),
PLAY_PLUGIN_MESSAGE(State.PLAY, Direction.OUTGOING, 0x3F, 0x18),
PLAY_NAMED_SOUND_EFFECT(State.PLAY, Direction.OUTGOING, 0x29, 0x19),
PLAY_DISCONNECT(State.PLAY, Direction.OUTGOING, 0x40, 0x1A),
PLAY_ENTITY_STATUS(State.PLAY, Direction.OUTGOING, 0x1A, 0x1B),
PLAY_EXPLOSION(State.PLAY, Direction.OUTGOING, 0x27, 0x1C),
PLAY_UNLOAD_CHUNK(State.PLAY, Direction.OUTGOING, -1, 0x1D),
PLAY_CHANGE_GAME_STATE(State.PLAY, Direction.OUTGOING, 0x2B, 0x1E),
PLAY_KEEP_ALIVE(State.PLAY, Direction.OUTGOING, 0x00, 0x1F),
PLAY_CHUNK_DATA(State.PLAY, Direction.OUTGOING, 0x21, 0x20),
PLAY_EFFECT(State.PLAY, Direction.OUTGOING, 0x28, 0x21),
PLAY_PARTICLE(State.PLAY, Direction.OUTGOING, 0x2A, 0x22),
PLAY_JOIN_GAME(State.PLAY, Direction.OUTGOING, 0x01, 0x23),
PLAY_MAP(State.PLAY, Direction.OUTGOING, 0x34, 0x24),
PLAY_ENTITY_RELATIVE_MOVE(State.PLAY, Direction.OUTGOING, 0x15, 0x25),
PLAY_ENTITY_LOOK_MOVE(State.PLAY, Direction.OUTGOING, 0x17, 0x26),
PLAY_ENTITY_LOOK(State.PLAY, Direction.OUTGOING, 0x16, 0x27),
PLAY_ENTITY(State.PLAY, Direction.OUTGOING, 0x14, 0x28),
PLAY_VEHICLE_MOVE(State.PLAY, Direction.OUTGOING, -1, 0x29),
PLAY_OPEN_SIGN_EDITOR(State.PLAY, Direction.OUTGOING, 0x36, 0x2A),
PLAY_PLAYER_ABILITIES(State.PLAY, Direction.OUTGOING, 0x39, 0x2B),
PLAY_COMBAT_EVENT(State.PLAY, Direction.OUTGOING, 0x42, 0x2C),
PLAY_PLAYER_LIST_ITEM(State.PLAY, Direction.OUTGOING, 0x38, 0x2D),
PLAY_PLAYER_POSITION_LOOK(State.PLAY, Direction.OUTGOING, 0x08, 0x2E),
PLAY_USE_BED(State.PLAY, Direction.OUTGOING, 0x0A, 0x2F),
PLAY_DESTROY_ENTITIES(State.PLAY, Direction.OUTGOING, 0x13, 0x30),
PLAY_REMOVE_ENTITY_EFFECT(State.PLAY, Direction.OUTGOING, 0x1E, 0x31),
PLAY_RESOURCE_PACK_SEND(State.PLAY, Direction.OUTGOING, 0x48, 0x32),
PLAY_RESPAWN(State.PLAY, Direction.OUTGOING, 0x07, 0x33),
PLAY_ENTITY_HEAD_LOOK(State.PLAY, Direction.OUTGOING, 0x19, 0x34),
PLAY_WORLD_BORDER(State.PLAY, Direction.OUTGOING, 0x44, 0x35),
PLAY_CAMERA(State.PLAY, Direction.OUTGOING, 0x43, 0x36),
PLAY_HELD_ITEM_CHANGE(State.PLAY, Direction.OUTGOING, 0x09, 0x37),
PLAY_DISPLAY_SCOREBOARD(State.PLAY, Direction.OUTGOING, 0x3D, 0x38),
PLAY_ENTITY_METADATA(State.PLAY, Direction.OUTGOING, 0x1C, 0x39),
PLAY_ATTACH_ENTITY(State.PLAY, Direction.OUTGOING, 0x1B, 0x3A),
PLAY_ENTITY_VELOCITY(State.PLAY, Direction.OUTGOING, 0x12, 0x3B),
PLAY_ENTITY_EQUIPMENT(State.PLAY, Direction.OUTGOING, 0x04, 0x3C),
PLAY_SET_XP(State.PLAY, Direction.OUTGOING, 0x1F, 0x3D),
PLAY_UPDATE_HEALTH(State.PLAY, Direction.OUTGOING, 0x06, 0x3E),
PLAY_SCOREBOARD_OBJ(State.PLAY, Direction.OUTGOING, 0x3B, 0x3F),
PLAY_SET_PASSENGERS(State.PLAY, Direction.OUTGOING, -1, 0x40),
PLAY_TEAM(State.PLAY, Direction.OUTGOING, 0x3E, 0x41),
PLAY_UPDATE_SCORE(State.PLAY, Direction.OUTGOING, 0x3C, 0x42),
PLAY_SPAWN_POSITION(State.PLAY, Direction.OUTGOING, 0x05, 0x43),
PLAY_TIME_UPDATE(State.PLAY, Direction.OUTGOING, 0x03, 0x44),
PLAY_TITLE(State.PLAY, Direction.OUTGOING, 0x45, 0x45),
PLAY_UPDATE_SIGN(State.PLAY, Direction.OUTGOING, 0x33, 0x46),
PLAY_SOUND_EFFECT(State.PLAY, Direction.OUTGOING, -1, 0x47),
PLAY_PLAYER_LIST_HEADER_FOOTER(State.PLAY, Direction.OUTGOING, 0x47, 0x48),
PLAY_COLLECT_ITEM(State.PLAY, Direction.OUTGOING, 0x0D, 0x49),
PLAY_ENTITY_TELEPORT(State.PLAY, Direction.OUTGOING, 0x18, 0x4A),
PLAY_ENTITY_PROPERTIES(State.PLAY, Direction.OUTGOING, 0x20, 0x4B),
PLAY_ENTITY_EFFECT(State.PLAY, Direction.OUTGOING, 0x1D, 0x4C),
PLAY_MAP_CHUNK_BULK(State.PLAY, Direction.OUTGOING, 0x26, -1),
PLAY_SET_COMPRESSION(State.PLAY, Direction.OUTGOING, 0x46, -1),
PLAY_UPDATE_ENTITY_NBT(State.PLAY, Direction.OUTGOING, 0x49, -1),;
private State state;
private Direction direction;
private int packetID;
private int newPacketID = -1;
PacketType(State state, Direction direction, int packetID) {
this.state = state;
this.direction = direction;
this.packetID = packetID;
this.newPacketID = packetID;
}
PacketType(State state, Direction direction, int packetID, int newPacketID) {
this.state = state;
this.direction = direction;
this.packetID = packetID;
this.newPacketID = newPacketID;
}
public State getState() {
return state;
}
public Direction getDirection() {
return direction;
}
public int getPacketID() {
return packetID;
}
public int getNewPacketID() {
return newPacketID;
}
public static PacketType findNewPacket(State state, Direction direction, int id) {
return newids.get(toShort((short) id, (short)direction.ordinal(), (short) state.ordinal()));
}
public static PacketType findOldPacket(State state, Direction direction, int id) {
return oldids.get(toShort((short) id, (short)direction.ordinal(), (short) state.ordinal()));
}
public static PacketType getIncomingPacket(State state, int id) {
return findNewPacket(state, Direction.INCOMING, id);
}
public static PacketType getOutgoingPacket(State state, int id) {
return findOldPacket(state, Direction.OUTGOING, id);
}
private static short toShort(short id, short direction, short state) {
return (short) ((id & 0x00FF) | (direction<<8) & 0x0F00 | (state << 12) & 0xF000);
}
private static HashMap<Short, PacketType> oldids = new HashMap<Short, PacketType>();
private static HashMap<Short, PacketType> newids = new HashMap<Short, PacketType>();
static {
for(PacketType pt : PacketType.values()) {
oldids.put(toShort((short) pt.getPacketID(), (short)pt.getDirection().ordinal(), (short)pt.getState().ordinal()), pt);
newids.put(toShort((short) pt.getNewPacketID(), (short)pt.getDirection().ordinal(), (short)pt.getState().ordinal()), pt);
}
}
}
@@ -329,8 +329,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,28 +1,31 @@
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;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.ViaVersionPlugin;
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;
import us.myles.ViaVersion.packets.PacketType;
import us.myles.ViaVersion.packets.State;
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.*;
@@ -31,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();
@@ -40,7 +44,9 @@ public class OutgoingTransformer {
private final Map<Integer, Integer> vehicleMap = new HashMap<>();
private final Set<Integer> validBlocking = new HashSet<>();
private final Set<Integer> knownHolograms = new HashSet<>();
private final Map<Integer, BossBar> bossBarMap = new HashMap<>();
private boolean autoTeam = false;
private boolean teamExists = false;
public OutgoingTransformer(ConnectionInfo info) {
this.info = info;
@@ -51,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\":\"\"}";
@@ -223,7 +229,7 @@ public class OutgoingTransformer {
int x = input.readInt();
output.writeDouble(x / 32D);
int y = input.readInt();
if(plugin.isHologramPatch() & knownHolograms.contains(id)){
if (plugin.isHologramPatch() & knownHolograms.contains(id)) {
y = (int) ((y) + (plugin.getHologramYOffset() * 32D));
}
output.writeDouble(y / 32D);
@@ -246,7 +252,7 @@ public class OutgoingTransformer {
int x = input.readByte();
output.writeShort(x * 128);
int y = input.readByte();
if(plugin.isHologramPatch() & knownHolograms.contains(id)){
if (plugin.isHologramPatch() & knownHolograms.contains(id)) {
y = (int) ((y) + plugin.getHologramYOffset());
}
output.writeShort(y * 128);
@@ -269,7 +275,7 @@ public class OutgoingTransformer {
short x = (short) (input.readByte());
output.writeShort(x * 128);
short y = (short) (input.readByte());
if(plugin.isHologramPatch() & knownHolograms.contains(id)){
if (plugin.isHologramPatch() & knownHolograms.contains(id)) {
y = (short) (y - 1);
}
output.writeShort(y * 128);
@@ -292,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;
@@ -384,6 +391,12 @@ public class OutgoingTransformer {
clientEntityTypes.remove(entityID);
knownHolograms.remove(entityID);
PacketUtil.writeVarInt(entityID, output);
// Remvoe boss bar
BossBar bar = bossBarMap.remove(entityID);
if (bar != null) {
bar.hide();
}
}
return;
}
@@ -595,7 +608,24 @@ public class OutgoingTransformer {
clientEntityTypes.put(id, EntityType.PLAYER);
info.setEntityID(id);
output.writeInt(id);
output.writeBytes(input);
int gamemode = input.readUnsignedByte();
output.writeByte(gamemode);
int dimension = input.readByte();
output.writeByte(dimension);
int difficulty = input.readUnsignedByte();
if (info.getProtocol() >= 108) {
// 1.8.1 Pre 2
output.writeInt(difficulty);
} else {
output.writeByte(difficulty);
}
int maxPlayers = input.readUnsignedByte();
output.writeByte(maxPlayers);
String level = PacketUtil.readString(input);
PacketUtil.writeString(level, output);
boolean reducedDebug = input.readBoolean();
output.writeBoolean(reducedDebug);
return;
}
if (packet == PacketType.PLAY_SERVER_DIFFICULTY) {
@@ -650,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) {
@@ -727,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);
@@ -805,19 +783,23 @@ public class OutgoingTransformer {
PacketUtil.writeString("viaversion", buf); // Use viaversion as name
if (b) {
// add
buf.writeByte(0); // make team
PacketUtil.writeString("viaversion", buf);
PacketUtil.writeString("", buf); // prefix
PacketUtil.writeString("", buf); // suffix
buf.writeByte(0); // friendly fire
PacketUtil.writeString("", buf); // nametags
PacketUtil.writeString("never", buf); // collision rule :)
buf.writeByte(0); // color
if (!teamExists) {
buf.writeByte(0); // make team
PacketUtil.writeString("viaversion", buf);
PacketUtil.writeString("", buf); // prefix
PacketUtil.writeString("", buf); // suffix
buf.writeByte(0); // friendly fire
PacketUtil.writeString("", buf); // nametags
PacketUtil.writeString("never", buf); // collision rule :)
buf.writeByte(0); // color
} else
buf.writeByte(3);
PacketUtil.writeVarInt(1, buf); // player count
PacketUtil.writeString(info.getUsername(), buf); // us
} else {
buf.writeByte(1); // remove team
}
teamExists = b;
info.sendRawPacket(buf);
}
@@ -832,6 +814,32 @@ public class OutgoingTransformer {
for (MetadataRewriter.Entry entry : list) {
handleMetadata(entityID, entry, type);
}
// Fix: wither (crash fix)
if (type == EntityType.WITHER) {
// Remove custom value if already exist
Iterator<Entry> it = list.iterator();
while (it.hasNext()) {
Entry e = it.next();
if (e.getOldID() == 10) {
it.remove();
}
}
list.add(new Entry(MetaIndex.WITHER_PROPERTIES, (byte) 0, 10));
}
// Fix: Dragon (crash fix)
if (type == EntityType.ENDER_DRAGON) {
// Remove custom value if already exist
Iterator<Entry> it = list.iterator();
while (it.hasNext()) {
Entry e = it.next();
if (e.getOldID() == 11) {
it.remove();
}
}
list.add(new Entry(MetaIndex.ENDERDRAGON_PHASE, (byte) 0, 11));
}
MetadataRewriter.writeMetadata1_9(type, list, output);
}
@@ -856,11 +864,11 @@ public class OutgoingTransformer {
}
}
}
if(type == EntityType.ARMOR_STAND && plugin.isHologramPatch()){
if(entry.getOldID() == 0){
if (type == EntityType.ARMOR_STAND && plugin.isHologramPatch()) {
if (entry.getOldID() == 0) {
byte data = (byte) entry.getValue();
if((data & 0x20) == 0x20){
if(!knownHolograms.contains(entityID)) {
if ((data & 0x20) == 0x20) {
if (!knownHolograms.contains(entityID)) {
knownHolograms.add(entityID);
// Send movement
ByteBuf buf = info.getChannel().alloc().buffer();
@@ -875,6 +883,39 @@ public class OutgoingTransformer {
}
}
}
// Boss bar
if (plugin.isBossbarPatch()) {
if (type == EntityType.ENDER_DRAGON || type == EntityType.WITHER) {
if (entry.getOldID() == 2) {
BossBar bar = bossBarMap.get(entityID);
String title = (String) entry.getValue();
title = title.isEmpty() ? (type == EntityType.ENDER_DRAGON ? "Ender Dragon" : "Wither") : title;
if (bar == null) {
bar = ViaVersion.getInstance().createBossBar(title, BossColor.PINK, BossStyle.SOLID);
bossBarMap.put(entityID, bar);
bar.addPlayer(info.getPlayer());
bar.show();
} else {
bar.setTitle(title);
}
} else if (entry.getOldID() == 6 && !plugin.isBossbarAntiflicker()) { // If anti flicker is enabled, don't update health
BossBar bar = bossBarMap.get(entityID);
// Make health range between 0 and 1
float maxHealth = type == EntityType.ENDER_DRAGON ? 200.0f : 300.0f;
float health = Math.max(0.0f, Math.min(((float) entry.getValue()) / maxHealth, 1.0f));
if (bar == null) {
String title = type == EntityType.ENDER_DRAGON ? "Ender Dragon" : "Wither";
bar = ViaVersion.getInstance().createBossBar(title, health, BossColor.PINK, BossStyle.SOLID);
bossBarMap.put(entityID, bar);
bar.addPlayer(info.getPlayer());
bar.show();
} else {
bar.setHealth(health);
}
}
}
}
}
@@ -0,0 +1,246 @@
package us.myles.ViaVersion.util;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Pattern;
public class Configuration extends YamlConfiguration {
private List<String> mainHeader = Lists.newArrayList();
private final Map<String, List<String>> headers = Maps.newConcurrentMap();
private final File file;
private boolean loadHeaders;
public Configuration(File file) {
this.file = file;
}
/**
* Set the main header displayed at top of config.
*
* @param header header
*/
public void mainHeader(String... header) {
mainHeader = Arrays.asList(header);
}
/**
* Get main header displayed at top of config.
*
* @return header
*/
public List<String> mainHeader() {
return mainHeader;
}
/**
* Set option header.
*
* @param key of option (or section)
* @param header of option (or section)
*/
public void header(String key, String... header) {
// String value = Joiner.on('\n').join(header);
headers.put(key, Arrays.asList(header));
}
/**
* Get header of option
*
* @param key of option (or section)
* @return Header
*/
public List<String> header(String key) {
return headers.get(key);
}
public <T> T get(String key, Class<T> type) {
return type.cast(get(key));
}
/**
* Reload config from file.
*/
public void reload() {
reload(headers.isEmpty());
}
/**
* Reload config from file.
*
* @param loadHeaders Whether or not to load headers.
*/
public void reload(boolean loadHeaders) {
this.loadHeaders = loadHeaders;
try {
load(file);
} catch(Exception e) {
Bukkit.getLogger().log(Level.WARNING, "failed to reload file", e);
}
}
@Override
public void loadFromString(String contents) throws InvalidConfigurationException {
if(!loadHeaders) {
super.loadFromString(contents);
return;
}
StringBuilder memoryData = new StringBuilder();
// Parse headers
final int indentLength = options().indent();
final String pathSeparator = Character.toString(options().pathSeparator());
int currentIndents = 0;
String key = "";
List<String> headers = Lists.newArrayList();
for(String line : contents.split("\n")) {
if(line.isEmpty()) continue; // Skip empty lines
int indent = getSuccessiveCharCount(line, ' ');
String subline = indent > 0 ? line.substring(indent) : line;
if(subline.startsWith("#")) {
if(subline.startsWith("#>")) {
String txt = subline.startsWith("#> ") ? subline.substring(3) : subline.substring(2);
mainHeader.add(txt);
continue; // Main header, handled by bukkit
}
// Add header to list
String txt = subline.startsWith("# ") ? subline.substring(2) : subline.substring(1);
headers.add(txt);
continue;
}
int indents = indent / indentLength;
if(indents <= currentIndents) {
// Remove last section of key
String[] array = key.split(Pattern.quote(pathSeparator));
int backspace = currentIndents - indents + 1;
key = join(array, options().pathSeparator(), 0, array.length - backspace);
}
// Add new section to key
String separator = key.length() > 0 ? pathSeparator : "";
String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line;
key += separator + lineKey.substring(indent);
currentIndents = indents;
memoryData.append(line).append('\n');
if(!headers.isEmpty()) {
this.headers.put(key, headers);
headers = Lists.newArrayList();
}
}
// Parse remaining text
super.loadFromString(memoryData.toString());
// Clear bukkit header
options().header(null);
}
/**
* Save config to file
*/
public void save() {
if(headers.isEmpty() && mainHeader.isEmpty()) {
try {
super.save(file);
} catch(IOException e) {
Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e);
}
return;
}
// Custom save
final int indentLength = options().indent();
final String pathSeparator = Character.toString(options().pathSeparator());
String content = saveToString();
StringBuilder fileData = new StringBuilder(buildHeader());
int currentIndents = 0;
String key = "";
for(String h : mainHeader) {
// Append main header to top of file
fileData.append("#> ").append(h).append('\n');
}
for(String line : content.split("\n")) {
if(line.isEmpty()) continue; // Skip empty lines
int indent = getSuccessiveCharCount(line, ' ');
int indents = indent / indentLength;
String indentText = indent > 0 ? line.substring(0, indent) : "";
if(indents <= currentIndents) {
// Remove last section of key
String[] array = key.split(Pattern.quote(pathSeparator));
int backspace = currentIndents - indents + 1;
key = join(array, options().pathSeparator(), 0, array.length - backspace);
}
// Add new section to key
String separator = key.length() > 0 ? pathSeparator : "";
String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line;
key += separator + lineKey.substring(indent);
currentIndents = indents;
List<String> header = headers.get(key);
String headerText = header != null ? addHeaderTags(header, indentText) : "";
fileData.append(headerText).append(line).append('\n');
}
// Write data to file
FileWriter writer = null;
try {
writer = new FileWriter(file);
writer.write(fileData.toString());
writer.flush();
} catch(IOException e) {
Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e);
} finally {
if(writer != null) {
try {
writer.close();
} catch(IOException e) {
}
}
}
}
private String addHeaderTags(List<String> header, String indent) {
StringBuilder builder = new StringBuilder();
for(String line : header) {
builder.append(indent).append("# ").append(line).append('\n');
}
return builder.toString();
}
private String join(String[] array, char joinChar, int start, int length) {
String[] copy = new String[length - start];
System.arraycopy(array, start, copy, 0, length - start);
return Joiner.on(joinChar).join(copy);
}
private int getSuccessiveCharCount(String text, char key) {
int count = 0;
for(int i = 0; i < text.length(); i++) {
if(text.charAt(i) == key) {
count += 1;
} else {
break;
}
}
return count;
}
}
@@ -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());
}
}
}
+7 -4
View File
@@ -1,8 +1,5 @@
# Should ViaVersion check for updates?
checkforupdates: true
# Should we send any bulk chunks, in sync (may be slower but fixes timings)
# Currently broken due to sign updates, possibly will return
sync-chunks: true
# No collide options, these allow you to configure how collision works.
# Do you want us to prevent collision?
prevent-collision: true
@@ -18,4 +15,10 @@ hologram-patch: false
# This is the offset, should work as default when enabled.
hologram-y: -1
# Enable player tick simulation, this fixes eating, drinking, nether portals.
simulate-pt: true
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
# This will show the new effect indicator in the top-right corner for 1.9 players.
use-new-effect-indicator: true