mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-12-28 03:57:33 +01:00
Print a hex dump in the case of very large arrays.
This commit is contained in:
parent
d8e8a88076
commit
2244f986bb
@ -49,6 +49,7 @@ 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;
|
||||
@ -76,6 +77,11 @@ class CommandPacket extends CommandBase {
|
||||
*/
|
||||
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;
|
||||
@ -465,9 +471,19 @@ class CommandPacket extends CommandBase {
|
||||
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
|
||||
@Override
|
||||
public boolean print(StringBuilder output, Object value) {
|
||||
if (value != null) {
|
||||
EquivalentConverter<Object> converter = findConverter(value.getClass());
|
||||
// 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;
|
||||
|
@ -0,0 +1,234 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Represents a class for printing hexadecimal dumps.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class HexDumper {
|
||||
private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
// Default values
|
||||
private int positionLength = 6;
|
||||
private char[] positionSuffix = ": ".toCharArray();
|
||||
private char[] delimiter = " ".toCharArray();
|
||||
private int groupLength = 2;
|
||||
private int groupCount = 24;
|
||||
private char[] lineDelimiter = "\n".toCharArray();
|
||||
|
||||
/**
|
||||
* Retrieve a hex dumper tuned for lines of 80 characters:
|
||||
* <table border="1">
|
||||
* <tr>
|
||||
* <th>Property</th>
|
||||
* <th>Value</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Position Length</td>
|
||||
* <td>6</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Position Suffix</td>
|
||||
* <td>": "</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Delimiter</td>
|
||||
* <td>" "</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Group Length</td>
|
||||
* <td>2</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Group Count</td>
|
||||
* <td>24</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>Line Delimiter</td>
|
||||
* <td>"\n"</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
* @return The default dumper.
|
||||
*/
|
||||
public static HexDumper defaultDumper() {
|
||||
return new HexDumper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delimiter between each new line.
|
||||
* @param lineDelimiter - the line delimiter.
|
||||
* @return This instance, for chaining.
|
||||
*/
|
||||
public HexDumper lineDelimiter(String lineDelimiter) {
|
||||
this.lineDelimiter = Preconditions.checkNotNull(lineDelimiter, "lineDelimiter cannot be NULL").toCharArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of hex characters in the position.
|
||||
* @param positionLength - number of characters, from 0 to 8.
|
||||
* @return This instance, for chaining.
|
||||
*/
|
||||
public HexDumper positionLength(int positionLength) {
|
||||
if (positionLength < 0)
|
||||
throw new IllegalArgumentException("positionLength cannot be less than zero.");
|
||||
if (positionLength > 8)
|
||||
throw new IllegalArgumentException("positionLength cannot be greater than eight.");
|
||||
this.positionLength = positionLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a suffix to write after each position.
|
||||
* @param positionSuffix - non-null string to write after the positions.
|
||||
* @return This instance, for chaining.
|
||||
*/
|
||||
public HexDumper positionSuffix(String positionSuffix) {
|
||||
this.positionSuffix = Preconditions.checkNotNull(positionSuffix, "positionSuffix cannot be NULL").toCharArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delimiter to write in between each group of hexadecimal characters.
|
||||
* @param delimiter - non-null string to write between each group.
|
||||
* @return This instance, for chaining.
|
||||
*/
|
||||
public HexDumper delimiter(String delimiter) {
|
||||
this.delimiter = Preconditions.checkNotNull(delimiter, "delimiter cannot be NULL").toCharArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of each group in hexadecimal characters.
|
||||
* @param groupLength - the length of each group.
|
||||
* @return This instance, for chaining.
|
||||
*/
|
||||
public HexDumper groupLength(int groupLength) {
|
||||
if (groupLength < 1)
|
||||
throw new IllegalArgumentException("groupLength cannot be less than one.");
|
||||
this.groupLength = groupLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of groups in each line. This is limited by the supply of bytes in the byte array.
|
||||
* <p>
|
||||
* Use {@link Integer#MAX_VALUE} to effectively disable lines.
|
||||
* @param groupLength - the length of each group.
|
||||
* @return This instance, for chaining.
|
||||
*/
|
||||
public HexDumper groupCount(int groupCount) {
|
||||
if (groupCount < 1)
|
||||
throw new IllegalArgumentException("groupCount cannot be less than one.");
|
||||
this.groupCount = groupCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the hex dump of the given data to the string builder, using the current formatting settings.
|
||||
* @param appendable - appendable source.
|
||||
* @param data - the data to dump.
|
||||
* @param start - the starting index of the data.
|
||||
* @param length - the number of bytes to dump.
|
||||
* @throws IOException Any underlying IO exception.
|
||||
*/
|
||||
public void appendTo(Appendable appendable, byte[] data) throws IOException {
|
||||
appendTo(appendable, data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the hex dump of the given data to the string builder, using the current formatting settings.
|
||||
* @param appendable - appendable source.
|
||||
* @param data - the data to dump.
|
||||
* @param start - the starting index of the data.
|
||||
* @param length - the number of bytes to dump.
|
||||
* @throws IOException Any underlying IO exception.
|
||||
*/
|
||||
public void appendTo(Appendable appendable, byte[] data, int start, int length) throws IOException {
|
||||
StringBuilder output = new StringBuilder();
|
||||
appendTo(output, data, start, length);
|
||||
appendable.append(output.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the hex dump of the given data to the string builder, using the current formatting settings.
|
||||
* @param builder - the builder.
|
||||
* @param data - the data to dump.
|
||||
* @param start - the starting index of the data.
|
||||
* @param length - the number of bytes to dump.
|
||||
*/
|
||||
public void appendTo(StringBuilder builder, byte[] data) {
|
||||
appendTo(builder, data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the hex dump of the given data to the string builder, using the current formatting settings.
|
||||
* @param builder - the builder.
|
||||
* @param data - the data to dump.
|
||||
* @param start - the starting index of the data.
|
||||
* @param length - the number of bytes to dump.
|
||||
*/
|
||||
public void appendTo(StringBuilder builder, byte[] data, int start, int length) {
|
||||
// Positions
|
||||
int dataIndex = start;
|
||||
int dataEnd = start + length;
|
||||
int groupCounter = 0;
|
||||
int currentGroupLength = 0;
|
||||
|
||||
// Current niblet in the byte
|
||||
int value = 0;
|
||||
boolean highNiblet = true;
|
||||
|
||||
while (dataIndex < dataEnd || !highNiblet) {
|
||||
// Prefix
|
||||
if (groupCounter == 0 && currentGroupLength == 0) {
|
||||
// Print the current dataIndex (print in reverse)
|
||||
for (int i = positionLength - 1; i >= 0; i--) {
|
||||
builder.append(HEX_DIGITS[(dataIndex >>> (4 * i)) & 0xF]);
|
||||
}
|
||||
builder.append(positionSuffix);
|
||||
}
|
||||
|
||||
// Print niblet
|
||||
if (highNiblet) {
|
||||
value = data[dataIndex++] & 0xFF;
|
||||
builder.append(HEX_DIGITS[value >>> 4]);
|
||||
} else {
|
||||
builder.append(HEX_DIGITS[value & 0x0F]);
|
||||
}
|
||||
highNiblet = !highNiblet;
|
||||
currentGroupLength++;
|
||||
|
||||
// See if we're dealing with the last element
|
||||
if (currentGroupLength >= groupLength) {
|
||||
currentGroupLength = 0;
|
||||
|
||||
// See if we've reached the last element in the line
|
||||
if (++groupCounter >= groupCount) {
|
||||
builder.append(lineDelimiter);
|
||||
groupCounter = 0;
|
||||
} else {
|
||||
// Write delimiter
|
||||
builder.append(delimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the length of each line.
|
||||
* @param byteCount - the maximum number of bytes
|
||||
* @return The lenght of the final line.
|
||||
*/
|
||||
public int getLineLength(int byteCount) {
|
||||
int constant = positionLength + positionSuffix.length + lineDelimiter.length;
|
||||
int groups = Math.min((2 * byteCount) / groupLength, groupCount);
|
||||
|
||||
// Total expected length of each line
|
||||
return constant + delimiter.length * (groups - 1) + groupLength * groups;
|
||||
}
|
||||
}
|
@ -627,6 +627,18 @@ public class MinecraftReflection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the World (NMS) class.
|
||||
* @return The world class.
|
||||
*/
|
||||
public static Class<?> getNmsWorldClass() {
|
||||
try {
|
||||
return getMinecraftClass("World");
|
||||
} catch (RuntimeException e) {
|
||||
return setMinecraftClass("World", getWorldServerClass().getSuperclass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback on the return value of a named method in order to get a NMS class.
|
||||
* @param nmsClass - the expected name of the Minecraft class.
|
||||
@ -1675,6 +1687,14 @@ public class MinecraftReflection {
|
||||
return getCraftBukkitClass("entity.CraftPlayer");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the CraftWorld class.
|
||||
* @return The CraftWorld class.
|
||||
*/
|
||||
public static Class<?> getCraftWorldClass() {
|
||||
return getCraftBukkitClass("CraftWorld");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the CraftEntity class.
|
||||
* @return CraftEntity class.
|
||||
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
@ -39,6 +40,7 @@ import org.bukkit.potion.PotionEffectType;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.injector.PacketConstructor;
|
||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
@ -46,6 +48,7 @@ import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
@ -86,6 +89,9 @@ public class BukkitConverters {
|
||||
private static volatile Constructor<?> mobEffectConstructor;
|
||||
private static volatile StructureModifier<Object> mobEffectModifier;
|
||||
|
||||
// Used for fetching the CraftWorld associated with a WorldServer
|
||||
private static FieldAccessor craftWorldField;
|
||||
|
||||
static {
|
||||
try {
|
||||
MinecraftReflection.getWorldTypeClass();
|
||||
@ -98,6 +104,15 @@ public class BukkitConverters {
|
||||
hasAttributeSnapshot = true;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
// Fetch CraftWorld field
|
||||
try {
|
||||
craftWorldField = Accessors.getFieldAccessor(
|
||||
MinecraftReflection.getNmsWorldClass(),
|
||||
MinecraftReflection.getCraftWorldClass(), true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -711,6 +726,29 @@ public class BukkitConverters {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert between a NMS World and a Bukkit world.
|
||||
* @return The potion effect converter.
|
||||
*/
|
||||
public static EquivalentConverter<World> getWorldConverter() {
|
||||
return new IgnoreNullConverter<World>() {
|
||||
@Override
|
||||
protected Object getGenericValue(Class<?> genericType, World specific) {
|
||||
return BukkitUnwrapper.getInstance().unwrapItem(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected World getSpecificValue(Object generic) {
|
||||
return (World) craftWorldField.get(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<World> getSpecificType() {
|
||||
return World.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect.
|
||||
* @return The potion effect converter.
|
||||
@ -827,8 +865,9 @@ public class BukkitConverters {
|
||||
put(NbtBase.class, (EquivalentConverter) getNbtConverter()).
|
||||
put(NbtCompound.class, (EquivalentConverter) getNbtConverter()).
|
||||
put(WrappedWatchableObject.class, (EquivalentConverter) getWatchableObjectConverter()).
|
||||
put(PotionEffect.class, (EquivalentConverter) getPotionEffectConverter());
|
||||
|
||||
put(PotionEffect.class, (EquivalentConverter) getPotionEffectConverter()).
|
||||
put(World.class, (EquivalentConverter) getWorldConverter());
|
||||
|
||||
// Types added in 1.7.2
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
builder.put(Material.class, (EquivalentConverter) getBlockConverter());
|
||||
@ -866,7 +905,8 @@ public class BukkitConverters {
|
||||
put(MinecraftReflection.getNBTBaseClass(), (EquivalentConverter) getNbtConverter()).
|
||||
put(MinecraftReflection.getNBTCompoundClass(), (EquivalentConverter) getNbtConverter()).
|
||||
put(MinecraftReflection.getWatchableObjectClass(), (EquivalentConverter) getWatchableObjectConverter()).
|
||||
put(MinecraftReflection.getMobEffectClass(), (EquivalentConverter) getPotionEffectConverter());
|
||||
put(MinecraftReflection.getMobEffectClass(), (EquivalentConverter) getPotionEffectConverter()).
|
||||
put(MinecraftReflection.getNmsWorldClass(), (EquivalentConverter) getWorldConverter());
|
||||
|
||||
if (hasWorldType)
|
||||
builder.put(MinecraftReflection.getWorldTypeClass(), (EquivalentConverter) getWorldTypeConverter());
|
||||
|
Loading…
Reference in New Issue
Block a user