[Util] Version independent NMSPacket class

Discussion in 'Resources' started by Cirno, Aug 8, 2013.

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

    Cirno

    I wrote this some time ago because I needed to use packets, but I really didn't want to go through importing NMS and making the plugin version-specific.

    It's completely version-independent and does not rely on external libraries or CraftBukkit.

    Variables inside of it:
    • packageName - Used to get the NMS version.
    • version - The version. See above.
    • packet - The packet itself; note that it's an Object.
    • nmsPacket - The packet class.
    Methods:
    • NMSPacket(String packetName) - Constructs a NMSPacket; use the full name of the Packet, example: new NMSPacket("Packet201PlayerInfo")
    • setField(String, Object) - Sets a non-private field to Object. (Using this on a private member would throw a FieldNotFoundException)
    • setDeclaredField(String, Object) - Sets a private field.
    • getField(String) - Get's a non-private variable inside of the packet.
    • getDeclaredField - Get's a private variable.
    • getPacketClass - Returns the packet class.
    • getPacket - Returns the packet object.
    • isUsable - Checks if "packet" is not null; useful if you're working with user-input or something similar.
    Note that getField and getDeclaredField will return null and print an exception if it cannot find the field. setField and setDeclaredField will only print an exception.

    Code:java
    1. package com.tenko.nms;
    2.  
    3. import java.lang.reflect.Field;
    4.  
    5. public class NMSPacket {
    6.  
    7. private static String packageName = Bukkit.getServer().getClass().getPackage().getName();
    8. private static String version = packageName.substring(packageName.lastIndexOf(".") + 1);
    9.  
    10. private Object packet;
    11. private Class<?> nmsPacket;
    12.  
    13. public NMSPacket(String packetName){
    14. try {
    15. nmsPacket = Class.forName("net.minecraft.server." + version + "." + packetName);
    16. packet = nmsPacket.newInstance();
    17. } catch (Exception e){
    18. e.printStackTrace();
    19. packet = null;
    20. nmsPacket = null;
    21. }
    22.  
    23. }
    24. public void setField(String fieldName, Object value){
    25. try {
    26. Field f = packet.getClass().getField(fieldName);
    27. f.setAccessible(true);
    28. f.set(packet, value);
    29. f.setAccessible(false);
    30. } catch (Exception e){
    31. e.printStackTrace();
    32. }
    33. }
    34.  
    35. public void setDeclaredField(String fieldName, Object value){
    36. try {
    37. Field f = packet.getClass().getDeclaredField(fieldName);
    38. f.setAccessible(true);
    39. f.set(packet, value);
    40. f.setAccessible(false);
    41. } catch (Exception e){
    42. e.printStackTrace();
    43. }
    44. }
    45.  
    46. public Object getField(String fieldName){
    47. try {
    48. Field f = packet.getClass().getField(fieldName);
    49. f.setAccessible(true);
    50. Object s = f.get(packet);
    51. f.setAccessible(false);
    52. return s;
    53. } catch (Exception e){
    54. e.printStackTrace();
    55. }
    56. return null;
    57. }
    58.  
    59. public Object getDeclaredField(String fieldName){
    60. try {
    61. Field f = packet.getClass().getDeclaredField(fieldName);
    62. f.setAccessible(true);
    63. Object s = f.get(packet);
    64. f.setAccessible(false);
    65. return s;
    66. } catch (Exception e){
    67. e.printStackTrace();
    68. }
    69. return null;
    70. }
    71.  
    72. public Class<?> getPacketClass(){
    73. return nmsPacket;
    74. }
    75.  
    76. public Object getPacket(){
    77. return packet;
    78. }
    79.  
    80. public boolean isUsable(){
    81. return packet != null;
    82. }
    83.  
    84. }


    You can send these packet objects like so:
    Code:java
    1. public static void sendPacket(Player plyr, Object o){
    2. try {
    3. Class<?> packet = Class.forName("net.minecraft.server." + version + ".Packet");
    4. Class<?> craftPlayer = Class.forName("org.bukkit.craftbukkit." + version + ".entity.CraftPlayer");
    5.  
    6. if(!packet.isAssignableFrom(o.getClass())){
    7. throw new IllegalArgumentException("Object o wasn't a packet!");
    8. }
    9.  
    10. Object cp = craftPlayer.cast(plyr);
    11. Object handle = craftPlayer.getMethod("getHandle").invoke(cp);
    12. Object con = handle.getClass().getField("playerConnection").get(handle);
    13. con.getClass().getMethod("sendPacket", packet).invoke(con, o);
    14. } catch (Exception e){
    15. e.printStackTrace();
    16. }
    17. }
    18.  
    19. public static void sendPackets(Object o){
    20. try {
    21. Class<?> packet = Class.forName("net.minecraft.server." + version + ".Packet");
    22. Class<?> craftPlayer = Class.forName("org.bukkit.craftbukkit." + version + ".entity.CraftPlayer");
    23. for (Player plyr : Bukkit.getOnlinePlayers()){
    24. if(!packet.isAssignableFrom(o.getClass())){
    25. throw new IllegalArgumentException("Object o wasn't a packet!");
    26. }
    27.  
    28. Object cp = craftPlayer.cast(plyr);
    29. Object handle = craftPlayer.getMethod("getHandle").invoke(cp);
    30. Object con = handle.getClass().getField("playerConnection").get(handle);
    31. con.getClass().getMethod("sendPacket", packet).invoke(con, o);
    32. }
    33. } catch (Exception e){
    34. e.printStackTrace();
    35. }
    36. }


    Example:
    Code:java
    1. NMSPacket packet = new NMSPacket("Packet0KeepAlive");
    2. packet.setField("a", 9);
    3. //Send the packet to every player
    4. sendPackets(packet.getPacket());
    5. //Send packet to only Cirno
    6. sendPackets(Bukkit.getExactPlayer("Cirno"));


    I edited this a bit and did a bit of double-checking, but if there is a mistake, please report it :)
     
    _DSH105_ and hawkfalcon like this.
  2. Offline

    Jogy34

    I don't think this is allowed. I believe one of the rules for when they started using the dynamic package names was that you can't create a work around like this as the point in the dynamic package names was so that plugin developers are somewhat forced to look through all of their NMS code for changes so as to avoid plugins breaking servers from careless developers.
     
  3. Offline

    DarkBladee12

    Jogy34 Well yeah, that was the actual point of the version in the package name, but most of the developers don't like to update their plugin every time a new bukkit version comes out, because it always takes some time until the new version is approved by mods! It's not safe though as you said that method names can change and this will be break the plugin, but NMS methods are rarely changed so it's not a big problem. I think the only advantage of importing NMS classes and using is that you can see in Eclipse when something has changed and you can easily correct it, on the other hand you'd also see that something isn't working anymore if you test your plugin (which uses reflection) after a new bukkit version comes out.
     
  4. Offline

    Jogy34

    A lot of NMS methods change on every minecraft version release. I run into problems on my own plugins that use a lot of NMS code to where I have to end up going through the updated NMS classes and search for the method that did one thing before but another thing now. Minecraft uses some sort of generalization thing with its code. That's why you see the classes a, aa, aaa, ab, b, bc, etc... and all the methods in there are also changed in a simmilar way to where the methods are a(), a(someObject), a(someOtherObject), b(), etc... and although the methods themselves may not change the name that they use can easily change between minecraft versions.
     
  5. Jogy34 They use Obfuscation...
     
Thread Status:
Not open for further replies.

Share This Page