blob: b5c5b0c75197100483fcc8d0bc406e9a8f0a1605 [file] [log] [blame] [raw]
package protocolsupport.protocol.serializer;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_10_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_10_R1.potion.CraftPotionUtil;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.spigotmc.LimitStream;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.util.UUIDTypeAdapter;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import net.minecraft.server.v1_10_R1.GameProfileSerializer;
import net.minecraft.server.v1_10_R1.NBTCompressedStreamTools;
import net.minecraft.server.v1_10_R1.NBTReadLimiter;
import protocolsupport.api.ProtocolVersion;
import protocolsupport.api.chat.ChatAPI;
import protocolsupport.api.events.ItemStackWriteEvent;
import protocolsupport.protocol.legacyremapper.LegacyPotion;
import protocolsupport.protocol.typeremapper.id.IdRemapper;
import protocolsupport.protocol.typeskipper.id.IdSkipper;
import protocolsupport.protocol.typeskipper.id.SkippingTable;
import protocolsupport.protocol.utils.types.ItemStackWrapper;
import protocolsupport.protocol.utils.types.MerchantData;
import protocolsupport.protocol.utils.types.MerchantData.TradeOffer;
import protocolsupport.protocol.utils.types.NBTTagCompoundWrapper;
import protocolsupport.protocol.utils.types.NBTTagListWrapper;
import protocolsupport.protocol.utils.types.Position;
import protocolsupport.utils.ServerPlatformUtils;
import protocolsupport.utils.netty.Allocator;
import protocolsupport.utils.netty.ChannelUtils;
import protocolsupport.utils.netty.WrappingBuffer;
public class ProtocolSupportPacketDataSerializer extends WrappingBuffer {
protected ProtocolVersion version;
public ProtocolSupportPacketDataSerializer(ByteBuf buf, ProtocolVersion version) {
this.buf = buf;
this.version = version;
}
public ProtocolVersion getVersion() {
return this.version;
}
public int readVarInt() {
return ChannelUtils.readVarInt(this);
}
public void writeVarInt(int varint) {
ChannelUtils.writeVarInt(this, varint);
}
public long readVarLong() {
long varlong = 0L;
int length = 0;
byte part;
do {
part = this.readByte();
varlong |= (part & 0x7F) << (length++ * 7);
if (length > 10) {
throw new RuntimeException("VarLong too big");
}
} while ((part & 0x80) == 0x80);
return varlong;
}
public void writeVarLong(long varlong) {
while ((varlong & 0xFFFFFFFFFFFFFF80L) != 0x0L) {
this.writeByte((int) (varlong & 0x7FL) | 0x80);
varlong >>>= 7;
}
this.writeByte((int) varlong);
}
public <T extends Enum<T>> T readEnum(Class<T> clazz) {
return clazz.getEnumConstants()[readVarInt()];
}
public void writeEnum(Enum<?> e) {
writeVarInt(e.ordinal());
}
public UUID readUUID() {
return new UUID(readLong(), readLong());
}
public void writeUUID(UUID uuid) {
writeLong(uuid.getMostSignificantBits());
writeLong(uuid.getLeastSignificantBits());
}
public String readString() {
return readString(Short.MAX_VALUE);
}
public String readString(int limit) {
if (getVersion().isBeforeOrEq(ProtocolVersion.MINECRAFT_1_6_4)) {
int length = readUnsignedShort() * 2;
checkLimit(length, limit * 4);
return new String(ChannelUtils.toArray(readBytes(length)), StandardCharsets.UTF_16BE);
} else {
int length = readVarInt();
checkLimit(length, limit * 4);
return new String(ChannelUtils.toArray(readBytes(length)), StandardCharsets.UTF_8);
}
}
public void writeString(String string) {
if (getVersion().isBeforeOrEq(ProtocolVersion.MINECRAFT_1_6_4)) {
writeShort(string.length());
writeBytes(string.getBytes(StandardCharsets.UTF_16BE));
} else {
byte[] data = string.getBytes(StandardCharsets.UTF_8);
writeVarInt(data.length);
writeBytes(data);
}
}
public Position readPosition() {
return Position.fromLong(readLong());
}
public Position readLegacyPositionB() {
return new Position(readInt(), readUnsignedByte(), readInt());
}
public Position readLegacyPositionS() {
return new Position(readInt(), readShort(), readInt());
}
public Position readLegacyPositionI() {
return new Position(readInt(), readInt(), readInt());
}
public void writePosition(Position position) {
writeLong(position.asLong());
}
public void writeLegacyPositionB(Position position) {
writeInt(position.getX());
writeByte(position.getY());
writeInt(position.getZ());
}
public void writeLegacyPositionS(Position position) {
writeInt(position.getX());
writeShort(position.getY());
writeInt(position.getZ());
}
public void writeLegacyPositionI(Position position) {
writeInt(position.getX());
writeInt(position.getY());
writeInt(position.getZ());
}
public byte[] readByteArray() {
return readByteArray(readableBytes());
}
public byte[] readByteArray(int limit) {
int size = -1;
if (getVersion().isBeforeOrEq(ProtocolVersion.MINECRAFT_1_7_10)) {
size = readShort();
} else {
size = readVarInt();
}
checkLimit(size, limit);
byte[] array = new byte[size];
readBytes(array);
return array;
}
public void writeByteArray(ByteBuf data) {
if (getVersion().isBeforeOrEq(ProtocolVersion.MINECRAFT_1_7_10)) {
writeShort(data.readableBytes());
} else {
writeVarInt(data.readableBytes());
}
writeBytes(data);
}
public void writeByteArray(byte[] data) {
if (getVersion().isBeforeOrEq(ProtocolVersion.MINECRAFT_1_7_10)) {
writeShort(data.length);
} else {
writeVarInt(data.length);
}
writeBytes(data);
}
public ItemStackWrapper readItemStack() {
int type = readShort();
if (type >= 0) {
ItemStackWrapper itemstack = ItemStackWrapper.create(type);
itemstack.setTypeId(type);
itemstack.setAmount(readByte());
itemstack.setData(readShort());
itemstack.setTag(readTag());
return itemstack;
}
return ItemStackWrapper.createNull();
}
public void writeItemStack(ItemStackWrapper itemstack) {
if (itemstack.isNull()) {
writeShort(-1);
return;
}
itemstack = transformItemStack(itemstack);
writeShort(IdRemapper.ITEM.getTable(version).getRemap(itemstack.getTypeId()));
writeByte(itemstack.getAmount());
writeShort(itemstack.getData());
writeTag(itemstack.getTag());
}
public NBTTagCompoundWrapper readTag() {
try {
if (getVersion().isBefore(ProtocolVersion.MINECRAFT_1_8)) {
final short length = readShort();
if (length < 0) {
return NBTTagCompoundWrapper.createNull();
}
return readLegacyNBT(new ByteArrayInputStream(ChannelUtils.toArray(readBytes(length))), new NBTReadLimiter(2097152L));
} else {
markReaderIndex();
if (readByte() == 0) {
return NBTTagCompoundWrapper.createNull();
}
resetReaderIndex();
return NBTTagCompoundWrapper.wrap(NBTCompressedStreamTools.a(new DataInputStream(new ByteBufInputStream(this)), new NBTReadLimiter(2097152L)));
}
} catch (IOException e) {
throw new DecoderException(e);
}
}
public void writeTag(NBTTagCompoundWrapper tag) {
ByteBuf buffer = Allocator.allocateBuffer();
try {
if (getVersion().isBefore(ProtocolVersion.MINECRAFT_1_8)) {
if (tag.isNull()) {
writeShort(-1);
} else {
encodeLegacyNBT(tag, buffer);
writeShort(buffer.readableBytes());
writeBytes(buffer);
}
} else {
if (tag.isNull()) {
writeByte(0);
} else {
NBTCompressedStreamTools.a(tag.unwrap(), (DataOutput) new DataOutputStream(new ByteBufOutputStream(buffer)));
writeBytes(buffer);
}
}
} catch (Throwable ioexception) {
throw new EncoderException(ioexception);
} finally {
buffer.release();
}
}
public MerchantData readMerchantData() {
MerchantData merchdata = new MerchantData(readInt());
int count = readUnsignedByte();
for (int i = 0; i < count; i++) {
ItemStackWrapper itemstack1 = readItemStack();
ItemStackWrapper result = readItemStack();
ItemStackWrapper itemstack2 = ItemStackWrapper.createNull();
if (readBoolean()) {
itemstack2 = readItemStack();
}
boolean disabled = readBoolean();
int uses = 0;
int maxuses = 7;
if (getVersion().isAfterOrEq(ProtocolVersion.MINECRAFT_1_8)) {
uses = readInt();
maxuses = readInt();
}
merchdata.addOffer(new TradeOffer(itemstack1, itemstack2, result, disabled ? maxuses : uses, maxuses));
}
return merchdata;
}
public void writeMerchantData(MerchantData merchdata) {
writeInt(merchdata.getWindowId());
writeByte(merchdata.getOffers().size());
for (TradeOffer offer : merchdata.getOffers()) {
writeItemStack(offer.getItemStack1());
writeItemStack(offer.getResult());
writeBoolean(offer.hasItemStack2());
if (offer.hasItemStack2()) {
writeItemStack(offer.getItemStack2());
}
writeBoolean(offer.isDisabled());
if (getVersion().isAfterOrEq(ProtocolVersion.MINECRAFT_1_8)) {
writeInt(offer.getUses());
writeInt(offer.getMaxUses());
}
}
}
protected void checkLimit(int size, int limit) {
if (size > limit) {
throw new DecoderException("Size " + size + " is bigger than allowed " + limit);
}
}
private ItemStackWrapper transformItemStack(ItemStackWrapper original) {
ItemStackWrapper itemstack = original.cloneItemStack();
Material item = itemstack.getType();
if (getVersion().isBefore(ProtocolVersion.MINECRAFT_1_9) && (item == Material.SKULL_ITEM) && (itemstack.getData() == 5)) {
itemstack.setData(3);
NBTTagCompoundWrapper wrapper = NBTTagCompoundWrapper.createEmpty();
wrapper.setCompound("SkullOwner", createDragonHeadSkullTag());
itemstack.setTag(wrapper);
}
NBTTagCompoundWrapper nbttagcompound = itemstack.getTag();
if (!nbttagcompound.isNull()) {
if (getVersion().isBefore(ProtocolVersion.MINECRAFT_1_8) && (item == Material.WRITTEN_BOOK)) {
if (nbttagcompound.hasKeyOfType("pages", NBTTagCompoundWrapper.TYPE_LIST)) {
NBTTagListWrapper pages = nbttagcompound.getList("pages", NBTTagCompoundWrapper.TYPE_STRING);
NBTTagListWrapper newpages = NBTTagListWrapper.create();
for (int i = 0; i < pages.size(); i++) {
newpages.addString(ChatAPI.fromJSON(pages.getString(i)).toLegacyText());
}
nbttagcompound.setList("pages", newpages);
}
}
if (getVersion().isBeforeOrEq(ProtocolVersion.MINECRAFT_1_7_5) && (item == Material.SKULL_ITEM)) {
transformSkull(nbttagcompound);
}
if (getVersion().isBefore(ProtocolVersion.MINECRAFT_1_9) && (item == Material.POTION || item == Material.SPLASH_POTION || item == Material.LINGERING_POTION)) {
String potion = nbttagcompound.getString("Potion");
if (!potion.isEmpty()) {
NBTTagListWrapper tagList = nbttagcompound.getList("CustomPotionEffects", NBTTagCompoundWrapper.TYPE_COMPOUND);
if (!tagList.isEmpty()) {
for (int i = 0; i < tagList.size(); i++) {
NBTTagCompoundWrapper nbtTag = tagList.getCompound(i);
int potionId = nbtTag.getNumber("Id");
PotionEffectType effectType = PotionEffectType.getById(potionId);
PotionType type = PotionType.getByEffect(effectType);
if (type != null) {
PotionData data = new PotionData(type, false, false);
potion = CraftPotionUtil.fromBukkit(data);
break;
}
}
}
Integer data = LegacyPotion.toLegacyId(potion, item != Material.POTION);
itemstack.setData(data);
String basicTypeName = LegacyPotion.getBasicTypeName(potion);
if (basicTypeName != null) {
itemstack.setDisplayName(basicTypeName);
}
}
}
if (getVersion().isBefore(ProtocolVersion.MINECRAFT_1_9) && (item == Material.MONSTER_EGG)) {
String entityId = nbttagcompound.getCompound("EntityTag").getString("id");
if (!entityId.isEmpty()) {
itemstack.setData(ServerPlatformUtils.getEntityIdByName(entityId));
}
}
if (nbttagcompound.hasKeyOfType("ench", 9)) {
nbttagcompound.setList("ench", filterEnchantList(nbttagcompound.getList("ench", 10)));
}
if (nbttagcompound.hasKeyOfType("stored-enchants", 9)) {
nbttagcompound.setList("stored-enchants", filterEnchantList(nbttagcompound.getList("stored-enchants", 10)));
}
}
if (ItemStackWriteEvent.getHandlerList().getRegisteredListeners().length > 0) {
ItemStackWriteEvent event = new InternalItemStackWriteEvent(getVersion(), original, itemstack);
Bukkit.getPluginManager().callEvent(event);
}
return itemstack;
}
private NBTTagListWrapper filterEnchantList(NBTTagListWrapper enchList) {
SkippingTable enchSkip = IdSkipper.ENCHANT.getTable(getVersion());
NBTTagListWrapper newList = NBTTagListWrapper.create();
for (int i = 0; i < enchList.size(); i++) {
NBTTagCompoundWrapper enchData = enchList.getCompound(i);
if (!enchSkip.shouldSkip(enchData.getNumber("id") & 0xFFFF)) {
newList.addCompound(enchData);
}
}
return enchList;
}
private static final GameProfile dragonHeadGameProfile = new GameProfile(UUIDTypeAdapter.fromString("d34aa2b831da4d269655e33c143f096c"), "EnderDragon");
static {
dragonHeadGameProfile.getProperties().put("textures", new Property(
"textures", "eyJ0aW1lc3RhbXAiOjE0NzE0Mzg3NTEzMjYsInByb2ZpbGVJZCI6ImQzNGFhMmI4MzFkYTRkMjY5NjU1ZTMzYzE0M2YwOTZjIiwicHJvZmlsZU5hbWUiOiJFbmRlckRyYWdvbiIsInNpZ25hdHVyZVJlcXVpcmVkIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWRlYzdhNGRlMDdiN2VkZWFjZTliOWI5OTY2NDRlMjFkNThjYmRhYzM5MmViZWIyNDdhY2NkMTg1MzRiNTQifX19",
"I/XY6is7j0RTGFwuVhc4PVuWUl5RRNToNUpw6jCF+V4TzXYzsBreiLQ6IM4NSJqlPXBmSlqibblDKVQL/4LnXt24/nRNVzChZv68HXB1quGxmYcSEvOLpoUk1cmXgqWJYHA68C+r1ytoJVerGWuIwuBzQZ9GpH0yb+qg7erFQgB24cbH6hh6uB6KYLwIYLQAg3TFjILv9sVJtC3FakXmtkCV3VfRUdjhigpfKP0JR3VhLVIWeW/7E+C4QCXnGlffc3Lz8PNahXtD4qRitVokId0t1qBcL8mM1qnZ/rHlNPLST61ycY9WNlRr6P83yDw2ha8QMiRH1vI5tvdXIwV7Dkn+JxfhOOeHtGunLBVe7ZEWBZfjePr/HqZGR6F7/cwZU32uH5MdTXQ+oKWUlb6HJOXxj7DfMr/uZWjrwjzmKpSDAinwvQM/8Sf96prufvcSfhZ0yopkumpnTjivgPxsJhwFXThIyFZ3ijTClMgm5NSzmB6hJ+HsBnVkDs7eyE5eI72/ES/6SksFezmBzDOqU31QbPA2mWoOYWdyZngtnf45oFnZ7NNpDW7ZKxY7FTsPEXoON/VX516KbnQ5OERI9YUpGzyKCjyMnf0L99gwgHpx5LpawdzIwk04sqFoy796BkGJf7xH6+h+AurMIenMt4on7T4FUE1ZaJvvaqexQME="
));
}
public static NBTTagCompoundWrapper createDragonHeadSkullTag() {
return NBTTagCompoundWrapper.wrap(GameProfileSerializer.serialize(NBTTagCompoundWrapper.createEmpty().unwrap(), dragonHeadGameProfile));
}
public static void transformSkull(NBTTagCompoundWrapper nbttagcompound) {
transformSkull(nbttagcompound, "SkullOwner", "SkullOwner");
transformSkull(nbttagcompound, "Owner", "ExtraType");
}
private static void transformSkull(NBTTagCompoundWrapper tag, String tagname, String newtagname) {
if (tag.hasKeyOfType(tagname, 10)) {
GameProfile gameprofile = GameProfileSerializer.deserialize(tag.getCompound(tagname).unwrap());
if (gameprofile.getName() != null) {
tag.setString(newtagname, gameprofile.getName());
} else {
tag.remove(tagname);
}
}
}
private static NBTTagCompoundWrapper readLegacyNBT(InputStream is, NBTReadLimiter nbtreadlimiter) {
try {
try (DataInputStream datainputstream = new DataInputStream(new BufferedInputStream(new LimitStream(new GZIPInputStream(is), nbtreadlimiter)))) {
return NBTTagCompoundWrapper.wrap(NBTCompressedStreamTools.a(datainputstream, nbtreadlimiter));
}
} catch (IOException e) {
throw new DecoderException(e);
}
}
private static void encodeLegacyNBT(NBTTagCompoundWrapper nbttagcompound, ByteBuf to) {
try {
try (DataOutputStream dataoutputstream = new DataOutputStream(new GZIPOutputStream(new ByteBufOutputStream(to)))) {
NBTCompressedStreamTools.a(nbttagcompound.unwrap(), (DataOutput) dataoutputstream);
}
} catch (IOException e) {
throw new EncoderException(e);
}
}
public static class InternalItemStackWriteEvent extends ItemStackWriteEvent {
private final CraftItemStack wrapped;
public InternalItemStackWriteEvent(ProtocolVersion version, ItemStackWrapper original, ItemStackWrapper itemstack) {
super(version, CraftItemStack.asCraftMirror(original.unwrap()));
this.wrapped = CraftItemStack.asCraftMirror(itemstack.unwrap());
}
@Override
public org.bukkit.inventory.ItemStack getResult() {
return wrapped;
}
}
}