Initial Commit, need to upload license. You may not claim credit for anything given here if used. All rights reversed excluding extracts from MCProtocolLib.

This commit is contained in:
Myles 2016-02-28 22:44:33 +00:00
commit 67b31c5060
31 changed files with 2561 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target/
.idea/
*.iml

33
pom.xml Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>us.myles</groupId>
<artifactId>ViaVersion</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<!-- Sorry about this path >:( I promise to fix it when I stop using NMS :) -->
<groupId>org.spigot</groupId>
<artifactId>spigot1.8</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>E:/spigot_server.jar</systemPath>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,126 @@
package org.spacehq.mc.protocol.data.game.chunk;
import io.netty.buffer.ByteBuf;
import us.myles.ViaVersion.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<Integer>();
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<Integer>();
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));
}
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<Integer>(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;
}
private static int index(int x, int y, int z) {
return y << 8 | z << 4 | x;
}
@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;
}
}

View File

@ -0,0 +1,46 @@
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;
}
}

View File

@ -0,0 +1,69 @@
package org.spacehq.mc.protocol.data.game.chunk;
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(int index = 0; index < chunks.length; index++) {
if(chunks[index] != null) {
if(chunks[index].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 int getX() {
return this.x;
}
public int getZ() {
return this.z;
}
public Chunk[] getChunks() {
return this.chunks;
}
public boolean hasBiomeData() {
return this.biomeData != null;
}
public byte[] getBiomeData() {
return this.biomeData;
}
public boolean hasSkylight() {
return this.skylight;
}
}

View File

@ -0,0 +1,104 @@
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;
}
}

View File

@ -0,0 +1,71 @@
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);
}
}

View File

