diff --git a/README.md b/README.md index 1e9b8b404..b64840faf 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,9 @@ -# ViaVersion 0.4.6 -**Allows the connection of 1.8 clients to 1.9** +# ViaVersion 0.4.9 +**Allows the connection of 1.9 clients to 1.8** This plugin modifies netty to allow connection of 1.9 clients to 1.8, -**Don't use late bind* - -**As of this point it doesn't have everything, I need to fix:** - -Attempt to make boats nicer when they don't work - -Remap spawn eggs - -If you have a bug with entities, please report the full stack trace +###**Don't use late bind* This took hours of work, so if you enjoy this consider looking into contacting me and supporting my projects. @@ -24,16 +16,27 @@ Sources: **MCProtocolLib** (used for chunk reading & some of writing.) +**OpenNBT** (used for slot rewriting) + + + Contributors: -------- **Myself** (harhar) + **Matsv/StamBoom** + **HugoDaBosss** + **SanderGielisse** + **Paulomart** + **gigosaurus** +**fillefilip8** + **Chat: ** https://gitter.im/MylesIsCool/ViaVersion We use it to collaborate and solve errors, sign in with github. diff --git a/pom.xml b/pom.xml index 7ab46f1b7..c87afbb2a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,30 @@ - + 4.0.0 us.myles ViaVersion - 1.0-SNAPSHOT + 0.4.9 + ViaVersion-${version} + + org.apache.maven.plugins + maven-shade-plugin + 1.4 + + false + + + + package + + shade + + + + org.apache.maven.plugins maven-compiler-plugin @@ -18,8 +34,19 @@ + + + src/main/resources + true + + + + org.spacehq + opennbt + 1.0 + org.bukkit bukkit @@ -50,5 +77,9 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - + + spacehq-repo + https://repo.spacehq.org/content/repositories/releases/ + + diff --git a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java index 0bdaf7820..5c6903e9e 100644 --- a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java +++ b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java @@ -12,12 +12,13 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; - import us.myles.ViaVersion.api.ViaVersion; import us.myles.ViaVersion.api.ViaVersionAPI; +import us.myles.ViaVersion.commands.ViaVersionCommand; import us.myles.ViaVersion.handlers.ViaVersionInitializer; import us.myles.ViaVersion.util.ReflectionUtil; +import java.lang.reflect.Field; import java.util.Collections; import java.util.List; import java.util.Set; @@ -33,12 +34,12 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI { @Override public void onEnable() { ViaVersion.setInstance(this); - if(System.getProperty("ViaVersion") != null){ + if (System.getProperty("ViaVersion") != null) { getLogger().severe("ViaVersion is already loaded, we don't support reloads. Please reboot if you wish to update."); return; } - getLogger().info("ViaVersion enabled, injecting. (Allows 1.8 to be accessed via 1.9)"); + getLogger().info("ViaVersion " + getDescription().getVersion() + " is now enabled, injecting. (Allows 1.8 to be accessed via 1.9)"); try { injectPacketHandler(); System.setProperty("ViaVersion", getDescription().getVersion()); @@ -52,24 +53,36 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI { setPorted(e.getPlayer().getUniqueId(), false); } }, this); + getCommand("viaversion").setExecutor(new ViaVersionCommand()); } public void injectPacketHandler() throws Exception { Class serverClazz = ReflectionUtil.nms("MinecraftServer"); Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer"); Object connection = serverClazz.getDeclaredMethod("getServerConnection").invoke(server); - - List futures = ReflectionUtil.get(connection, "g", List.class); - if (futures.size() == 0) { - throw new Exception("Could not find server to inject (Please ensure late-bind in your spigot.yml is false)"); + // loop through all fields checking if list + boolean injected = false; + for (Field field : connection.getClass().getDeclaredFields()) { + field.setAccessible(true); + Object value = field.get(connection); + if (value instanceof List) { + for (Object o : (List) value) { + if (o instanceof ChannelFuture) { + ChannelFuture future = (ChannelFuture) o; + ChannelPipeline pipeline = future.channel().pipeline(); + ChannelHandler bootstrapAcceptor = pipeline.first(); + ChannelInitializer oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class); + ChannelInitializer newInit = new ViaVersionInitializer(oldInit); + ReflectionUtil.set(bootstrapAcceptor, "childHandler", newInit); + injected = true; + } else { + break; // not the right list. + } + } + } } - - for (ChannelFuture future : futures) { - ChannelPipeline pipeline = future.channel().pipeline(); - ChannelHandler bootstrapAcceptor = pipeline.first(); - ChannelInitializer oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class); - ChannelInitializer newInit = new ViaVersionInitializer(oldInit); - ReflectionUtil.set(bootstrapAcceptor, "childHandler", newInit); + if (!injected) { + throw new Exception("Could not find server to inject (Please ensure late-bind in your spigot.yml is false)"); } } @@ -78,6 +91,11 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI { return portedPlayers.contains(player.getUniqueId()); } + @Override + public String getVersion() { + return getDescription().getVersion(); + } + public void setPorted(UUID id, boolean value) { if (value) { portedPlayers.add(id); diff --git a/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java b/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java index cdccc80ce..ba3fe0b0e 100644 --- a/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java +++ b/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java @@ -3,6 +3,11 @@ package us.myles.ViaVersion.api; import org.bukkit.entity.Player; public interface ViaVersionAPI { - + /** + * Is player using 1.9? + * @param player + * @return + */ boolean isPorted(Player player); + String getVersion(); } diff --git a/src/main/java/us/myles/ViaVersion/commands/ViaVersionCommand.java b/src/main/java/us/myles/ViaVersion/commands/ViaVersionCommand.java new file mode 100644 index 000000000..11872a831 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/commands/ViaVersionCommand.java @@ -0,0 +1,48 @@ +package us.myles.ViaVersion.commands; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import us.myles.ViaVersion.api.ViaVersion; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by fillefilip8 on 2016-03-03. + */ +public class ViaVersionCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (sender.hasPermission("viaversion.admin")) { + if (args.length == 0) { + sender.sendMessage(color("&aViaVersion &c" + ViaVersion.getInstance().getVersion())); + sender.sendMessage(color("&6Commands:")); + sender.sendMessage(color("&2/viaversion list &7- &6Shows lists of all 1.9 clients and 1.8 clients.")); + } else if (args.length == 1) { + if (args[0].equalsIgnoreCase("list")) { + List portedPlayers = new ArrayList(); + List normalPlayers = new ArrayList(); + for (Player p : Bukkit.getOnlinePlayers()) { + if (ViaVersion.getInstance().isPorted(p)) { + portedPlayers.add(p.getName()); + } else { + normalPlayers.add(p.getName()); + } + } + + sender.sendMessage(color("&8[&61.9&8]: &b" + portedPlayers.toString())); + sender.sendMessage(color("&8[&61.8&8]: &b" + normalPlayers.toString())); + } + } + + } + return false; + } + public String color(String string){ + return string.replace("&", "ยง"); + } +} diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java index 0a2d0f735..590a9539a 100644 --- a/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java @@ -31,6 +31,7 @@ public class ViaDecodeHandler extends ByteToMessageDecoder { ByteBuf newPacket = ctx.alloc().buffer(); try { incomingTransformer.transform(id, bytebuf, newPacket); + bytebuf.readBytes(bytebuf.readableBytes()); bytebuf = newPacket; } catch (CancelException e) { bytebuf.readBytes(bytebuf.readableBytes()); @@ -44,13 +45,7 @@ public class ViaDecodeHandler extends ByteToMessageDecoder { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (!(cause.getCause().getCause() instanceof CancelException)) { - if (!(cause.getCause() instanceof CancelException)) { - if (!(cause instanceof CancelException)) { - super.exceptionCaught(ctx, cause); - } - } - } + if (PacketUtil.containsCause(cause, CancelException.class)) return; + super.exceptionCaught(ctx, cause); } - } diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java index dac12dc59..915f2d9d3 100644 --- a/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java @@ -72,12 +72,7 @@ public class ViaEncodeHandler extends MessageToByteEncoder { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (!(cause.getCause().getCause() instanceof CancelException)) { - if (!(cause.getCause() instanceof CancelException)) { - if (!(cause instanceof CancelException)) { - super.exceptionCaught(ctx, cause); - } - } - } + if (PacketUtil.containsCause(cause, CancelException.class)) return; + super.exceptionCaught(ctx, cause); } } diff --git a/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java b/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java index 0224f5401..0cc37828b 100644 --- a/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java +++ b/src/main/java/us/myles/ViaVersion/metadata/MetaIndex.java @@ -27,10 +27,10 @@ public enum MetaIndex { 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_SKIN_FLAGS(HumanEntity.class, 10, Type.Byte, 12, NewType.Byte), // 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), + PLAYER_ADDITIONAL_HEARTS(HumanEntity.class, 17, Type.Float, 10, NewType.Float), + PLAYER_SCORE(HumanEntity.class, 18, Type.Int, 11, NewType.VarInt), // horse HORSE_INFO(Horse.class, 16, Type.Int, 12, NewType.Byte), HORSE_TYPE(Horse.class, 19, Type.Byte, 13, NewType.VarInt), diff --git a/src/main/java/us/myles/ViaVersion/metadata/MetadataRewriter.java b/src/main/java/us/myles/ViaVersion/metadata/MetadataRewriter.java index 1dad62f05..f034bdb16 100644 --- a/src/main/java/us/myles/ViaVersion/metadata/MetadataRewriter.java +++ b/src/main/java/us/myles/ViaVersion/metadata/MetadataRewriter.java @@ -11,6 +11,8 @@ import org.bukkit.util.Vector; import io.netty.buffer.ByteBuf; +import us.myles.ViaVersion.slot.ItemSlotRewriter; +import us.myles.ViaVersion.slot.ItemSlotRewriter.ItemStack; import us.myles.ViaVersion.util.PacketUtil; public class MetadataRewriter { @@ -92,7 +94,9 @@ public class MetadataRewriter { output.writeBoolean(((Byte) value).byteValue() != 0); break; case Slot: - PacketUtil.writeItem(value, output); + ItemStack item = (ItemStack) value; + ItemSlotRewriter.fixIdsFrom1_8To1_9(item); + ItemSlotRewriter.writeItemStack(item, output); break; case Position: Vector vector = (Vector) value; @@ -148,8 +152,13 @@ public class MetadataRewriter { case String: entries.add(new Entry(index, PacketUtil.readString(buf))); break; - case Slot: - entries.add(new Entry(index, PacketUtil.readItem(buf))); + case Slot: { + try { + entries.add(new Entry(index, ItemSlotRewriter.readItemStack(buf))); + } catch (Exception e) { + e.printStackTrace(); + } + } break; case Position: { int x = buf.readInt(); diff --git a/src/main/java/us/myles/ViaVersion/slot/ItemSlotRewriter.java b/src/main/java/us/myles/ViaVersion/slot/ItemSlotRewriter.java new file mode 100644 index 000000000..3a01d3d95 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/slot/ItemSlotRewriter.java @@ -0,0 +1,297 @@ +package us.myles.ViaVersion.slot; + +import io.netty.buffer.ByteBuf; +import org.bukkit.Material; +import org.spacehq.opennbt.tag.builtin.CompoundTag; +import org.spacehq.opennbt.tag.builtin.StringTag; +import us.myles.ViaVersion.CancelException; +import us.myles.ViaVersion.util.PacketUtil; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class ItemSlotRewriter { + + public static void rewrite1_9To1_8(ByteBuf input, ByteBuf output) throws CancelException { + try { + ItemStack item = readItemStack(input); + fixIdsFrom1_9To1_8(item); + writeItemStack(item, output); + } catch (Exception e) { + System.out.println("Error while rewriting an item slot."); + e.printStackTrace(); + throw new CancelException(); + } + } + + public static void rewrite1_8To1_9(ByteBuf input, ByteBuf output) throws CancelException { + try { + ItemStack item = readItemStack(input); + fixIdsFrom1_8To1_9(item); + writeItemStack(item, output); + } catch (Exception e) { + System.out.println("Error while rewriting an item slot."); + e.printStackTrace(); + throw new CancelException(); + } + } + + public static void fixIdsFrom1_9To1_8(ItemStack item) { + if (item != null) { + if (item.id == Material.MONSTER_EGG.getId() && item.data == 0) { + CompoundTag tag = item.tag; + int data = 0; + if (tag != null && tag.get("EntityTag") instanceof CompoundTag) { + CompoundTag entityTag = tag.get("EntityTag"); + if (entityTag.get("id") instanceof StringTag) { + StringTag id = entityTag.get("id"); + if (ENTTIY_NAME_TO_ID.containsKey(id.getValue())) + data = ENTTIY_NAME_TO_ID.get(id.getValue()); + } + } + item.tag = null; + item.data = (short) data; + } + if (item.id == Material.POTION.getId()) { + CompoundTag tag = item.tag; + int data = 0; + if (tag != null && tag.get("Potion") instanceof StringTag) { + StringTag potion = tag.get("Potion"); + String potionName = potion.getValue().replace("minecraft:", ""); + if (POTION_NAME_TO_ID.containsKey(potionName)) { + data = POTION_NAME_TO_ID.get(potionName); + } + } + item.tag = null; + item.data = (short) data; + } + if (item.id == 438) { + CompoundTag tag = item.tag; + int data = 0; + item.id = (short) Material.POTION.getId(); + if (tag != null && tag.get("Potion") instanceof StringTag) { + StringTag potion = tag.get("Potion"); + String potionName = potion.getValue().replace("minecraft:", ""); + if (POTION_NAME_TO_ID.containsKey(potionName)) { + data = POTION_NAME_TO_ID.get(potionName) + 8192; + } + } + item.tag = null; + item.data = (short) data; + } + } + } + + public static void fixIdsFrom1_8To1_9(ItemStack item) { + if (item != null) { + if (item.id == Material.MONSTER_EGG.getId() && item.data != 0) { + CompoundTag tag = item.tag; + if (tag == null) { + tag = new CompoundTag("tag"); + } + CompoundTag entityTag = new CompoundTag("EntityTag"); + if (ENTTIY_ID_TO_NAME.containsKey(Integer.valueOf(item.data))) { + StringTag id = new StringTag("id", ENTTIY_ID_TO_NAME.get(Integer.valueOf(item.data))); + entityTag.put(id); + tag.put(entityTag); + } + item.tag = tag; + item.data = 0; + } + if (item.id == Material.POTION.getId()) { + CompoundTag tag = item.tag; + if (tag == null) { + tag = new CompoundTag("tag"); + } + if(item.data >= 16384){ + item.id = 438; // splash id + item.data = (short) (item.data - 8192); + } + if (POTION_ID_TO_NAME.containsKey(Integer.valueOf(item.data))) { + String name = POTION_ID_TO_NAME.get(Integer.valueOf(item.data)); + StringTag potion = new StringTag("Potion", "minecraft:" + name); + tag.put(potion); + } + item.tag = tag; + item.data = 0; + } + } + } + + public static ItemStack readItemStack(ByteBuf input) throws IOException { + short id = input.readShort(); + if (id < 0) { + + return null; + } else { + ItemStack item = new ItemStack(); + item.id = id; + item.amount = input.readByte(); + item.data = input.readShort(); + item.tag = PacketUtil.readNBT(input); + return item; + } + } + + public static void writeItemStack(ItemStack item, ByteBuf output) throws IOException { + if (item == null) { + output.writeShort(-1); + } else { + output.writeShort(item.id); + output.writeByte(item.amount); + output.writeShort(item.data); + PacketUtil.writeNBT(output, item.tag); + } + } + + public static class ItemStack { + + public short id; + public byte amount; + public short data; + public CompoundTag tag; + + public static ItemStack fromBukkit(org.bukkit.inventory.ItemStack stack) { + ItemStack item = new ItemStack(); + item.id = (short) stack.getTypeId(); + item.amount = (byte) stack.getAmount(); + item.data = stack.getData().getData(); + // TODO: nbt + return item; + } + } + + private static Map ENTTIY_NAME_TO_ID = new HashMap<>(); + private static Map ENTTIY_ID_TO_NAME = new HashMap<>(); + + private static Map POTION_NAME_TO_ID = new HashMap<>(); + private static Map POTION_ID_TO_NAME = new HashMap<>(); + + static { + /* Entities */ + registerEntity(1, "Item"); + registerEntity(2, "XPOrb"); + registerEntity(7, "ThrownEgg"); + registerEntity(8, "LeashKnot"); + registerEntity(9, "Painting"); + registerEntity(10, "Arrow"); + registerEntity(11, "Snowball"); + registerEntity(12, "Fireball"); + registerEntity(13, "SmallFireball"); + registerEntity(14, "ThrownEnderpearl"); + registerEntity(15, "EyeOfEnderSignal"); + registerEntity(16, "ThrownPotion"); + registerEntity(17, "ThrownExpBottle"); + registerEntity(18, "ItemFrame"); + registerEntity(19, "WitherSkull"); + registerEntity(20, "PrimedTnt"); + registerEntity(21, "FallingSand"); + registerEntity(22, "FireworksRocketEntity"); + registerEntity(30, "ArmorStand"); + registerEntity(40, "MinecartCommandBlock"); + registerEntity(41, "Boat"); + registerEntity(42, "MinecartRideable"); + registerEntity(43, "MinecartChest"); + registerEntity(44, "MinecartFurnace"); + registerEntity(45, "MinecartTNT"); + registerEntity(46, "MinecartHopper"); + registerEntity(47, "MinecartSpawner"); + registerEntity(48, "Mob"); + registerEntity(49, "Monster"); + registerEntity(50, "Creeper"); + registerEntity(51, "Skeleton"); + registerEntity(52, "Spider"); + registerEntity(53, "Giant"); + registerEntity(54, "Zombie"); + registerEntity(55, "Slime"); + registerEntity(56, "Ghast"); + registerEntity(57, "PigZombie"); + registerEntity(58, "Enderman"); + registerEntity(59, "CaveSpider"); + registerEntity(60, "Silverfish"); + registerEntity(61, "Blaze"); + registerEntity(62, "LavaSlime"); + registerEntity(63, "EnderDragon"); + registerEntity(64, "WitherBoss"); + registerEntity(65, "Bat"); + registerEntity(66, "Witch"); + registerEntity(67, "Endermite"); + registerEntity(68, "Guardian"); + registerEntity(90, "Pig"); + registerEntity(91, "Sheep"); + registerEntity(92, "Cow"); + registerEntity(93, "Chicken"); + registerEntity(94, "Squid"); + registerEntity(95, "Wolf"); + registerEntity(96, "MushroomCow"); + registerEntity(97, "SnowMan"); + registerEntity(98, "Ozelot"); + registerEntity(99, "VillagerGolem"); + registerEntity(100, "EntityHorse"); + registerEntity(101, "Rabbit"); + registerEntity(120, "Villager"); + registerEntity(200, "EnderCrystal"); + + /* Potions */ + registerPotion(0, "water"); + registerPotion(64, "mundane"); + registerPotion(32, "thick"); + registerPotion(16, "awkward"); + + registerPotion(8198, "night_vision"); + registerPotion(8262, "long_night_vision"); + + registerPotion(8206, "invisibility"); + registerPotion(8270, "long_invisibility"); + + registerPotion(8203, "leaping"); + registerPotion(8267, "long_leaping"); + registerPotion(8235, "strong_leaping"); + + registerPotion(8195, "fire_resistance"); + registerPotion(8259, "long_fire_resistance"); + + registerPotion(8194, "swiftness"); + registerPotion(8258, "long_swiftness"); + registerPotion(8226, "strong_swiftness"); + + registerPotion(8202, "slowness"); + registerPotion(8266, "long_slowness"); + + registerPotion(8205, "water_breathing"); + registerPotion(8269, "long_water_breathing"); + + registerPotion(8197, "healing"); + registerPotion(8229, "strong_healing"); + + registerPotion(8204, "harming"); + registerPotion(8236, "strong_harming"); + + registerPotion(8196, "poison"); + registerPotion(8260, "long_poison"); + registerPotion(8228, "strong_poison"); + + registerPotion(8193, "regeneration"); + registerPotion(8257, "long_regeneration"); + registerPotion(8225, "strong_regeneration"); + + registerPotion(8201, "strength"); + registerPotion(8265, "long_strength"); + registerPotion(8233, "strong_strength"); + + registerPotion(8200, "weakness"); + registerPotion(8264, "long_weakness"); + + } + + private static void registerEntity(Integer id, String name) { + ENTTIY_ID_TO_NAME.put(id, name); + ENTTIY_NAME_TO_ID.put(name, id); + } + + private static void registerPotion(Integer id, String name) { + POTION_ID_TO_NAME.put(id, name); + POTION_NAME_TO_ID.put(name, id); + } +} diff --git a/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java b/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java index b7c99669d..bc4549e85 100644 --- a/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java +++ b/src/main/java/us/myles/ViaVersion/transformers/IncomingTransformer.java @@ -1,17 +1,26 @@ package us.myles.ViaVersion.transformers; +import com.avaje.ebeaninternal.server.cluster.Packet; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.bukkit.Material; import org.bukkit.inventory.ItemStack; +import org.spacehq.opennbt.tag.builtin.ListTag; +import org.spacehq.opennbt.tag.builtin.StringTag; +import org.spacehq.opennbt.tag.builtin.Tag; import us.myles.ViaVersion.CancelException; import us.myles.ViaVersion.ConnectionInfo; import us.myles.ViaVersion.ViaVersionPlugin; +import us.myles.ViaVersion.slot.ItemSlotRewriter; import us.myles.ViaVersion.packets.PacketType; import us.myles.ViaVersion.packets.State; import us.myles.ViaVersion.util.PacketUtil; import us.myles.ViaVersion.util.ReflectionUtil; +import java.io.IOException; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.nio.charset.Charset; public class IncomingTransformer { private final ConnectionInfo info; @@ -82,7 +91,7 @@ public class IncomingTransformer { return; } if (packet == PacketType.PLAY_PLAYER_DIGGING) { - byte status = input.readByte(); + int status = input.readByte() & 0xFF; // unsign if (status == 6) { // item swap throw new CancelException(); } @@ -102,7 +111,9 @@ public class IncomingTransformer { if (slot == 45 && windowID == 0) { try { Class setSlot = ReflectionUtil.nms("PacketPlayOutSetSlot"); - Object setSlotPacket = setSlot.getConstructors()[1].newInstance(windowID, slot, null); + Constructor setSlotConstruct = setSlot.getDeclaredConstructor(int.class, int.class, ReflectionUtil.nms("ItemStack")); + // properly construct + Object setSlotPacket = setSlotConstruct.newInstance(windowID, slot, null); info.getChannel().pipeline().writeAndFlush(setSlotPacket); // slot is empty slot = -999; // we're evil, they'll throw item on the ground } catch (ClassNotFoundException e) { @@ -113,6 +124,8 @@ public class IncomingTransformer { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); } } @@ -121,7 +134,7 @@ public class IncomingTransformer { output.writeByte(button); output.writeShort(action); output.writeByte(mode); - output.writeBytes(input); + ItemSlotRewriter.rewrite1_9To1_8(input, output); return; } if (packet == PacketType.PLAY_CLIENT_SETTINGS) { @@ -166,6 +179,26 @@ public class IncomingTransformer { } return; } + if(packet == PacketType.PLAY_PLUGIN_MESSAGE_REQUEST) { + String name = PacketUtil.readString(input); + PacketUtil.writeString(name, output); + byte[] b = new byte[input.readableBytes()]; + input.readBytes(b); + // patch books + if(name.equals("MC|BSign")){ + ByteBuf in = Unpooled.wrappedBuffer(b); + try { + ItemSlotRewriter.ItemStack stack = ItemSlotRewriter.readItemStack(in); + stack.id = (short) Material.WRITTEN_BOOK.getId(); + // write + ItemSlotRewriter.writeItemStack(stack, output); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + output.writeBytes(b); + } if (packet == PacketType.PLAY_PLAYER_BLOCK_PLACEMENT) { Long position = input.readLong(); output.writeLong(position); @@ -174,22 +207,14 @@ public class IncomingTransformer { int hand = PacketUtil.readVarInt(input); ItemStack inHand = ViaVersionPlugin.getHandItem(info); - Object item = null; try { - Method m = ReflectionUtil.obc("inventory.CraftItemStack").getDeclaredMethod("asNMSCopy", ItemStack.class); - item = m.invoke(null, inHand); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { + ItemSlotRewriter.ItemStack item = ItemSlotRewriter.ItemStack.fromBukkit(inHand); + ItemSlotRewriter.fixIdsFrom1_9To1_8(item); + ItemSlotRewriter.writeItemStack(item, output); + } catch (Exception e) { e.printStackTrace(); } - PacketUtil.writeItem(item, output); - short curX = input.readUnsignedByte(); output.writeByte(curX); short curY = input.readUnsignedByte(); @@ -206,26 +231,25 @@ public class IncomingTransformer { output.writeByte(255); // write item in hand ItemStack inHand = ViaVersionPlugin.getHandItem(info); - Object item = null; try { - Method m = ReflectionUtil.obc("inventory.CraftItemStack").getDeclaredMethod("asNMSCopy", ItemStack.class); - item = m.invoke(null, inHand); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { + ItemSlotRewriter.ItemStack item = ItemSlotRewriter.ItemStack.fromBukkit(inHand); + ItemSlotRewriter.fixIdsFrom1_9To1_8(item); + ItemSlotRewriter.writeItemStack(item, output); + } catch (Exception e) { e.printStackTrace(); } - PacketUtil.writeItem(item, output); output.writeByte(-1); output.writeByte(-1); output.writeByte(-1); return; } + if (packet == PacketType.PLAY_CREATIVE_INVENTORY_ACTION) { + short slot = input.readShort(); + output.writeShort(slot); + + ItemSlotRewriter.rewrite1_9To1_8(input, output); + } 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 index 667d50be3..9609fc1f5 100644 --- a/src/main/java/us/myles/ViaVersion/transformers/OutgoingTransformer.java +++ b/src/main/java/us/myles/ViaVersion/transformers/OutgoingTransformer.java @@ -1,15 +1,17 @@ package us.myles.ViaVersion.transformers; -import com.google.gson.Gson; -import com.google.gson.JsonObject; - import io.netty.buffer.ByteBuf; - +import io.netty.buffer.Unpooled; +import org.bukkit.Material; import org.bukkit.entity.EntityType; +import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.spacehq.mc.protocol.data.game.chunk.Column; import org.spacehq.mc.protocol.util.NetUtil; - +import org.spacehq.opennbt.tag.builtin.ListTag; +import org.spacehq.opennbt.tag.builtin.StringTag; +import org.spacehq.opennbt.tag.builtin.Tag; import us.myles.ViaVersion.CancelException; import us.myles.ViaVersion.ConnectionInfo; import us.myles.ViaVersion.ViaVersionPlugin; @@ -17,6 +19,7 @@ import us.myles.ViaVersion.api.ViaVersion; import us.myles.ViaVersion.metadata.MetadataRewriter; import us.myles.ViaVersion.packets.PacketType; import us.myles.ViaVersion.packets.State; +import us.myles.ViaVersion.slot.ItemSlotRewriter; import us.myles.ViaVersion.sounds.SoundEffect; import us.myles.ViaVersion.util.EntityUtil; import us.myles.ViaVersion.util.PacketUtil; @@ -28,7 +31,6 @@ import java.util.*; import static us.myles.ViaVersion.util.PacketUtil.*; public class OutgoingTransformer { - private static Gson gson = new Gson(); private final ConnectionInfo info; private final ViaVersionPlugin plugin = (ViaVersionPlugin) ViaVersion.getInstance(); private boolean cancel = false; @@ -50,7 +52,7 @@ public class OutgoingTransformer { 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 && (!packet.name().toLowerCase().contains("move") && !packet.name().contains("look"))) +// if (packet != PacketType.PLAY_CHUNK_DATA && packet != PacketType.PLAY_KEEP_ALIVE && packet != PacketType.PLAY_TIME_UPDATE && (!packet.name().toLowerCase().contains("move") && !packet.name().toLowerCase().contains("look"))) // System.out.println("Packet Type: " + packet + " Original ID: " + packetID + " State:" + info.getState()); if (packet.getPacketID() != -1) { packetID = packet.getNewPacketID(); @@ -60,15 +62,14 @@ public class OutgoingTransformer { PacketUtil.writeVarInt(packetID, output); if (packet == PacketType.PLAY_NAMED_SOUND_EFFECT) { String name = PacketUtil.readString(input); + SoundEffect effect = SoundEffect.getByName(name); int catid = 0; String newname = name; if (effect != null) { - if(effect.isBreakPlaceSound()) { - input.readBytes(input.readableBytes()); - output.clear(); - return; - } + if (effect.isBreakPlaceSound()) { + throw new CancelException(); + } catid = effect.getCategory().getId(); newname = effect.getNewName(); } @@ -87,12 +88,12 @@ public class OutgoingTransformer { if (!vehicleMap.containsKey(passenger)) throw new CancelException(); vehicle = vehicleMap.remove(passenger); - writeVarInt(vehicle,output); + writeVarInt(vehicle, output); writeVarIntArray(Collections.emptyList(), output); - } else{ + } else { writeVarInt(vehicle, output); writeVarIntArray(Collections.singletonList(passenger), output); - vehicleMap.put(passenger,vehicle); + vehicleMap.put(passenger, vehicle); } return; } @@ -100,6 +101,17 @@ public class OutgoingTransformer { output.writeInt(vehicle); return; } + if (packet == PacketType.PLAY_PLUGIN_MESSAGE) { + String name = PacketUtil.readString(input); + PacketUtil.writeString(name, output); + byte[] b = new byte[input.readableBytes()]; + input.readBytes(b); + // patch books + if(name.equals("MC|BOpen")){ + PacketUtil.writeVarInt(0, output); + } + output.writeBytes(b); + } if (packet == PacketType.PLAY_DISCONNECT) { String reason = readString(input); writeString(fixJson(reason), output); @@ -115,6 +127,55 @@ public class OutgoingTransformer { output.writeBytes(input); return; } + if (packet == PacketType.PLAY_PLAYER_LIST_ITEM) { + int action = readVarInt(input); + writeVarInt(action, output); + int players = readVarInt(input); + writeVarInt(players, output); + + // loop through players + for (int i = 0; i < players; i++) { + UUID uuid = readUUID(input); + writeUUID(uuid, output); + if (action == 0) { // add player + writeString(readString(input), output); // name + + int properties = readVarInt(input); + writeVarInt(properties, output); + + // loop through properties + for (int j = 0; j < properties; j++) { + writeString(readString(input), output); // name + writeString(readString(input), output); // value + boolean isSigned = input.readBoolean(); + output.writeBoolean(isSigned); + if (isSigned) { + writeString(readString(input), output); // signature + } + } + + writeVarInt(readVarInt(input), output); // gamemode + writeVarInt(readVarInt(input), output); // ping + boolean hasDisplayName = input.readBoolean(); + output.writeBoolean(hasDisplayName); + if (hasDisplayName) { + writeString(fixJson(readString(input)), output); // display name + } + } else if ((action == 1) || (action == 2)) { // update gamemode || update latency + writeVarInt(readVarInt(input), output); + } else if (action == 3) { // update display name + boolean hasDisplayName = input.readBoolean(); + output.writeBoolean(hasDisplayName); + if (hasDisplayName) { + writeString(fixJson(readString(input)), output); // display name + } + } else if (action == 4) { // remove player + // no fields + } + } + + return; + } if (packet == PacketType.PLAY_PLAYER_LIST_HEADER_FOOTER) { String header = readString(input); String footer = readString(input); @@ -192,9 +253,14 @@ public class OutgoingTransformer { 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); + try { + JSONObject json = (JSONObject) new JSONParser().parse(original); + JSONObject version = (JSONObject) json.get("version"); + version.put("protocol", info.getProtocol()); + PacketUtil.writeString(json.toJSONString(), output); + } catch (ParseException e) { + e.printStackTrace(); + } return; } if (packet == PacketType.LOGIN_SUCCESS) { @@ -221,7 +287,9 @@ public class OutgoingTransformer { slot += 1; // add 1 so it's now 2-5 } PacketUtil.writeVarInt(slot, output); - output.writeBytes(input); + + ItemSlotRewriter.rewrite1_8To1_9(input, output); + return; } if (packet == PacketType.PLAY_ENTITY_METADATA) { int id = PacketUtil.readVarInt(input); @@ -338,6 +406,28 @@ public class OutgoingTransformer { output.writeBytes(input); return; } + if (packet == PacketType.PLAY_SET_SLOT) { + int windowId = input.readUnsignedByte(); + output.writeByte(windowId); + + short slot = input.readShort(); + output.writeShort(slot); + + ItemSlotRewriter.rewrite1_8To1_9(input, output); + return; + } + if (packet == PacketType.PLAY_WINDOW_ITEMS) { + int windowId = input.readUnsignedByte(); + output.writeByte(windowId); + + short count = input.readShort(); + output.writeShort(count); + + for (int i = 0; i < count; i++) { + ItemSlotRewriter.rewrite1_8To1_9(input, output); + } + return; + } if (packet == PacketType.PLAY_SPAWN_MOB) { int id = PacketUtil.readVarInt(input); PacketUtil.writeVarInt(id, output); @@ -428,6 +518,20 @@ public class OutgoingTransformer { output.writeBytes(input); return; } + if (packet == PacketType.PLAY_ENTITY_EFFECT) { + int id = PacketUtil.readVarInt(input); + PacketUtil.writeVarInt(id, output); + byte effectID = input.readByte(); + output.writeByte(effectID); + byte amplifier = input.readByte(); + output.writeByte(amplifier); + int duration = PacketUtil.readVarInt(input); + PacketUtil.writeVarInt(duration, output); + // we need to write as a byte instead of boolean + boolean hideParticles = input.readBoolean(); + output.writeByte(hideParticles ? 1 : 0); + return; + } if (packet == PacketType.PLAY_TEAM) { String teamName = PacketUtil.readString(input); PacketUtil.writeString(teamName, output); @@ -460,7 +564,7 @@ public class OutgoingTransformer { int bitMask = input.readUnsignedShort(); - if (bitMask == 0) { + if (bitMask == 0 && groundUp) { output.clear(); PacketUtil.writeVarInt(PacketType.PLAY_UNLOAD_CHUNK.getNewPacketID(), output); output.writeInt(chunkX); @@ -497,7 +601,7 @@ public class OutgoingTransformer { output.writeBytes(input); } - private String fixJson(String line) { + public static String fixJson(String line) { if (line == null || line.equalsIgnoreCase("null")) { line = "{\"text\":\"\"}"; } else { @@ -507,11 +611,10 @@ public class OutgoingTransformer { line = "{\"text\":" + line + "}"; } try { - new JSONParser().parse(line); - } - catch (org.json.simple.parser.ParseException e) { - System.out.println("Invalid JSON String: \"" + line + "\" Please report this issue to the ViaVersion Github!"); - return "{\"text\":\"\"}"; + new JSONParser().parse(line); + } catch (Exception e) { + System.out.println("Invalid JSON String: \"" + line + "\" Please report this issue to the ViaVersion Github: " + e.getMessage()); + return "{\"text\":\"\"}"; } return line; } diff --git a/src/main/java/us/myles/ViaVersion/util/PacketUtil.java b/src/main/java/us/myles/ViaVersion/util/PacketUtil.java index 1c2dde784..333cab806 100644 --- a/src/main/java/us/myles/ViaVersion/util/PacketUtil.java +++ b/src/main/java/us/myles/ViaVersion/util/PacketUtil.java @@ -3,6 +3,8 @@ package us.myles.ViaVersion.util; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; @@ -11,6 +13,9 @@ import us.myles.ViaVersion.chunks.PacketChunk; import us.myles.ViaVersion.chunks.PacketChunkData; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.ByteBuffer; @@ -21,6 +26,9 @@ import java.util.BitSet; import java.util.List; import java.util.UUID; +import org.spacehq.opennbt.NBTIO; +import org.spacehq.opennbt.tag.builtin.CompoundTag; + public class PacketUtil { private static Method DECODE_METHOD; private static Method ENCODE_METHOD; @@ -42,6 +50,25 @@ public class PacketUtil { } } + public static CompoundTag readNBT(ByteBuf input) throws IOException { + int readerIndex = input.readerIndex(); + byte b = input.readByte(); + if (b == 0) { + return null; + } else { + input.readerIndex(readerIndex); + return (CompoundTag) NBTIO.readTag(new DataInputStream(new ByteBufInputStream(input))); + } + } + + public static void writeNBT(ByteBuf output, CompoundTag tag) throws IOException { + if (tag == null) { + output.writeByte(0); + } else { + NBTIO.writeTag(new DataOutputStream(new ByteBufOutputStream(output)), tag); + } + } + public static List callDecode(ByteToMessageDecoder decoder, ChannelHandlerContext ctx, Object input) { List output = new ArrayList(); try { @@ -354,45 +381,6 @@ public class PacketUtil { return output; } - public static void writeItem(Object value, ByteBuf output) { - try { - Class serializer = ReflectionUtil.nms("PacketDataSerializer"); - Object init = serializer.getDeclaredConstructor(ByteBuf.class).newInstance(output); - Method toCall = init.getClass().getDeclaredMethod("a", ReflectionUtil.nms("ItemStack")); - toCall.invoke(init, value); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - - public static Object readItem(ByteBuf output) { - try { - Class serializer = ReflectionUtil.nms("PacketDataSerializer"); - Object init = serializer.getDeclaredConstructor(ByteBuf.class).newInstance(output); - Method toCall = init.getClass().getDeclaredMethod("i"); - return toCall.invoke(init); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - return null; - } - public static long[] readBlockPosition(ByteBuf buf) { long val = buf.readLong(); long x = (val >> 38); // signed @@ -414,4 +402,12 @@ public class PacketUtil { return data; } + + public static boolean containsCause(Throwable t, Class c) { + while (t != null) { + t = t.getCause(); + if (c.isAssignableFrom(t.getClass())) return true; + } + return false; + } } diff --git a/src/main/java/us/myles/ViaVersion/util/ReflectionUtil.java b/src/main/java/us/myles/ViaVersion/util/ReflectionUtil.java index e5f19187e..9b7829d3e 100644 --- a/src/main/java/us/myles/ViaVersion/util/ReflectionUtil.java +++ b/src/main/java/us/myles/ViaVersion/util/ReflectionUtil.java @@ -27,6 +27,18 @@ public class ReflectionUtil { return m.invoke(o); } + public static T getStatic(Class clazz, String f, Class t) throws NoSuchFieldException, IllegalAccessException { + Field field = clazz.getDeclaredField(f); + field.setAccessible(true); + return (T) field.get(null); + } + + public static T get(Object instance, Class clazz, String f, Class t) throws NoSuchFieldException, IllegalAccessException { + Field field = clazz.getDeclaredField(f); + field.setAccessible(true); + return (T) field.get(instance); + } + public static T get(Object o, String f, Class t) throws NoSuchFieldException, IllegalAccessException { Field field = o.getClass().getDeclaredField(f); field.setAccessible(true); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3f3eaa8a2..18a0b5f85 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,11 @@ name: ViaVersion main: us.myles.ViaVersion.ViaVersionPlugin author: _MylesC -version: 0.4.6 +version: ${version} load: startup -loadbefore: [ProtocolLib, ProxyPipe] \ No newline at end of file +loadbefore: [ProtocolLib, ProxyPipe] +commands: + viaversion: + description: Shows ViaVersion Version and more. + permission: viaversion.admin + aliases: [viaver] \ No newline at end of file