Package com.comphenix.protocol

Source Code of com.comphenix.protocol.CommandPacket

/*
*  ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
*  Copyright (C) 2012 Kristian S. Stangeland
*
*  This program is free software; you can redistribute it and/or modify it under the terms of the
*  GNU General Public License as published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
*  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*  See the GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License along with this program;
*  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
*  02111-1307 USA
*/

package com.comphenix.protocol;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.cglib.proxy.Factory;

import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.HexDumper;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;

/**
* Handles the "packet" debug command.
*
* @author Kristian
*/
class CommandPacket extends CommandBase {
  public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");

  private enum SubCommand {
    ADD, REMOVE, NAMES, PAGE;
  }
 
  /**
   * Name of this command.
   */
  public static final String NAME = "packet";

  /**
   * Number of lines per page.
   */
  public static final int PAGE_LINE_COUNT = 9;
 
  /**
   * Number of bytes before we do a hex dump.
   */
  private static final int HEX_DUMP_THRESHOLD = 256;
 
  private Plugin plugin;
  private Logger logger;
  private ProtocolManager manager;
   
  private ChatExtensions chatter;
 
  // The main parser
  private PacketTypeParser typeParser = new PacketTypeParser();
 
  // Paged message
  private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
 
  // Current registered packet types
  private PacketTypeSet packetTypes = new PacketTypeSet();
  private PacketTypeSet extendedTypes = new PacketTypeSet();
 
  // Compare listeners
  private PacketTypeSet compareTypes = new PacketTypeSet();
  private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
 
  // The packet listener
  private PacketListener listener;
 
  // Compare listener
  private PacketListener compareListener;
 
  // Filter packet events
  private CommandFilter filter;
 
