[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

    ProtocolLib 3.4.0
    Certain tasks are impossible to perform with the standard Bukkit API, and may require working with and even modify Minecraft directly. A common technique is to modify incoming and outgoing packets, or inject custom packets into the stream. This is quite cumbersome to do, however, and most implementations will break as soon as a new version of Minecraft has been released, mostly due to obfuscation.

    Critically, different plugins that use this approach may hook into the same classes, with unpredictable outcomes. More than often this causes plugins to crash, but it may also lead to more subtle bugs.

    This plugin is compatible with CraftBukkit 1.7.9-R0.1.

    Resources
    Useful links:
    Example program(s):
    TagHelper:
    For server operators:
    Just download ProtocolLib from the link above. It doesn't do anything on its own, it simply allows other plugins to function.

    For developers:
    Look at the BukkitDev page for more information (Maven), especially the developer tutorial.

    Changelog
    Note: I will no longer maintain the same page on BukkitDev and the forums, as the edit post function seems to be fairly broken. I can no longer get it to work at all in Firefox, and there's a bunch of paragraph problems when editing in Chrome. I don't think it's worth the effort.
     
  2. Offline

    md_5

    You really haven't taken shortcuts on this. The fuzzy reflection would have been a great experience to write, although imho a waste of time as not many people care about really old version. You support reloads etc etc etc.

    Nice job!

    EDIT: Just saw:
    throw new IllegalArgumentException("side cannot be NULL.");

    are you a C programmer?

    EDIT: https://github.com/aadnk/ProtocolLi...otocol/injector/PacketFilterManager.java#L426 are you kidding me?

    also how do you pick which class NetworkManager etc is in cross version? Or is a literal hard coded, I couldn't find anything.
     
  3. Offline

    Comphenix

    Thanks, but I think that's the least you can do when writing a library. :)

    True, although a surprising amount of people are still using 1.2.5, as extensive mods such as EE and BuildCraft can take months to update.

    But the main idea was to future-proof the implementation by using as much dynamic loading code as possible. If it works in Beta 1.8.0 it might just work in 1.4.0. :p

    Well, I mostly write C# or Java, actually. But enums can be null in Java too.

    And no, I'm not kidding you. :p

    I had to get the old event system working when I tried testing the library in Beta 1.8.0, and seeing that it doesn't cause any problems as is, I just left it in. Compatibility feels nice, sometimes.

    It's actually a bit of both. I read the network manager from the server handler using player.getHandle(). You might also notice that I search for NetServerHandler and NetworkManager using a regular expression. That way, I'll match both NetworkManager and INetworkManager (in 1.3.1).

    All the magic is happening here:
    https://github.com/aadnk/ProtocolLi...nix/protocol/injector/PlayerInjector.java#L78
     
  4. Offline

    md_5

    I asked if you were a C programmer because you had capitalised NULL. Thanks for the in depth response :D
     
  5. Offline

    Comphenix

    Ah, now I see. :)

    I think I'll work on more example plugins next. And I might have to make a TagAPI replacement, if this library is ever going to be used in practice.
     
  6. Offline

    Comphenix

    ProtocolLib 1.1.0

    I've now released a new version of ProtocolLib! New features include a list of packet IDs in the newest client, the ability to use packet constructors and most importantly: an updateEntity-function.

    The packet ID list can be found in com.comphenix.protocol.Packets and is divided into sever and client packets. They derive from IntEnum instead of Enum as they must be extendible for future versions. Still, you'll be able to easily convert between packet ID and the corresponding packet name with valueOf() and getDeclaredName().

    Packet constructors allow you to call the actual constructors of a given packet. For instance, by looking at Minecraf's source code you'll discover that Packet20NamedEntitySpawn can be easily created by using the EntityHuman constructor:
    Code:java
    1. public Packet20NamedEntitySpawn(EntityHuman entityhuman) {
    2. this.a = entityhuman.id;
    3.  
    4. if (entityhuman.name.length() > 16)
    5. this.b = entityhuman.name.substring(0, 16);
    6. else {
    7. this.b = entityhuman.name;
    8. }
    9.  
    10. this.c = MathHelper.floor(entityhuman.locX * 32.0D);
    11. this.d = MathHelper.floor(entityhuman.locY * 32.0D);
    12. this.e = MathHelper.floor(entityhuman.locZ * 32.0D);
    13. this.f = (byte)(int)(entityhuman.yaw * 256.0F / 360.0F);
    14. this.g = (byte)(int)(entityhuman.pitch * 256.0F / 360.0F);
    15. ItemStack itemstack = entityhuman.inventory.getItemInHand();
    16.  
    17. this.h = (itemstack == null ? 0 : itemstack.id);
    18. this.i = entityhuman.getDataWatcher();
    19. }

    This packet is necessary if you're implementing something like TagAPI. But how can you call this constructor without referencing CraftBukkit or using reflection? Here's where the new packet constructor method comes in.

    If a packet has a constructor, you'll be able to call it using the new createPacketConstructor() method:
    Code:java
    1. public class TagHelperMod extends JavaPlugin {
    2. private static PacketConstructor namedEntity;
    3. private ProtocolMananger manager;
    4.  
    5. public static void refreshPlayer(Player watched) {
    6. if (namedEntity == null)
    7. namedEntity = manager.createPacketConstructor(Packets.Server.NAMED_ENTITY_SPAWN, watched);
    8.  
    9. try {
    10. PacketContainer watchedPacket = namedEntity.createPacket(watched);
    11.  
    12. for (Player player : watched.getWorld().getPlayers()) {
    13. if (watched.canSee(player)) {
    14. manager.sendServerPacket(player, watchedPacket);
    15. }
    16. }
    17.  
    18. } catch (FieldAccessException e) {
    19. e.printStackTrace();
    20. e.printStackTrace();
    21. }
    22. }
    23. // ect.
    24. }


    But if you're sending a "named entity spawn" packet, you must also send packets for updating the armor players are wearing and so on.

    The new version of ProtocolLib has a method that forces Minecraft itself to resend all the necessary packets, making your code more future-proof and compatible. All you need to do, is call updateEntity() with the player that is being updated, and a list of players that will see the update.


    TagHelper

    In addition to ProtocolLib, I've also published an API, TagHelper, for changing the displayed tag of players. It's very simple to use. You just subscribe to a Bukkit event and change the tag there:
    Code:java
    1. public class RandomColorListener implements Listener {
    2.  
    3. private Random rnd = new Random();
    4.  
    5. @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
    6. public void onReceiveNameTagEvent(ReceiveNameTagEvent event) {
    7. // TESTING CODE
    8. event.setTag(getRandomColor() + event.getTag());
    9. }
    10.  
    11. private ChatColor getRandomColor() {
    12. ChatColor[] list = ChatColor.values();
    13. return list[rnd.nextInt(list.length)];
    14. }
    15. }


    More example code can be found in TagExample.
     
  7. This looks very nice, good job!
     
  8. Offline

    Comphenix

    Thanks a lot. :)

    If anyone has any feature requests, let me know.
     
  9. Offline

    ELCHILEN0

    This looks great! +1
     
  10. Offline

    Murderscene

    This is great, although how come you do not have support for all packets? I want to access
    Player List Item (0xC9) and when I look at the source code it doesn't seam to be there
     
  11. Offline

    Comphenix

    Oh, you mean this packet?
    Code:
    public static final int PLAYER_INFO = 201;
    I don't use hexadecimal in the packet definitions, as I feel it's mostly a distraction. The underlying Minecraft code (or at least MCP) uses decimal, so that's what I chose to use.
     
  12. Offline

    Murderscene

    Thanks for the reply!

    At this link: http://www.wiki.vg/Protocol

    You can see it as "Player List Item (0xC9)". But maybe we're talking about the same packet? I was trying packet #65 as well as trying to call 0xC9 directly.

    I couldn't seam to get it to work at all, but I never tried packet 201. I'll give that a shot later :)

    Code:
              protocolManager.addPacketListener(
                      new PacketAdapter(this, ConnectionSide.SERVER_SIDE, 201) {
                          @Override
                          public void onPacketSending(PacketEvent event) {
                              // Item packets
                              logger.info("packet sending");
                              if (event.getPacketID() == 201) {
                                  logger.info("packet ID 201");
                                  try {
                                      PacketContainer packet = event.getPacket();
                                      String pName = (String) packet.getModifier().read(0);
                                      logger.info("name: "+pName);
                                  } catch (FieldAccessException e) {
                                      // TODO Auto-generated catch block
                                      e.printStackTrace();
                                  }
                              }
                          }
                    });
    That's what I have, but not one of by logger.info's show up in console

    I basically would like to intercept the player list packet and change how the player names appear in a way that bukkit does not support. But I cannot find the packet :(

    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

    That's really weird. I just tried it on craftbukkit-1.3.1-R2.0, using ProtocolLib 1.1.0, and I got the following text printed to the console:
    Code:
    2012-09-27 07:10:54 [INFO] [ExamplePlugin] packet sending
    2012-09-27 07:10:54 [INFO] [ExamplePlugin] packet ID 201
    2012-09-27 07:10:54 [INFO] [ExamplePlugin] name: aadnk
    2012-09-27 07:10:54 [INFO] [ExamplePlugin] packet sending
    2012-09-27 07:10:54 [INFO] [ExamplePlugin] packet ID 201
    2012-09-27 07:10:54 [INFO] [ExamplePlugin] name: aadnk
    Have you checked the console for any suspicious error messages? And that your plugin is loading correctly?

    Here's the source code for my test plugin:
    http://pastebin.com/0dZzmhZ7

    Could you post the rest of your plugin's source code? Might help me track down the problem. :)

    Also, I see you're manually casting objects to strings. You can use PacketContainer.getSpecificModifier(Class) instead - it's a bit cleaner, and much safer.

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

    Supertt007

    Thank you comphenix for making this lib! Expect a plugin from me using this soon :D
     
    Comphenix likes this.
  15. Offline

    Murderscene

    Code:
    protocolManager = ProtocolLibrary.getProtocolManager();
     
              protocolManager.addPacketListener(
                new PacketAdapter(this, ConnectionSide.BOTH, 14, 0xC9, 15) {
                    @Override
                    public void onPacketReceiving(PacketEvent event) {
                        // Item packets
                        if (event.getPacketID() == 14) {
                            try {
                                PacketContainer packet = event.getPacket();
                                int message = (Integer) packet.getModifier().read(4);
                                if (message == 5)  {// Bow has been released
                                    bowRelease(event.getPlayer());
                                    logger.info("bow release");
                                }
                            } catch (FieldAccessException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                      @Override
                      public void onPacketSending(PacketEvent event) {
                          // Item packets
                          logger.info("packet sending");
                          if (event.getPacketID() == 0xC9) {
                              logger.info("packet ID 201"); // Player name event
                              try {
                                  PacketContainer packet = event.getPacket();
                                  String pName = (String) packet.getModifier().read(0);
                                  logger.info("name: "+pName);
                              } catch (FieldAccessException e) {
                                  // TODO Auto-generated catch block
                                  e.printStackTrace();
                              }
                          }
                      }
              });
    This is what I have at the moment. The "bow release" (Packet 14) works perfectly. but packet 0xC9 does not. I have also tried it with packet 201 instead of 0xC9

    It's wierd that the first one works and not the second?

    PS I am using Bukkit-1.3.2-R0.1

    UPDATE: I have no idea what I did but I got it working.
    Problem now is by trying to cancel the packet I get
    "java.lang.RuntimeException: Reverting cancelled packet failed."
    I want no players to show up in the player list so I can rebuild it myself. Wonder if it's possible

    UPDATE2: This managed to work, not sure if its right but it worked.
    event.setPacket(new PacketContainer(0));

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

    Comphenix

    Could you post the full error message, stack trace and all? That error occurs when my "hack" to revert the received buffer count fails.

    In any case, I'm working on a new version of ProtocolLib (1.2.0) that uses a different hook method, and doesn't need hacks like that. I'd release it, but I would like to add support for async packet listeners first (for plugins like Orebfuscator). I shouldn't needlessly update the API too many times.
     
  17. Offline

    Murderscene

    Yeah I'll get you the full error when I get home. Until then, is there another way I could essentially cancel the packet until you have fixed this? Setpacket(0) is actually sending out keep alive packets which are lagging my server. And setpacket(null) returns an error
     
  18. Offline

    Supertt007

    Comphenix likes this.
  19. Offline

    Comphenix

    Very nice. :)

    I'm eager to try it out. It's going to be interesting to compare this approach to the other sneaking plugins out there.

    I hope you don't mind if I add a link to your plugin. :)
     
  20. Offline

    Supertt007

    Sure thing! With this packet api there are endless possibilities! I will be looking forward to this a lot :)
     
  21. Offline

    Milkywayz

    This library is very cool, I'll probably make a plugin that uses it.
    The plugin should be eliminated and moved into the library, so we include the packages in our plugins to remove the dependency. That'd be really helpful :D

    When canceling the keep alive packet, this is the error that pops up in console:
    HTML:
    20:49:35 [WARNING] Failed to handle packet: java.lang.NullPointerException
    java.lang.NullPointerException
        at java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:782)
        at java.util.Collections$SetFromMap.contains(Collections.java:3580)
        at com.comphenix.protocol.injector.PlayerInjector.handlePacketRecieved(PlayerInjector.java:203)
        at com.comphenix.protocol.injector.InjectedArrayList.add(InjectedArrayList.java:48)
        at com.comphenix.protocol.injector.InjectedArrayList.add(InjectedArrayList.java:1)
        at java.util.Collections$SynchronizedCollection.add(Collections.java:1577)
        at net.minecraft.server.NetworkManager.queue(NetworkManager.java:89)
        at net.minecraft.server.NetServerHandler.sendPacket(NetServerHandler.java:764)
        at net.minecraft.server.NetServerHandler.d(NetServerHandler.java:115)
        at net.minecraft.server.ServerConnection.b(SourceFile:35)
        at net.minecraft.server.DedicatedServerConnection.b(SourceFile:30)
        at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:583)
        at net.minecraft.server.DedicatedServer.q(DedicatedServer.java:212)
        at net.minecraft.server.MinecraftServer.p(MinecraftServer.java:476)
        at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:408)
        at net.minecraft.server.ThreadServerApplication.run(SourceFile:539)
    I guess it works in a sense, however the error can't be fixed with try or catch, making it a problem.
     
    Comphenix likes this.
  22. Offline

    Comphenix

    Thanks. :)

    I'm not sure if that possible, though. ProtocolLib injects into a whole number of NMS classes with a variety of different methods. While I could ensure that only the first loaded "ProtocolLib"-dependent plugin would do the injection, and then delegate to the subsequent plugins, it would be quite a rewrite and be needlessly complex. I can also see that causing huge problems if multiple versions of ProcotolLib were in use.

    Secondly ... the library is already clocking in at a hefty 480 kB. It would be even worse if every plugin included its own version

    I'm not entirely sure what's causing this problem, but I included a simple NULL check just to be sure.

    It doesn't matter though. I've switched to a different hook technique entirely in 1.2.0. I pretty much had to, as injecting into the queue fields or overriding the network manager misses every chunk packet (53, etc.) that is diverted to the compression thread.

    ProtocolLib 1.2.0 is almost ready. I just have to write the changelog. :)
     
  23. Offline

    Milkywayz

    Yeah, never mind my previous statement of including it in the plugin, its way to large :p. It's better to just have owners install the plugin. Also can't wait for the next release :D. I really want to get my hands dirty in code. Also I have a few questions. Does this library cause any adverse effects on bandwidth usage? I will be getting an i7 3770 (don't ask why I didn't go for a xeon) for my server with 16gb of some good ram so hardware is not quite the issue, is there any sort of estimate on how this could effect network latency? Since the packets are inspected or whatnot before being sent? Also would blocking a bunch of "un-needed" packets make bandwidth usage lower? Because as of now I'm working with 30mbps download and 20mbps upload, so i'd like to be able to fit as many players in as possible. I'll hopefully be getting 55 / 30 internet soon.
     
  24. Offline

    Comphenix

    ProtocolLib 1.2.0

    Yet another version of ProtocolLib is out. This time I've mainly focused on improving performance, but I've also added asynchronous listeners and the ability to listen for map chunk packets.


    Asynchronous listeners

    Normally, when a server packet event is broadcasted to the listeners, they all receive the notification on the main thread. This is perfectly reasonable if the listener is just doing some simple work, like deciding if the packet should be cancelled, or calling additional methods in the Bukkit API. But more extensive processing, such as what's necessary for a plugin like Orebfuscator, should never be executed on the main thread as it will disrupt the game tick loop and cause the entire server to lag. The only solution is to push the packet processing on a separate thread.

    You might be tempted to simply cancel the packet event, do all the processing on an asynchronous task, and send the packet when you're done. While this might work fine with a single registered plugin, multiple listeners will cause problems. What happens if another plugin un-cancels the packet event after it has been cancelled? The packet would be sent twice, once unmodified by your thread, and then again after the modification - which, depending on the packet, may crash the client. You could raise the priority of your listener, but even if you use MONITOR (which you shouldn't, as monitor must be read-only), you can't prevent another listener with the same priority from being executed after yours.

    Not only is this problematic in terms of synchronous listeners, but multiple asynchronous listeners will also cause problems. Either the first asynchronous listener causes the second to think the event is cancelled, when it really isn't, or they both launch their processing in separate threads, which results in two different packets, just like the situation with the un-cancel listener above.

    The solution is to use asynchronous packet listeners:
    Code:java
    1. ProtocolManager manager = ProtocolLibrary.getProtocolManager();
    2.  
    3. manager.getAsynchronousManager().registerAsyncHandler(
    4. new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, ListenerPriority.NORMAL, Packets.Client.CHAT) {
    5. @Override
    6. public void onPacketReceiving(PacketEvent event) {
    7. try {
    8. System.out.println("Waiting ... A");
    9.  
    10. // Simulate doing an awful amount of work
    11. Thread.sleep(2500);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }).start();
    17.  
    18. manager.getAsynchronousManager().registerAsyncHandler(
    19. new PacketAdapter(plugin, ConnectionSide.CLIENT_SIDE, ListenerPriority.HIGH, Packets.Client.CHAT) {
    20. @Override
    21. public void onPacketReceiving(PacketEvent event) {
    22. try {
    23. System.out.println("Waiting ... B");
    24. Thread.sleep(1000);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. }).start();

    The syntax for registering an asynchronous listener is nearly identical to a synchronous listener - the only difference is that you must get the getAsynchronousManager(), and also remember to call start() on the return value. You can also manage the thread yourself by executing the runnable from getListenerLoop().

    Also note that multiple asynchronous listeners can work on the same packet event, just like synchronous packet listeners. In the example above, the first listener (A) will run for 2.5 seconds before the next listener (B) runs for one second. ProtocolLib does all the dirty thread synchronization for you. After each packet has completed processing, they will automatically be queued for transmission.

    This sending queue ensures that each packet is sent in a sensible order. By default, this is the same order as they were originally received or sent, but listeners can alter this by changing the sending index in the packet event's async marker. A lower index causes the packet to be sent sooner. This is useful if you want to prioritize certain packets, such as chunks near the player. A packet with a sending index of zero will be sent immediately after it has been processed. It's not yet possible to prioritize packets for processing - that's still done in the same order as they enter the processing pipeline, but it may be implemented in a later version.

    Packets will timeout after sixty seconds of processing - if you need more time, change the timeout value in the async marker.


    Improving performance

    The structure modifier class is quite useful, not just for preventing some version conflicts (a string field may get renamed but still keep the same index among the string fields), but also for doing all the heavy lifting of converting between the net.minecraft.server-classes and Bukkit.

    It relies on reflection, however, which is something you should minimize as there is limited amount of processing time on the main thread. Adding by one a thousand times can take over 2.69 ms in the current version:
    Code:
    Packet20NamedEntitySpawn target = new Packet20NamedEntitySpawn();
    StructureModifier<Integer> a = StructureCache.getStructure(20).<Integer>withType(int.class).withTarget(target);
     
    for (int i = 0; i < 1000; i++) {
        a.write(0, a.read(0) + 1);
    }
    A single listener is unlikely to perform that many operations, but if you consider that every packet received/sent is passed to multiple listeners, and packets can be sent to multiple players multiple times per second, everything adds up. Asynchronous processing is overkill for something like this, however, so improving performance is the only option.

    Version 1.2.0 now automatically compiles structure modifiers in a background thread, replacing reflection with direct field lookup wherever possible. This means that the code above now executes in 0.45 ms, an improvement of over 500%. Obviously, accessing the field directly is far quicker, but with the drawbacks listed above.

    You can see the detailed performance results here. The benchmark code can be downloaded from GitHub Gist.


    Other improvements

    In addition, I've also changed the server packet detection code entirely. This new method is able to receive map chunk packets, making it possible to implement something like Orebfuscator with this API. :)

    For more details, take a look at the GitHub commit log.

    I'm fairly certain it wouldn't affect the bandwidth by itself. But plugins may use it to send additional packets, drop or even stall packets for a limited duration. That would definitely affect the bandwidth.

    Obviously, hooking into very frequent packets (like keep alive) may affect performance negatively, depending on the amount of processing done in the listeners. I don't have access to a server with multiple players to perform any extensive benchmarking, so I hope people will report performance problems instead. :p

    Maybe. Sounds like a good idea for a new plugin. :)

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

    Milkywayz

    Yeah that's what I was thinking of making. Have it not send some non-crucial packets such as sound, explosions, particles, etc. For servers with limits on bandwidth.
     
  26. Offline

    Comphenix

    I've posted a minor fix to ProtocolLib. The old version used the wrong injection hack (the old method), causing listeners of map chunk packets to crash the server.

    In addition, this version now correctly orders packets by sending index before they get processed by the asynchronous listeners. This makes it possible to prioritize packets for sending AS WELL as processing.

    With this, it should be possible to recreate a plugin like Orebfuscator without referencing CraftBukkit or NMS. You can even override the default compression thread - simply use setPacketStream() to redirect the packets to your own custom PacketStream.

    ProtocolLib 1.2.2 HOTFIX

    Any player logging into ProtocolLib 1.2.1 on CraftBukkit 1.3.2 R2.0 is gurateneed to fall into the void. This is caused by the NetServerHandler proxy class not getting the initial player position, which results in a teleport to x: 0, y: 0, z:0 by CraftBukkit. I've fixed this by copying the proxy in the InjectedServerConnection instead.

    Those affected should upgrade to ProtocolLib 1.2.2.

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

    Comphenix

    ProtocolLib 1.3.0

    I've been working on making ProtocolLib compatible with Orebfuscator and Spout, and I think I've found a pretty good solution. :)

    New features:
    • Compatibility with Orebfuscator and Spout (and other plugins).
    • The ability to schedule multiple workers (either automatically, or by manually managing threads) per listener.
    You can get ProtocolLib 1.3.0 on GitHub. The JavaDoc is, as always, on GitHub Pages.

    Compatibility

    Now, both plugins "override" the NetServerHandler object ProtocolLib uses, but with slightly different methods - Spout recreates the original object, while Orebfuscator uses the proxy pattern. In previous versions this conflict would lead to various forms of crashes, but I've now added a bunch of workarounds that can dynamically extend and inject into whatever hook is already in place (Spout, Orebfuscator, ect.).

    A proxy of a proxy may sound a bit excessive, but it's the only alternative that will work with Spout-compatible plugins. Unfortunately, it doesn't look like Spout supports asynchronous event processing, so plugins such as Orebfuscator must cancel each map chunk packet, and do all the processing manually. That may not sound like such a big deal, but Spout doesn't support uncancelling packets either, nor has it any concept of listener priority. Essentially, I can't use a Spout listener to process sent packets, as any previously registered Spout listener can prevent a packet from being seen by my listener.

    The new hook now handles these proxy objects correctly, but there is a problem. I can only do per-processing of packets, before Spout and its listeners get their hand on it. So in the case of BlockPatcher and Orebfuscator, the BlockPatcher plugin is processed first, which is incorrect. BlockPatcher is supposed to conceal non-vanilla blocks with vanilla blocks, and must get the final say to prevent any client crashes. In addition, Orebfuscator/Spout (any any other plugins) may decide to cache the output of BlockPatcher instead of the real map data, which is also incorrect. The best option is to manually add support for ProtocolLib, but that will probably not happen until (or if) it gets widely used.


    New worker scheduler

    ProtocolLib now supports multiple worker threads per listener. The easiest and most efficient method is to use the new methods start(int) and stop(int) on the AsyncListenerHandler object you get from registering an asynchronous listener.

    But you can also manage the threads yourself, like I did for Orebfuscator. All you need to do, is call getListenerLoop() to get an AsyncRunnable that you can pass on to the worker thread. You can use the same object to stop() the worker thread manually, or get the worker ID that will be passed to the event listener in AsyncMarker.getWorkerID().
     
  28. Offline

    itreptau

    Comphenix, can you tell me how to edit the EntityEquipment packet (0x05)? I did something like packet.toString() and tried to print it out the console to see the content of the packet but that does nothin.
     
    Comphenix likes this.
  29. Offline

    Comphenix

    itreptau I use two tools to get the structure and semantics of a packet - JG Gui and MinecraftCoalition. I'm assuming you're already using the Wiki, but for the purpose of this mini-tutorial I'll revise it.

    The Wiki states that the EntityEquipment packet has three fields: EntityID, Slot and Item. This is the fields as they appear in the network stream, but they may differ from the internal copy in memory, which is what StructureModifier is actually reading and modifying. To get that version, you have to fire up JD GUI and open your current CraftBukkit.jar.

    Once you've opened CraftBukkit, you should see a bunch of packages. Start by expanding net.minecraft, and then server. Scroll down until you find a class named Packet[ID]SomethingDescriptive. Note that the ID here is in decimal, not hexadecimal like on MinecraftCoalition, so you have to convert it (Google it).

    Click the class and the decompiled source-code should appear in the screen to the right. You should get something like this:
    Code:java
    1. public class Packet5EntityEquipment extends Packet
    2. {
    3. public int a;
    4. public int b;
    5. private ItemStack c;
    6.  
    7. public Packet5EntityEquipment()
    8. {
    9. }
    10.  
    11. public Packet5EntityEquipment(int paramInt1, int paramInt2, ItemStack paramItemStack)
    12. {
    13. this.a = paramInt1;
    14. this.b = paramInt2;
    15. this.c = (paramItemStack == null ? null : paramItemStack.cloneItemStack());
    16. }
    17.  
    18. public void a(DataInputStream paramDataInputStream)
    19. {
    20. this.a = paramDataInputStream.readInt();
    21. this.b = paramDataInputStream.readShort();
    22. this.c = c(paramDataInputStream);
    23. }
    24. // ect.
    25.  

    Look at the "a"-method with a DataInputStream parameter. That's obviously a "readPacket" method, as it's reading from an input stream and writing the result into local fields. The Wiki told us that the first field in the network stream is an int and represents the entity ID, so "a" must actually be entityID. Similarily, "b" is slot ID. Now, while the "c" field is read using an unknown "c" method (it's defined in Packet), it's easy to determine that it's the item field as the type is ItemStack.

    Now that we know what each field means, we can use StructureModifier to easily read and write both public and private fields. It will also convert between Bukkit and the internal Minecraft classes (such as net.minecraft.server.Entity -> org.bukkit.entity.Player).

    The first field is an entity ID, so we'll use the getEntityModifier method:
    Code:java
    1. protocolManager.addPacketListener(new PacketAdapter(plugin, ConnectionSide.SERVER_SIDE, 0x5) {
    2. @Override
    3. public void onPacketSending(PacketEvent event) {
    4. PacketContainer packet = event.getPacket();
    5.  
    6. try {
    7.  
    8. // Player equipment
    9. if (event.getPacketID() == 0x5) {
    10. Entity playerEntity = packet.getEntityModifier(event.getPlayer().getWorld()).read(0);
    11. Integer slot = packet.getSpecificModifier(int.class).read(1);
    12. ItemStack stack = packet.getItemModifier().read(0);
    13.  
    14. }
    15.  
    16. } catch (FieldAccessException e) {
    17. logger.log(Level.SEVERE, "Couldn't access field.", e);
    18. }
    19. }
    20. });

    The index (0, 1 and 2) correspond to the order the field appear in the source code FOR THAT TYPE. So, while "a" and "b" has index 0 and 1 respectively as they're both integers in memory, the item stack has index 0 because it's the first item stack in the packet. You can use "getModifier" if you want the index to correspond to the actual field indexes in the packet, or if you just want to enumerate every field.

    Also note that I use int.class in the specific modifier for the integer slot, even though I'm expecting an Integer result. This is only because generics doesn't support primitive types.

    Now, if you want to modify the value in the packet, you'll have to write back the result of your modification. You do that in nearly the same way as you read values, except that you call "write" with the appropriate field index. Certain fields, such as ItemStack or ItemStack arrays, doesn't have to be written back after they're modified. This is true for all non-primitive mutable objects.
     
    CeramicTitan and itreptau like this.
  30. Offline

    itreptau

    WOW, havent thought something this big would come. Thank you so much for the very informative and detailed tutorial. Your're awesome, really :D

    I cant manage to modify the packets, what am i doing wrong?
    Code:
    public class ProtocolLibTest extends JavaPlugin {
     
        @Override
        public void onDisable() {
            System.out.println("ProtocolLibTest disabled!");
        }
       
        private ProtocolManager protocolManager;
       
        public void onLoad() {
        protocolManager = ProtocolLibrary.getProtocolManager();
        }
       
        @Override
        public void onEnable() {
            System.out.println("ProtocolLibTest enabled!");
            test();
        }
       
        public void test() {
            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) {
                            ItemStack stack = new ItemStack(2, 1);
                            packet.getItemModifier().write(0, stack);
                            System.out.println("modified packet");
                        }
                   
                    } catch (FieldAccessException e) {
                        System.out.println("Couldn't access field.");
                    }
                }
                });
        }
    }
    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 28, 2016
    CeramicTitan likes this.
Thread Status:
Not open for further replies.

Share This Page