commit 67b31c50608937df1d21bba5239132dad50a2c82 Author: Myles Date: Sun Feb 28 22:44:33 2016 +0000 Initial Commit, need to upload license. You may not claim credit for anything given here if used. All rights reversed excluding extracts from MCProtocolLib. diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ec1055155 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +.idea/ +*.iml diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..380c401cc --- /dev/null +++ b/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + us.myles + ViaVersion + 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + + + + + + + org.spigot + spigot1.8 + 1.8 + system + E:/spigot_server.jar + + + + \ No newline at end of file diff --git a/src/main/java/org/spacehq/mc/protocol/data/game/chunk/BlockStorage.java b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/BlockStorage.java new file mode 100644 index 000000000..273b195d6 --- /dev/null +++ b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/BlockStorage.java @@ -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 states; + private FlexibleStorage storage; + + public BlockStorage() { + this.bitsPerEntry = 4; + + this.states = new ArrayList(); + this.states.add(0); + + this.storage = new FlexibleStorage(this.bitsPerEntry, 4096); + } + + public BlockStorage(ByteBuf in) throws IOException { + this.bitsPerEntry = in.readUnsignedByte(); + + this.states = new ArrayList(); + int stateCount = PacketUtil.readVarInt(in); + for (int i = 0; i < stateCount; i++) { + this.states.add(PacketUtil.readVarInt(in)); + } + + this.storage = new FlexibleStorage(this.bitsPerEntry, PacketUtil.readLongs(PacketUtil.readVarInt(in), in)); + } + + 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 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 oldStates = this.states; + if (this.bitsPerEntry > 8) { + oldStates = new ArrayList(this.states); + this.states.clear(); + this.bitsPerEntry = 13; + } + + FlexibleStorage oldStorage = this.storage; + this.storage = new FlexibleStorage(this.bitsPerEntry, this.storage.getSize()); + for (int index = 0; index < this.storage.getSize(); index++) { + int value = oldStorage.get(index); + this.storage.set(index, this.bitsPerEntry <= 8 ? value : oldStates.get(value)); + } + } + + id = this.bitsPerEntry <= 8 ? this.states.indexOf(state) : state; + } + + this.storage.set(ind, id); + } + + public boolean isEmpty() { + for (int index = 0; index < this.storage.getSize(); index++) { + if (this.storage.get(index) != 0) { + return false; + } + } + + return true; + } + + 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; + } +} \ No newline at end of file diff --git a/src/main/java/org/spacehq/mc/protocol/data/game/chunk/Chunk.java b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/Chunk.java new file mode 100644 index 000000000..6fb2af4b2 --- /dev/null +++ b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/Chunk.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/spacehq/mc/protocol/data/game/chunk/Column.java b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/Column.java new file mode 100644 index 000000000..1c7ff1464 --- /dev/null +++ b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/Column.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/spacehq/mc/protocol/data/game/chunk/FlexibleStorage.java b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/FlexibleStorage.java new file mode 100644 index 000000000..fcb75b4fc --- /dev/null +++ b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/FlexibleStorage.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/org/spacehq/mc/protocol/data/game/chunk/NibbleArray3d.java b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/NibbleArray3d.java new file mode 100644 index 000000000..a77fd10f8 --- /dev/null +++ b/src/main/java/org/spacehq/mc/protocol/data/game/chunk/NibbleArray3d.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/org/spacehq/mc/protocol/util/NetUtil.java b/src/main/java/org/spacehq/mc/protocol/util/NetUtil.java new file mode 100644 index 000000000..f8f332ccf --- /dev/null +++ b/src/main/java/org/spacehq/mc/protocol/util/NetUtil.java @@ -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; + } +} diff --git a/src/main/java/us/myles/ViaVersion/CancelException.java b/src/main/java/us/myles/ViaVersion/CancelException.java new file mode 100644 index 000000000..401657ac2 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/CancelException.java @@ -0,0 +1,4 @@ +package us.myles.ViaVersion; + +public class CancelException extends Exception{ +} diff --git a/src/main/java/us/myles/ViaVersion/ConnectionInfo.java b/src/main/java/us/myles/ViaVersion/ConnectionInfo.java new file mode 100644 index 000000000..bdb0651cd --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/ConnectionInfo.java @@ -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); + } +} diff --git a/src/main/java/us/myles/ViaVersion/Core.java b/src/main/java/us/myles/ViaVersion/Core.java new file mode 100644 index 000000000..2b7fbff96 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/Core.java @@ -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 futures = getPrivateField(connection, "g", List.class); + + for (ChannelFuture future : futures) { + ChannelPipeline pipeline = future.channel().pipeline(); + ChannelHandler bootstrapAcceptor = pipeline.first(); + ChannelInitializer 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() { + @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 getPrivateField(Object o, String f, Class 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() { + @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; + } + } +} diff --git a/src/main/java/us/myles/ViaVersion/PacketUtil.java b/src/main/java/us/myles/ViaVersion/PacketUtil.java new file mode 100644 index 000000000..dad6bdc95 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/PacketUtil.java @@ -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 output = new ArrayList(); + 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 s, ByteBuf buf) { + writeVarInt(s.size(), buf); + for (String str : s) { + writeString(str, buf); + } + } + + public static List readStringArray(ByteBuf buf) { + int len = readVarInt(buf); + List ret = new ArrayList(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; + } + +} diff --git a/src/main/java/us/myles/ViaVersion/Testing.java b/src/main/java/us/myles/ViaVersion/Testing.java new file mode 100644 index 000000000..1a9b19ac1 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/Testing.java @@ -0,0 +1,41 @@ +package us.myles.ViaVersion; + +import us.myles.ViaVersion.chunks.ByteWriter; + +public class Testing { + public static void main(String[] args) { +// Map> conversions = new HashMap<>(); +// for (MetaIndex metaIndex : MetaIndex.values()) { +// if (!conversions.containsKey(metaIndex.getNewType())) { +// conversions.put(metaIndex.getNewType(), new HashSet()); +// } +// conversions.get(metaIndex.getNewType()).add(metaIndex.getOldType()); +// } +// for (Map.Entry> 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)); + } +} diff --git a/src/main/java/us/myles/ViaVersion/chunks/ByteWriter.java b/src/main/java/us/myles/ViaVersion/chunks/ByteWriter.java new file mode 100644 index 000000000..d3939c2ec --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/chunks/ByteWriter.java @@ -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]; + } +} diff --git a/src/main/java/us/myles/ViaVersion/chunks/MagicBitSet.java b/src/main/java/us/myles/ViaVersion/chunks/MagicBitSet.java new file mode 100644 index 000000000..9b61c611d --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/chunks/MagicBitSet.java @@ -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(); + } +} diff --git a/src/main/java/us/myles/ViaVersion/chunks/PacketChunk.java b/src/main/java/us/myles/ViaVersion/chunks/PacketChunk.java new file mode 100644 index 000000000..549ef6d59 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/chunks/PacketChunk.java @@ -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; + } +} diff --git a/src/main/java/us/myles/ViaVersion/chunks/PacketChunkData.java b/src/main/java/us/myles/ViaVersion/chunks/PacketChunkData.java new file mode 100644 index 000000000..69bef5dae --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/chunks/PacketChunkData.java @@ -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; + } +} diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaInboundHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaInboundHandler.java new file mode 100644 index 000000000..0eeb5ea85 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaInboundHandler.java @@ -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); + } + + +} diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaOutboundHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaOutboundHandler.java new file mode 100644 index 000000000..a8dfbaee6 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaOutboundHandler.java @@ -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(); + } + } + +} diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaOutboundPacketHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaOutboundPacketHandler.java new file mode 100644 index 000000000..6e787b300 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaOutboundPacketHandler.java @@ -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 { + private final ChannelInitializer oldInit; + private Method method; + private ConnectionInfo info; + private ViaInboundHandler inbound; + private ViaOutboundHandler outbound; + private SocketChannel socketChannel; + private ViaOutboundPacketHandler outbound2; + + public ViaVersionInitializer(ChannelInitializer 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"); + } + +} diff --git a/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java b/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java new file mode 100644 index 000000000..ae4fb7405 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java @@ -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 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; + } +} + diff --git a/src/main/java/us/myles/ViaVersion/metadata/NewType.java b/src/main/java/us/myles/ViaVersion/metadata/NewType.java new file mode 100644 index 000000000..375e8c61a --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/metadata/NewType.java @@ -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; + } +} diff --git a/src/main/java/us/myles/ViaVersion/metadata/Type.java b/src/main/java/us/myles/ViaVersion/metadata/Type.java new file mode 100644 index 000000000..48ceb3ac5 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/metadata/Type.java @@ -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; + } +} diff --git a/src/main/java/us/myles/ViaVersion/packets/Direction.java b/src/main/java/us/myles/ViaVersion/packets/Direction.java new file mode 100644 index 000000000..93e5a567d --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/packets/Direction.java @@ -0,0 +1,6 @@ +package us.myles.ViaVersion.packets; + +public enum Direction { + OUTGOING, + INCOMING +} diff --git a/src/main/java/us/myles/ViaVersion/packets/PacketType.java b/src/main/java/us/myles/ViaVersion/packets/PacketType.java new file mode 100644 index 000000000..eb596044e --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/packets/PacketType.java @@ -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); + } +} diff --git a/src/main/java/us/myles/ViaVersion/packets/State.java b/src/main/java/us/myles/ViaVersion/packets/State.java new file mode 100644 index 000000000..60a17f8f6 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/packets/State.java @@ -0,0 +1,8 @@ +package us.myles.ViaVersion.packets; + +public enum State { + HANDSHAKE, + STATUS, + LOGIN, + PLAY +} diff --git a/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java b/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java new file mode 100644 index 000000000..79f961c0a --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java @@ -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); + } +} diff --git a/src/main/java/us/myles/ViaVersion/transformers/OutgoingTransformer.java b/src/main/java/us/myles/ViaVersion/transformers/OutgoingTransformer.java new file mode 100644 index 000000000..b84d0cf59 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/transformers/OutgoingTransformer.java @@ -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 uuidMap = new HashMap(); + + 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 dw, ByteBuf output) { + PacketDataSerializer packetdataserializer = new PacketDataSerializer(output); + + if (dw != null) { + short id = -1; + int data = -1; + + Iterator 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; + } + } + +} diff --git a/src/main/resources/TODO.txt b/src/main/resources/TODO.txt new file mode 100644 index 000000000..74bf9a4ef --- /dev/null +++ b/src/main/resources/TODO.txt @@ -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 :) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 000000000..afaeb2073 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,4 @@ +name: ViaVersion +main: us.myles.ViaVersion.Core +author: _MylesC +version: 0.1 \ No newline at end of file