ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java

1012 lines
37 KiB
Java

/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S.
* Stangeland
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.events;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.EquivalentConverter;
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.cloning.SerializableCloner;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectList;
import net.minecraft.world.entity.ai.attributes.AttributeBase;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.animal.CatVariant;
import net.minecraft.world.entity.animal.FrogVariant;
import org.apache.commons.lang.SerializationUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.assertItemCollectionsEqual;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static com.comphenix.protocol.utility.TestUtils.equivalentItem;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class PacketContainerTest {
private static BaseComponent[] TEST_COMPONENT;
// Helper converters
private final EquivalentConverter<WrappedDataWatcher> watchConvert = BukkitConverters.getDataWatcherConverter();
private final EquivalentConverter<ItemStack> itemConvert = BukkitConverters.getItemStackConverter();
@BeforeAll
public static void initializeBukkit() {
BukkitInitialization.initializeAll();
TEST_COMPONENT = new ComponentBuilder("Hit or miss?")
.event(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://reddit.com"))
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("The \"front page\" of the internet")))
.append("I guess they never miss, huh?")
.create();
}
private <T> void testPrimitive(StructureModifier<T> modifier, int index, T initialValue, T testValue) {
// Check initial value
assertEquals(initialValue, modifier.read(index));
// Test assignment
modifier.write(index, testValue);
assertEquals(testValue, modifier.read(0));
}
private <T> void testObjectArray(StructureModifier<T[]> modifier, int index, T[] initialValue, T[] testValue) {
// Check initial value
assertEquals(modifier.read(index).length, initialValue.length);
modifier.writeDefaults();
// Test initial
assertArrayEquals(initialValue, modifier.read(index));
// Test assignment
modifier.write(index, testValue);
assertArrayEquals(testValue, modifier.read(0));
}
@Test
public void testGetByteArrays() {
// Contains a byte array we will test
PacketContainer customPayload = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
StructureModifier<byte[]> bytes = customPayload.getByteArrays();
byte[] testArray = new byte[]{1, 2, 3};
// It's NULL at first
// assertEquals(null, bytes.read(0));
customPayload.getModifier().writeDefaults();
// Then it should create an empty array
assertArrayEquals(new byte[0], bytes.read(0));
// Check and see if we can write to it
bytes.write(0, testArray);
assertArrayEquals(testArray, bytes.read(0));
}
@Test
public void testGetBytes() {
PacketContainer spawnMob = new PacketContainer(PacketType.Play.Server.NAMED_ENTITY_SPAWN);
this.testPrimitive(spawnMob.getBytes(), 0, (byte) 0, (byte) 1);
}
@Test
public void testGetShorts() {
PacketContainer itemData = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE);
this.testPrimitive(itemData.getShorts(), 0, (short) 0, (short) 1);
}
@Test
public void testGetIntegers() {
PacketContainer updateSign = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW);
this.testPrimitive(updateSign.getIntegers(), 0, 0, 1);
}
@Test
public void testGetLongs() {
PacketContainer updateTime = new PacketContainer(PacketType.Play.Server.UPDATE_TIME);
this.testPrimitive(updateTime.getLongs(), 0, (long) 0, (long) 1);
}
@Test
public void testGetFloat() {
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
this.testPrimitive(explosion.getFloat(), 0, (float) 0, (float) 0.8);
}
@Test
public void testGetDoubles() {
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
this.testPrimitive(explosion.getDoubles(), 0, (double) 0, 0.8);
}
@Test
public void testGetStrings() {
PacketContainer explosion = new PacketContainer(PacketType.Play.Client.CHAT);
this.testPrimitive(explosion.getStrings(), 0, "", "hello");
}
@Test
public void testGetStringArrays() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.UPDATE_SIGN);
this.testObjectArray(packet.getStringArrays(), 0,
new String[]{"", "", "", ""},
new String[]{"hello", "world"}
);
}
@Test
public void testGetIntegerArrays() {
// Contains a byte array we will test
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MOUNT);
StructureModifier<int[]> integers = packet.getIntegerArrays();
int[] testArray = new int[]{1, 2, 3};
assertArrayEquals(new int[0], integers.read(0));
integers.write(0, testArray);
assertArrayEquals(testArray, integers.read(0));
}
@Test
public void testGetItemModifier() {
PacketContainer windowClick = new PacketContainer(PacketType.Play.Client.WINDOW_CLICK);
ItemStack item = this.itemWithData();
StructureModifier<ItemStack> items = windowClick.getItemModifier();
// assertNull(items.read(0));
// Insert the item and check if it's there
items.write(0, item);
assertTrue(equivalentItem(item, items.read(0)), "Item " + item + " != " + items.read(0));
}
private ItemStack itemWithData() {
ItemStack item = new ItemStack(Material.GREEN_WOOL, 1);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(ChatColor.GREEN + "Green Wool");
meta.setLore(Lists.newArrayList(ChatColor.WHITE + "This is lore."));
item.setItemMeta(meta);
return item;
}
@Test
public void testGetItemListModifier() {
PacketContainer windowItems = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS);
StructureModifier<List<ItemStack>> itemAccess = windowItems.getItemListModifier();
List<ItemStack> items = new ArrayList<>();
items.add(this.itemWithData());
items.add(new ItemStack(Material.DIAMOND_AXE));
assertEquals(itemAccess.read(0).size(), 0);
// Insert and check that it was succesful
itemAccess.write(0, items);
// Read back array
List<ItemStack> comparison = itemAccess.read(0);
assertItemCollectionsEqual(items, comparison);
}
@Test
public void testGetNbtModifier() {
PacketContainer updateTileEntity = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA);
NbtCompound compound = NbtFactory.ofCompound("test");
compound.put("test", "name");
compound.put(NbtFactory.ofList("ages", 1, 2, 3));
updateTileEntity.getNbtModifier().write(0, compound);
NbtCompound result = (NbtCompound) updateTileEntity.getNbtModifier().read(0);
assertEquals(compound.getString("test"), result.getString("test"));
assertEquals(compound.getList("ages"), result.getList("ages"));
}
// TODO They removed DataWatchers from packets, it's all entity metadata packets now
/* @Test
public void testGetDataWatcherModifier() {
PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
WrappedDataWatcher dataWatcher = new WrappedDataWatcher();
dataWatcher.setObject(new WrappedDataWatcherObject(1, Registry.get(Byte.class)), (byte) 1);
dataWatcher.setObject(new WrappedDataWatcherObject(2, Registry.get(String.class)), "Lorem");
dataWatcher.setObject(new WrappedDataWatcherObject(3, Registry.get(Boolean.class)), true);
dataWatcher.setObject(new WrappedDataWatcherObject(4, Registry.getUUIDSerializer(true)), Optional.of(UUID.randomUUID()));
assertNull(watcherAccessor.read(0));
// Insert and read back
watcherAccessor.write(0, dataWatcher);
assertEquals(dataWatcher, watcherAccessor.read(0));
} */
// Unfortunately, it might be too difficult to mock this one
//
// @Test
// public void testGetEntityModifier() { }
// No packet expose this type directly.
//
// @Test
// public void testGetPositionModifier() { }
@Test
public void testEntityTypeModifier() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
packet.getEntityTypeModifier().write(0, EntityType.ARROW);
assertEquals(packet.getEntityTypeModifier().read(0), EntityType.ARROW);
}
@Test
public void testGetPositionCollectionModifier() {
PacketContainer explosionPacket = new PacketContainer(PacketType.Play.Server.EXPLOSION);
StructureModifier<List<BlockPosition>> positionAccessor = explosionPacket.getBlockPositionCollectionModifier();
assertEquals(positionAccessor.read(0).size(), 0);
List<BlockPosition> positions = new ArrayList<>();
positions.add(new BlockPosition(1, 2, 3));
positions.add(new BlockPosition(3, 4, 5));
// Insert and read back
positionAccessor.write(0, positions);
List<BlockPosition> cloned = positionAccessor.read(0);
assertEquals(positions, cloned);
}
@Test
public void testGetDataValueCollectionModifier() {
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<List<WrappedDataValue>> watchableAccessor = entityMetadata.getDataValueCollectionModifier();
assertEquals(0, watchableAccessor.read(0).size());
List<WrappedDataValue> values = Lists.newArrayList(
new WrappedDataValue(0, Registry.get(Byte.class), (byte) 21),
new WrappedDataValue(1, Registry.get(String.class), "World"));
// Insert and read back
watchableAccessor.write(0, values);
assertEquals(values, watchableAccessor.read(0));
}
@Test
public void testGameProfiles() {
PacketContainer spawnEntity = new PacketContainer(PacketType.Login.Server.SUCCESS);
WrappedGameProfile profile = new WrappedGameProfile(UUID.fromString("d7047a08-3150-4aa8-a2f2-7c1e2b17e298"),
"name");
spawnEntity.getGameProfiles().write(0, profile);
assertEquals(profile, spawnEntity.getGameProfiles().read(0));
}
@Test
public void testChatComponents() {
PacketContainer chatPacket = new PacketContainer(PacketType.Login.Server.DISCONNECT);
chatPacket.getChatComponents().write(0,
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
assertEquals("{\"extra\":[{\"text\":\"You shall not \"},{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
chatPacket.getChatComponents().read(0).getJson());
}
@Test
public void testSerialization() {
PacketContainer useItem = new PacketContainer(PacketType.Play.Client.USE_ITEM);
useItem.getMovingBlockPositions().write(0, new MovingObjectPositionBlock(
new BlockPosition(0, 1, 0),
new Vector(0, 1, 0),
Direction.DOWN,
false));
useItem.getHands().write(0, Hand.MAIN_HAND);
useItem.getIntegers().write(0, 5);
useItem.getLongs().write(0, System.currentTimeMillis());
PacketContainer copy = (PacketContainer) SerializationUtils.clone(useItem);
assertEquals(PacketType.Play.Client.USE_ITEM, copy.getType());
assertEquals(Hand.MAIN_HAND, copy.getHands().read(0));
assertEquals(5, copy.getIntegers().read(0));
MovingObjectPositionBlock pos = copy.getMovingBlockPositions().read(0);
assertEquals(1, pos.getBlockPosition().getY());
assertEquals(Direction.DOWN, pos.getDirection());
assertFalse(pos.isInsideBlock());
}
@Test
public void testBigPacketSerialization() {
PacketContainer payload = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
payload.getMinecraftKeys().write(0, new com.comphenix.protocol.wrappers.MinecraftKey("test"));
byte[] randomData = new byte[8192];
ThreadLocalRandom.current().nextBytes(randomData);
ByteBuf serializer = (ByteBuf) MinecraftReflection.createPacketDataSerializer(randomData.length);
serializer.writeBytes(randomData);
payload.getModifier().withType(MinecraftReflection.getPacketDataSerializerClass()).write(0, serializer);
PacketContainer cloned = SerializableCloner.clone(payload);
com.comphenix.protocol.wrappers.MinecraftKey clonedKey = cloned.getMinecraftKeys().read(0);
byte[] clonedData = new byte[randomData.length];
ByteBuf clonedBuffer = (ByteBuf) cloned.getModifier()
.withType(MinecraftReflection.getPacketDataSerializerClass())
.read(0);
clonedBuffer.readBytes(clonedData);
assertEquals("minecraft:test", clonedKey.getFullKey());
assertArrayEquals(randomData, clonedData);
}
@Test
public void testIntList() {
PacketContainer destroy = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);
destroy.getIntLists().write(0, new ArrayList<Integer>() {{
this.add(420);
this.add(69);
}});
List<Integer> back = destroy.getIntLists().read(0);
assertEquals(back.size(), 2);
assertEquals((int) back.get(0), 420);
assertEquals((int) back.get(1), 69);
}
@Test
public void testAttributeList() {
PacketContainer attribute = new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES);
attribute.getIntegers().write(0, 123); // Entity ID
// Initialize some test data
List<AttributeModifier> modifiers = Lists.newArrayList(
new AttributeModifier(UUID.randomUUID(), "Unknown synced attribute modifier", 10,
AttributeModifier.Operation.a));
// Obtain an AttributeSnapshot instance. This is complicated by the fact that AttributeSnapshots
// are inner classes (which is ultimately pointless because AttributeSnapshots don't access any
// members of the packet itself)
PacketPlayOutUpdateAttributes packet = (PacketPlayOutUpdateAttributes) attribute.getHandle();
AttributeBase base = BuiltInRegistries.u.a(MinecraftKey.a("generic.max_health"));
AttributeSnapshot snapshot = new AttributeSnapshot(base, 20.0D, modifiers);
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
PacketContainer cloned = attribute.deepClone();
AttributeSnapshot
clonedSnapshot = (AttributeSnapshot) cloned.getSpecificModifier(List.class).read(0).get(0);
// Compare the fields, because apparently the packet is a field in AttributeSnapshot
for (Field field : AttributeSnapshot.class.getDeclaredFields()) {
try {
// Skip the packet
if (field.getType().equals(packet.getClass())) {
continue;
}
field.setAccessible(true);
this.testEquality(field.get(snapshot), field.get(clonedSnapshot));
} catch (AssertionError e) {
throw e;
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
@Test
public void testBlocks() {
PacketContainer blockAction = new PacketContainer(PacketType.Play.Server.BLOCK_ACTION);
blockAction.getBlocks().write(0, Material.STONE);
assertEquals(Material.STONE, blockAction.getBlocks().read(0));
}
@Test
public void testBlockData() {
PacketContainer blockChange = new PacketContainer(PacketType.Play.Server.BLOCK_CHANGE);
Material material = Material.GLOWSTONE;
WrappedBlockData data = WrappedBlockData.createData(material);
blockChange.getBlockData().write(0, data);
WrappedBlockData read = blockChange.getBlockData().read(0);
assertEquals(material, read.getType());
}
@Test
@SuppressWarnings("deprecation")
public void testPotionEffect() {
PotionEffect effect = new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 60, 1);
MobEffect mobEffect = new MobEffect(MobEffectList.a(effect.getType().getId()), effect.getDuration(),
effect.getAmplifier(), effect.isAmbient(),
effect.hasParticles());
int entityId = 42;
// The constructor we want to call
PacketConstructor creator = PacketConstructor.DEFAULT.withPacket(
PacketType.Play.Server.ENTITY_EFFECT, new Class<?>[]{int.class, MobEffect.class});
PacketContainer packet = creator.createPacket(entityId, mobEffect);
assertEquals(entityId, packet.getIntegers().read(0));
assertEquals(effect.getAmplifier(), (byte) packet.getBytes().read(0));
assertEquals(effect.getDuration(), packet.getIntegers().read(1));
WrappedRegistry registry = WrappedRegistry.getRegistry(MinecraftReflection.getMobEffectListClass());
Object effectList = assertInstanceOf(MobEffectList.class, packet.getStructures().read(0).getHandle());
assertEquals(effect.getType().getId(), registry.getId(effectList));
int e = 0;
if (effect.isAmbient()) {
e |= 1;
}
if (effect.hasParticles()) {
e |= 2;
}
if (effect.hasIcon()) {
e |= 4;
}
assertEquals(e, (byte) packet.getBytes().read(1));
}
@Test
public void testPlayerAction() {
PacketContainer container = new PacketContainer(PacketType.Play.Client.ENTITY_ACTION);
// no change across nms versions
container.getPlayerActions().write(0, EnumWrappers.PlayerAction.OPEN_INVENTORY);
assertEquals(container.getPlayerActions().read(0), EnumWrappers.PlayerAction.OPEN_INVENTORY);
// changed in 1.15
container.getPlayerActions().write(0, EnumWrappers.PlayerAction.START_SNEAKING);
assertEquals(container.getPlayerActions().read(0), EnumWrappers.PlayerAction.START_SNEAKING);
}
@Test
public void testMobEffectList() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.REMOVE_ENTITY_EFFECT);
container.getEffectTypes().write(0, PotionEffectType.GLOWING);
assertEquals(container.getEffectTypes().read(0), PotionEffectType.GLOWING);
}
@Test
public void testSoundCategory() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.NAMED_SOUND_EFFECT);
container.getSoundCategories().write(0, SoundCategory.PLAYERS);
assertEquals(SoundCategory.PLAYERS, container.getSoundCategories().read(0));
}
@Test
public void testSoundEffects() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.NAMED_SOUND_EFFECT);
container.getSoundEffects().optionRead(0);
container.getSoundEffects().write(0, Sound.ENTITY_CAT_HISS);
assertEquals(container.getSoundEffects().read(0), Sound.ENTITY_CAT_HISS);
}
// @Test
public void testGenericEnums() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.BOSS);
container.getEnumModifier(Action.class, 1).write(0, Action.UPDATE_PCT);
// assertEquals(container.getEnumModifier(Action.class, PacketPlayOutBoss.d.class).read(0), Action.UPDATE_PCT);
}
@Test
public void testInternalStructures() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
Optional<InternalStructure> optStruct = container.getOptionalStructures().read(0);
assertTrue(optStruct.isPresent());
InternalStructure struct = optStruct.get();
struct.getChatComponents().write(0, WrappedChatComponent.fromText("hi there"));
container.getOptionalStructures().write(0, Optional.of(struct));
optStruct = container.getOptionalStructures().read(0);
assertTrue(optStruct.isPresent());
struct = optStruct.get();
this.testEquality(
struct.getChatComponents().read(0),
WrappedChatComponent.fromText("hi there")
);
}
// @Test
public void testDimensions() {
// TODO this won't work in testing, but hopefully will in live
PacketContainer container = new PacketContainer(PacketType.Play.Server.RESPAWN);
container.getDimensions().write(0, 1);
assertEquals((Object) 1, container.getDimensions().read(0));
}
@Test
public void testEntityEquipment() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT);
List<Pair<EnumWrappers.ItemSlot, ItemStack>> data = new ArrayList<>();
data.add(new Pair<>(EnumWrappers.ItemSlot.CHEST, new ItemStack(Material.NETHERITE_CHESTPLATE)));
data.add(new Pair<>(EnumWrappers.ItemSlot.LEGS, new ItemStack(Material.GOLDEN_LEGGINGS)));
container.getSlotStackPairLists().write(0, data);
List<Pair<EnumWrappers.ItemSlot, ItemStack>> written = container.getSlotStackPairLists().read(0);
assertEquals(data, written);
}
@Test
public void testMovingBlockPos() {
PacketContainer container = new PacketContainer(PacketType.Play.Client.USE_ITEM);
Vector vector = new Vector(0, 1, 2);
BlockPosition position = new BlockPosition(3, 4, 5);
EnumWrappers.Direction direction = EnumWrappers.Direction.DOWN;
MovingObjectPositionBlock movingPos = new MovingObjectPositionBlock(position, vector, direction, true);
container.getMovingBlockPositions().write(0, movingPos);
MovingObjectPositionBlock back = container.getMovingBlockPositions().read(0);
assertEquals(back.getPosVector(), vector);
assertEquals(back.getBlockPosition(), position);
assertEquals(back.getDirection(), direction);
assertTrue(back.isInsideBlock());
}
@Test
public void testMultiBlockChange() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE);
packet.getShortArrays().writeSafely(0, new short[]{420, 69});
assertArrayEquals(new short[]{420, 69}, packet.getShortArrays().readSafely(0));
packet.getBlockDataArrays().writeSafely(0, new WrappedBlockData[]{
WrappedBlockData.createData(Material.IRON_BARS),
WrappedBlockData.createData(Material.IRON_BLOCK)
});
assertArrayEquals(new WrappedBlockData[]{
WrappedBlockData.createData(Material.IRON_BARS),
WrappedBlockData.createData(Material.IRON_BLOCK)
}, packet.getBlockDataArrays().readSafely(0));
packet.getSectionPositions().writeSafely(0, new BlockPosition(42, 43, 44));
assertEquals(new BlockPosition(42, 43, 44), packet.getSectionPositions().readSafely(0));
PacketContainer clone = packet.deepClone();
assertNotSame(clone, packet);
}
@Test
public void testGameStateChange() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.GAME_STATE_CHANGE);
assertEquals(0, (int) packet.getGameStateIDs().read(0));
packet.getGameStateIDs().write(0, 2);
assertEquals(2, (int) packet.getGameStateIDs().read(0));
}
@Test
public void testUseEntity() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.USE_ENTITY);
WrappedEnumEntityUseAction action;
WrappedEnumEntityUseAction clone;
// test attack
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.attack());
action = packet.getEnumEntityUseActions().read(0);
// attack's handle should always be the same
assertEquals(WrappedEnumEntityUseAction.attack(), action);
assertEquals(EntityUseAction.ATTACK, action.getAction());
// hand & position should not be available
assertThrows(IllegalArgumentException.class, action::getHand);
assertThrows(IllegalArgumentException.class, action::getPosition);
// test cloning
clone = action.deepClone();
assertSame(WrappedEnumEntityUseAction.attack(), clone);
// test interact
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interact(Hand.OFF_HAND));
action = packet.getEnumEntityUseActions().read(0);
assertEquals(EntityUseAction.INTERACT, action.getAction());
assertEquals(Hand.OFF_HAND, action.getHand());
// position should not be available
assertThrows(IllegalArgumentException.class, action::getPosition);
// test cloning
clone = action.deepClone();
assertEquals(EntityUseAction.INTERACT, clone.getAction());
assertEquals(Hand.OFF_HAND, clone.getHand());
// test interact_at
Vector position = new Vector(1, 199, 4);
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interactAt(Hand.MAIN_HAND, position));
action = packet.getEnumEntityUseActions().read(0);
assertEquals(EntityUseAction.INTERACT_AT, action.getAction());
assertEquals(Hand.MAIN_HAND, action.getHand());
assertEquals(position, action.getPosition());
// test cloning
clone = action.deepClone();
assertEquals(EntityUseAction.INTERACT_AT, clone.getAction());
assertEquals(Hand.MAIN_HAND, clone.getHand());
assertEquals(position, clone.getPosition());
}
@Test
public void testSetSimulationDistance() {
// first packet which is a record - set will fail if we missed something during patching
PacketContainer container = new PacketContainer(PacketType.Play.Server.UPDATE_SIMULATION_DISTANCE);
container.getIntegers().write(0, 1234);
assertEquals(1234, (int) container.getIntegers().read(0));
}
@Test
public void testMapChunk() {
// this is a special case as we are generating a data serializer class (we only need to construct the packet)
PacketContainer container = new PacketContainer(PacketType.Play.Server.MAP_CHUNK);
// check if we can read an nbt compound from the class
assertTrue(container.getStructures().read(0).getNbtModifier().optionRead(0).isPresent());
}
@Test
public void testComponentArrays() {
PacketContainer signChange = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA);
WrappedChatComponent[] components = new WrappedChatComponent[]{
WrappedChatComponent.fromText("hello world"), WrappedChatComponent.fromText(""),
WrappedChatComponent.fromText(""), WrappedChatComponent.fromText("")
};
signChange.getChatComponentArrays().write(0, components);
WrappedChatComponent[] back = signChange.getChatComponentArrays().read(0);
assertArrayEquals(components, back);
}
@Test
public void testPlayerInfoActions() {
PacketContainer updatePlayerInfoActions = new PacketContainer(PacketType.Play.Server.PLAYER_INFO);
EnumSet<EnumWrappers.PlayerInfoAction> actions = EnumSet.of(
EnumWrappers.PlayerInfoAction.ADD_PLAYER,
EnumWrappers.PlayerInfoAction.UPDATE_LATENCY,
EnumWrappers.PlayerInfoAction.UPDATE_LISTED);
updatePlayerInfoActions.getPlayerInfoActions().write(0, actions);
UUID id = UUID.randomUUID();
PlayerInfoData data = new PlayerInfoData(
id,
20,
false,
NativeGameMode.CREATIVE,
new WrappedGameProfile(new UUID(0, 0), "system"),
null,
(WrappedRemoteChatSessionData) null);
updatePlayerInfoActions.getPlayerInfoDataLists().write(1, Collections.singletonList(data));
Set<EnumWrappers.PlayerInfoAction> readActions = updatePlayerInfoActions.getPlayerInfoActions().read(0);
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.ADD_PLAYER));
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.UPDATE_LATENCY));
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.UPDATE_LISTED));
Collection<PlayerInfoData> readData = updatePlayerInfoActions.getPlayerInfoDataLists().read(1);
Assertions.assertEquals(1, readData.size());
PlayerInfoData firstData = readData.iterator().next();
Assertions.assertFalse(data.isListed());
Assertions.assertEquals(id, data.getProfileId());
Assertions.assertEquals("system", firstData.getProfile().getName());
Assertions.assertEquals(20, firstData.getLatency());
Assertions.assertEquals(NativeGameMode.CREATIVE, firstData.getGameMode());
}
// TODO: fix this this at some point
/*
@Test
public void testSignedChatMessage() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Client.CHAT);
byte[] signature = new byte[512];
long salt = 124L;
WrappedSaltedSignature wrappedSignature = new WrappedSaltedSignature(salt, signature);
chatPacket.getSignatures().write(0, wrappedSignature);
WrappedSaltedSignature read = chatPacket.getSignatures().read(0);
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}*/
private void assertPacketsEqualAndSerializable(PacketContainer constructed, PacketContainer cloned) {
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
assertEquals(firstMod.size(), secondMod.size());
if (PacketType.Status.Server.SERVER_INFO.equals(constructed.getType())) {
assertArrayEquals(SerializationUtils.serialize(constructed), SerializationUtils.serialize(cloned));
} else {
// Make sure all the fields are equivalent
for (int i = 0; i < firstMod.size(); i++) {
if (firstMod.getField(i).getType().isArray()) {
assertArrayEquals(this.getArray(firstMod.read(i)), this.getArray(secondMod.read(i)));
} else {
this.testEquality(firstMod.read(i), secondMod.read(i));
}
}
}
Object buffer = MinecraftReflection.createPacketDataSerializer(0);
MinecraftMethods.getPacketWriteByteBufMethod().invoke(cloned.getHandle(), buffer);
}
@Test
public void testCloning() {
// Try constructing all the packets
for (PacketType type : PacketType.values()) {
// TODO: try to support chat - for now chat contains to many sub classes to properly clone it
if (type.isDeprecated() || !type.isSupported() || type.name().contains("CUSTOM_PAYLOAD") || type.name().contains("CHAT")
|| type.name().contains("BUNDLE")) {
continue;
}
try {
PacketContainer constructed = new PacketContainer(type);
// Initialize default values
constructed.getModifier().writeDefaults();
// Make sure watchable collections can be cloned
if (type == PacketType.Play.Server.ENTITY_METADATA) {
constructed.getDataValueCollectionModifier().write(0, Lists.newArrayList(
new WrappedDataValue(0, Registry.get(Byte.class), (byte) 1),
new WrappedDataValue(0, Registry.get(Float.class), 5F),
new WrappedDataValue(0, Registry.get(String.class), "String"),
new WrappedDataValue(0, Registry.get(Boolean.class), true),
new WrappedDataValue(
0,
Registry.getChatComponentSerializer(true),
Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())),
new WrappedDataValue(
0,
Registry.getItemStackSerializer(false),
BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.WOODEN_AXE))),
new WrappedDataValue(0, Registry.get(CatVariant.class), BuiltInRegistries.ai.e(CatVariant.e)),
new WrappedDataValue(0, Registry.get(FrogVariant.class), FrogVariant.a)
));
} else if (type == PacketType.Play.Server.CHAT) {
constructed.getChatComponents().write(0, ComponentConverter.fromBaseComponent(TEST_COMPONENT));
} else if (type == PacketType.Play.Server.REMOVE_ENTITY_EFFECT || type == PacketType.Play.Server.ENTITY_EFFECT) {
constructed.getEffectTypes().write(0, PotionEffectType.GLOWING);
} else if (type == PacketType.Play.Server.GAME_STATE_CHANGE) {
constructed.getStructures().write(
0,
InternalStructure.getConverter().getSpecific(PacketPlayOutGameStateChange.a));
} else if (type == PacketType.Play.Client.USE_ITEM || type == PacketType.Play.Client.BLOCK_PLACE) {
constructed.getLongs().write(0, 0L); // timestamp of the packet, not sent over the network
}
// gives some indication which cloning process fails as the checks itself are happening outside this method
// Clone the packet all three ways
PacketContainer shallowCloned = constructed.shallowClone();
this.assertPacketsEqualAndSerializable(constructed, shallowCloned);
PacketContainer deepCloned = constructed.deepClone();
this.assertPacketsEqualAndSerializable(constructed, deepCloned);
PacketContainer serializedCloned = SerializableCloner.clone(constructed);
if (type == PacketType.Play.Client.USE_ITEM || type == PacketType.Play.Client.BLOCK_PLACE) {
// shit fix - but what are we supposed to do :/
serializedCloned.getLongs().write(0, 0L);
}
this.assertPacketsEqualAndSerializable(constructed, serializedCloned);
} catch (Throwable t) {
Assertions.fail("Unable to clone " + type, t);
}
}
}
// Convert to objects that support equals()
private void testEquality(Object a, Object b) {
if (a == null) {
if (b == null) {
return;
} else {
throw new AssertionError("a was null, but b was not");
}
} else if (b == null) {
throw new AssertionError("a was not null, but b was null");
}
if (a instanceof Optional) {
if (b instanceof Optional) {
this.testEquality(((Optional<?>) a).orElse(null), ((Optional<?>) b).orElse(null));
return;
} else {
throw new AssertionError("a was optional, but b was not");
}
}
if (a.equals(b) || Objects.equals(a, b) || this.stringEquality(a, b)) {
return;
}
if (MinecraftReflection.isDataWatcher(a)) {
a = this.watchConvert.getSpecific(a);
b = this.watchConvert.getSpecific(b);
} else if (MinecraftReflection.isItemStack(a)) {
a = this.itemConvert.getSpecific(a);
b = this.itemConvert.getSpecific(b);
}
if (a instanceof ItemStack && b instanceof ItemStack) {
assertItemsEqual((ItemStack) a, (ItemStack) b);
return;
}
if (a instanceof List<?>) {
if (b instanceof List<?>) {
List<?> listA = (List<?>) a;
List<?> listB = (List<?>) b;
assertEquals(listA.size(), listB.size());
for (int i = 0; i < listA.size(); i++) {
this.testEquality(listA.get(i), listB.get(i));
}
return;
} else {
throw new AssertionError("a was a list, but b was not");
}
}
if (a.getClass().isArray()) {
if (b.getClass().isArray()) {
int arrayLengthA = Array.getLength(a);
int arrayLengthB = Array.getLength(b);
assertEquals(arrayLengthA, arrayLengthB);
for (int i = 0; i < arrayLengthA; i++) {
Object elementA = Array.get(a, i);
Object elementB = Array.get(b, i);
testEquality(elementA, elementB);
}
return;
} else {
throw new AssertionError("a was an array, but b was not");
}
}
if (!a.getClass().isAssignableFrom(b.getClass())) {
assertEquals(a, b);
return;
}
Set<Field> fields = FuzzyReflection.fromObject(a, true).getFields();
for (Field field : fields) {
if (!Modifier.isStatic(field.getModifiers())) {
FieldAccessor accessor = Accessors.getFieldAccessor(field);
testEquality(accessor.get(a), accessor.get(b));
}
}
}
private boolean stringEquality(Object a, Object b) {
try {
return a.toString().equals(b.toString());
} catch (Exception ex) {
// internal null pointers, usually
return false;
}
}
/**
* Get the underlying array as an object array.
*
* @param val - array wrapped as an Object.
* @return An object array.
*/
private Object[] getArray(Object val) {
if (val instanceof Object[]) {
return (Object[]) val;
}
if (val == null) {
return null;
}
int arrlength = Array.getLength(val);
Object[] outputArray = new Object[arrlength];
for (int i = 0; i < arrlength; ++i) {
outputArray[i] = Array.get(val, i);
}
return outputArray;
}
/**
* Actions from the outbound Boss packet. Used for testing generic enums.
*
* @author dmulloy2
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}