  public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
    super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
    this.plugin = plugin;
    this.logger = logger;
    this.manager = manager;
    this.filter = filter;
    this.chatter = new ChatExtensions(manager);
  }

  /**
   * Send a message without invoking the packet listeners.
   * @param receiver - the player to send it to.
   * @param message - the message to send.
   * @return TRUE if the message was sent successfully, FALSE otherwise.
   */
  public void sendMessageSilently(CommandSender receiver, String message) {
    try {
      chatter.sendMessageSilently(receiver, message);
    } catch (InvocationTargetException e) {
      reporter.reportDetailed(this,
          Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
      );
    }
  }
 
  /**
   * Broadcast a message without invoking any packet listeners.
   * @param message - message to send.
   * @param permission - permission required to receieve the message. NULL to target everyone.
   */
  public void broadcastMessageSilently(String message, String permission) {
    try {
      chatter.broadcastMessageSilently(message, permission);
    } catch (InvocationTargetException e) {
      reporter.reportDetailed(this,
          Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
      );
    }
  }
 
  private void printPage(CommandSender sender, int pageIndex) {
    List<String> paged = pagedMessage.get(sender);
   
    // Make sure the player has any pages
    if (paged != null) {
      int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
     
      for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
        if (i < paged.size()) {
          sendMessageSilently(sender, " " + paged.get(i));
        }
      }
     
      // More data?
      if (pageIndex < lastPage) {
        sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
      }
     
    } else {
      sendMessageSilently(sender, ChatColor.RED + "No pages found.");
    }
  }
 
  /*
   * Description: Adds or removes a simple packet listener.
       Usage:       /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
   */
  @Override
  protected boolean handleCommand(CommandSender sender, String[] args) {
    try {
      Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
      SubCommand subCommand = parseCommand(arguments);

      // Commands with different parameters
      if (subCommand == SubCommand.PAGE) {
        if (args.length <= 1) {
          sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
          return true;
        }
       
        int page = Integer.parseInt(args[1]);
       
        if (page > 0)
          printPage(sender, page);
        else
          sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
        return true;
      }
     
      Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
      Boolean detailed = parseBoolean(arguments, "detailed");
      Boolean compare = parseBoolean(arguments, "compare");
     
      // Notify user
      if (typeParser.getLastProtocol() == null) {
        sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
      }
      if (arguments.size() > 0) {
        throw new IllegalArgumentException("Cannot parse " + arguments);
      }
   
      // The last elements are optional
      if (detailed == null) {
        detailed = false;
      }
      if (compare == null) {
        compare = false;
      } else {
        // This is implied - we cannot compare content without going detailed
        detailed = true;
      }

      // Perform commands
      if (subCommand == SubCommand.ADD) {
        // The add command is dangerous - don't default on the connection side
        if (args.length == 1) {
          sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
          return false;
        }
       
        executeAddCommand(sender, types, detailed, compare);
      } else if (subCommand == SubCommand.REMOVE) {
        executeRemoveCommand(sender, types);
      } else if (subCommand == SubCommand.NAMES) {
        executeNamesCommand(sender, types);
      }
     
    } catch (NumberFormatException e) {
      sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
    } catch (IllegalArgumentException e) {
      sendMessageSilently(sender, ChatColor.RED + e.getMessage());
    }
   
    return true;
  }
 
  private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
    packetTypes.addAll(addition);
   
    // Also mark these types as "detailed"
    if (detailed) {
      extendedTypes.addAll(addition);
    }
    // Whether or not to compare the packet with the initial state
    if (compare) {
      compareTypes.addAll(addition);
    }
   
    updatePacketListener();
    sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
  }
 
  private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
    packetTypes.removeAll(removal);
    extendedTypes.removeAll(removal);
    compareTypes.removeAll(removal);
    updatePacketListener();
    sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
  }
 
  private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
    List<String> messages = new ArrayList<String>();
   
    // Print the equivalent name of every given ID
    for (PacketType type : types) {
      messages.add(ChatColor.YELLOW + type.toString());
    }
   
    if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
      // Divide the messages into chuncks
      pagedMessage.put(sender, messages);
      printPage(sender, 1);
     
    } else {
      // Just print the damn thing
      for (String message : messages) {
        sendMessageSilently(sender, message);
      }
    }
  }

  /**
   * Retrieve whitelist information about a given listener.
   * @param listener - the given listener.
   * @return Whitelist information.
   */
  private String getWhitelistInfo(PacketListener listener) {
    boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
    boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
   
    if (!sendingEmpty && !receivingEmpty)
      return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
    else if (!sendingEmpty)
      return listener.getSendingWhitelist().toString();
    else if (!receivingEmpty)
      return listener.getReceivingWhitelist().toString();
    else
      return "[None]";
  }
   
  private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
    Set<PacketType> result = Sets.newHashSet();
   
    for (PacketType type : types) {
      if (type.getSender() == sender) {
        result.add(type);
      }
    }
    return result;
  }
 
  public PacketListener createPacketListener(Set<PacketType> type) {
    final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
        types(filterTypes(type, Sender.SERVER)).
        gamePhaseBoth().
        monitor().
        build();
   
    final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
        types(filterTypes(type, Sender.CLIENT)).
        monitor().
        build();
   
    return new PacketListener() {
      @Override
      public void onPacketSending(PacketEvent event) {
        if (filter.filterEvent(event)) {
          printInformation(event);
        }
      }
     
      @Override
      public void onPacketReceiving(PacketEvent event) {
        if (filter.filterEvent(event)) {
          printInformation(event);
        }
      }
     
      private void printInformation(PacketEvent event) {
        String verb = event.isServerPacket() ? "Sent" : "Received";
        String format = event.isServerPacket() ?
            "%s %s to %s" :
            "%s %s from %s";
       
        String shortDescription = String.format(format,
            event.isCancelled() ? "Cancelled" : verb,
            event.getPacketType(),
            event.getPlayer().getName()
        );
       
        // Detailed will print the packet's content too
        if (extendedTypes.contains(event.getPacketType())) {
          try {
            String original = originalPackets.remove(event);
           
            // Also print original
            if (original != null) {
              logger.info("Initial packet:\n" + original + " -> ");
            }
           
            logger.info(shortDescription + ":\n" + getPacketDescription(
              event.getPacket())
            );
          } catch (IllegalAccessException e) {
            logger.log(Level.WARNING, "Unable to use reflection.", e);
          }
        } else {
          logger.info(shortDescription + ".");
        }
      }
     
      @Override
      public ListeningWhitelist getSendingWhitelist() {
        return serverList;
      }
     
      @Override
      public ListeningWhitelist getReceivingWhitelist() {
        return clientList;
      }
     
      @Override
      public Plugin getPlugin() {
        return plugin;
      }
    };
  }
 
  public PacketListener createCompareListener(Set<PacketType> type) {
    final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
        types(filterTypes(type, Sender.SERVER)).
        gamePhaseBoth().
        lowest().
        build();
   
    final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
        types(filterTypes(type, Sender.CLIENT)).
        lowest().
        build();
   
    return new PacketListener() {
      @Override
      public void onPacketSending(PacketEvent event) {
        savePacketState(event);
      }
     
      @Override
      public void onPacketReceiving(PacketEvent event) {
        savePacketState(event);
      }
     
      /**
       * Save the original value.
       * @param event - the event with the packet to save.
       */
      private void savePacketState(PacketEvent event) {
        try {
          originalPackets.put(event, getPacketDescription(event.getPacket()));
        } catch (IllegalAccessException e) {
          throw new RuntimeException("Cannot read packet.", e);
        }
      }
     
      @Override
      public ListeningWhitelist getSendingWhitelist() {
        return serverList;
      }
     
      @Override
      public ListeningWhitelist getReceivingWhitelist() {
        return clientList;
      }
     
      @Override
      public Plugin getPlugin() {
        return plugin;
      }
    };
  }
 
  /**
   * Retrieve a detailed string representation of the given packet.
   * @param packetContainer - the packet to describe.
   * @return The detailed description.
   * @throws IllegalAccessException An error occured.
   */
  public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
    Object packet = packetContainer.getHandle();
    Class<?> clazz = packet.getClass();
   
    // Get the first Minecraft super class
    while (clazz != null && clazz != Object.class &&
        (!MinecraftReflection.isMinecraftClass(clazz) ||
         Factory.class.isAssignableFrom(clazz))) {
      clazz = clazz.getSuperclass();
    }
   
    return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
      @Override
      public boolean print(StringBuilder output, Object value) {
        // Special case
        if (value instanceof byte[]) {
          byte[] data = (byte[]) value;
         
          if (data.length > HEX_DUMP_THRESHOLD) {
            output.append("[");
            HexDumper.defaultDumper().appendTo(output, data);
            output.append("]");
            return true;
          }
        } else if (value != null) {
          EquivalentConverter<Object> converter = findConverter(value.getClass());

          if (converter != null) {
            output.append(converter.getSpecific(value));
            return true;
          }
        }
        return false;
      }
    });
  }
 
  /**
   * Retrieve the closest equivalent converter to a specific class.
   * @param clazz - the class.
   * @return The closest converter, or NULL if not found,
   */
  private EquivalentConverter<Object> findConverter(Class<?> clazz) {
    Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
   
    while (clazz != null) {
      EquivalentConverter<Object> result = converters.get(clazz);
     
      if (result != null)
        return result;
      else
        clazz = clazz.getSuperclass();
    }
    return null;
  }
   
  public PacketListener updatePacketListener() {
    if (listener != null) {
      manager.removePacketListener(listener);
    }
    if (compareListener != null) {
      manager.removePacketListener(compareListener);
    }
   
    // Register the new listeners
    listener = createPacketListener(packetTypes.values());
    compareListener = createCompareListener(compareTypes.values());
    manager.addPacketListener(listener);
    manager.addPacketListener(compareListener);
    return listener;
  }
 
  private SubCommand parseCommand(Deque<String> arguments) {
    String text = arguments.poll().toLowerCase();
   
    // Parse this too
    if ("add".startsWith(text))
      return SubCommand.ADD;
    else if ("remove".startsWith(text))
      return SubCommand.REMOVE;
    else if ("names".startsWith(text))
      return SubCommand.NAMES;
    else if ("page".startsWith(text))
      return SubCommand.PAGE;
    else
      throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
  }
}
TOP

Related Classes of com.comphenix.protocol.CommandPacket

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.