/** * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2016 dmulloy2 *

* 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. *

* 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. *

* 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; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflectionTestUtil; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.WrappedChatComponent; import net.minecraft.network.EnumProtocol; import net.minecraft.network.protocol.EnumProtocolDirection; import net.minecraft.network.protocol.login.PacketLoginInStart; import org.apache.commons.lang.WordUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; /** * @author dmulloy2 */ public class PacketTypeTest { @BeforeAll public static void beforeClass() { BukkitInitialization.initializeAll(); // I'm well aware this is jank, but it does in fact work correctly and give the desired result /*PacketType.onDynamicCreate = className -> { throw new RuntimeException("Dynamically generated packet " + className); };*/ } @AfterAll public static void afterClass() { PacketType.onDynamicCreate = __ -> { }; } @SuppressWarnings("unchecked") // @Test // public static void main(String[] args) throws Exception { public void generateNewPackets() throws Exception { MinecraftReflectionTestUtil.init(); Set> allTypes = new HashSet<>(); List> newTypes = new ArrayList<>(); EnumProtocol[] protocols = EnumProtocol.values(); for (EnumProtocol protocol : protocols) { System.out.println(WordUtils.capitalize(protocol.name().toLowerCase())); Field field = EnumProtocol.class.getDeclaredField("k"); field.setAccessible(true); Map map = (Map) field.get(protocol); for (Entry entry : map.entrySet()) { Field mapField = entry.getValue().getClass().getDeclaredField("b"); mapField.setAccessible(true); Map, Integer> reverseMap = (Map, Integer>) mapField.get(entry.getValue()); Map> treeMap = new TreeMap<>(); for (Entry, Integer> entry1 : reverseMap.entrySet()) { treeMap.put(entry1.getValue(), entry1.getKey()); } System.out.println(" " + entry.getKey()); for (Entry> entry1 : treeMap.entrySet()) { System.out.println(generateNewType(entry1.getKey(), entry1.getValue())); allTypes.add(entry1.getValue()); try { PacketType.fromClass(entry1.getValue()); } catch (Exception ex) { newTypes.add(entry1.getValue()); } } } } System.out.println("New types: " + newTypes); for (PacketType type : PacketType.values()) { if (type.isDeprecated()) { continue; } if (!allTypes.contains(type.getPacketClass())) { System.out.println(type + " was removed"); } } } private static String formatHex(int dec) { if (dec < 0) { return "0xFF"; } String hex = Integer.toHexString(dec).toUpperCase(); return "0x" + (hex.length() < 2 ? "0" : "") + hex; } private static List splitOnCaps(String string) { List list = new ArrayList<>(); StringBuilder builder = new StringBuilder(); char[] chars = string.toCharArray(); for (int i = 0; i < chars.length; i++) { char c = chars[i]; if (i != 0 && Character.isUpperCase(c)) { list.add(builder.toString()); builder = new StringBuilder(); } builder.append(c); } list.add(builder.toString()); return list; } private static String generateNewType(int packetId, Class clazz) { StringBuilder builder = new StringBuilder(); builder.append("\t\t\t"); builder.append("public static final PacketType "); String fullName = clazz.getName(); fullName = fullName.substring(fullName.lastIndexOf(".") + 1); String className; List classNames = new ArrayList<>(); if (fullName.endsWith("Packet")) { for (String name : fullName.split("\\$")) { List split = splitOnCaps(name); StringBuilder nameBuilder = new StringBuilder(); for (int i = 1; i < split.size() - 1; i++) { nameBuilder.append(split.get(i)); } classNames.add(nameBuilder.toString()); } } else { for (String name : fullName.split("\\$")) { List split = splitOnCaps(name); StringBuilder nameBuilder = new StringBuilder(); for (int i = 3; i < split.size(); i++) { nameBuilder.append(split.get(i)); } classNames.add(nameBuilder.toString()); } } className = classNames.get(classNames.size() - 1); PacketType existing = null; try { existing = PacketType.fromClass(clazz); if (existing.isDynamic()) { existing = null; } } catch (Exception ignored) { // doesn't exist } String fieldName; if (existing == null) { // Format it like SET_PROTOCOL StringBuilder fieldNameBuilder = new StringBuilder(); char[] chars = className.toCharArray(); for (int i = 0; i < chars.length; i++) { char c = chars[i]; if (i != 0 && Character.isUpperCase(c)) { fieldNameBuilder.append("_"); } fieldNameBuilder.append(Character.toUpperCase(c)); } fieldName = fieldNameBuilder.toString().replace("N_B_T", "NBT"); } else { fieldName = existing.name(); } builder.append(fieldName); builder.append(" = "); // Add spacing if (builder.length() > 65) { builder.append("\n"); } else { while (builder.length() < 65) { builder.append(" "); } } builder.append("new "); builder.append("PacketType(PROTOCOL, SENDER, "); builder.append(formatHex(packetId)); builder.append(", "); StringBuilder nameBuilder = new StringBuilder(); for (int i = 0; i < classNames.size(); i++) { if (i != 0) { nameBuilder.append("$"); } nameBuilder.append(classNames.get(i)); } String name = nameBuilder.toString(); String namesArg = listToString(getAllNames(clazz, name)); builder.append(namesArg); builder.append(");"); return builder.toString(); } private static List getAllNames(Class packetClass, String newName) { List names = new ArrayList<>(); names.add(newName); try { PacketType type = PacketType.fromClass(packetClass); for (String alias : type.names) { alias = alias.substring(alias.lastIndexOf('.') + 1); if (!names.contains(alias)) { names.add(alias); } } } catch (Exception ignored) { } return names; } private static String listToString(List list) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < list.size(); i++) { if (i != 0) { builder.append(", "); } builder.append("\"").append(list.get(i)).append("\""); } return builder.toString(); } @Test public void testFindCurrent() { assertEquals(PacketType.Play.Client.STEER_VEHICLE, PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, "SteerVehicle")); } @Test public void testLoginStart() { // This packet is critical for handleLoin assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass()); } @Test public void testDeprecation() { assertTrue(PacketType.Status.Server.OUT_SERVER_INFO.isDeprecated(), "Packet isn't properly deprecated"); assertTrue(PacketRegistry.getServerPacketTypes().contains(PacketType.Status.Server.OUT_SERVER_INFO), "Deprecated packet isn't properly included"); assertFalse(PacketType.Play.Server.CHAT.isDeprecated(), "Packet isn't properly deprecated"); assertEquals(PacketType.Status.Server.OUT_SERVER_INFO, PacketType.Status.Server.SERVER_INFO, "Deprecated packets aren't equal"); } @Test @SuppressWarnings("unchecked") public void ensureTypesAreCorrect() throws Exception { PacketType.onDynamicCreate = className -> { throw new RuntimeException("Dynamically generated packet " + className); }; try { boolean fail = false; EnumProtocol[] protocols = EnumProtocol.values(); for (EnumProtocol protocol : protocols) { Field field = EnumProtocol.class.getDeclaredField("k"); field.setAccessible(true); Map map = (Map) field.get(protocol); for (Entry entry : map.entrySet()) { Field mapField = entry.getValue().getClass().getDeclaredField("b"); mapField.setAccessible(true); Map, Integer> reverseMap = (Map, Integer>) mapField.get(entry.getValue()); Map> treeMap = new TreeMap<>(); for (Entry, Integer> entry1 : reverseMap.entrySet()) { treeMap.put(entry1.getValue(), entry1.getKey()); } for (Entry> entry1 : treeMap.entrySet()) { try { PacketType type = PacketType.fromClass(entry1.getValue()); if (type.getCurrentId() != entry1.getKey()) { throw new IllegalStateException( "Packet ID for " + type + " is incorrect. Expected " + entry1.getKey() + ", but got " + type.getCurrentId()); } } catch (Throwable ex) { if (ex.getMessage().contains("BundleDelimiterPacket")) { continue; } ex.printStackTrace(); fail = true; } } } } assertFalse(fail, "Packet type(s) were incorrect!"); } finally { PacketType.onDynamicCreate = __ -> { }; } } @Test public void testPacketCreation() { boolean fail = false; for (PacketType type : PacketType.values()) { if (type.isSupported()) { try { new PacketContainer(type); } catch (Exception ex) { ex.printStackTrace(); fail = true; } } } assertFalse(fail, "Packet type(s) failed to instantiate"); } @Test public void testPacketBundleWriting() { PacketContainer bundlePacket = new PacketContainer(PacketType.Play.Server.BUNDLE); assertEquals(MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet Bundle class is not present")), bundlePacket.getHandle().getClass()); List bundle = new ArrayList<>(); PacketContainer chatMessage = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT); chatMessage.getStrings().write(0, WrappedChatComponent.fromText("Test").getJson()); chatMessage.getBooleans().write(0, false); bundle.add(chatMessage); bundlePacket.getPacketBundles().write(0, bundle); } }