package net.glowstone.net; import com.flowpowered.networking.util.ByteBufUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import net.glowstone.GlowServer; import net.glowstone.entity.meta.MetadataIndex; import net.glowstone.entity.meta.MetadataMap; import net.glowstone.entity.meta.MetadataType; import net.glowstone.inventory.GlowItemFactory; import net.glowstone.util.TextMessage; import net.glowstone.util.nbt.CompoundTag; import net.glowstone.util.nbt.NBTInputStream; import net.glowstone.util.nbt.NBTOutputStream; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.logging.Level; /** * Contains several utility methods for writing special data types to @{link ByteBuf}s. */ public final class GlowBufUtils { private GlowBufUtils() { } /** * Read a list of mob metadata entries from the buffer. * @param buf The buffer. * @return The metadata. */ public static List<MetadataMap.Entry> readMetadata(ByteBuf buf) throws IOException { List<MetadataMap.Entry> entries = new ArrayList<>(); byte item; while ((item = buf.readByte()) != 0x7F) { MetadataType type = MetadataType.byId(item >> 5); int id = item & 0x1f; MetadataIndex index = MetadataIndex.getIndex(id, type); switch (type) { case BYTE: entries.add(new MetadataMap.Entry(index, buf.readByte())); break; case SHORT: entries.add(new MetadataMap.Entry(index, buf.readShort())); break; case INT: entries.add(new MetadataMap.Entry(index, buf.readInt())); break; case FLOAT: entries.add(new MetadataMap.Entry(index, buf.readFloat())); break; case STRING: entries.add(new MetadataMap.Entry(index, ByteBufUtils.readUTF8(buf))); break; case ITEM: entries.add(new MetadataMap.Entry(index, readSlot(buf))); break; } } return entries; } /** * Write a list of mob metadata entries to the buffer. * @param buf The buffer. * @param entries The metadata. */ public static void writeMetadata(ByteBuf buf, List<MetadataMap.Entry> entries) throws IOException { for (MetadataMap.Entry entry : entries) { MetadataIndex index = entry.index; Object value = entry.value; if (value == null) continue; int type = index.getType().getId(); int id = index.getIndex(); buf.writeByte((type << 5) | id); switch (index.getType()) { case BYTE: buf.writeByte((Byte) value); break; case SHORT: buf.writeShort((Short) value); break; case INT: buf.writeInt((Integer) value); break; case FLOAT: buf.writeFloat((Float) value); break; case STRING: ByteBufUtils.writeUTF8(buf, (String) value); break; case ITEM: writeSlot(buf, (ItemStack) value); break; } } buf.writeByte(127); } /** * Read an uncompressed compound NBT tag from the buffer. * @param buf The buffer. * @return The tag read, or null. */ public static CompoundTag readCompound(ByteBuf buf) { int idx = buf.readerIndex(); if (buf.readByte() == 0) { return null; } buf.readerIndex(idx); try (NBTInputStream str = new NBTInputStream(new ByteBufInputStream(buf), false)) { return str.readCompound(); } catch (IOException e) { return null; } } /** * Write an uncompressed compound NBT tag to the buffer. * @param buf The buffer. * @param data The tag to write, or null. */ public static void writeCompound(ByteBuf buf, CompoundTag data) { if (data == null) { buf.writeByte(0); return; } ByteArrayOutputStream out = new ByteArrayOutputStream(); try (NBTOutputStream str = new NBTOutputStream(out, false)) { str.writeTag(data); } catch (IOException e) { GlowServer.logger.log(Level.WARNING, "Error serializing NBT: " + data, e); return; } buf.writeBytes(out.toByteArray()); } /** * Read an item stack from the buffer. * @param buf The buffer. * @return The stack read, or null. */ public static ItemStack readSlot(ByteBuf buf) { short type = buf.readShort(); if (type == -1) { return null; } int amount = buf.readUnsignedByte(); short durability = buf.readShort(); Material material = Material.getMaterial(type); if (material == null) { return null; } CompoundTag tag = readCompound(buf); ItemStack stack = new ItemStack(material, amount, durability); stack.setItemMeta(GlowItemFactory.instance().readNbt(material, tag)); return stack; } /** * Write an item stack to the buffer. * @param buf The buffer. * @param stack The stack to write, or null. */ public static void writeSlot(ByteBuf buf, ItemStack stack) { if (stack == null || stack.getTypeId() == 0) { buf.writeShort(-1); } else { buf.writeShort(stack.getTypeId()); buf.writeByte(stack.getAmount()); buf.writeShort(stack.getDurability()); if (stack.hasItemMeta()) { CompoundTag tag = GlowItemFactory.instance().writeNbt(stack.getItemMeta()); writeCompound(buf, tag); } else { writeCompound(buf, null); } } } /** * Read an encoded block vector (position) from the buffer. * @param buf The buffer. * @return The vector read. */ public static BlockVector readBlockPosition(ByteBuf buf) { long val = buf.readLong(); long x = (val >> 38); // signed long y = (val >> 26) & 0xfff; // unsigned // this shifting madness is used to preserve sign long z = (val << 38) >> 38; // signed return new BlockVector((double) x, y, z); } /** * Write an encoded block vector (position) to the buffer. * @param buf The buffer. * @param vector The vector to write. */ public static void writeBlockPosition(ByteBuf buf, Vector vector) { writeBlockPosition(buf, vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); } /** * Write an encoded block vector (position) to the buffer. * @param buf The buffer. * @param x The x value. * @param y The y value. * @param z The z value. */ public static void writeBlockPosition(ByteBuf buf, long x, long y, long z) { buf.writeLong(((x & 0x3ffffff) << 38) | ((y & 0xfff) << 26) | (z & 0x3ffffff)); } /** * Read a UUID encoded as two longs from the buffer. * @param buf The buffer. * @return The UUID read. */ public static UUID readUuid(ByteBuf buf) { return new UUID(buf.readLong(), buf.readLong()); } /** * Write a UUID encoded as two longs to the buffer. * @param buf The buffer. * @param uuid The UUID to write. */ public static void writeUuid(ByteBuf buf, UUID uuid) { buf.writeLong(uuid.getMostSignificantBits()); buf.writeLong(uuid.getLeastSignificantBits()); } /** * Read an encoded chat message from the buffer. * @param buf The buffer. * @return The chat message read. * @throws IOException on read failure. */ public static TextMessage readChat(ByteBuf buf) throws IOException { return TextMessage.decode(ByteBufUtils.readUTF8(buf)); } /** * Write an encoded chat message to the buffer. * @param buf The buffer. * @param text The chat message to write. * @throws IOException on write failure. */ public static void writeChat(ByteBuf buf, TextMessage text) throws IOException { ByteBufUtils.writeUTF8(buf, text.encode()); } }