[Lib] [1.7.9] ProtocolLib 3.4.0 - Safely and easily modify sent and recieved packets

Discussion in 'Resources' started by Comphenix, Sep 15, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    Comphenix

    Did you try this code with multiple players on the server? This packet is sent along with Packet20NamedEntitySpawn to notify other players that a player exist at a given location with this equipment and so on. The equipment packet is not sent to the player, however.

    Looks like I forgot to test what happens when you supply a normal ItemStack (and not a CraftItemStack), as your plugin crashes when a player is nearby:
    Code:
    2012-10-09 15:55:59 [SEVERE] [ProtocolLib] Exception occured in onPacketSending() for ExamplePlugin
    java.lang.ClassCastException: org.bukkit.inventory.ItemStack cannot be cast to org.bukkit.craftbukkit.inventory.CraftItemStack
        at com.comphenix.protocol.events.PacketContainer$1.getGeneric(PacketContainer.java:148)
        at com.comphenix.protocol.events.PacketContainer$1.getGeneric(PacketContainer.java:1)
        at com.comphenix.protocol.reflect.StructureModifier.write(StructureModifier.java:170)
        at com.comphenix.example.ExampleMod$1.onPacketSending(ExampleMod.java:48)
        at com.comphenix.protocol.injector.SortedPacketListenerList.invokePacketSending(SortedPacketListenerList.java:56)
        at com.comphenix.protocol.injector.PacketFilterManager.handlePacket(PacketFilterManager.java:284)
        at com.comphenix.protocol.injector.PacketFilterManager.invokePacketSending(PacketFilterManager.java:265)
        at com.comphenix.protocol.injector.player.PlayerInjector.handlePacketRecieved(PlayerInjector.java:296)
        at com.comphenix.protocol.injector.player.NetworkServerInjector.handlePacketRecieved(NetworkServerInjector.java:1)
        at com.comphenix.protocol.injector.player.NetworkServerInjector$1.intercept(NetworkServerInjector.java:134)
        at net.minecraft.server.NetServerHandler$$EnhancerByCGLIB$$a668a3bb.sendPacket(<generated>)
        at net.minecraft.server.EntityTrackerEntry.updatePlayer(EntityTrackerEntry.java:263)
        at net.minecraft.server.EntityTrackerEntry.scanPlayers(EntityTrackerEntry.java:308)
        at net.minecraft.server.EntityTracker.addEntity(EntityTracker.java:97)
        at net.minecraft.server.EntityTracker.addEntity(EntityTracker.java:80)
        at net.minecraft.server.EntityTracker.track(EntityTracker.java:23)
        at net.minecraft.server.WorldManager.a(WorldManager.java:18)
        at net.minecraft.server.World.a(World.java:879)
        at net.minecraft.server.WorldServer.a(WorldServer.java:682)
        at net.minecraft.server.World.addEntity(World.java:868)
        at net.minecraft.server.World.addEntity(World.java:813)
        at net.minecraft.server.ServerConfigurationManagerAbstract.c(ServerConfigurationManagerAbstract.java:171)
        at net.minecraft.server.ServerConfigurationManagerAbstract.a(ServerConfigurationManagerAbstract.java:92)
        at net.minecraft.server.NetLoginHandler.d(NetLoginHandler.java:129)
        at net.minecraft.server.NetLoginHandler.c(NetLoginHandler.java:42)
        at net.minecraft.server.DedicatedServerConnectionThread.a(DedicatedServerConnectionThread.java:44)
        at net.minecraft.server.DedicatedServerConnection.b(SourceFile:29)
        at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:577)
        at net.minecraft.server.DedicatedServer.q(DedicatedServer.java:213)
        at net.minecraft.server.MinecraftServer.p(MinecraftServer.java:473)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:405)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:539)
    I'll see if I can't fix it, but it's only a minor gotcha with the API and is easily avoided. Simply "read" the ItemStack instead and modify it on the spot. This will only change the local copy of the item stack in the packet, and not the actual item stack the player carries.

    The result is quite silly. I also took the liberty of injecting the WINDOW_SET and SET_SLOT packets, so that you can see the changed equipment on yourself as well:
    Code:java
    1. public class ExampleMod extends JavaPlugin implements Listener {
    2.  
    3. private ProtocolManager protocolManager;
    4.  
    5. @Override
    6. public void onDisable() {
    7. System.out.println("ProtocolLibTest disabled!");
    8. }
    9.  
    10. public void onLoad() {
    11. protocolManager = ProtocolLibrary.getProtocolManager();
    12. }
    13.  
    14. @Override
    15. public void onEnable() {
    16. System.out.println("ProtocolLibTest enabled!");
    17. getServer().getPluginManager().registerEvents(this, this);
    18. test();
    19. }
    20.  
    21. @EventHandler(ignoreCancelled = false, priority = EventPriority.MONITOR)
    22. public void onInventoryClickEvent(InventoryClickEvent event) {
    23.  
    24. HumanEntity entity = event.getWhoClicked();
    25. Inventory inventory = event.getInventory();
    26.  
    27. if (inventory.getType() == InventoryType.CRAFTING || inventory.getType() == InventoryType.PLAYER) {
    28. System.out.println("SlotID: " + event.getSlot());
    29. if ((event.getSlot() >= 36 && event.getSlot() < 40) || event.isShiftClick()) {
    30. // Send a new inventory update
    31. if (entity instanceof Player) {
    32. scheduleUpdate((Player) entity);
    33. }
    34. }
    35. }
    36. }
    37.  
    38. private void scheduleUpdate(final Player player) {
    39. getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
    40. @SuppressWarnings("deprecation")
    41. @Override
    42. public void run() {
    43. player.updateInventory();
    44. }
    45. });
    46. }
    47.  
    48. public void test() {
    49. protocolManager.addPacketListener(new PacketAdapter(this, ConnectionSide.SERVER_SIDE,
    50. Packets.Server.ENTITY_EQUIPMENT,
    51. Packets.Server.SET_SLOT,
    52. Packets.Server.WINDOW_ITEMS) {
    53. @Override
    54. public void onPacketSending(PacketEvent event) {
    55. PacketContainer packet = event.getPacket();
    56.  
    57. try {
    58. // Player equipment
    59. switch (event.getPacketID()) {
    60. case Packets.Server.ENTITY_EQUIPMENT:
    61. modifyItemStack(packet.getItemModifier().read(0));
    62. break;
    63.  
    64. case Packets.Server.SET_SLOT:
    65. int slotID = packet.getSpecificModifier(int.class).read(1);
    66.  
    67. // Modify equipment slots
    68. if (slotID >= 5 && slotID < 9)
    69. modifyItemStack(packet.getItemModifier().read(0));
    70. break;
    71.  
    72. case Packets.Server.WINDOW_ITEMS:
    73. ItemStack[] stack = packet.getItemArrayModifier().read(0);
    74.  
    75. for (int i = 5; i < 9; i++) {
    76. modifyItemStack(stack[i]);
    77. }
    78. break;
    79. }
    80.  
    81. } catch (FieldAccessException e) {
    82. System.out.println("Couldn't access field.");
    83. }
    84. }
    85. });
    86. }
    87.  
    88. private void modifyItemStack(ItemStack stack) {
    89. if (stack != null) {
    90. stack.setTypeId(2);
    91. stack.setAmount(1);
    92. }
    93. }
    94. }[/i]
     
  2. Offline

    itreptau

    Theres also another way. I did something like this:
    Code:
    CraftItemStack stack = new CraftItemStack(2,1);
    //instead of
    ItemStack stack = new ItemStack(2,1);
    To be able to do this i had to include the craftbukkit.jar in my buildpath.
     
  3. Offline

    Comphenix

    Ah, yeah. That would work too, if you don't mind depending on CraftBukkit.jar.

    I've also updated ProtocolLib to do what you're doing automatically, but I'll wait until I have something more before I release the next version.
     
    itreptau likes this.
  4. Offline

    itreptau

    Tried to download the Project and compile it with the chagnes you made (can't wait for the enxt version :D) but i get several errors :/
    PS:
    I get the errors in Eclipse, not while compiling (he won't even do that).
     
  5. Offline

    Comphenix

    Yeah, the build script really needs to be fixed. It doesn't automatically download any remote dependencies, and instead links to local files on my computer.

    But it should be pretty straight forward to fix. I think you only need to fix CraftBukkit, so open the build path and remove the "missing" library. Then add CraftBukkit 1.3.1 or 1.32 back, and build the project. The errors should disappear.

    To package the project into a JAR, you must (in Eclipse) right-click the Java.xml ant-file, choose Run -> Ant Build. The JAR will then end up in the target sub-directory.
     
    itreptau likes this.
  6. Offline

    itreptau

    Thanks, worked great :D (i also had to change the JRE System Library because i have JRE7 installed). You really answer every question and that quite fast :D
     
  7. Offline

    Comphenix

    ProtocolLib 1.3.1

    Another version of ProtocolLib is out. :)

    This time I've mainly worked on the capability of the asynchronous listeners, along with the packet registry. I've also included a couple of bug fixes.

    This version can be downloaded here.

    Bug fixes:
    New features:
    Compatibility improvements:

    Developer changes:

    An asynchronous listener can now delay a packet from being processed by calling incrementProcessingDelay() on AsyncMarker. This can be useful if an asynchronous listener is waiting for further information before the packet can be sent to the user. But, a packet listener MUST eventually call AsyncFilterManager.signalPacketTransmission(), even if the packet is cancelled, if the packet has been delayed. In addition, packet processing outside a packet listener should be wrapped in a synchronized block using the getProcessingLock().

    The new Packets.Server.isSupported() and Packets.Client.isSupported() methods allow you to determine if the current Minecraft version supports a given packet ID.
     
  8. Offline

    itreptau

    Comphenix
    It says there is an unhandled exceptiontype FieldAccesException when i do:
    Code:
    int EntityId = packet.getSpecificModifier(int.class).read(0);
    But why? Only happens when i try to write into the ItemModifier.
     
  9. Offline

    Comphenix

    What packet are you reading? And, if you're reading an entity ID, you might as well use getEntityModifier().

    Could you post a bit more of your packet listener code? And the error description + stack trace of FieldAccessException. :)
     
  10. Offline

    itreptau

    Here is the stacktrace (i dont know how make spoilers, so be warned :D):
    Code:
    2012-10-11 19:43:26 [SEVERE] Couldn't access field.
    com.comphenix.protocol.reflect.FieldAccessException: Field index must be within 0 - count
        at com.comphenix.protocol.reflect.StructureModifier.read(StructureModifier.java:122)
        at me.itreptau.protocolLibTest.ProtocolLibTest$1.onPacketSending(ProtocolLibTest.java:104)
        at com.comphenix.protocol.injector.SortedPacketListenerList.invokePacketSending(SortedPacketListenerList.java:56)
        at com.comphenix.protocol.injector.PacketFilterManager.handlePacket(PacketFilterManager.java:298)
        at com.comphenix.protocol.injector.PacketFilterManager.invokePacketSending(PacketFilterManager.java:279)
        at com.comphenix.protocol.injector.player.PlayerInjector.handlePacketRecieved(PlayerInjector.java:296)
        at com.comphenix.protocol.injector.player.NetworkServerInjector.handlePacketRecieved(NetworkServerInjector.java:33)
        at com.comphenix.protocol.injector.player.NetworkServerInjector$1.intercept(NetworkServerInjector.java:134)
        at net.minecraft.server.NetServerHandler$$EnhancerByCGLIB$$ee4c4ea2.sendPacket(<generated>)
        at net.minecraft.server.EntityTrackerEntry.updatePlayer(EntityTrackerEntry.java:263)
        at net.minecraft.server.EntityTrackerEntry.scanPlayers(EntityTrackerEntry.java:308)
        at net.minecraft.server.EntityTrackerEntry.track(EntityTrackerEntry.java:67)
        at net.minecraft.server.EntityTracker.updatePlayers(EntityTracker.java:130)
        at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:565)
        at net.minecraft.server.DedicatedServer.q(DedicatedServer.java:213)
        at net.minecraft.server.MinecraftServer.p(MinecraftServer.java:473)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:405)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:539)
    Caused by: java.lang.IndexOutOfBoundsException: Out of bounds
        ... 18 more
    and
    Code:
    2012-10-11 19:43:26 [SEVERE] Couldn't access field.
    com.comphenix.protocol.reflect.FieldAccessException: Field index must be within 0 - count
        at com.comphenix.protocol.reflect.StructureModifier.read(StructureModifier.java:122)
        at me.itreptau.protocolLibTest.ProtocolLibTest$1.onPacketSending(ProtocolLibTest.java:104)
        at com.comphenix.protocol.injector.SortedPacketListenerList.invokePacketSending(SortedPacketListenerList.java:56)
        at com.comphenix.protocol.injector.PacketFilterManager.handlePacket(PacketFilterManager.java:298)
        at com.comphenix.protocol.injector.PacketFilterManager.invokePacketSending(PacketFilterManager.java:279)
        at com.comphenix.protocol.injector.player.PlayerInjector.handlePacketRecieved(PlayerInjector.java:296)
        at com.comphenix.protocol.injector.player.NetworkServerInjector.handlePacketRecieved(NetworkServerInjector.java:33)
        at com.comphenix.protocol.injector.player.NetworkServerInjector$1.intercept(NetworkServerInjector.java:134)
        at net.minecraft.server.NetServerHandler$$EnhancerByCGLIB$$4f4df111.sendPacket(<generated>)
        at net.minecraft.server.EntityTrackerEntry.updatePlayer(EntityTrackerEntry.java:263)
        at net.minecraft.server.EntityTrackerEntry.scanPlayers(EntityTrackerEntry.java:308)
        at net.minecraft.server.EntityTracker.addEntity(EntityTracker.java:97)
        at net.minecraft.server.EntityTracker.addEntity(EntityTracker.java:80)
        at net.minecraft.server.EntityTracker.track(EntityTracker.java:23)
        at net.minecraft.server.WorldManager.a(WorldManager.java:18)
        at net.minecraft.server.World.a(World.java:879)
        at net.minecraft.server.WorldServer.a(WorldServer.java:682)
        at net.minecraft.server.World.addEntity(World.java:868)
        at net.minecraft.server.World.addEntity(World.java:813)
        at net.minecraft.server.ServerConfigurationManagerAbstract.c(ServerConfigurationManagerAbstract.java:171)
        at net.minecraft.server.ServerConfigurationManagerAbstract.a(ServerConfigurationManagerAbstract.java:92)
        at net.minecraft.server.NetLoginHandler.d(NetLoginHandler.java:129)
        at net.minecraft.server.NetLoginHandler.c(NetLoginHandler.java:42)
        at net.minecraft.server.DedicatedServerConnectionThread.a(DedicatedServerConnectionThread.java:44)
        at net.minecraft.server.DedicatedServerConnection.b(SourceFile:29)
        at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:577)
        at net.minecraft.server.DedicatedServer.q(DedicatedServer.java:213)
        at net.minecraft.server.MinecraftServer.p(MinecraftServer.java:473)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:405)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:539)
    Caused by: java.lang.IndexOutOfBoundsException: Out of bounds
        ... 30 more
    This is my packetlistener:
    Code:
    protocolManager.addPacketListener(new PacketAdapter(this, ConnectionSide.SERVER_SIDE, 0x5) {
                @Override
                public void onPacketSending(PacketEvent event) {
                    PacketContainer packet = event.getPacket();
                 
                    try {
                     
                        // Player equipment
                        if (event.getPacketID() == 0x05) {
                            int EntityId = packet.getSpecificModifier(int.class).read(0);
                            Player player = getPlayerFromEntityId(EntityId);
                            Inventory outfitInventory = getOutfitInventory(player);
                            //Helmet
                            if(packet.getSpecificModifier(short.class).read(0) == 1) {
                                packet.getItemModifier().write(0, getFirstItemStack(outfitInventory.getContents(), Armortype.HELMET));
                            }
                            //Chestplate
                            if(packet.getSpecificModifier(short.class).read(0) == 2) {
                                packet.getItemModifier().write(0, getFirstItemStack(outfitInventory.getContents(), Armortype.CHESTPLATE));
                            }
                            //Leggings
                            if(packet.getSpecificModifier(short.class).read(0) == 3) {
                                packet.getItemModifier().write(0, getFirstItemStack(outfitInventory.getContents(), Armortype.LEGGINGS));
                            }
                            //Shoes
                            if(packet.getSpecificModifier(short.class).read(0) == 4) {
                                packet.getItemModifier().write(0, getFirstItemStack(outfitInventory.getContents(), Armortype.BOOTS));
                            }
                        }
                 
                    } catch (FieldAccessException e) {
                        logger.log(Level.SEVERE, "Couldn't access field.", e);
                    }
                }
                });
    getPlayerFromEntityId() returns the player with the given EntityId,
    getOutfitInventory() returns an Inventory,
    getFirstItemStack() returns the first ItemStack with the Armortype.
    I hope everything is understandable (i know my code is messy :D)
     
  11. Offline

    Comphenix

    Nah, it's perfectly readable.

    But I see the problem. You're trying to read a short, even though the packet fields are all integers:
    Code:java
    1.  
    2. public class Packet5EntityEquipment extends Packet
    3. {
    4. public int a;
    5. public int b;
    6. private ItemStack c;
    7.  

    So - just use the code I gave you last time:
    Code:java
    1.  
    2. Entity playerEntity = packet.getEntityModifier(event.getPlayer().getWorld()).read(0);
    3. Integer slot = packet.getSpecificModifier(int.class).read(1);
    4. ItemStack stack = packet.getItemModifier().read(0);
    5.  

    That way, you don't have to use getPlayerFromEntityId() at all. :)
     
  12. Offline

    itreptau

    Oh, I looked at http://wiki.vg/Protocol#Entity_Equipment_.280x05.29 and it says that the slot is a short. Once again, thanks for the help :D

    Im getting several NullPointerExceptions when i try to modify the items in the packet (and i think im doing it right, atleast i hope)
    Here is the StackTrace:
    http://pastebin.com/w9qXwcMs
    and this is the Plugin:
    http://pastebin.com/P35cKaK7
    Sorry for asking so many questions, i hope im not disturbing.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
  13. Offline

    Comphenix

  14. Offline

    libraryaddict

    Code:
        war.protocolManager.addPacketListener(new PacketAdapter(war,
            ConnectionSide.BOTH, ListenerPriority.NORMAL, 0x03) {
          @Override
          public void onPacketReceiving(PacketEvent event) {
            if (event.getPacketID() == 0x03) {
              try {
                PacketContainer packet = event.getPacket();
                String message = packet.getSpecificModifier(String.class).read(0);
                System.out.println("Censored");
                if (message.contains("shit") || message.contains("damn")) {
                  event.setCancelled(true);
                  event.getPlayer().sendMessage("Bad manners!");
                }
              } catch (FieldAccessException e) {
                war.getLogger().log(Level.SEVERE, "Couldn't access field.", e);
              }
            }
          }
        });
    "Censored" is only appearing when the player talks, I'm trying to modify messages sent from the server to the player
     
  15. Offline

    Comphenix

    Right, then you should use onPacketSending() instead:
    Code:java
    1. protocolManager.addPacketListener(new PacketAdapter(this,
    2. ConnectionSide.SERVER_SIDE, ListenerPriority.NORMAL, 0x03) {
    3.  
    4. @Override
    5. public void onPacketSending(PacketEvent event) {
    6. if (event.getPacketID() == 0x03) {
    7. try {
    8. PacketContainer packet = event.getPacket();
    9. String message = packet.getSpecificModifier(
    10. String.class).read(0);
    11. System.out.println("Censored. SERVER: " + event.isServerPacket());
    12. if (message.contains("shit")
    13. || message.contains("damn")) {
    14. event.setCancelled(true);
    15. event.getPlayer().sendMessage("Bad manners!");
    16. }
    17. } catch (FieldAccessException e) {
    18. getLogger().log(Level.SEVERE, "Couldn't access field.",
    19. e);
    20. }
    21. }
    22. }
    23. });

    You only use ConnectionSide.Both if you're using onPacketSending() and onPacketReceiving() at the same time. In fact, if's probably better if you do stop packets both ways, especially if the packet contains a command:
    Code:java
    1. protocolManager.addPacketListener(new PacketAdapter(this,
    2. ConnectionSide.BOTH, ListenerPriority.NORMAL, 0x03) {
    3. @Override
    4. public void onPacketReceiving(PacketEvent event) {
    5. censorPacket(event);
    6. }
    7.  
    8. @Override
    9. public void onPacketSending(PacketEvent event) {
    10. censorPacket(event);
    11. }
    12.  
    13. private void censorPacket(PacketEvent event) {
    14. if (event.getPacketID() == 0x03) {
    15. try {
    16. PacketContainer packet = event.getPacket();
    17. String message = packet.getSpecificModifier(
    18. String.class).read(0);
    19. System.out.println("Censored. SERVER: " + event.isServerPacket());
    20. if (message.contains("shit")
    21. || message.contains("damn")) {
    22. event.setCancelled(true);
    23. event.getPlayer().sendMessage("Bad manners!");
    24. }
    25. } catch (FieldAccessException e) {
    26. getLogger().log(Level.SEVERE, "Couldn't access field.",
    27. e);
    28. }
    29. }
    30. }
    31. });
     
  16. Offline

    libraryaddict

    Ah, I did not see otherwise so I assumed.
     
  17. Offline

    _Waffles_

    Is it possible to create a listener that does the same method no matter what packet has been sent?
    I want to monitor each and every packet that gets sent/received.
     
    Comphenix likes this.
  18. Offline

    Comphenix

    It is, but you should be a bit careful. I haven't included any easy way of doing it almost purposefully, as I'm unsure about the performance impact.

    I did write a very simple listener for this purpose though. You can get it here.

    Using it is a piece of cake:
    Code:java
    1. public class ExampleMod extends JavaPlugin implements Listener {
    2.  
    3. private ProtocolManager protocolManager;
    4.  
    5. @Override
    6. public void onEnable() {
    7. protocolManager = ProtocolLibrary.getProtocolManager();
    8. protocolManager.addPacketListener(new MonitorListener(this, ConnectionSide.BOTH) {
    9. @Override
    10. public void onPacketSending(PacketEvent event) {
    11. if (!event.isCancelled()) {
    12. System.out.println("Sending packet " + event.getPacketID());
    13. }
    14. }
    15.  
    16. @Override
    17. public void onPacketReceiving(PacketEvent event) {
    18. if (!event.isCancelled()) {
    19. System.out.println("Receiving packet " + event.getPacketID());
    20. }
    21. }
    22. });
    23. }
    24. }
     
  19. Offline

    _Waffles_

    Thank you.
     
  20. Offline

    itreptau

    Is there somewhere a documentation (or something like that) that has all info about miencraft packets? I want to know which packets are sent when and so on (like the equipment is sent with the entityspawn packet and after the window update packet, i think).
     
  21. Offline

    Comphenix

    I haven't seen anything better than the MinecraftCoalition page, and although it's very nicely put together it's no substitute for the real thing.

    Yes, there's probably no way around it. You'll have to read the obfuscated source code if you want to know when certain packets are sent, and in what order. MCP is an invaluable tool, as are the different IDE features like call hierarchy, declarations and the humble file search. It's a difficult job, but it's easier than writing the documentation. :p
     
  22. Offline

    md_5

    You seem like the kind of guy who should hang out in our (Minecraft Coalition) irc channel. #mcdevs @ irc.esper.net
     
  23. Offline

    Supertt007

    Wow, so many new changes just in a few weeks! This is getting even better and better everyday! I have ran through a problem earlier today though, packet login request(0x01) is not being tracked by the event
     
  24. Offline

    Comphenix

    Thanks. :)

    It's a bit more complicated than just fixing a simple bug, I'm afraid. I don't starting hooking Minecraft - that is, do all the magic" to start intercepting packets - until I receive a PlayerJoinEvent, which occurs a good deal after Packet1Login has been received and processed.

    Now, it may be possible to hook Minecraft before that, but this is before the player object is properly created, so what do I put in the "player" field of PacketEvent? I might be able to "fake" the player object like in the Bukkit event PlayerLoginEvent, but I have to be careful and don't surprise people that expect players to have names and so on. I also have to handle the sendServerPacket() and recieveClientPacket().

    In addition, the current code base is designed around the Player object, so it's quite a pain to fix this. I have to think about this more in detail. I want the API to be as clean as possible.

    But I'll keep working on the problem. :)
     
  25. Offline

    Comphenix

    ProtocolLib 1.4.0

    A new version is now out on GitHub. This time I decided to address the limitation Supertt007 mentioned, and I've added support for intercepting packets during the login phase. This includes the server information displayed in the client's multiplayer screen (ServerListPingEvent), making it possible to alter the displayed number of currently logged in players.

    ProtocolLib will now only use hooks when it's strictly necessary, so a server with only ProtocolLib running will not use any additional resources. It's only when a plugin adds packet listeners that this extra resource consumption will kick in.

    New features:
    Performance:
    API changes:
    Examples

    The following example may allow you to ban a player by pretending the server is full.
    Code:java
    1. final Splitter paramSplitter = Splitter.on('\u00A7');
    2. final Joiner paramJoiner = Joiner.on('\u00A7');
    3.  
    4. ProtocolLibrary.getProtocolManager().addPacketListener(
    5. new PacketAdapter(this, ConnectionSide.SERVER_SIDE, ListenerPriority.NORMAL, GamePhase.LOGIN, Packets.Server.KICK_DISCONNECT) {
    6. @Override
    7. public void onPacketSending(PacketEvent event) {
    8. try {
    9. // From CraftBukkit
    10. // String s = pingEvent.getMotd() + "\u00A7" + this.server.getServerConfigurationManager().getPlayerCount() +
    11. // "\u00A7" + pingEvent.getMaxPlayers();
    12.  
    13. StructureModifier<String> stringFields = event.getPacket().getSpecificModifier(String.class);
    14.  
    15. // Get the individual parameters
    16. String message = stringFields.read(0);
    17. String[] parameters = Iterables.toArray(paramSplitter.split(message), String.class);
    18.  
    19. // Only modify if we know what it is
    20. if (parameters.length == 3) {
    21. // Display as FULL
    22. parameters[1] = parameters[2];
    23.  
    24. // Replace it back
    25. stringFields.write(0, paramJoiner.join(parameters));
    26. }
    27.  
    28. } catch (FieldAccessException e) {
    29. // TODO Auto-generated catch block
    30. e.printStackTrace();
    31. }
    32. }
    33. });

    Note that these packet notifications are ONLY sent when a listener with a GamePhase of type LOGIN or BOTH has been registered. This is to avoid having to add the injected hooks onto every NetLoginHandler created, as most listeners are only interested in the packets sent after a player has logged in.

    Also note that the Player object during the Login phase is NOT a CraftPlayer (as the client hasn't yet been authenticated), and only a small subset of the methods available is safe to use:
    • getPlayer()
    • getAddress()
    • getName()
    • getServer()
    • chat(String)
    • sendMessage(String)
    • sendMessage(String[])
    • kickPlayer(String)
    The getName() method will return a name with the format UNKNOWN[ IP-address of the client]. You can still use this "fake" Player object to send and receive new packets, however.
     
  26. Offline

    Supertt007

    This looks amazing Comphenix! Thank you for fixing my issues. Again, great work!

    (I will test it as soon as possible :p)
     
    Comphenix likes this.
  27. Offline

    Comphenix

    ProtocolLib 1.4.1

    Another hotfix, I'm afraid. Turns out that constructing certain packets (Packet20NamedEntitySpawn) would crash with a StackOverflowException. The problem was that any specific CompiledStructureModifer$Packet would incorrectly call its super-method in CompiledStructureModifer instead of StructureModifier (reflection is necessary on non-public fields). A simple workaround was to make the read- and write-methods available to the subclass by using two protected methods.
     
  28. Offline

    Comphenix

    Analysis of packet order

    Although the existing protocol documentation is fairly extensive, it's unfortunately lacking in certain key areas. A single packet is rarely sent alone, but is instead part of a much larger sequence or "conversation".

    For instance, when the server is notifying a client of a nearby player, it sends a packet #20 (spawn named entity) to initialize the new player entity, but this packet is not sufficient to fully initialize the player. The visible armor is sent using packet #5 (Entity Equipment), one for each armor piece equipped, along with a separate packet for displaying each potion effects and so on.

    These details are fairly easy to overlook if you're not paying extra attention to the underlying Minecraft code, and not surprisingly, many implementations of tag hiding did get this wrong (not TagAPI, however).

    The Wiki documentation is kept up-to-date using automated tools, so perhaps what we need is a automated tool for documenting every packet sequence in use. Once approach is to model the packet stream with a simple 1st-order Markov chain, which should highlight every likely packet sequence observed.

    Using ProtocolLib, I wrote a quick and dirty implementation - PacketStat 0.0.2. It simply records every sent and received packet in the Markov chain, and prints the result in a TSV file after it has been disabled or the server has been stopped.

    The output is a 256*256 transition table where each cell is the probability that a packet of "row" ID is followed by a packet of "column" ID. For instance, imagine there were only three packets instead of 256. Then the statistical output might look something like this:
    Code:
          |     0     |     1     |     2     |
    -------------------------------------------
    |  0  |    0.0    |    1.0    |    0.0    |
    |  1  |    0.0    |    0.8    |    0.1    |
    |  2  |    0.0    |    0.0    |    0.0    |
    -------------------------------------------
    I've marked each row and column with a packet ID. Now, you'll notice that the first row only contains zeroes - this means the probability of any packet being sent after packet 2 has been sent is zero. If it's the last packet, then it's probably a disconnect packet.

    For packet 0, you'll notice that the column for packet 1 is 1.0. That means that there's a probably of 100% that packet 0 will be followed by packet 1, meaning that it's only received once per player. Packet 1, on the other hand, seems to be followed by itself (80%), or packet 2 (10%), so it's probably the "keep alive" packet.

    Results

    I ran PacketStat on a private server with a single player for about 30 minutes. By compiling the 256x256 matrix into diagram form and removing connections with 35% or less, I got the following result. This diagram contains orientation-related packets, along with the login sequence itself:

    [​IMG]


    Note that the probabilities are all rounded to the nearest fractional ten.

    This diagram contains movement packets, along with spawning client-side entities and collecting items:

    [​IMG]


    Packets for interacting with the inventory and the world in general:

    [​IMG]

    And finally, general world data:
    [​IMG]


    Now, these were all drawn manually in Dia (diagram can be found here), so if anyone knows of a program that can generate these kinds of diagrams from Markov tables, please let me know. Otherwise, I'd have to update the diagrams manually next time Minecraft updates.

    Also, these diagrams are far from complete, so I may have to revisit them later when I have some time. Drawing them manually is truly a chore. :p

    ProtocolLib 1.4.2

    Due to a fairly serious bug, I've decided to push out the next version without any significant new features. If you're affected by the issues below, I'd recommend updating as soon as possible.

    I expect the new version will be out on BukkitDev in a day, but you can also get it on GitHub in the meantime.

    And as always, the new version is also available in the Maven repo.

    Bug fixes:
    Minor fixes:

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
    MHF_Wither, desht and Ammar Askar like this.
  29. Offline

    Bryguy

    How would one use this to modify information sent in the Packet52MapChunk (Or whatever that packet is named)? (I believe that it'd be referenced in this as 0x33) I've tried looking into it myself, but it's done little more than leave me confused.
     
  30. Offline

    Comphenix

    Take a look at the source code for BlockPather (compiled JAR-file here).

    Essentially, I register an async packet listener that process each map chunk- and map chunk bulk-packet in two worker threads. The processing itself reads and modifies the packaged block ID and metadata. The packet also contains light and skylight data, as well as "extra" bits for blocks with an ID over 256 and biome data. BlockPatcher doesn't touch any of that information, but it should be fairly easy to modify the existing code if you do need it.

    Otherwise, you can probably just copy the code entirely.

    By the way, please set the ListenerPriority to NORMAL unless you absolutely need to process the packet after every other plugin. That way, you won't get any surprises from other plugins (such as Orebfuscator).
     
Thread Status:
Not open for further replies.

Share This Page