[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

    Ah, thanks for letting me know. This should be fixed (1, 2) in the latest development build.
     
  2. Offline

    Minnymin3

  3. Offline

    Roelmb

    Comphenix
    Is it possible to send a package to a client that hasn't fully logged in yet? I cancelled a packet from server.LOGIN
    But I can't seem to find out how to send another one in return as the getPlayer() returns an UNKNOWN player.
     
  4. Offline

    Comphenix

    You should be able to send the packet using ProtocolManager.sendServerPacket(player, Packet).

    I have added special checks for temporary players in both Netty and the old packet injectors, so that you can send packets to them.

    So you both want to know how to write a disguise plugin?

    Why not just depend on all the existing disguise plugins out there? I believe most of them have an API you can use. As for which; you have DisguiseCraft, of course, and plenty others - just Google "disguise plugin bukkit".

    But if you really want to make you own, I suggest you start by looking over all the packets that contain an entity ID. Use PacketWrapper if you want to simplify the code on your end. Then you have at least three general approaches:
    • When disguising a player as a mod, spawn a mob entity on the client side only. Then construct the correct movement and orientation packets using Bukkit events, and send them to the client. Use Player.hidePlayer() to hide the player entity. This is essentially what DisguiseCraft have been doing.
    • Destroy the original player entity (using the same entity ID), and construct a corresponding mob entity with the same entity ID using the SPAWN_LIVING packet. This does work somewhat (with hilarious side-effects, such as upside-down dragons), but I don't think it is as simple as it used to be. Look for iDisguise for more information.
    • Edit or cancel existing packets directly in the packet stream, often synthesizing entirely new packets in the process (such as entity sounds). I think at least one of the other plugins is doing it this way, but it is fairly complicated. Especially since you have to handle the different DataWatcher's for each entity type (packet 40 or ENTITY_METADATA).
    Still, if you want to get started, here's a list of all the packets you'll need to intercept or synthesize:
    Code:java
    1. import static com.comphenix.protocol.PacketType.Play.Server.*;
    2. // etc.
    3.  
    4. // Packets that update remote player entities
    5. private static final PacketType[] ENTITY_PACKETS = {
    6. ENTITY_EQUIPMENT, BED, ANIMATION, NAMED_ENTITY_SPAWN,
    7. COLLECT, SPAWN_ENTITY, SPAWN_ENTITY_LIVING, SPAWN_ENTITY_PAINTING, SPAWN_ENTITY_EXPERIENCE_ORB,
    8. ENTITY_VELOCITY, REL_ENTITY_MOVE, ENTITY_LOOK, ENTITY_MOVE_LOOK,
    9. ENTITY_TELEPORT, ENTITY_HEAD_ROTATION, ENTITY_STATUS, ATTACH_ENTITY, ENTITY_METADATA,
    10. ENTITY_EFFECT, REMOVE_ENTITY_EFFECT, BLOCK_BREAK_ANIMATION
    11.  
    12. // We don't handle DESTROY_ENTITY though
    13. };

    I won't even try to write an example though, as it either be too long (a significant fraction of those disguise plugins) or too flawed. And it's pointless, seeing how it has already been written by other people.
     
  5. Offline

    Ultimate_n00b

    Comphenix Yeah, I figured that much out. I'm currently using DisguiseCraft, I suppose it fits my client's needs for now.
     
  6. Offline

    Roelmb

    Comphenix
    Code:
            StructureModifier<WrappedGameProfile> Profile = event.getPacket().getGameProfiles();
            String name = Profile.getValues().listIterator().next().getName();
    Is their any other way then getting the username out of the GameProfileWrapper?
     
  7. Offline

    Comphenix

    There will only be one GameProfile field, so just use read(0):
    Code:java
    1. event.getPacket().getGameProfiles().read(0).getName();
     
  8. Offline

    Comphenix

    Download:
    Build: #203

    This is another bug fix update, this time focusing on memory leaks triggered by clients that ping the server (in the server multiplayer menu). This applies to users of both CraftBukkit 1.7.2 and Spigot 1.6.4. Upgrading is advisable.

    In addition, I've also improved the WrappedServerPing class - it now supports hiding the player count. There's also been a number of small fixes and performance improvements.

    Minecraft 1.7.2
    API
    Features
    Fixes
    Small fixes
    Stability
     
    Minecrell likes this.
  9. Offline

    BungeeTheCookie

    Thank you comphenix
     
  10. Offline

    Comphenix

  11. Offline

    Soren025

    Is there a way to switch case the id or some identifier in a packet listener like before when the ids were static constants?

    I don't really want to use an if else ladder of (type instanceof...) or (type.getCurrentId() == CONSTANT.getCurrentId()) for a listener listening for more than one packet
     
  12. Offline

    MoeMix

    hey Comphenix, I need help with setting up the thing where you hover over the player count. Do you have to create a plugin and use protocollib as a reference source? Because that is what I did. If that is the case what would the plugin look like? Mine is like this:
    Code:java
    1. package me.moemix.ymmserver;
    2.  
    3. import java.util.Arrays;
    4. import java.util.logging.Logger;
    5.  
    6. import org.bukkit.ChatColor;
    7. import org.bukkit.event.Listener;
    8. import org.bukkit.plugin.java.JavaPlugin;
    9.  
    10. import com.comphenix.protocol.PacketType;
    11. import com.comphenix.protocol.events.ListenerOptions;
    12. import com.comphenix.protocol.events.ListenerPriority;
    13. import com.comphenix.protocol.events.PacketAdapter;
    14. import com.comphenix.protocol.events.PacketEvent;
    15. import com.comphenix.protocol.wrappers.WrappedGameProfile;
    16. import com.comphenix.protocol.wrappers.WrappedServerPing;
    17.  
    18.  
    19. public class ServerMessage extends JavaPlugin implements Listener{
    20. Logger logger = Logger.getLogger("Minecraft");
    21. public void onEnable() {
    22. new PacketAdapter(this, ListenerPriority.NORMAL,
    23. Arrays.asList(PacketType.Status.Server.OUT_SERVER_INFO), ListenerOptions.ASYNC) {
    24.  
    25. public void onPacketSending(PacketEvent event) {
    26. handlePing(event.getPacket().getServerPings().read(0));
    27. }
    28. };
    29. }
    30.  
    31. public void handlePing(WrappedServerPing ping) {
    32. ping.setPlayers(Arrays.asList(
    33. new WrappedGameProfile("id1", ChatColor.GOLD + "HI. " + ChatColor.GREEN +
    34. "Test1!"),
    35. new WrappedGameProfile("id2", "test2"),
    36. new WrappedGameProfile("id3", "Test3")
    37. ));
    38. }
    39. }
     
  13. Offline

    Comphenix

    I explain how here. You need to register your PacketAdapter as a packet listener, in particular.

    You can safely compare instances directly when dealing with PacketTypes from any API function in ProtocolLib:
    Code:java
    1. if (event.getType() == PacketType.Play.Server.NAMED_ENTITY_SPAWN) {
    2. // etc.
    3. }

    If you absolutely must use a switch statement, you could store each PacketType in a Map<PacketType, Integer>, and lookup an integer:
    Code:java
    1. switch (map.get(type)) {
    2. case 0:
    3. // etc.
    4. break;
    5. }
    You might get away with using getLegacyId() here, but I can't guarantee those IDs won't change in the future (depending on what Mojang decides to refactor). So I'd recommend using your own custom index.
     
  14. Offline

    Soren025

    That would work but would take a bit of work as I would have to do something along the lines of...

    Code:java
    1. public class PacketNumber {
    2. public static int PLAY_SERVER_CHAT = 0;
    3. //Insert random number for every other packet
    4.  
    5. private static Map<PacketType, Integer> map = new HashMap<>();
    6.  
    7. static {
    8. map.put(PacketType.Play.Server.CHAT, PLAY_SERVER_CHAT);
    9. //do same for every other packet
    10. }
    11.  
    12. public static int get(PacketType type) {
    13. return map.get(type);
    14. }
    15. }

    Code:java
    1. switch (PacketNumber.get(type)) {
    2. case PacketNumber.PLAY_SERVER_CHAT:
    3. //code...
    4. }


    also I'm not 100% sure but I think the if else ladder would be faster then the map lookup just to switch case the packets.
     
  15. Offline

    fromgate Retired Staff

    Hello!

    I'm trying to handle sending of PacketPlayOutChat:

    Code:java
    1.  
    2. public void initPacketListener(){
    3. ProtocolLibrary.getProtocolManager().addPacketListener(
    4. new PacketAdapter(this, PacketType.Play.Server.CHAT) {
    5. @Override
    6. public void onPacketSending(PacketEvent event) {
    7. if (event.getPacket().getStrings().size()>0)
    8. Bukkit.getLogger().info(event.getPacket().getStrings().read(0));
    9. }
    10. });
    11. }
    12.  


    Here event.getPacket().getStrings().size() always returns 0. So I cannot obtain a message from the packet.

    event.getPacket().getChatComponents().getValues().get(0).getJson() returns a JSON representation of message, but I need normal string?
    How I can get a message as text? Or I need to operate with JSON format?
     
  16. Offline

    Comphenix

    Unfortunately, I haven't written an API for accessing and modifying chat components, so you will either have to modify the raw JSON, or get the underlying ChatComponent instance:
    Code:java
    1. event.getPacket().getChatComponents().read(0).getHandle(); // type ChatBaseComponent

    But modifying the JSON isn't all that hard - just recurse over it, and replace its text elements:
    https://gist.github.com/aadnk/8129389
    Probably - there's a good deal of overhead with having to lookup a boxed integer, unbox it, and then perform the switch (which might effectively become if-statements regardless).

    But then again, this overhead is negligible compared to every use of reflection, in particular when accessing private fields with PacketContainer. Remember, "premature optimization is the root of all evil".
     
    fromgate likes this.
  17. Offline

    fromgate Retired Staff

    Thank you! Your example greatly helped me :)
     
    Comphenix likes this.
  18. Offline

    HeavyMine13

    What is the packet name for playing and stopping records(Music disks) for only a specific client?
     
  19. Offline

    Comphenix

    If you type in "/packet add play server true", you'll see every packet sent in the console window. If you're in The End, or a flat world with no entity spawning, only your entity will generate packets.

    This is what I get when I do just that:
    Code:
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutWorldEvent[40, legacy: 61] to aadnk:
    { a = 1005, b = 2264, c = 843, d = 64, e = -1281, f = false }
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1281, block = JUKEBOX, data = 1 }
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1280, block = AIR, data = 0 }
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1281, block = JUKEBOX, data = 1 }
    [12:40:51] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutWorldEvent[40, legacy: 61] to aadnk:
    { a = 1005, b = 0, c = 843, d = 64, e = -1281, f = false }
    (...)
    [12:40:51] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1281, block = JUKEBOX, data = 0 }
    [12:40:51] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1280, block = AIR, data = 0 }
    [12:40:51] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1281, block = JUKEBOX, data = 0 }
    Looks like the server momentarily changes the Jukebox into air in order to stop any existing playback:
    Code:
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1281, block = JUKEBOX, data = 1 }
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1280, block = AIR, data = 0 }
    [12:40:12] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutBlockChange[35, legacy: 53] to aadnk:
    { a = 843, b = 64, c = -1281, block = JUKEBOX, data = 1 }
    It then sends an EFFECT (called WORLD_EVENT in Bukkit) packet to start the playback:
    Code:
    [12:40:51] [Server thread/INFO]: [ProtocolLib] Sent PacketPlayOutWorldEvent[40, legacy: 61] to aadnk:
    { a = 1005, b = 0, c = 843, d = 64, e = -1281, f = false }
    At the end, you see it sending JUKEBOX - AIR - JUKEBOX to reset the sound.
     
  20. Offline

    HeavyMine13

    I meant with code.... not with commands
     
  21. Offline

    Comphenix

    I was trying to explain how you would go about finding the packets responsible, along with answering your question. It's PacketPlayOutWorldEvent and PacketPlayOutBlockChange, by the way.

    You can fairly easily send a WORLD_EVENT packet with PacketWrapper. Just copy in WrapperPlayServerWorldEvent and AbstractPacket into your plugin. Set the effect ID to
    SoundEffects.PLAY_MUSIC_DISK and data to the item ID of the record. Then set the location to the jukebox that will be playing the record.

    There's no need for PacketWrapper when stopping the playback, however. Just use sendBlockChange():
    Code:java
    1. p.sendBlockChange(paramLocation, Material.JUKEBOX, 0);
    2. p.sendBlockChange(paramLocation, Material.AIR, 0);
    3. p.sendBlockChange(paramLocation, Material.JUKEBOX, 0);
     
  22. Comphenix The ItemDisguise link doesn't work. I really need it please.
     
  23. Offline

    Comphenix

    Ah, sorry, I moved it into "Examples".

    Here's the correct link.
     
    MinecraftShamrock likes this.
  24. Comphenix likes this.
  25. Offline

    HeavyMine13

    Comphenix

    SoundEffects.PLAY_MUSIC_DISC(1, event.gePlayer().getLocation());

    Like that?
     
  26. Offline

    Comphenix

    Err, no, that doesn't compile.

    But I experimented a bit, and it turns out you don't actually need to use sendBlockChange(). It's enough to send a WORLD_EVENT packet with the same location and data 0 to cancel the sound (download):
    Code:java
    1. public class ExampleMod extends JavaPlugin implements Listener {
    2. private Map<Player, Vector> sounds = new MapMaker().weakKeys().makeMap();
    3.  
    4. @Override
    5. public void onEnable() {
    6. getServer().getPluginManager().registerEvents(this, this);
    7. }
    8.  
    9. @Override
    10. public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    11. if (sender instanceof Player) {
    12. Player player = (Player) sender;
    13. Vector playing = sounds.get(player);
    14.  
    15. if (playing == null) {
    16. sounds.put(player, playing = player.getLocation().toVector());
    17. playRecord(player, playing, Material.RECORD_10);
    18. } else {
    19. sounds.remove(player);
    20. playRecord(player, playing, null);
    21. }
    22. }
    23. return true;
    24. }
    25.  
    26. @EventHandler
    27. public void onPlayerChangedWorldEvent(PlayerChangedWorldEvent e) {
    28. // This will reset the playback
    29. sounds.remove(e.getPlayer());
    30. }
    31.  
    32. public void playRecord(Player player, Vector loc, Material record) {
    33. WrapperPlayServerWorldEvent event = new WrapperPlayServerWorldEvent();
    34. event.setData(record != null ? record.getId() : 0);
    35. event.setEffectId(WrapperPlayServerWorldEvent.SoundEffects.PLAY_MUSIC_DISK);
    36. event.setX(loc.getBlockX());
    37. event.setY(loc.getBlockY());
    38. event.setZ(loc.getBlockZ());
    39. event.sendPacket(player);
    40. }
    41. }
    42.  
     
  27. Offline

    HeavyMine13

    Comphenix

    the "playing" after sound.put(player, playing = player.getLocation().toVector()); give this error:
    "The final local variable playing cannot be assigned, since it is defined in an enclosing type."

    Here is what I have now (Correct me please.):
    Code:java
    1. package me.HeavyMine13.ms;
    2.  
    3. import java.util.Map;
    4.  
    5. import net.minecraft.util.com.google.common.collect.MapMaker;
    6.  
    7. import org.bukkit.Bukkit;
    8. import org.bukkit.ChatColor;
    9. import org.bukkit.Effect;
    10. import org.bukkit.Material;
    11. import org.bukkit.block.Jukebox;
    12. import org.bukkit.entity.Player;
    13. import org.bukkit.event.EventHandler;
    14. import org.bukkit.event.EventPriority;
    15. import org.bukkit.event.Listener;
    16. import org.bukkit.event.block.Action;
    17. import org.bukkit.event.entity.EntityDeathEvent;
    18. import org.bukkit.event.entity.FoodLevelChangeEvent;
    19. import org.bukkit.event.player.AsyncPlayerChatEvent;
    20. import org.bukkit.event.player.PlayerChangedWorldEvent;
    21. import org.bukkit.event.player.PlayerInteractEvent;
    22. import org.bukkit.event.player.PlayerLoginEvent;
    23. import org.bukkit.inventory.Inventory;
    24. import org.bukkit.inventory.ItemStack;
    25. import org.bukkit.plugin.java.JavaPlugin;
    26. import org.bukkit.util.Vector;
    27.  
    28. public class MS extends JavaPlugin implements Listener {
    29. private Map<Player, Vector> sounds = new MapMaker().weakKeys().makeMap();
    30. public static Inventory mij;
    31.  
    32.  
    33. public void onEnable(){
    34. Bukkit.getPluginManager().registerEvents(this, this);
    35. }
    36.  
    37. public void onDisable(){
    38.  
    39. }
    40.  
    41. @EventHandler
    42. public void onPlayerChat(AsyncPlayerChatEvent e) {
    43. e.setFormat("%s: %s");
    44. }
    45.  
    46. @EventHandler
    47. public void Hunger(FoodLevelChangeEvent event){
    48. if(event.getEntity() instanceof Player){
    49. Player player = (Player) event.getEntity();
    50. if(player.getWorld().getName().equals("world"));
    51. event.setCancelled(true);
    52. event.setFoodLevel(20);
    53. player.setFoodLevel(20);
    54. }
    55. }
    56.  
    57.  
    58.  
    59. @EventHandler
    60. public void JoinP(PlayerLoginEvent event){
    61. Player player = (Player) event.getPlayer();
    62. if(!player.hasPermission("mj.pp")){
    63.  
    64. }
    65. }
    66.  
    67. @EventHandler(priority = EventPriority.HIGHEST)
    68. public void onPlayerDeath(EntityDeathEvent e) {
    69. if (e.getEntity() instanceof Player) {
    70. final Player p = (Player) e.getEntity();
    71. p.teleport(p.getWorld().getSpawnLocation());
    72. p.setHealth(20.0D);
    73. Bukkit.getServer().getScheduler()
    74. .scheduleSyncDelayedTask(this, new Runnable() {
    75. public void run() {
    76. p.teleport(p.getWorld().getSpawnLocation());
    77. p.setHealth(20.0D);
    78. }
    79. }, 1L);
    80. }
    81. }
    82.  
    83.  
    84. @EventHandler
    85. public boolean JukeClick(PlayerInteractEvent event){
    86. final Player player = (Player) event.getPlayer();
    87. final Vector playing = sounds.get(player);
    88. if (!(event.getAction() == Action.RIGHT_CLICK_BLOCK))
    89. return false;
    90. if (event.getClickedBlock().getState() instanceof Jukebox) {
    91. if(!player.hasPermission("mj.use")){
    92. event.setCancelled(true);
    93.  
    94. player.sendMessage(ChatColor.DARK_RED + "Only" + " " + ChatColor.BLUE + "MVPs" + " " + ChatColor.DARK_RED + "can use this feature...");
    95. } else{
    96. IconMenu menu = new IconMenu("Magical Music List", 9, new IconMenu.OptionClickEventHandler() {
    97.  
    98. @Override
    99. public void onOptionClick(IconMenu.OptionClickEvent event) {
    100. if(event.getPosition() == 3){
    101. sounds.put(player, playing = player.getLocation().toVector());
    102. playRecord(player, playing, Material.RECORD_11);
    103. event.setWillClose(true);
    104. }else if(event.getPosition() == 4){
    105.  
    106. player.playEffect(player.getLocation(), Effect.RECORD_PLAY, Material.RECORD_3);
    107. event.setWillClose(true);
    108. }else if(event.getPosition() == 5){
    109. player.playEffect(player.getLocation(), Effect.RECORD_PLAY, Material.RECORD_5);
    110. event.setWillClose(true);
    111. }else if(event.getPosition() == 1){
    112.  
    113. player.playEffect(player.getLocation(), Effect.RECORD_PLAY, Material.RECORD_6);
    114. }
    115. }
    116. }, MS.this);
    117. menu.setOption(3, new ItemStack(Material.RECORD_11, 1), "Scary", "Just Dont....");
    118. menu.setOption(4, new ItemStack(Material.RECORD_3, 1), "Charming", "Its Awesome!");
    119. menu.setOption(5, new ItemStack(Material.RECORD_5, 1), "Weird", "HUh");
    120. menu.setOption(1, new ItemStack(Material.RECORD_8, 1),ChatColor.BLUE + "GOofy", "Give dis a try");
    121. menu.open(player);
    122. return true;
    123. }
    124. }
    125.  
    126. return false;
    127. }
    128. @EventHandler
    129. public void onPlayerChangedWorldEvent(PlayerChangedWorldEvent e) {
    130. // This will reset the playback
    131. sounds.remove(e.getPlayer());
    132. }
    133.  
    134. @SuppressWarnings("deprecation")
    135. public void playRecord(Player player, Vector loc, Material record) {
    136. WrapperPlayServerWorldEvent event = new WrapperPlayServerWorldEvent();
    137. event.setData(record != null ? record.getId() : 0);
    138. event.setEffectId(WrapperPlayServerWorldEvent.SoundEffects.PLAY_MUSIC_DISK);
    139. event.setX(loc.getBlockX());
    140. event.setY(loc.getBlockY());
    141. event.setZ(loc.getBlockZ());
    142. event.sendPacket(player);
    143. }
    144.  
    145. }
    146.  
    147.  
    148.  
    149.  
    150.  
    151.  
    152.  
    153.  
    154.  
    155.  
    156.  
    157.  
    158.  
    159.  
    160.  
    161.  
    162.  
    163.  
    164.  
    165.  
     
  28. Offline

    Comphenix

    Final variables cannot be modified, but neither can you access non-final variables inside an anonymous class.

    I don't think you quite understand what I'm doing here. I'm saving the location where I play a record for a player, so that I can cancel it if the command is executed again. If you want to, say, cancel the previous record playback when the player clicks the item in the "option menu", you should just call this:
    Code:java
    1. playRecord(player, playing, null);


    But you should move line 87. down to line 100, and remove final.
     
  29. Offline

    572

    How do I cancel a block breaking animation like playing "gamemode 2"?


    Reply :
    Thanks, it is clear and easy to understand
    vvv
     
  30. Offline

    Comphenix

    You can only cancel other player's block break animations (cancel WrapperPlayServerBlockBreakAnimation), the main block break animation is client-side only.
     
Thread Status:
Not open for further replies.

Share This Page