465 lines
16 KiB
Java
465 lines
16 KiB
Java
package com.comphenix.protocol.utility;
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.Unpooled;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.DataInput;
|
|
import java.io.DataInputStream;
|
|
import java.io.DataOutput;
|
|
import java.io.DataOutputStream;
|
|
import java.io.IOException;
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
import org.apache.commons.lang.Validate;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
|
|
|
|
import com.comphenix.protocol.injector.netty.NettyByteBufAdapter;
|
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
|
import com.comphenix.protocol.reflect.accessors.Accessors;
|
|
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
|
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
|
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
|
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
|
import com.comphenix.protocol.wrappers.nbt.NbtType;
|
|
import com.google.common.base.Preconditions;
|
|
|
|
/**
|
|
* Utility methods for reading and writing Minecraft objects to streams.
|
|
*
|
|
* @author Kristian
|
|
*/
|
|
public class StreamSerializer {
|
|
private static final StreamSerializer DEFAULT = new StreamSerializer();
|
|
|
|
// Cached methods
|
|
private static MethodAccessor READ_ITEM_METHOD;
|
|
private static MethodAccessor WRITE_ITEM_METHOD;
|
|
|
|
private static MethodAccessor READ_NBT_METHOD;
|
|
private static MethodAccessor WRITE_NBT_METHOD;
|
|
|
|
private static MethodAccessor READ_STRING_METHOD;
|
|
private static MethodAccessor WRITE_STRING_METHOD;
|
|
|
|
/**
|
|
* Retrieve a default stream serializer.
|
|
* @return A serializer.
|
|
*/
|
|
public static StreamSerializer getDefault() {
|
|
return DEFAULT;
|
|
}
|
|
|
|
/**
|
|
* Write a variable integer to an output stream.
|
|
* @param destination - the destination.
|
|
* @param value - the value to write.
|
|
* @throws IOException The destination stream threw an exception.
|
|
*/
|
|
public void serializeVarInt(@Nonnull DataOutputStream destination, int value) throws IOException {
|
|
Preconditions.checkNotNull(destination, "source cannot be NULL");
|
|
|
|
while ((value & 0xFFFFFF80) != 0) {
|
|
destination.writeByte(value & 0x7F | 0x80);
|
|
value >>>= 7;
|
|
}
|
|
destination.writeByte(value);
|
|
}
|
|
|
|
/**
|
|
* Read a variable integer from an input stream.
|
|
* @param source - the source.
|
|
* @return The integer.
|
|
* @throws IOException The source stream threw an exception.
|
|
*/
|
|
public int deserializeVarInt(@Nonnull DataInputStream source) throws IOException {
|
|
Preconditions.checkNotNull(source, "source cannot be NULL");
|
|
|
|
int result = 0;
|
|
int length = 0;
|
|
byte currentByte;
|
|
do {
|
|
currentByte = source.readByte();
|
|
result |= (currentByte & 0x7F) << length++ * 7;
|
|
if (length > 5)
|
|
throw new RuntimeException("VarInt too big");
|
|
} while ((currentByte & 0x80) == 0x80);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Write or serialize a NBT compound to the given output stream.
|
|
* <p>
|
|
* Note: An NBT compound can be written to a stream even if it's NULL.
|
|
*
|
|
* @param output - the target output stream.
|
|
* @param compound - the NBT compound to be serialized, or NULL to represent nothing.
|
|
* @throws IOException If the operation fails due to reflection problems.
|
|
*/
|
|
public void serializeCompound(@Nonnull DataOutputStream output, NbtCompound compound) throws IOException {
|
|
if (output == null)
|
|
throw new IllegalArgumentException("Output stream cannot be NULL.");
|
|
|
|
// Get the NMS version of the compound
|
|
Object handle = compound != null ? NbtFactory.fromBase(compound).getHandle() : null;
|
|
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
if (WRITE_NBT_METHOD == null) {
|
|
WRITE_NBT_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("writeNbtCompound", /* a */
|
|
MinecraftReflection.getNBTCompoundClass())
|
|
);
|
|
}
|
|
|
|
ByteBuf buf = NettyByteBufAdapter.packetWriter(output);
|
|
buf.writeByte(NbtType.TAG_COMPOUND.getRawID());
|
|
|
|
WRITE_NBT_METHOD.invoke(buf, handle);
|
|
} else {
|
|
if (WRITE_NBT_METHOD == null) {
|
|
WRITE_NBT_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(2).
|
|
parameterDerivedOf(MinecraftReflection.getNBTBaseClass(), 0).
|
|
parameterDerivedOf(DataOutput.class, 1).
|
|
returnTypeVoid().
|
|
build())
|
|
);
|
|
}
|
|
|
|
WRITE_NBT_METHOD.invoke(null, handle, output);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read or deserialize an NBT compound from a input stream.
|
|
* @param input - the target input stream.
|
|
* @return The resulting compound, or NULL.
|
|
* @throws IOException If the operation failed due to reflection or corrupt data.
|
|
*/
|
|
public NbtCompound deserializeCompound(@Nonnull DataInputStream input) throws IOException {
|
|
if (input == null)
|
|
throw new IllegalArgumentException("Input stream cannot be NULL.");
|
|
Object nmsCompound = null;
|
|
|
|
// Invoke the correct method
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
if (READ_NBT_METHOD == null) {
|
|
READ_NBT_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("readNbtCompound", /* h */
|
|
MinecraftReflection.getNBTCompoundClass(), new Class<?>[0])
|
|
);
|
|
}
|
|
|
|
ByteBuf buf = NettyByteBufAdapter.packetReader(input);
|
|
nmsCompound = READ_NBT_METHOD.invoke(buf);
|
|
} else {
|
|
if (READ_NBT_METHOD == null) {
|
|
READ_NBT_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(1).
|
|
parameterDerivedOf(DataInput.class).
|
|
returnDerivedOf(MinecraftReflection.getNBTBaseClass()).
|
|
build())
|
|
);
|
|
}
|
|
|
|
try {
|
|
nmsCompound = READ_NBT_METHOD.invoke(null, input);
|
|
} catch (Exception e) {
|
|
throw new IOException("Cannot read item stack.", e);
|
|
}
|
|
}
|
|
|
|
// Convert back to an NBT Compound
|
|
if (nmsCompound != null)
|
|
return NbtFactory.fromNMSCompound(nmsCompound);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Serialize a string using the standard Minecraft UTF-16 encoding.
|
|
* <p>
|
|
* Note that strings cannot exceed 32767 characters, regardless if maximum lenght.
|
|
* @param output - the output stream.
|
|
* @param text - the string to serialize.
|
|
* @throws IOException If the data in the string cannot be written.
|
|
*/
|
|
public void serializeString(@Nonnull DataOutputStream output, String text) throws IOException {
|
|
if (output == null)
|
|
throw new IllegalArgumentException("output stream cannot be NULL.");
|
|
if (text == null)
|
|
throw new IllegalArgumentException("text cannot be NULL.");
|
|
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
if (WRITE_STRING_METHOD == null) {
|
|
WRITE_STRING_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("writeString", /* a */
|
|
String.class)
|
|
);
|
|
}
|
|
|
|
ByteBuf buf = NettyByteBufAdapter.packetWriter(output);
|
|
WRITE_STRING_METHOD.invoke(buf, text);
|
|
} else {
|
|
if (WRITE_STRING_METHOD == null) {
|
|
WRITE_STRING_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(2).
|
|
parameterExactType(String.class, 0).
|
|
parameterDerivedOf(DataOutput.class, 1).
|
|
returnTypeVoid().
|
|
build())
|
|
);
|
|
}
|
|
|
|
WRITE_STRING_METHOD.invoke(null, text, output);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deserialize a string using the standard Minecraft UTF-16 encoding.
|
|
* <p>
|
|
* Note that strings cannot exceed 32767 characters, regardless if maximum length.
|
|
* @param input - the input stream.
|
|
* @param maximumLength - the maximum length of the string.
|
|
* @return The deserialized string.
|
|
* @throws IOException If deserializing fails
|
|
*/
|
|
public String deserializeString(@Nonnull DataInputStream input, int maximumLength) throws IOException {
|
|
if (input == null)
|
|
throw new IllegalArgumentException("Input stream cannot be NULL.");
|
|
if (maximumLength > 32767)
|
|
throw new IllegalArgumentException("Maximum length cannot exceed 32767 characters.");
|
|
if (maximumLength < 0)
|
|
throw new IllegalArgumentException("Maximum length cannot be negative.");
|
|
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
if (READ_STRING_METHOD == null) {
|
|
READ_STRING_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("readString", /* c */
|
|
String.class, new Class<?>[] { int.class })
|
|
);
|
|
}
|
|
|
|
ByteBuf buf = NettyByteBufAdapter.packetReader(input);
|
|
return (String) READ_STRING_METHOD.invoke(buf, maximumLength);
|
|
} else {
|
|
if (READ_STRING_METHOD == null) {
|
|
READ_STRING_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(2).
|
|
parameterDerivedOf(DataInput.class, 0).
|
|
parameterExactType(int.class, 1).
|
|
returnTypeExact(String.class).
|
|
build())
|
|
);
|
|
}
|
|
|
|
return (String) READ_STRING_METHOD.invoke(null, input, maximumLength);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serialize an item stack as a base-64 encoded string.
|
|
* <p>
|
|
* Note: An ItemStack can be written to the serialized text even if it's NULL.
|
|
*
|
|
* @param stack - the item stack to serialize, or NULL to represent air/nothing.
|
|
* @return A base-64 representation of the given item stack.
|
|
* @throws IOException If the operation fails due to reflection problems.
|
|
*/
|
|
public String serializeItemStack(ItemStack stack) throws IOException {
|
|
return Base64Coder.encodeLines(serializeItemStackToByteArray(stack));
|
|
}
|
|
|
|
/**
|
|
* Deserialize an item stack from a base-64 encoded string.
|
|
* @param input - base-64 encoded string.
|
|
* @return A deserialized item stack, or NULL if the serialized ItemStack was also NULL.
|
|
* @throws IOException If the operation failed due to reflection or corrupt data.
|
|
*/
|
|
public ItemStack deserializeItemStack(String input) throws IOException {
|
|
Validate.notNull(input, "input cannot be null!");
|
|
|
|
return deserializeItemStackFromByteArray(Base64Coder.decodeLines(input));
|
|
}
|
|
|
|
/**
|
|
* Serialize an item stack as byte array.
|
|
* <p>
|
|
* Note: An ItemStack can be written to the serialized text even if it's NULL.
|
|
*
|
|
* @param stack - the item stack to serialize, or NULL to represent air/nothing.
|
|
* @return A binary representation of the given item stack.
|
|
* @throws IOException If the operation fails due to reflection problems.
|
|
*/
|
|
public byte[] serializeItemStackToByteArray(ItemStack stack) throws IOException {
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
DataOutputStream output = new DataOutputStream(outputStream);
|
|
|
|
serializeItemStack(output, stack);
|
|
|
|
return outputStream.toByteArray();
|
|
}
|
|
|
|
/**
|
|
* Deserialize an item stack from a byte array.
|
|
* @param input - serialized item.
|
|
* @return A deserialized item stack, or NULL if the serialized ItemStack was also NULL.
|
|
* @throws IOException If the operation failed due to reflection or corrupt data.
|
|
*/
|
|
public ItemStack deserializeItemStackFromByteArray(byte[] input) throws IOException {
|
|
Validate.notNull(input, "input cannot be null!");
|
|
|
|
Object nmsItem;
|
|
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
ByteBuf buf = Unpooled.copiedBuffer(input);
|
|
Object serializer = MinecraftReflection.getPacketDataSerializer(buf);
|
|
|
|
if (READ_ITEM_METHOD == null) {
|
|
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("readItemStack", // i(ItemStack)
|
|
MinecraftReflection.getItemStackClass(), new Class<?>[0]));
|
|
}
|
|
|
|
nmsItem = READ_ITEM_METHOD.invoke(serializer);
|
|
} else {
|
|
if (READ_ITEM_METHOD == null) {
|
|
READ_ITEM_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(1).
|
|
parameterDerivedOf(DataInput.class).
|
|
returnDerivedOf(MinecraftReflection.getItemStackClass()).
|
|
build())
|
|
);
|
|
}
|
|
|
|
ByteArrayInputStream byteStream = new ByteArrayInputStream(input);
|
|
DataInputStream inputStream = new DataInputStream(byteStream);
|
|
|
|
nmsItem = READ_ITEM_METHOD.invoke(null, inputStream);
|
|
}
|
|
|
|
return nmsItem != null ? MinecraftReflection.getBukkitItemStack(nmsItem) : null;
|
|
}
|
|
|
|
/**
|
|
* Write or serialize an item stack to the given output stream.
|
|
* <p>
|
|
* To supply a byte array, wrap it in a {@link java.io.ByteArrayOutputStream ByteArrayOutputStream}
|
|
* and {@link java.io.DataOutputStream DataOutputStream}.
|
|
* <p>
|
|
* Note: An ItemStack can be written to a stream even if it's NULL.
|
|
*
|
|
* @param output - the target output stream.
|
|
* @param stack - the item stack that will be written, or NULL to represent air/nothing.
|
|
* @throws IOException If the operation fails due to reflection problems.
|
|
*/
|
|
public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException {
|
|
Validate.notNull(output, "output cannot be null!");
|
|
|
|
// Get the NMS version of the ItemStack
|
|
Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
|
|
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
if (WRITE_ITEM_METHOD == null) {
|
|
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("writeStack", /* a */
|
|
MinecraftReflection.getItemStackClass())
|
|
);
|
|
}
|
|
|
|
ByteBuf buf = Unpooled.buffer();
|
|
Object serializer = MinecraftReflection.getPacketDataSerializer(buf);
|
|
|
|
WRITE_ITEM_METHOD.invoke(serializer, nmsItem);
|
|
|
|
output.write(buf.array());
|
|
} else {
|
|
if (WRITE_ITEM_METHOD == null)
|
|
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(2).
|
|
parameterDerivedOf(MinecraftReflection.getItemStackClass(), 0).
|
|
parameterDerivedOf(DataOutput.class, 1).
|
|
build())
|
|
);
|
|
|
|
WRITE_ITEM_METHOD.invoke(null, nmsItem, output);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read or deserialize an item stack from an underlying input stream.
|
|
* <p>
|
|
* To supply a byte array, wrap it in a {@link java.io.ByteArrayInputStream ByteArrayInputStream}
|
|
* and {@link java.io.DataInputStream DataInputStream}.
|
|
*
|
|
* @param input - the target input stream.
|
|
* @return The resulting item stack, or NULL if the serialized item stack was NULL.
|
|
* @throws IOException If the operation failed due to reflection or corrupt data.
|
|
* @deprecated This is a pretty hacky solution for backwards compatibility. See {@link #deserializeItemStack(DataInputStream)}
|
|
*/
|
|
@Deprecated
|
|
public ItemStack deserializeItemStack(DataInputStream input) throws IOException {
|
|
Validate.notNull(input, "input cannot be null!");
|
|
Object nmsItem;
|
|
|
|
if (MinecraftReflection.isUsingNetty()) {
|
|
if (READ_ITEM_METHOD == null) {
|
|
READ_ITEM_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true).
|
|
getMethodByParameters("readItemStack", /* i */
|
|
MinecraftReflection.getItemStackClass(), new Class<?>[0])
|
|
);
|
|
}
|
|
|
|
byte[] bytes = new byte[8192];
|
|
input.read(bytes);
|
|
|
|
ByteBuf buf = Unpooled.copiedBuffer(bytes);
|
|
Object serializer = MinecraftReflection.getPacketDataSerializer(buf);
|
|
|
|
nmsItem = READ_ITEM_METHOD.invoke(serializer);
|
|
} else {
|
|
if (READ_ITEM_METHOD == null) {
|
|
READ_ITEM_METHOD = Accessors.getMethodAccessor(
|
|
FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod(
|
|
FuzzyMethodContract.newBuilder().
|
|
parameterCount(1).
|
|
parameterDerivedOf(DataInput.class).
|
|
returnDerivedOf(MinecraftReflection.getItemStackClass()).
|
|
build())
|
|
);
|
|
}
|
|
|
|
nmsItem = READ_ITEM_METHOD.invoke(null, input);
|
|
}
|
|
|
|
// Convert back to a Bukkit item stack
|
|
if (nmsItem != null)
|
|
return MinecraftReflection.getBukkitItemStack(nmsItem);
|
|
else
|
|
return null;
|
|
}
|
|
}
|