@ -0,0 +1,114 @@
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) {
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 = 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];
for(int pass = 0; pass < 4; pass++) {
for(int ind = 0; ind < 16; ind++) {
if((bitmask & 1 << ind) != 0) {
if(pass == 0) {
// Block length + Blocklight length
expected += (4096 * 2) + 2048;
}
if(pass == 1) {
chunks[ind] = new Chunk(sky || hasSkyLight);
buf.position(pos / 2);
short[] tempData = new short[4096];
buf.get(tempData, 0, tempData.length);
// convert short array to new one
int index = 0;
for(short ss:tempData){
// 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);
index++;
}
pos += tempData.length * 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(chunks[ind].getSkyLight() != null) {
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) {
biomeData = new byte[256];
System.arraycopy(input, pos, biomeData, 0, biomeData.length);
}
Column column = new Column(x, z, chunks, biomeData);
return column;
}
}

View File

@ -0,0 +1,4 @@
package us.myles.ViaVersion;
public class CancelException extends Exception{
}

View File

@ -0,0 +1,59 @@
package us.myles.ViaVersion;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import us.myles.ViaVersion.packets.State;
import java.util.UUID;
public class ConnectionInfo {
private int protocol = 0;
private State state = State.HANDSHAKE;
private int compression = 0;
private Object lastPacket;
private java.util.UUID UUID;
public int getProtocol() {
return protocol;
}
public void setProtocol(int protocol) {
this.protocol = protocol;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void setCompression(int compression) {
this.compression = compression;
}
public int getCompression() {
return compression;
}
public void setLastPacket(Object lastPacket) {
this.lastPacket = lastPacket;
}
public Object getLastPacket() {
return lastPacket;
}
public void setUUID(UUID UUID) {
this.UUID = UUID;
}
public java.util.UUID getUUID() {
return UUID;
}
public Player getPlayer() {
return UUID == null ? null : Bukkit.getPlayer(UUID);
}
}

View File

@ -0,0 +1,117 @@
package us.myles.ViaVersion;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import net.minecraft.server.v1_8_R3.Entity;
import net.minecraft.server.v1_8_R3.MinecraftServer;
import net.minecraft.server.v1_8_R3.ServerConnection;
import net.minecraft.server.v1_8_R3.WorldServer;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_8_R3.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import us.myles.ViaVersion.handlers.ViaVersionInitializer;
import java.lang.reflect.Field;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class Core extends JavaPlugin {
@Override
public void onEnable() {
System.out.println("ViaVersion enabled, injecting. (Allows 1.8 to be accessed via 1.9)");
/* Obvious message here:
If loading this plugin nobody will be on 1.9 cause only 1.8 so we're fine, as for reloading ugh.
Clients might crash cause of it being a bum maybe? :P
*/
try {
injectPacketHandler();
} catch (Exception e) {
e.printStackTrace();
System.out.println("Unable to inject handlers, this version only supports 1.8.");
}
}
public void injectPacketHandler() throws NoSuchFieldException, IllegalAccessException {
MinecraftServer server = MinecraftServer.getServer();
ServerConnection connection = server.getServerConnection();
List<ChannelFuture> futures = getPrivateField(connection, "g", List.class);
for (ChannelFuture future : futures) {
ChannelPipeline pipeline = future.channel().pipeline();
ChannelHandler bootstrapAcceptor = pipeline.first();
ChannelInitializer<SocketChannel> oldInit = getPrivateField(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
ChannelInitializer newInit = new ViaVersionInitializer(oldInit);
setPrivateField(bootstrapAcceptor, "childHandler", newInit);
}
}
public static Entity getEntity(final UUID player, final int id) {
try {
return Bukkit.getScheduler().callSyncMethod(getPlugin(Core.class), new Callable<Entity>() {
@Override
public Entity call() throws Exception {
Player p = Bukkit.getPlayer(player);
if (p == null) return null;
WorldServer ws = ((CraftWorld) p.getWorld()).getHandle();
for (Entity e : ws.entityList) {
if (e.getId() == id) {
return e;
}
}
System.out.println("Couldn't find in the world!!");
return null;
}
}).get(10, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println("Error fetching entity ");
e.printStackTrace();
return null;
}
}
public static <T> T getPrivateField(Object o, String f, Class<T> t) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getDeclaredField(f);
field.setAccessible(true);
return (T) field.get(o);
}
public static void setPrivateField(Object o, String f, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getDeclaredField(f);
field.setAccessible(true);
field.set(o, value);
}
@Override
public void onDisable() {
}
public static ItemStack getHandItem(final ConnectionInfo info) {
try {
return Bukkit.getScheduler().callSyncMethod(getPlugin(Core.class), new Callable<ItemStack>() {
@Override
public ItemStack call() throws Exception {
if (info.getPlayer() != null) {
return info.getPlayer().getItemInHand();
}
return null;
}
}).get(10, TimeUnit.SECONDS);
} catch (Exception e) {
System.out.println("Error fetching hand item ");
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,341 @@
package us.myles.ViaVersion;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.chunks.MagicBitSet;
import us.myles.ViaVersion.chunks.PacketChunk;
import us.myles.ViaVersion.chunks.PacketChunkData;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.UUID;
public class PacketUtil {
private static Method DECODE_METHOD;
private static Method ENCODE_METHOD;
static {
try {
DECODE_METHOD = ByteToMessageDecoder.class.getDeclaredMethod("decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
DECODE_METHOD.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
System.out.println("Netty issue?");
}
try {
ENCODE_METHOD = MessageToByteEncoder.class.getDeclaredMethod("encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
ENCODE_METHOD.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
System.out.println("Netty issue?");
}
}
public static ByteBuf decompress(ChannelHandlerContext ctx, ByteBuf msg) {
ByteToMessageDecoder x = (ByteToMessageDecoder) ctx.pipeline().get("decompress");
List<Object> output = new ArrayList<Object>();
try {
PacketUtil.DECODE_METHOD.invoke(x, ctx, msg, output);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
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();
try {
PacketUtil.ENCODE_METHOD.invoke(x, ctx, msg, output);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
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) {
Preconditions.checkArgument(s.length() <= Short.MAX_VALUE, "Cannot send string longer than Short.MAX_VALUE (got %s characters)", s.length());
byte[] b = s.getBytes(Charsets.UTF_8);
writeVarInt(b.length, buf);
buf.writeBytes(b);
}
public static String readString(ByteBuf buf) {
int len = readVarInt(buf);
Preconditions.checkArgument(len <= Short.MAX_VALUE, "Cannot receive string longer than Short.MAX_VALUE (got %s characters)", len);
byte[] b = new byte[len];
buf.readBytes(b);
return new String(b, Charsets.UTF_8);
}
public static void writeArrayLegacy(byte[] b, ByteBuf buf, boolean allowExtended) {
// (Integer.MAX_VALUE & 0x1FFF9A ) = 2097050 - Forge's current upper limit
if (allowExtended) {
Preconditions.checkArgument(b.length <= (Integer.MAX_VALUE & 0x1FFF9A), "Cannot send array longer than 2097050 (got %s bytes)", b.length);
} else {
Preconditions.checkArgument(b.length <= Short.MAX_VALUE, "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.length);
}
// Write a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for Forge only)
// No vanilla packet should give a 3 byte packet, this method will still retain vanilla behaviour.
writeVarShort(buf, b.length);
buf.writeBytes(b);
}
public static byte[] readArrayLegacy(ByteBuf buf) {
// Read in a 2 or 3 byte number that represents the length of the packet. (3 byte "shorts" for Forge only)
// No vanilla packet should give a 3 byte packet, this method will still retain vanilla behaviour.
int len = readVarShort(buf);
// (Integer.MAX_VALUE & 0x1FFF9A ) = 2097050 - Forge's current upper limit
Preconditions.checkArgument(len <= (Integer.MAX_VALUE & 0x1FFF9A), "Cannot receive array longer than 2097050 (got %s bytes)", len);
byte[] ret = new byte[len];
buf.readBytes(ret);
return ret;
}
public static void writeArray(byte[] b, ByteBuf buf) {
writeVarInt(b.length, buf);
buf.writeBytes(b);
}
public static byte[] readArray(ByteBuf buf) {
byte[] ret = new byte[readVarInt(buf)];
buf.readBytes(ret);
return ret;
}
public static void writeStringArray(List<String> s, ByteBuf buf) {
writeVarInt(s.size(), buf);
for (String str : s) {
writeString(str, buf);
}
}
public static List<String> readStringArray(ByteBuf buf) {
int len = readVarInt(buf);
List<String> ret = new ArrayList<String>(len);
for (int i = 0; i < len; i++) {
ret.add(readString(buf));
}
return ret;
}
public static int readVarInt(ByteBuf input) {
return readVarInt(input, 5);
}
public static int readVarInt(ByteBuf input, int maxBytes) {
int out = 0;
int bytes = 0;
byte in;
while (true) {
in = input.readByte();
out |= (in & 0x7F) << (bytes++ * 7);
if (bytes > maxBytes) {
throw new RuntimeException("VarInt too big");
}
if ((in & 0x80) != 0x80) {
break;
}
}
return out;
}
public static void writeVarInt(int value, ByteBuf output) {
int part;
while (true) {
part = value & 0x7F;
value >>>= 7;
if (value != 0) {
part |= 0x80;
}
output.writeByte(part);
if (value == 0) {
break;
}
}
}
public static int readVarShort(ByteBuf buf) {
int low = buf.readUnsignedShort();
int high = 0;
if ((low & 0x8000) != 0) {
low = low & 0x7FFF;
high = buf.readUnsignedByte();
}
return ((high & 0xFF) << 15) | low;
}
public static void writeVarShort(ByteBuf buf, int toWrite) {
int low = toWrite & 0x7FFF;
int high = (toWrite & 0x7F8000) >> 15;
if (high != 0) {
low = low | 0x8000;
}
buf.writeShort(low);
if (high != 0) {
buf.writeByte(high);
}
}
public static void writeUUID(UUID value, ByteBuf output) {
output.writeLong(value.getMostSignificantBits());
output.writeLong(value.getLeastSignificantBits());
}
public static UUID readUUID(ByteBuf input) {
return new UUID(input.readLong(), input.readLong());
}
public static void writeLongs(long[] data, ByteBuf output) {
for (int index = 0; index < data.length; index++) {
output.writeLong(data[index]);
}
}
public static long[] readLongs(int amount, ByteBuf output) {
long data[] = new long[amount];
for (int index = 0; index < amount; index++) {
data[index] = output.readLong();
}
return data;
}
// This method is based on one from here
// https://github.com/Steveice10/MCProtocolLib/blob/master/src/main/java/org/spacehq/mc/protocol/util/NetUtil.java
public static PacketChunk readChunkData(boolean isFullChunk, int bitmask, byte[] input) {
PacketChunkData[] chunks = new PacketChunkData[16];
boolean sky = false;
int expected = 0;
int position = 0;
ShortBuffer blockData = ByteBuffer.wrap(input).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
for (int i = 0; i < chunks.length; i++) {
if ((bitmask & 1 << i) != 0) {
expected += (4096 * 2) + 2048;
}
}
// If there is more data, then there must be skylights :D
if (input.length > expected) {
sky = true;
}
// Read block data
for (int i = 0; i < chunks.length; i++) {
if ((bitmask & 1 << i) != 0) {
chunks[i] = new PacketChunkData(sky);
blockData.position(position / 2);
blockData.get(chunks[i].getBlocks(), 0, 4096);
position = position + (4096 * 2);
} else {
chunks[i] = new PacketChunkData(sky);
}
}
// Read blocklight data
for (int i = 0; i < chunks.length; i++) {
if ((bitmask & 1 << i) != 0) {
System.arraycopy(input, position, chunks[i].getBlockLight(), 0, 2048);
position = position + 2048;
}
}
// Read skylight data
if (sky) {
for (int i = 0; i < chunks.length; i++) {
if ((bitmask & 1 << i) != 0) {
System.arraycopy(input, position, chunks[i].getSkyLight(), 0, 2048);
position = position + 2048;
}
}
}
byte[] biomeData = null;
if (isFullChunk) {
// yay im a full chunk meaning i know my biomes, mommy get the camera!
biomeData = new byte[256];
System.arraycopy(input, position, biomeData, 0, 256);
}
return new PacketChunk(chunks, biomeData);
}
public static void writeNewChunk(ByteBuf buffer, PacketChunkData chunk) {
// Bits Per Block (We use 0, cause we're not gonna write a palette ;) )
buffer.writeByte(0);
// No Palette nor length :D
// Data Array Length
byte[] blockData = convertBlockArray(chunk.getBlocks());
writeVarInt(blockData.length / 8, buffer); // Notchian is divide by 8
buffer.writeBytes(blockData);
// Block Light
buffer.writeBytes(chunk.getBlockLight());
// If has skylight, write it
if (chunk.getSkyLight() != null) {
buffer.writeBytes(chunk.getSkyLight());
}
}
private static byte[] convertBlockArray(short[] blocks) {
// block ID for the first 9 bits, and the block damage value for the last 4 bits
byte[] output = new byte[6664]; // (16 * 16 * 16 * 13) / 8 :) (plus some for padding ...)
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (short block : blocks) {
int blockID = block >> 4; // (Needs to be to 9 bits)
int data = block & 0xF; // 8 bits
}
return null; // todo: finish
}
private static BitSet append(BitSet base, int index, MagicBitSet toAdd) {
int length = index;
for (int i = 0; i < toAdd.getTrueLength(); i++) {
base.set(length + i, toAdd.get(i));
}
return base;
}
private static MagicBitSet getPaddedBitSet(int value, int bitSize) {
MagicBitSet output = new MagicBitSet(bitSize);
BitSet temp = BitSet.valueOf(new long[]{value});
for (int i = 0; i < bitSize; i++) {
output.set(i, false);
}
int toShift = bitSize - temp.length();
for (int i = 0; i < temp.length(); i++) {
output.set(toShift + i, temp.get(i));
}
return output;
}
}

View File

@ -0,0 +1,41 @@
package us.myles.ViaVersion;
import us.myles.ViaVersion.chunks.ByteWriter;
public class Testing {
public static void main(String[] args) {
// Map<NewType, Set<Type>> conversions = new HashMap<>();
// for (MetaIndex metaIndex : MetaIndex.values()) {
// if (!conversions.containsKey(metaIndex.getNewType())) {
// conversions.put(metaIndex.getNewType(), new HashSet<Type>());
// }
// conversions.get(metaIndex.getNewType()).add(metaIndex.getOldType());
// }
// for (Map.Entry<NewType, Set<Type>> conv : conversions.entrySet()) {
// System.out.println("Type: " + conv.getKey().name());
// for (Type t : conv.getValue()) {
// System.out.println(" -> " + t.name());
// }
// }
ByteWriter bytes = new ByteWriter(2);
int id = 255;
int data = 4;
bytes.writeByte(0, 1);
bytes.writeFullByte(id);
bytes.writeByte(data, 4);
System.out.println("output bytes: ");
for(byte b:bytes.getOutput()){
System.out.println("B: " + b + " I: " + ((int)b & 0xff));
}
}
public static byte writeByte(byte a, byte b, int index){
int intA = (int) a & 0xff;
int intB = (int) b & 0xff;
System.out.println("As int: " + (intA | (intB >> index)));
return (byte) (intA | (intB >> index));
}
}

View File

@ -0,0 +1,52 @@
package us.myles.ViaVersion.chunks;
public class ByteWriter {
private final byte[] bytes;
private int byteIndex;
private int bitIndex;
public ByteWriter(int size) {
this.bytes = new byte[size];
this.byteIndex = 0;
this.bitIndex = 0;
}
public byte[] getOutput() {
return this.bytes;
}
public void writeFullByte(int b){
writeByte(b, 8);
}
public void writeByte(int b, int length) {
byte current = getCurrentByte();
byte byteB = (byte)(int)((b) & 0xff);
System.out.println("Input: " + byteB);
System.out.println(Integer.toBinaryString(byteB));
int space = (8 - bitIndex);
int written = space > length ? length : space;
System.out.println("Written is " + written);
bytes[byteIndex] = (byte) (current | (extractRange(byteB, 0, written) >> (bitIndex - 1)));
System.out.println("output value: " + bytes[byteIndex]);
this.bitIndex += length;
if(this.bitIndex >= 8) {
this.byteIndex += 1;
this.bitIndex = bitIndex - 8;
// write remaining into this
System.out.println("Writing from " + written + " to " + (written + bitIndex));
System.out.println("Value: " + extractRange(byteB, written, written + bitIndex));
bytes[byteIndex] = (byte) (extractRange(byteB, written, written + bitIndex) << written);
}
}
private byte extractRange(int in, int begin, int end){
return (byte) ((in >> begin) & ((1 << (end - begin)) - 1));
}
public byte getCurrentByte() {
if(byteIndex == bytes.length) throw new RuntimeException("ByteWriter overflow!");
return bytes[byteIndex];
}
}

View File

@ -0,0 +1,16 @@
package us.myles.ViaVersion.chunks;
import java.util.BitSet;
public class MagicBitSet extends BitSet{
private final int initLength;
public MagicBitSet(int nbits) {
super(nbits);
this.initLength = nbits;
}
public int getTrueLength() {
return length() == 0 ? initLength : length();
}
}

View File

@ -0,0 +1,19 @@
package us.myles.ViaVersion.chunks;
public class PacketChunk {
private PacketChunkData[] chunkData;
private byte[] biomeData;
public PacketChunk(PacketChunkData[] chunkData, byte[] biomeData) {
this.chunkData = chunkData;
this.biomeData = biomeData;
}
public PacketChunkData[] getChunkData() {
return chunkData;
}
public byte[] getBiomeData() {
return biomeData;
}
}

View File

@ -0,0 +1,25 @@
package us.myles.ViaVersion.chunks;
public class PacketChunkData {
private short[] blocks = new short[4096];
private byte[] blockLight = new byte[2048];
private byte[] skyLight;
public PacketChunkData(boolean isSkyLightData) {
if(isSkyLightData){
skyLight = new byte[2048];
}
}
public short[] getBlocks() {
return this.blocks;
}
public byte[] getBlockLight() {
return this.blockLight;
}
public byte[] getSkyLight() {
return this.skyLight;
}
}

View File

@ -0,0 +1,51 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.PacketUtil;
import us.myles.ViaVersion.transformers.IncomingTransformer;
@ChannelHandler.Sharable
public class ViaInboundHandler extends ChannelInboundHandlerAdapter {
private final IncomingTransformer incomingTransformer;
private final ViaVersionInitializer init;
public ViaInboundHandler(Channel c, ConnectionInfo info, ViaVersionInitializer init) {
this.init = init;
this.incomingTransformer = new IncomingTransformer(c, info, init);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean compression = ctx.pipeline().get("compress") != null;
if (msg instanceof ByteBuf) {
ByteBuf bytebuf = (ByteBuf) msg;
if (compression) {
// decompress :)
bytebuf = PacketUtil.decompress(ctx, bytebuf);
}
int id = PacketUtil.readVarInt(bytebuf);
// Transform
ByteBuf newPacket = ctx.alloc().buffer();
try {
incomingTransformer.transform(id, bytebuf, newPacket);
} catch (CancelException e) {
return;
}
if (compression) {
// recompress :)
newPacket = PacketUtil.compress(ctx, newPacket);
}
msg = newPacket;
}
super.channelRead(ctx, msg);
}
}

View File

@ -0,0 +1,51 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.PacketUtil;
import us.myles.ViaVersion.transformers.OutgoingTransformer;
@ChannelHandler.Sharable
public class ViaOutboundHandler extends ChannelOutboundHandlerAdapter {
private final OutgoingTransformer outgoingTransformer;
private final ViaVersionInitializer init;
public ViaOutboundHandler(Channel c, ConnectionInfo info, ViaVersionInitializer init) {
this.init = init;
this.outgoingTransformer = new OutgoingTransformer(c, info, init);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise channelPromise) throws Exception {
try {
if (channelPromise.isDone()) return; // don't break any <3s
boolean compression = ctx.pipeline().get("compress") != null;
if (msg instanceof ByteBuf) {
ByteBuf bytebuf = (ByteBuf) msg;
if (compression) {
// decompress :)
bytebuf = PacketUtil.decompress(ctx, bytebuf);
}
int id = PacketUtil.readVarInt(bytebuf);
// Transform
ByteBuf newPacket = ctx.alloc().buffer();
try {
outgoingTransformer.transform(id, bytebuf, newPacket);
} catch (CancelException e) {
return;
}
if (compression) {
// recompress :)
newPacket = PacketUtil.compress(ctx, newPacket);
}
msg = newPacket;
}
super.write(ctx, msg, channelPromise);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,41 @@
package us.myles.ViaVersion.handlers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import net.minecraft.server.v1_8_R3.Packet;
import net.minecraft.server.v1_8_R3.PacketPlayOutMapChunk;
import net.minecraft.server.v1_8_R3.PacketPlayOutMapChunkBulk;
import net.minecraft.server.v1_8_R3.World;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.Core;
public class ViaOutboundPacketHandler extends ChannelOutboundHandlerAdapter {
private final ConnectionInfo info;
public ViaOutboundPacketHandler(Channel c, ConnectionInfo info) {
this.info = info;
}
@Override
public void write(ChannelHandlerContext channelHandlerContext, Object o, ChannelPromise channelPromise) throws Exception {
if(o instanceof Packet){
info.setLastPacket(o);
/* This transformer is more for fixing issues which we find hard at byte level :) */
if(o instanceof PacketPlayOutMapChunkBulk){
PacketPlayOutMapChunkBulk bulk = (PacketPlayOutMapChunkBulk) o;
int[] locX = Core.getPrivateField(bulk, "a", int[].class);
int[] locZ = Core.getPrivateField(bulk, "b", int[].class);
World world = Core.getPrivateField(bulk, "world", World.class);
for(int i = 0;i<locX.length;i++){
int x = locX[i];
int z = locZ[i];
channelHandlerContext.write(new PacketPlayOutMapChunk(world.getChunkAt(x, z), true, 65535)); // magic was 65535
}
return;
}
}
super.write(channelHandlerContext, o, channelPromise);
}
}

View File

@ -0,0 +1,51 @@
package us.myles.ViaVersion.handlers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import us.myles.ViaVersion.ConnectionInfo;
import java.lang.reflect.Method;
public class ViaVersionInitializer extends ChannelInitializer<SocketChannel> {
private final ChannelInitializer<SocketChannel> oldInit;
private Method method;
private ConnectionInfo info;
private ViaInboundHandler inbound;
private ViaOutboundHandler outbound;
private SocketChannel socketChannel;
private ViaOutboundPacketHandler outbound2;
public ViaVersionInitializer(ChannelInitializer<SocketChannel> oldInit) {
this.oldInit = oldInit;
try {
this.method = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
this.method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
info = new ConnectionInfo();
// Add originals
this.method.invoke(this.oldInit, socketChannel);
// Add our transformers
this.socketChannel = socketChannel;
this.inbound = new ViaInboundHandler(socketChannel, info, this);
this.outbound = new ViaOutboundHandler(socketChannel, info, this);
this.outbound2 = new ViaOutboundPacketHandler(socketChannel, info);
socketChannel.pipeline().addBefore("decoder", "via_incoming", this.inbound);
socketChannel.pipeline().addBefore("packet_handler", "via_outgoing2", this.outbound2);
socketChannel.pipeline().addBefore("encoder", "via_outgoing", this.outbound);
}
public void remove(){
socketChannel.pipeline().remove("via_incoming");
socketChannel.pipeline().remove("via_outgoing");
socketChannel.pipeline().remove("via_outgoing2");
}
}

View File

@ -0,0 +1,199 @@
package us.myles.ViaVersion.metadata;
import net.minecraft.server.v1_8_R3.Entity;
import net.minecraft.server.v1_8_R3.EntityPlayer;
import net.minecraft.server.v1_8_R3.EntityTypes;
import org.bukkit.entity.*;
public enum MetaIndex {
// entity
ENTITY_STATUS(org.bukkit.entity.Entity.class, 0, Type.Byte, NewType.Byte),
ENTITY_AIR(org.bukkit.entity.Entity.class, 1, Type.Short, NewType.VarInt),
ENTITY_SILENT(org.bukkit.entity.Entity.class, 4, Type.Byte, NewType.Boolean),
// living entity
LIVINGENTITY_NAMETAG(LivingEntity.class, 2, Type.String, NewType.String),
LIVINGENTITY_ALWAYS_SHOW_NAMETAG(LivingEntity.class, 3, Type.Byte, NewType.Boolean),
LIVINGENTITY_HEALTH(LivingEntity.class, 6, Type.Float, NewType.Float),
LIVINGENTITY_POTION_EFFECT_COLOR(LivingEntity.class, 7, Type.Int, NewType.VarInt),
LIVINGENTITY_IS_POTION_AMBIENT(LivingEntity.class, 8, Type.Byte, NewType.Boolean),
LIVINGENTITY_NUMBER_OF_ARROWS_IN(LivingEntity.class, 9, Type.Byte, NewType.VarInt),
LIVINGENTITY_NO_AI(LivingEntity.class, 15, Type.Byte, 10, NewType.Byte), // in 1.9 this is combined with Left handed, oh.
// ageable
AGEABLE_AGE(Ageable.class, 12, Type.Byte, 11, NewType.Boolean),
// armour stand
STAND_INFO(ArmorStand.class, 10, Type.Byte, NewType.Byte),
STAND_HEAD_POS(ArmorStand.class, 11, Type.Rotation, NewType.Vector3F),
STAND_BODY_POS(ArmorStand.class, 12, Type.Rotation, NewType.Vector3F),
STAND_LA_POS(ArmorStand.class, 13, Type.Rotation, NewType.Vector3F),
STAND_RA_POS(ArmorStand.class, 14, Type.Rotation, NewType.Vector3F),
STAND_LL_POS(ArmorStand.class, 15, Type.Rotation, NewType.Vector3F),
STAND_RL_POS(ArmorStand.class, 16, Type.Rotation, NewType.Vector3F),
// human, discountined?
PLAYER_SKIN_FLAGS(HumanEntity.class, 10, Type.Byte, NewType.Discontinued), // unsigned on 1.8
PLAYER_HUMAN_BYTE(HumanEntity.class, 16, Type.Byte, NewType.Discontinued), // unused on 1.8
PLAYER_ADDITIONAL_HEARTS(HumanEntity.class, 17, Type.Float, NewType.Discontinued),
PLAYER_SCORE(HumanEntity.class, 18, Type.Int, NewType.Discontinued),
// horse
HORSE_INFO(Horse.class, 16, Type.Int, 12, NewType.Byte),
HORSE_TYPE(Horse.class, 19, Type.Byte, 13, NewType.VarInt),
HORSE_SUBTYPE(Horse.class, 20, Type.Int, 14, NewType.VarInt),
HORSE_OWNER(Horse.class, 21, Type.String, 15, NewType.OptUUID),
HORSE_ARMOR(Horse.class, 22, Type.Int, 16, NewType.VarInt),
// bat
BAT_ISHANGING(Bat.class, 16, Type.Byte, 11, NewType.Byte),
// tameable
TAMING_INFO(Tameable.class, 16, Type.Byte, 12, NewType.Byte),
TAMING_OWNER(Tameable.class, 17, Type.String, 13, NewType.OptUUID),
// ocelot
OCELOT_TYPE(Ocelot.class, 18, Type.Byte, 14, NewType.VarInt),
// wolf
WOLF_HEALTH(Wolf.class, 18, Type.Float, 14, NewType.Float),
WOLF_BEGGING(Wolf.class, 19, Type.Byte, 15, NewType.Boolean),
WOLF_COLLAR(Wolf.class, 20, Type.Byte, 16, NewType.VarInt),
// pig
PIG_SADDLE(Pig.class, 16, Type.Byte, 12, NewType.Boolean),
// rabbit
RABBIT_TYPE(Rabbit.class, 18, Type.Byte, 12, NewType.VarInt),
// sheep
SHEEP_COLOR(Sheep.class, 16, Type.Byte, 12, NewType.Byte),
// villager
VILLAGER_PROFESSION(Villager.class, 16, Type.Int, 12, NewType.VarInt), // TODO write this to wiki.vg
// enderman
ENDERMAN_BLOCK(Enderman.class, 16, Type.Short, 11, NewType.BlockID), // special case
ENDERMAN_BLOCKDATA(Enderman.class, 17, Type.Byte, 11, NewType.BlockID), // special case
ENDERMAN_ISSCREAMING(Enderman.class, 18, Type.Byte, 12, NewType.Boolean),
// zombie
ZOMBIE_ISCHILD(Zombie.class, 12, Type.Byte, 11, NewType.Boolean),
ZOMBIE_ISVILLAGER(Zombie.class, 13, Type.Byte, 12, NewType.VarInt),
ZOMBIE_ISCONVERTING(Zombie.class, 14, Type.Byte, 13, NewType.Boolean),
// ZOMBIE_RISINGHANDS added in 1.9
// blaze
BLAZE_ONFIRE(Blaze.class, 16, Type.Byte, 11, NewType.Byte),
// spider
SPIDER_CIMBING(Spider.class, 16, Type.Byte, 11, NewType.Byte),
// creeper
CREEPER_FUSE(Creeper.class, 16, Type.Byte, 11, NewType.VarInt), // -1 idle, 1 is fuse
CREEPER_ISPOWERED(Creeper.class, 17, Type.Byte, 12, NewType.Boolean),
CREEPER_ISIGNITED(Creeper.class, 18, Type.Byte, 13, NewType.Boolean), // TODO: Write on wiki.vg for current prot
// ghast
GHAST_ISATTACKING(Ghast.class, 16, Type.Byte, 11, NewType.Boolean),
// slime
SLIME_SIZE(Slime.class, 16, Type.Byte, 11, NewType.VarInt),
// skeleton
SKELETON_TYPE(Skeleton.class, 13, Type.Byte, 11, NewType.VarInt),
// witch
WITCH_AGGRO(Witch.class, 21, Type.Byte, 11, NewType.Boolean),
// iron golem
IRON_PLAYERMADE(IronGolem.class, 16, Type.Byte, 11, NewType.Byte),
// wither
WITHER_TARGET1(Wither.class, 17, Type.Int, 11, NewType.VarInt),
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),
// guardian
GUARDIAN_INFO(Guardian.class, 16, Type.Byte, 11, NewType.Byte),
GUARDIAN_TARGET(Guardian.class, 17, Type.Int, 12, NewType.VarInt),
// boat
BOAT_SINCEHIT(Boat.class, 17, Type.Int, 5, NewType.VarInt),
BOAT_FORWARDDIR(Boat.class, 18, Type.Int, 6, NewType.VarInt),
BOAT_DMGTAKEN(Boat.class, 19, Type.Float, 7, NewType.Float),
// BOAT_TYPE in 1.9
// minecart
MINECART_SHAKINGPOWER(Minecart.class, 17, Type.Int, 5, NewType.VarInt),
MINECART_SHAKINGDIRECTION(Minecart.class, 18, Type.Int, 6, NewType.VarInt),
MINECART_DAMAGETAKEN(Minecart.class, 19, Type.Float, 7, NewType.Float), // also shaking modifier :P
MINECART_BLOCK(Minecart.class, 20, Type.Int, 8, NewType.VarInt),
MINECART_BLOCK_Y(Minecart.class, 21, Type.Int, 9, NewType.VarInt),
MINECART_SHOWBLOCK(Minecart.class, 22, Type.Byte, 10, NewType.Boolean),
// furnace cart
FURNACECART_ISPOWERED(org.bukkit.entity.minecart.PoweredMinecart.class, 16, Type.Byte, 11, NewType.Boolean),
// item drop
ITEM_ITEM(Item.class, 10, Type.Slot, 5, NewType.Slot),
// arrow
ARROW_ISCRIT(Arrow.class, 16, Type.Byte, 5, NewType.Byte),
// firework
FIREWORK_INFO(Firework.class, 8, Type.Slot, 5, NewType.Slot),
// item frame
ITEMFRAME_ITEM(ItemFrame.class, 8, Type.Slot, 5, NewType.Slot),
ITEMFRAME_ROTATION(ItemFrame.class, 9, Type.Byte, 5, NewType.VarInt),
// ender crystal
ENDERCRYSTAL_HEALTH(EnderCrystal.class, 8, Type.Int, NewType.Discontinued),;
private Class<?> clazz;
private int newIndex;
private NewType newType;
private Type oldType;
private int index;
MetaIndex(Class<?> type, int index, Type oldType, NewType newType) {
this.clazz = type;
this.index = index;
this.newIndex = index;
this.oldType = oldType;
this.newType = newType;
}
MetaIndex(Class<?> type, int index, Type oldType, int newIndex, NewType newType) {
this.clazz = type;
this.index = index;
this.oldType = oldType;
this.newIndex = newIndex;
this.newType = newType;
}
public int getNewIndex() {
return newIndex;
}
public NewType getNewType() {
return newType;
}
public Type getOldType() {
return oldType;
}
public int getIndex() {
return index;
}
public Class<?> getApplicableClass() {
return this.clazz;
}
public static MetaIndex getIndex(Entity entity, int index) {
EntityType type;
if (entity instanceof EntityPlayer) {
type = EntityType.PLAYER;
} else {
int entityID = EntityTypes.a(entity);
type = EntityType.fromId(entityID);
}
return getIndex(type, index);
}
public static MetaIndex getIndex(EntityType type, int index) {
Class<? extends org.bukkit.entity.Entity> entityClass = type.getEntityClass();
for (MetaIndex mi : MetaIndex.values()) {
if (mi.getIndex() == index) {
if (mi.getApplicableClass().isAssignableFrom(entityClass) ||
mi.getApplicableClass().equals(entityClass)) {
return mi;
}
}
}
// fall back to living entity
for (MetaIndex mi : MetaIndex.values()) {
if (mi.getIndex() == index) {
if (mi.getApplicableClass().isAssignableFrom(LivingEntity.class) ||
mi.getApplicableClass().equals(LivingEntity.class)) {
return mi;
}
}
}
return null;
}
}

View File

@ -0,0 +1,27 @@
package us.myles.ViaVersion.metadata;
public enum NewType {
Byte(0),
VarInt(1),
Float(2),
String(3),
Chat(4),
Slot(5),
Boolean(6),
Vector3F(7),
Position(8),
OptPosition(9),
Direction(10),
OptUUID(11),
BlockID(12),
Discontinued(99);
private final int typeID;
NewType(int typeID){
this.typeID = typeID;
}
public int getTypeID() {
return typeID;
}
}

View File

@ -0,0 +1,21 @@
package us.myles.ViaVersion.metadata;
public enum Type {
Byte(0),
Short(1),
Int(2),
Float(3),
String(4),
Slot(5),
Position(6),
Rotation(7);
private final int typeID;
Type(int typeID){
this.typeID = typeID;
}
public int getTypeID() {
return typeID;
}
}

View File

@ -0,0 +1,6 @@
package us.myles.ViaVersion.packets;
public enum Direction {
OUTGOING,
INCOMING
}

View File

@ -0,0 +1,196 @@
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_CLICHAT_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, 0x2F, 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_TEAMS(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) {
for (PacketType pt : values()) {
if (pt.getNewPacketID() == id && id != -1
&& pt.getState() == state
&& pt.getDirection() == direction)
return pt;
}
return null;
}
public static PacketType findOldPacket(State state, Direction direction, int id) {
for (PacketType pt : values()) {
if (pt.getPacketID() == id && id != -1
&& 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);
}
}

View File

@ -0,0 +1,8 @@
package us.myles.ViaVersion.packets;
public enum State {
HANDSHAKE,
STATUS,
LOGIN,
PLAY
}

View File

@ -0,0 +1,175 @@
package us.myles.ViaVersion.transformers;
import com.google.gson.Gson;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import net.minecraft.server.v1_8_R3.PacketDataSerializer;
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.Core;
import us.myles.ViaVersion.PacketUtil;
import us.myles.ViaVersion.handlers.ViaVersionInitializer;
import us.myles.ViaVersion.packets.PacketType;
import us.myles.ViaVersion.packets.State;
public class IncomingTransformer {
private static Gson gson = new Gson();
private final Channel channel;
private final ConnectionInfo info;
private final ViaVersionInitializer init;
public IncomingTransformer(Channel channel, ConnectionInfo info, ViaVersionInitializer init) {
this.channel = channel;
this.info = info;
this.init = init;
}
public void transform(int packetID, ByteBuf input, ByteBuf output) throws CancelException {
PacketType packet = PacketType.getIncomingPacket(info.getState(), packetID);
if (packet == null) {
System.out.println("incoming packet not found " + packetID + " state: " + info.getState());
throw new RuntimeException("Incoming Packet not found? " + packetID + " State: " + info.getState() + " Version: " + info.getProtocol());
}
int original = packetID;
if (packet.getPacketID() != -1) {
packetID = packet.getPacketID();
}
if (packet != PacketType.PLAY_PLAYER_POSITION_LOOK_REQUEST && packet != PacketType.PLAY_KEEP_ALIVE_REQUEST && packet != PacketType.PLAY_PLAYER_POSITION_REQUEST && packet != PacketType.PLAY_PLAYER_LOOK_REQUEST) {
System.out.println("Packet Type: " + packet + " New ID: " + packetID + " Original: " + original);
}
if (packet == PacketType.PLAY_TP_CONFIRM) {
System.out.println("Cancelling TP Confirm");
throw new CancelException();
}
PacketUtil.writeVarInt(packetID, output);
if (packet == PacketType.HANDSHAKE) {
System.out.println("Readable Bytes: " + input.readableBytes());
int protVer = PacketUtil.readVarInt(input);
info.setProtocol(protVer);
PacketUtil.writeVarInt(protVer <= 102 ? protVer : 47, output); // pretend to be older
System.out.println("Incoming prot ver: " + protVer);
if (protVer <= 102) {
// Not 1.9 remove pipes
this.init.remove();
}
String serverAddress = PacketUtil.readString(input);
PacketUtil.writeString(serverAddress, output);
int serverPort = input.readUnsignedShort();
output.writeShort(serverPort);
int nextState = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(nextState, output);
if (nextState == 1) {
info.setState(State.STATUS);
}
if (nextState == 2) {
info.setState(State.LOGIN);
}
return;
}
if(packet == PacketType.PLAY_TAB_COMPLETE_REQUEST){
String text = PacketUtil.readString(input);
PacketUtil.writeString(text, output);
input.readBoolean(); // assume command
output.writeBytes(input);
return;
}
if (packet == PacketType.PLAY_PLAYER_DIGGING) {
byte status = input.readByte();
if (status == 6) { // item swap
throw new CancelException();
}
output.writeByte(status);
// read position
Long pos = input.readLong();
output.writeLong(pos);
short face = input.readUnsignedByte();
output.writeByte(face);
return;
}
if (packet == PacketType.PLAY_CLIENT_SETTINGS) {
String locale = PacketUtil.readString(input);
PacketUtil.writeString(locale, output);
byte view = input.readByte();
output.writeByte(view);
int chatMode = PacketUtil.readVarInt(input);
output.writeByte(chatMode);
boolean chatColours = input.readBoolean();
output.writeBoolean(chatColours);
short skinParts = input.readUnsignedByte();
output.writeByte(skinParts);
int mainHand = PacketUtil.readVarInt(input);
System.out.println("Main hand: " + mainHand);
return;
}
if (packet == PacketType.PLAY_ANIMATION_REQUEST) {
int hand = PacketUtil.readVarInt(input);
System.out.println("Animation request " + hand);
return;
}
if (packet == PacketType.PLAY_USE_ENTITY) {
int target = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(target, output);
int type = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(type, output);
if (type == 2) {
float targetX = input.readFloat();
output.writeFloat(targetX);
float targetY = input.readFloat();
output.writeFloat(targetY);
float targetZ = input.readFloat();
output.writeFloat(targetZ);
}
if (type == 0 || type == 2) {
int hand = PacketUtil.readVarInt(input); // lel
}
return;
}
if (packet == PacketType.PLAY_PLAYER_BLOCK_PLACEMENT) {
Long position = input.readLong();
output.writeLong(position);
int face = PacketUtil.readVarInt(input);
output.writeByte(face);
int hand = PacketUtil.readVarInt(input);
System.out.println("hand: " + hand);
// write item in hand
output.writeShort(-1);
short curX = input.readUnsignedByte();
output.writeByte(curX);
short curY = input.readUnsignedByte();
output.writeByte(curY);
short curZ = input.readUnsignedByte();
output.writeByte(curZ);
return;
}
if (packet == PacketType.PLAY_USE_ITEM) {
output.clear();
PacketUtil.writeVarInt(PacketType.PLAY_PLAYER_BLOCK_PLACEMENT.getPacketID(), output);
// Simulate using item :)
output.writeLong(-1L);
output.writeByte(-1);
int hand = PacketUtil.readVarInt(input);
System.out.println("hand: " + hand);
// write item in hand
output.writeShort(-1);
output.writeByte(-1);
output.writeByte(-1);
output.writeByte(-1);
return;
}
output.writeBytes(input);
}
}

View File

@ -0,0 +1,484 @@
package us.myles.ViaVersion.transformers;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import net.minecraft.server.v1_8_R3.*;
import net.minecraft.server.v1_8_R3.ItemStack;
import org.bukkit.craftbukkit.v1_8_R3.inventory.CraftItemStack;
import org.bukkit.inventory.*;
import org.spacehq.mc.protocol.data.game.chunk.Column;
import org.spacehq.mc.protocol.util.NetUtil;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.Core;
import us.myles.ViaVersion.PacketUtil;
import us.myles.ViaVersion.handlers.ViaVersionInitializer;
import us.myles.ViaVersion.metadata.MetaIndex;
import us.myles.ViaVersion.metadata.NewType;
import us.myles.ViaVersion.metadata.Type;
import us.myles.ViaVersion.packets.PacketType;
import us.myles.ViaVersion.packets.State;
import java.io.IOException;
import java.util.*;
public class OutgoingTransformer {
private static Gson gson = new Gson();
private final Channel channel;
private final ConnectionInfo info;
private final ViaVersionInitializer init;
private boolean cancel = false;
private Map<Integer, UUID> uuidMap = new HashMap<Integer, UUID>();
public OutgoingTransformer(Channel channel, ConnectionInfo info, ViaVersionInitializer init) {
this.channel = channel;
this.info = info;
this.init = init;
}
public void transform(int packetID, ByteBuf input, ByteBuf output) throws CancelException {
if (cancel) {
throw new CancelException();
}
PacketType packet = PacketType.getOutgoingPacket(info.getState(), packetID);
if (packet == null) {
throw new RuntimeException("Outgoing Packet not found? " + packetID + " State: " + info.getState() + " Version: " + info.getProtocol());
}
if (packet != PacketType.PLAY_CHUNK_DATA && packet != PacketType.PLAY_KEEP_ALIVE && packet != PacketType.PLAY_TIME_UPDATE)
System.out.println("Packet Type: " + packet + " Original ID: " + packetID + " State:" + info.getState());
if (packet.getPacketID() != -1) {
packetID = packet.getNewPacketID();
}
// By default no transform
PacketUtil.writeVarInt(packetID, output);
if (packet == PacketType.PLAY_NAMED_SOUND_EFFECT) {
// TODO: Port this over
System.out.println("cancelling");
throw new CancelException();
}
if (packet == PacketType.PLAY_ATTACH_ENTITY) {
int id = input.readInt();
output.writeInt(id);
int target = input.readInt();
output.writeInt(target);
return;
}
if (packet == PacketType.PLAY_ENTITY_TELEPORT) {
// Port this so that it relative moves :P
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
int x = input.readInt();
output.writeDouble(x / 32D);
int y = input.readInt();
output.writeDouble(y / 32D);
int z = input.readInt();
output.writeDouble(z / 32D);
byte yaw = input.readByte();
output.writeByte(yaw);
byte pitch = input.readByte();
output.writeByte(pitch);
boolean onGround = input.readBoolean();
output.writeBoolean(onGround);
return;
}
if (packet == PacketType.PLAY_ENTITY_LOOK_MOVE) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
int x = input.readByte();
output.writeShort(x);
int y = input.readByte();
output.writeShort(y);
int z = input.readByte();
output.writeShort(z);
byte yaw = input.readByte();
output.writeByte(yaw);
byte pitch = input.readByte();
output.writeByte(pitch);
boolean onGround = input.readBoolean();
output.writeBoolean(onGround);
return;
}
if (packet == PacketType.PLAY_ENTITY_RELATIVE_MOVE) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
int x = input.readByte();
output.writeShort(x);
int y = input.readByte();
output.writeShort(y);
int z = input.readByte();
output.writeShort(z);
boolean onGround = input.readBoolean();
output.writeBoolean(onGround);
return;
}
// If login success
if (packet == PacketType.LOGIN_SUCCESS) {
info.setState(State.PLAY);
}
if (packet == PacketType.LOGIN_SETCOMPRESSION) {
int factor = PacketUtil.readVarInt(input);
info.setCompression(factor);
PacketUtil.writeVarInt(factor, output);
return;
}
if (packet == PacketType.STATUS_RESPONSE) {
String original = PacketUtil.readString(input);
JsonObject object = gson.fromJson(original, JsonObject.class);
object.get("version").getAsJsonObject().addProperty("protocol", info.getProtocol());
PacketUtil.writeString(gson.toJson(object), output);
return;
}
if (packet == PacketType.LOGIN_SUCCESS) {
String uu = PacketUtil.readString(input);
PacketUtil.writeString(uu, output);
info.setUUID(UUID.fromString(uu));
output.writeBytes(input);
return;
}
if (packet == PacketType.PLAY_PLAYER_POSITION_LOOK) {
output.writeBytes(input);
PacketUtil.writeVarInt(0, output);
return;
}
if (packet == PacketType.PLAY_ENTITY_EQUIPMENT) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
short slot = input.readShort();
if (slot > 1) {
slot += 1; // add 1 so it's now 2-5
}
PacketUtil.writeVarInt(slot, output);
output.writeBytes(input);
}
if (packet == PacketType.PLAY_ENTITY_METADATA) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
try {
List dw = Core.getPrivateField(info.getLastPacket(), "b", List.class);
// get entity via entityID, not preferred but we need it.
Entity entity = Core.getEntity(info.getUUID(), id);
if (entity != null) {
transformMetadata(entity, dw, output);
} else {
// Died before we could get to it. rip
throw new CancelException();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return;
}
if (packet == PacketType.PLAY_SPAWN_OBJECT) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
PacketUtil.writeUUID(getUUID(id), output);
byte type = input.readByte();
output.writeByte(type);
double x = input.readInt();
output.writeDouble(x / 32D);
double y = input.readInt();
output.writeDouble(y / 32D);
double z = input.readInt();
output.writeDouble(z / 32D);
byte pitch = input.readByte();
output.writeByte(pitch);
byte yaw = input.readByte();
output.writeByte(yaw);
int data = input.readInt();
output.writeInt(data);
short vX = input.readableBytes() >= 16 ? input.readShort() : 0;
output.writeShort(vX);
short vY = input.readableBytes() >= 16 ? input.readShort() : 0;
output.writeShort(vY);
short vZ = input.readableBytes() >= 16 ? input.readShort() : 0;
output.writeShort(vZ);
return;
}
if (packet == PacketType.PLAY_SPAWN_XP_ORB) { // TODO: Verify
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
double x = input.readInt();
output.writeDouble(x / 32D);
double y = input.readInt();
output.writeDouble(y / 32D);
double z = input.readInt();
output.writeDouble(z / 32D);
short data = input.readShort();
output.writeShort(data);
return;
}
if (packet == PacketType.PLAY_SPAWN_MOB) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
PacketUtil.writeUUID(getUUID(id), output);
short type = input.readUnsignedByte();
output.writeByte(type);
double x = input.readInt();
output.writeDouble(x / 32D);
double y = input.readInt();
output.writeDouble(y / 32D);
double z = input.readInt();
output.writeDouble(z / 32D);
byte yaw = input.readByte();
output.writeByte(yaw);
byte pitch = input.readByte();
output.writeByte(pitch);
byte headPitch = input.readByte();
output.writeByte(headPitch);
short vX = input.readShort();
output.writeShort(vX);
short vY = input.readShort();
output.writeShort(vY);
short vZ = input.readShort();
output.writeShort(vZ);
try {
DataWatcher dw = Core.getPrivateField(info.getLastPacket(), "l", DataWatcher.class);
transformMetadata(dw, output);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return;
}
if (packet == PacketType.PLAY_SPAWN_PLAYER) {
int id = PacketUtil.readVarInt(input);
PacketUtil.writeVarInt(id, output);
UUID playerUUID = PacketUtil.readUUID(input);
PacketUtil.writeUUID(playerUUID, output);
double x = input.readInt();
output.writeDouble(x / 32D);
double y = input.readInt();
output.writeDouble(y / 32D);
double z = input.readInt();
output.writeDouble(z / 32D);
byte pitch = input.readByte();
output.writeByte(pitch);
byte yaw = input.readByte();
output.writeByte(yaw);
// We don't use currentItem short lel
// transform entity meta data ugh
// get data watcher
try {
System.out.println("Last Packet Type: " + info.getLastPacket().getClass().getName());
DataWatcher dw = Core.getPrivateField(info.getLastPacket(), "i", DataWatcher.class);
transformMetadata(dw, output);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
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();
if (bitMask == 0) {
output.clear();
PacketUtil.writeVarInt(PacketType.PLAY_UNLOAD_CHUNK.getNewPacketID(), output);
output.writeInt(chunkX);
output.writeInt(chunkZ);
return;
}
int size = PacketUtil.readVarInt(input);
byte[] data = new byte[size];
input.readBytes(data);
boolean sk = false;
if (info.getLastPacket() instanceof PacketPlayOutMapChunkBulk) {
try {
sk = Core.getPrivateField(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);
// 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();
}
return;
}
output.writeBytes(input);
}
private void transformMetadata(DataWatcher dw, ByteBuf output) {
// get entity
try {
transformMetadata(Core.getPrivateField(dw, "a", Entity.class), dw.b(), output);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void transformMetadata(Entity entity, List<DataWatcher.WatchableObject> dw, ByteBuf output) {
PacketDataSerializer packetdataserializer = new PacketDataSerializer(output);
if (dw != null) {
short id = -1;
int data = -1;
Iterator<DataWatcher.WatchableObject> iterator = dw.iterator();
while (iterator.hasNext()) {
DataWatcher.WatchableObject obj = iterator.next();
MetaIndex metaIndex = MetaIndex.getIndex(entity, obj.a());
if (metaIndex.getNewType() != NewType.Discontinued) {
if (metaIndex.getNewType() != NewType.BlockID || id != -1 && data == -1 || id == -1 && data != -1) { // block ID is only written if we have both parts
output.writeByte(metaIndex.getNewIndex());
output.writeByte(metaIndex.getNewType().getTypeID());
}
switch (metaIndex.getNewType()) {
case Byte:
// convert from int, byte
if (metaIndex.getOldType() == Type.Byte) {
packetdataserializer.writeByte(((Byte) obj.b()).byteValue());
}
if (metaIndex.getOldType() == Type.Int) {
packetdataserializer.writeByte(((Integer) obj.b()).byteValue());
}
break;
case OptUUID:
String owner = (String) obj.b();
UUID toWrite = null;
if (owner.length() != 0) {
try {
toWrite = UUID.fromString(owner);
} catch (Exception ignored) {
}
}
packetdataserializer.writeBoolean(toWrite != null);
if (toWrite != null)
packetdataserializer.a(toWrite);
break;
case BlockID:
// if we have both sources :))
if (metaIndex.getOldType() == Type.Byte) {
data = ((Byte) obj.b()).byteValue();
}
if (metaIndex.getOldType() == Type.Short) {
id = ((Short) obj.b()).shortValue();
}
if (id != -1 && data != -1) {
int combined = id << 4 | data;
data = -1;
id = -1;
PacketUtil.writeVarInt(combined, output);
}
break;
case VarInt:
// convert from int, short, byte
if (metaIndex.getOldType() == Type.Byte) {
PacketUtil.writeVarInt(((Byte) obj.b()).intValue(), output);
}
if (metaIndex.getOldType() == Type.Short) {
PacketUtil.writeVarInt(((Short) obj.b()).intValue(), output);
}
if (metaIndex.getOldType() == Type.Int) {
PacketUtil.writeVarInt(((Integer) obj.b()).intValue(), output);
}
break;
case Float:
packetdataserializer.writeFloat(((Float) obj.b()).floatValue());
break;
case String:
packetdataserializer.a((String) obj.b());
break;
case Boolean:
packetdataserializer.writeBoolean(((Byte) obj.b()).byteValue() != 0);
break;
case Slot:
ItemStack itemstack = (ItemStack) obj.b();
packetdataserializer.a(itemstack);
break;
case Position:
BlockPosition blockposition = (BlockPosition) obj.b();
packetdataserializer.writeInt(blockposition.getX());
packetdataserializer.writeInt(blockposition.getY());
packetdataserializer.writeInt(blockposition.getZ());
break;
case Vector3F:
Vector3f vector3f = (Vector3f) obj.b();
packetdataserializer.writeFloat(vector3f.getX());
packetdataserializer.writeFloat(vector3f.getY());
packetdataserializer.writeFloat(vector3f.getZ());
}
}
}
}
output.writeByte(255);
}
private UUID getUUID(int id) {
if (uuidMap.containsKey(id)) {
return uuidMap.get(id);
} else {
UUID uuid = UUID.randomUUID();
uuidMap.put(id, uuid);
return uuid;
}
}
}

View File

@ -0,0 +1,7 @@
Howdy Partner:
[-] Fix arrows going down.
[-] Make teleport relative if not 1024 diff.
[-] Fix boats / minecarts if possible otherwise tell them nicely they don't work.
[-] Cry because how long it took to get this far haha.
[-] Clean up so we're not using NMS and more reflection based :)

View File

@ -0,0 +1,4 @@
name: ViaVersion
main: us.myles.ViaVersion.Core
author: _MylesC
version: 0.1