Compare commits

..
27 Commits
Author SHA1 Message Date
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
20 changed files with 1218 additions and 329 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
# ViaVersion 0.6.2
# ViaVersion 0.6.4
[![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**
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>us.myles</groupId>
<artifactId>viaversion</artifactId>
<version>0.6.3-SNAPSHOT</version>
<version>0.6.5-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ViaVersion</name>
@@ -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() {
@@ -24,9 +24,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 +66,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 +95,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()) {
@@ -215,11 +235,6 @@ 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);
}
@@ -236,6 +251,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);
}
@@ -77,11 +77,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();
}
@@ -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);
}
}
@@ -28,38 +28,6 @@ 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);
}
@@ -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);
}
}
@@ -91,6 +91,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 +125,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);
}
}
}
@@ -15,7 +15,14 @@ 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;
@@ -40,7 +47,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;
@@ -223,7 +232,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 +255,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 +278,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);
@@ -384,6 +393,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 +610,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) {
@@ -746,54 +778,15 @@ public class OutgoingTransformer {
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 +798,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 +829,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 +879,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 +898,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) {
@@ -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();
@@ -50,4 +54,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());
}
}
}
+5 -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,8 @@ 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