diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/Readme.md b/Readme.md index f1ccef0e..922a3ac1 100644 --- a/Readme.md +++ b/Readme.md @@ -1,167 +1,167 @@ -# ProtocolLib - -Certain tasks are impossible to perform with the standard Bukkit API, and may require -working with and even modifying Minecraft directly. A common technique is to modify incoming -and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the -stream. This is quite cumbersome to do, however, and most implementations will break -as soon as a new version of Minecraft has been released, mostly due to obfuscation. - -Critically, different plugins that use this approach may _hook_ into the same classes, -with unpredictable outcomes. More than often this causes plugins to crash, but it may also -lead to more subtle bugs. - -Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/). - -### Resources - -* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/) -* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib) -* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html) - -### Compilation - -ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run -`mvn package` in the root project folder. - -### A new API - -__ProtocolLib__ attempts to solve this problem by providing an event API, much like Bukkit, -that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly, -the API also hides all the gritty, obfuscated classes with a simple index based read/write system. -You no longer have to reference CraftBukkit! - -### Using ProtocolLib - -To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib -as a dependency or soft dependency to your plugin.yml file like any other plugin: - -````yml -depend: [ ProtocolLib ] -```` - -You can also add ProtocolLib as a Maven dependency: - -````xml - - - dmulloy2-repo - https://repo.dmulloy2.net/repository/public/ - - - - - - com.comphenix.protocol - ProtocolLib - 4.7.0 - provided - - -```` - -Or use the maven dependency with gradle: - -```gradle -repositories { - maven { url "https://repo.dmulloy2.net/repository/public/" } -} - -dependencies { - compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.7.0"; -} -``` - -Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go. - -````java -private ProtocolManager protocolManager; - -public void onLoad() { - protocolManager = ProtocolLibrary.getProtocolManager(); -} -```` - -To listen for packets sent by the server to a client, add a server-side listener: - -````java -// Disable all sound effects -protocolManager.addPacketListener(new PacketAdapter( - this, - ListenerPriority.NORMAL, - PacketType.Play.Server.NAMED_SOUND_EFFECT -) { - @Override - public void onPacketSending(PacketEvent event) { - event.setCancelled(true); - } -}); -```` - -It's also possible to read and modify the content of these packets. For instance, you can create a global -censor by listening for Packet3Chat events: - -````java -// Censor -protocolManager.addPacketListener(new PacketAdapter( - this, - ListenerPriority.NORMAL, - PacketType.Play.Client.CHAT -) { - @Override - public void onPacketReceiving(PacketEvent event) { - PacketContainer packet = event.getPacket(); - String message = packet.getStrings().read(0); - - if (message.contains("shit") || message.contains("damn")) { - event.setCancelled(true); - event.getPlayer().sendMessage("Bad manners!"); - } - } -}); -```` - -### Sending packets - -Normally, you might have to do something ugly like the following: - -````java -PacketPlayOutExplosion fakeExplosion = new PacketPlayOutExplosion( - player.getLocation().getX(), - player.getLocation().getY(), - player.getLocation().getZ(), - 3.0F, - new ArrayList<>(), - new Vec3D( - player.getVelocity().getX() + 1, - player.getVelocity().getY() + 1, - player.getVelocity().getZ() + 1 - ) -); - -((CraftPlayer) player).getHandle().b.a(fakeExplosion); -```` - -But with ProtocolLib, you can turn that into something more manageable: - -````java -PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION); -fakeExplosion.getDoubles() - .write(0, player.getLocation().getX()) - .write(1, player.getLocation().getY()) - .write(2, player.getLocation().getZ()); -fakeExplosion.getFloat().write(0, 3.0F); -fakeExplosion.getBlockPositionCollectionModifier().write(0, new ArrayList<>()); -fakeExplosion.getVectors().write(0, player.getVelocity().add(new Vector(1, 1, 1))); - -protocolManager.sendServerPacket(player, fakeExplosion); -```` - -### Compatibility - -One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end -result is quite flexible. It's likely that I won't have to update ProtocolLib for anything but bug fixes -and new features. - -How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded - -every field, method and class is deduced by looking at field types, package names or parameter -types. It's remarkably consistent across different versions. +# ProtocolLib + +Certain tasks are impossible to perform with the standard Bukkit API, and may require +working with and even modifying Minecraft directly. A common technique is to modify incoming +and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the +stream. This is quite cumbersome to do, however, and most implementations will break +as soon as a new version of Minecraft has been released, mostly due to obfuscation. + +Critically, different plugins that use this approach may _hook_ into the same classes, +with unpredictable outcomes. More than often this causes plugins to crash, but it may also +lead to more subtle bugs. + +Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/). + +### Resources + +* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/) +* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib) +* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html) + +### Compilation + +ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run +`mvn package` in the root project folder. + +### A new API + +__ProtocolLib__ attempts to solve this problem by providing an event API, much like Bukkit, +that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly, +the API also hides all the gritty, obfuscated classes with a simple index based read/write system. +You no longer have to reference CraftBukkit! + +### Using ProtocolLib + +To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib +as a dependency or soft dependency to your plugin.yml file like any other plugin: + +````yml +depend: [ ProtocolLib ] +```` + +You can also add ProtocolLib as a Maven dependency: + +````xml + + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + + + + + + com.comphenix.protocol + ProtocolLib + 4.7.0 + provided + + +```` + +Or use the maven dependency with gradle: + +```gradle +repositories { + maven { url "https://repo.dmulloy2.net/repository/public/" } +} + +dependencies { + compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.7.0"; +} +``` + +Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go. + +````java +private ProtocolManager protocolManager; + +public void onLoad() { + protocolManager = ProtocolLibrary.getProtocolManager(); +} +```` + +To listen for packets sent by the server to a client, add a server-side listener: + +````java +// Disable all sound effects +protocolManager.addPacketListener(new PacketAdapter( + this, + ListenerPriority.NORMAL, + PacketType.Play.Server.NAMED_SOUND_EFFECT +) { + @Override + public void onPacketSending(PacketEvent event) { + event.setCancelled(true); + } +}); +```` + +It's also possible to read and modify the content of these packets. For instance, you can create a global +censor by listening for Packet3Chat events: + +````java +// Censor +protocolManager.addPacketListener(new PacketAdapter( + this, + ListenerPriority.NORMAL, + PacketType.Play.Client.CHAT +) { + @Override + public void onPacketReceiving(PacketEvent event) { + PacketContainer packet = event.getPacket(); + String message = packet.getStrings().read(0); + + if (message.contains("shit") || message.contains("damn")) { + event.setCancelled(true); + event.getPlayer().sendMessage("Bad manners!"); + } + } +}); +```` + +### Sending packets + +Normally, you might have to do something ugly like the following: + +````java +PacketPlayOutExplosion fakeExplosion = new PacketPlayOutExplosion( + player.getLocation().getX(), + player.getLocation().getY(), + player.getLocation().getZ(), + 3.0F, + new ArrayList<>(), + new Vec3D( + player.getVelocity().getX() + 1, + player.getVelocity().getY() + 1, + player.getVelocity().getZ() + 1 + ) +); + +((CraftPlayer) player).getHandle().b.a(fakeExplosion); +```` + +But with ProtocolLib, you can turn that into something more manageable: + +````java +PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION); +fakeExplosion.getDoubles() + .write(0, player.getLocation().getX()) + .write(1, player.getLocation().getY()) + .write(2, player.getLocation().getZ()); +fakeExplosion.getFloat().write(0, 3.0F); +fakeExplosion.getBlockPositionCollectionModifier().write(0, new ArrayList<>()); +fakeExplosion.getVectors().write(0, player.getVelocity().add(new Vector(1, 1, 1))); + +protocolManager.sendServerPacket(player, fakeExplosion); +```` + +### Compatibility + +One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end +result is quite flexible. It's likely that I won't have to update ProtocolLib for anything but bug fixes +and new features. + +How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded - +every field, method and class is deduced by looking at field types, package names or parameter +types. It's remarkably consistent across different versions. diff --git a/src/main/java/com/comphenix/protocol/PacketLogging.java b/src/main/java/com/comphenix/protocol/PacketLogging.java index 10c1d683..fea0d9b1 100644 --- a/src/main/java/com/comphenix/protocol/PacketLogging.java +++ b/src/main/java/com/comphenix/protocol/PacketLogging.java @@ -1,284 +1,284 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2017 Dan Mulloy - * - * 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 com.comphenix.protocol.PacketType.Protocol; -import com.comphenix.protocol.PacketType.Sender; -import com.comphenix.protocol.events.ListeningWhitelist; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.events.PacketListener; -import com.comphenix.protocol.injector.netty.WirePacket; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.text.MessageFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.FileHandler; -import java.util.logging.Formatter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; - -/** - * Logs packets to a given stream - * @author dmulloy2 - */ -public class PacketLogging implements CommandExecutor, PacketListener { - public static final String NAME = "packetlog"; - - private static MethodAccessor HEX_DUMP; - - private List sendingTypes = new ArrayList<>(); - private List receivingTypes = new ArrayList<>(); - - private ListeningWhitelist sendingWhitelist; - private ListeningWhitelist receivingWhitelist; - - private Logger fileLogger; - private LogLocation location = LogLocation.FILE; - - private final ProtocolManager manager; - private final Plugin plugin; - - PacketLogging(Plugin plugin, ProtocolManager manager) { - this.plugin = plugin; - this.manager = manager; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - PacketType type = null; - - try { - if (args.length > 2) { - Protocol protocol; - - try { - protocol = Protocol.valueOf(args[0].toUpperCase()); - } catch (IllegalArgumentException ex) { - sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]); - return true; - } - - Sender pSender; - - try { - pSender = Sender.valueOf(args[1].toUpperCase()); - } catch (IllegalArgumentException ex) { - sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]); - return true; - } - - try { - try { // Try IDs first - int id = Integer.parseInt(args[2]); - type = PacketType.findCurrent(protocol, pSender, id); - } catch (NumberFormatException ex) { // Check packet names - String name = args[2]; - for (PacketType packet : PacketType.values()) { - if (packet.getProtocol() == protocol && packet.getSender() == pSender) { - if (packet.name().equalsIgnoreCase(name)) { - type = packet; - break; - } - for (String className : packet.getClassNames()) { - if (className.equalsIgnoreCase(name)) { - type = packet; - break; - } - } - } - } - } - } catch (IllegalArgumentException ex) { // RIP - type = null; - } - - if (type == null) { - sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]); - return true; - } - - if (args.length > 3) { - if (args[3].equalsIgnoreCase("console")) { - this.location = LogLocation.CONSOLE; - } else { - this.location = LogLocation.FILE; - } - } - - if (pSender == Sender.CLIENT) { - if (receivingTypes.contains(type)) { - receivingTypes.remove(type); - } else { - receivingTypes.add(type); - } - } else { - if (sendingTypes.contains(type)) { - sendingTypes.remove(type); - } else { - sendingTypes.add(type); - } - } - - startLogging(); - sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName()); - return true; - } - - sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog [location]"); - return true; - } catch (Throwable ex) { - sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString()); - return true; - } - } - - private void startLogging() { - manager.removePacketListener(this); - - if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) { - return; - } - - this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build(); - this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build(); - - // Setup the file logger if it hasn't been already - if (location == LogLocation.FILE && fileLogger == null) { - fileLogger = Logger.getLogger("ProtocolLib-FileLogging"); - - for (Handler handler : fileLogger.getHandlers()) - fileLogger.removeHandler(handler); - fileLogger.setUseParentHandlers(false); - - try { - File logFile = new File(plugin.getDataFolder(), "log.log"); - FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true); - handler.setFormatter(new LogFormatter()); - fileLogger.addHandler(handler); - } catch (IOException ex) { - plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex); - return; - } - } - - manager.addPacketListener(this); - } - - @Override - public void onPacketSending(PacketEvent event) { - log(event); - } - - @Override - public void onPacketReceiving(PacketEvent event) { - log(event); - } - - // Here's where the magic happens - - private static String hexDump(byte[] bytes) throws IOException { - try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { - if (HEX_DUMP == null) { - Class hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump"); - HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass) - .getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class)); - } - - HEX_DUMP.invoke(null, bytes, 0, output, 0); - return new String(output.toByteArray(), StandardCharsets.UTF_8); - } - } - - private void log(PacketEvent event) { - try { - byte[] bytes = WirePacket.bytesFromPacket(event.getPacket()); - String hexDump = hexDump(bytes); - - if (location == LogLocation.FILE) { - fileLogger.log(Level.INFO, event.getPacketType() + ":"); - fileLogger.log(Level.INFO, hexDump); - fileLogger.log(Level.INFO, ""); - } else { - System.out.println(event.getPacketType() + ":"); - System.out.println(hexDump); - System.out.println(); - } - } catch (Throwable ex) { - plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex); - plugin.getLogger().log(Level.WARNING, "Clearing packet logger..."); - - sendingTypes.clear(); - receivingTypes.clear(); - startLogging(); - } - } - - @Override - public ListeningWhitelist getSendingWhitelist() { - return sendingWhitelist; - } - - @Override - public ListeningWhitelist getReceivingWhitelist() { - return receivingWhitelist; - } - - @Override - public Plugin getPlugin() { - return plugin; - } - - private enum LogLocation { - CONSOLE, FILE - } - - private static class LogFormatter extends Formatter { - private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - private static final String FORMAT = "[{0}] {1}"; - - @Override - public String format(LogRecord record) { - String string = formatMessage(record); - if (string.isEmpty()) { - return LINE_SEPARATOR; - } - - StringBuilder message = new StringBuilder(); - message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string)); - message.append(LINE_SEPARATOR); - return message.toString(); - } - } +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2017 Dan Mulloy + * + * 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 com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.PacketType.Sender; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.netty.WirePacket; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +/** + * Logs packets to a given stream + * @author dmulloy2 + */ +public class PacketLogging implements CommandExecutor, PacketListener { + public static final String NAME = "packetlog"; + + private static MethodAccessor HEX_DUMP; + + private List sendingTypes = new ArrayList<>(); + private List receivingTypes = new ArrayList<>(); + + private ListeningWhitelist sendingWhitelist; + private ListeningWhitelist receivingWhitelist; + + private Logger fileLogger; + private LogLocation location = LogLocation.FILE; + + private final ProtocolManager manager; + private final Plugin plugin; + + PacketLogging(Plugin plugin, ProtocolManager manager) { + this.plugin = plugin; + this.manager = manager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + PacketType type = null; + + try { + if (args.length > 2) { + Protocol protocol; + + try { + protocol = Protocol.valueOf(args[0].toUpperCase()); + } catch (IllegalArgumentException ex) { + sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]); + return true; + } + + Sender pSender; + + try { + pSender = Sender.valueOf(args[1].toUpperCase()); + } catch (IllegalArgumentException ex) { + sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]); + return true; + } + + try { + try { // Try IDs first + int id = Integer.parseInt(args[2]); + type = PacketType.findCurrent(protocol, pSender, id); + } catch (NumberFormatException ex) { // Check packet names + String name = args[2]; + for (PacketType packet : PacketType.values()) { + if (packet.getProtocol() == protocol && packet.getSender() == pSender) { + if (packet.name().equalsIgnoreCase(name)) { + type = packet; + break; + } + for (String className : packet.getClassNames()) { + if (className.equalsIgnoreCase(name)) { + type = packet; + break; + } + } + } + } + } + } catch (IllegalArgumentException ex) { // RIP + type = null; + } + + if (type == null) { + sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]); + return true; + } + + if (args.length > 3) { + if (args[3].equalsIgnoreCase("console")) { + this.location = LogLocation.CONSOLE; + } else { + this.location = LogLocation.FILE; + } + } + + if (pSender == Sender.CLIENT) { + if (receivingTypes.contains(type)) { + receivingTypes.remove(type); + } else { + receivingTypes.add(type); + } + } else { + if (sendingTypes.contains(type)) { + sendingTypes.remove(type); + } else { + sendingTypes.add(type); + } + } + + startLogging(); + sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName()); + return true; + } + + sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog [location]"); + return true; + } catch (Throwable ex) { + sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString()); + return true; + } + } + + private void startLogging() { + manager.removePacketListener(this); + + if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) { + return; + } + + this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build(); + this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build(); + + // Setup the file logger if it hasn't been already + if (location == LogLocation.FILE && fileLogger == null) { + fileLogger = Logger.getLogger("ProtocolLib-FileLogging"); + + for (Handler handler : fileLogger.getHandlers()) + fileLogger.removeHandler(handler); + fileLogger.setUseParentHandlers(false); + + try { + File logFile = new File(plugin.getDataFolder(), "log.log"); + FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true); + handler.setFormatter(new LogFormatter()); + fileLogger.addHandler(handler); + } catch (IOException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex); + return; + } + } + + manager.addPacketListener(this); + } + + @Override + public void onPacketSending(PacketEvent event) { + log(event); + } + + @Override + public void onPacketReceiving(PacketEvent event) { + log(event); + } + + // Here's where the magic happens + + private static String hexDump(byte[] bytes) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + if (HEX_DUMP == null) { + Class hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump"); + HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass) + .getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class)); + } + + HEX_DUMP.invoke(null, bytes, 0, output, 0); + return new String(output.toByteArray(), StandardCharsets.UTF_8); + } + } + + private void log(PacketEvent event) { + try { + byte[] bytes = WirePacket.bytesFromPacket(event.getPacket()); + String hexDump = hexDump(bytes); + + if (location == LogLocation.FILE) { + fileLogger.log(Level.INFO, event.getPacketType() + ":"); + fileLogger.log(Level.INFO, hexDump); + fileLogger.log(Level.INFO, ""); + } else { + System.out.println(event.getPacketType() + ":"); + System.out.println(hexDump); + System.out.println(); + } + } catch (Throwable ex) { + plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex); + plugin.getLogger().log(Level.WARNING, "Clearing packet logger..."); + + sendingTypes.clear(); + receivingTypes.clear(); + startLogging(); + } + } + + @Override + public ListeningWhitelist getSendingWhitelist() { + return sendingWhitelist; + } + + @Override + public ListeningWhitelist getReceivingWhitelist() { + return receivingWhitelist; + } + + @Override + public Plugin getPlugin() { + return plugin; + } + + private enum LogLocation { + CONSOLE, FILE + } + + private static class LogFormatter extends Formatter { + private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + private static final String FORMAT = "[{0}] {1}"; + + @Override + public String format(LogRecord record) { + String string = formatMessage(record); + if (string.isEmpty()) { + return LINE_SEPARATOR; + } + + StringBuilder message = new StringBuilder(); + message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string)); + message.append(LINE_SEPARATOR); + return message.toString(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/comphenix/protocol/events/PacketMetadata.java b/src/main/java/com/comphenix/protocol/events/PacketMetadata.java index 49a5da35..9a0696ff 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketMetadata.java +++ b/src/main/java/com/comphenix/protocol/events/PacketMetadata.java @@ -1,143 +1,143 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2017 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.events; - -import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; - -import org.apache.commons.lang.Validate; - -/** - * Stores and retrieves metadata for applicable packet objects. - * @author dmulloy2 - */ -class PacketMetadata { - - private static class MetaObject { - private final String key; - private final T value; - - private MetaObject(String key, T value) { - this.key = key; - this.value = value; - } - - @Override - public int hashCode() { - return Objects.hash(key, value); - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - - if (o instanceof MetaObject) { - MetaObject that = (MetaObject) o; - return that.key.equals(this.key) && - that.value.equals(this.value); - } - - return false; - } - - @Override - public String toString() { - return "MetaObject[" + key + "=" + value + "]"; - } - } - - // Packet meta cache - private static Cache> META_CACHE; - - public static Optional get(Object packet, String key) { - Validate.notNull(key, "Null keys are not permitted!"); - - if (META_CACHE == null) { - return Optional.empty(); - } - - List meta = META_CACHE.getIfPresent(packet); - if (meta == null) { - return Optional.empty(); - } - - for (MetaObject object : meta) { - if (object.key.equals(key)) { - return Optional.of((T) object.value); - } - } - - return Optional.empty(); - } - - private static void createCache() { - META_CACHE = CacheBuilder - .newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES) - .build(); - } - - public static void set(Object packet, String key, T value) { - Validate.notNull(key, "Null keys are not permitted!"); - - if (META_CACHE == null) { - createCache(); - } - - List packetMeta; - - try { - packetMeta = META_CACHE.get(packet, ArrayList::new); - } catch (ExecutionException ex) { - // Not possible, but let's humor the array list constructor having an issue - packetMeta = new ArrayList<>(); - } - - packetMeta.removeIf(meta -> meta.key.equals(key)); - packetMeta.add(new MetaObject<>(key, value)); - META_CACHE.put(packet, packetMeta); - } - - public static Optional remove(Object packet, String key) { - Validate.notNull(key, "Null keys are not permitted!"); - - if (META_CACHE == null) { - return Optional.empty(); - } - - List packetMeta = META_CACHE.getIfPresent(packet); - if (packetMeta == null) { - return Optional.empty(); - } - - Optional value = Optional.empty(); - Iterator iter = packetMeta.iterator(); - while (iter.hasNext()) { - MetaObject meta = iter.next(); - if (meta.key.equals(key)) { - value = Optional.of((T) meta.value); - iter.remove(); - } - } - - return value; - } -} +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2017 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.events; + +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import org.apache.commons.lang.Validate; + +/** + * Stores and retrieves metadata for applicable packet objects. + * @author dmulloy2 + */ +class PacketMetadata { + + private static class MetaObject { + private final String key; + private final T value; + + private MetaObject(String key, T value) { + this.key = key; + this.value = value; + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + + if (o instanceof MetaObject) { + MetaObject that = (MetaObject) o; + return that.key.equals(this.key) && + that.value.equals(this.value); + } + + return false; + } + + @Override + public String toString() { + return "MetaObject[" + key + "=" + value + "]"; + } + } + + // Packet meta cache + private static Cache> META_CACHE; + + public static Optional get(Object packet, String key) { + Validate.notNull(key, "Null keys are not permitted!"); + + if (META_CACHE == null) { + return Optional.empty(); + } + + List meta = META_CACHE.getIfPresent(packet); + if (meta == null) { + return Optional.empty(); + } + + for (MetaObject object : meta) { + if (object.key.equals(key)) { + return Optional.of((T) object.value); + } + } + + return Optional.empty(); + } + + private static void createCache() { + META_CACHE = CacheBuilder + .newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + } + + public static void set(Object packet, String key, T value) { + Validate.notNull(key, "Null keys are not permitted!"); + + if (META_CACHE == null) { + createCache(); + } + + List packetMeta; + + try { + packetMeta = META_CACHE.get(packet, ArrayList::new); + } catch (ExecutionException ex) { + // Not possible, but let's humor the array list constructor having an issue + packetMeta = new ArrayList<>(); + } + + packetMeta.removeIf(meta -> meta.key.equals(key)); + packetMeta.add(new MetaObject<>(key, value)); + META_CACHE.put(packet, packetMeta); + } + + public static Optional remove(Object packet, String key) { + Validate.notNull(key, "Null keys are not permitted!"); + + if (META_CACHE == null) { + return Optional.empty(); + } + + List packetMeta = META_CACHE.getIfPresent(packet); + if (packetMeta == null) { + return Optional.empty(); + } + + Optional value = Optional.empty(); + Iterator iter = packetMeta.iterator(); + while (iter.hasNext()) { + MetaObject meta = iter.next(); + if (meta.key.equals(key)) { + value = Optional.of((T) meta.value); + iter.remove(); + } + } + + return value; + } +} diff --git a/src/main/java/com/comphenix/protocol/reflect/cloning/JavaOptionalCloner.java b/src/main/java/com/comphenix/protocol/reflect/cloning/JavaOptionalCloner.java index 65847cb2..f8ae0730 100644 --- a/src/main/java/com/comphenix/protocol/reflect/cloning/JavaOptionalCloner.java +++ b/src/main/java/com/comphenix/protocol/reflect/cloning/JavaOptionalCloner.java @@ -1,43 +1,43 @@ -/** - * (c) 2018 dmulloy2 - */ -package com.comphenix.protocol.reflect.cloning; - -import java.util.Optional; -import java.util.OptionalInt; - -/** - * A cloner that can clone Java Optional objects - * @author dmulloy2 - */ -public class JavaOptionalCloner implements Cloner { - protected Cloner wrapped; - - public JavaOptionalCloner(Cloner wrapped) { - this.wrapped = wrapped; - } - - @Override - public boolean canClone(Object source) { - return source instanceof Optional || source instanceof OptionalInt; - } - - @Override - public Object clone(Object source) { - if (source instanceof Optional) { - Optional optional = (Optional) source; - return optional.map(o -> wrapped.clone(o)); - } else if (source instanceof OptionalInt) { - // why Java felt the need to make each optional class distinct is beyond me - // like why couldn't they have given us at least a common interface or something - OptionalInt optional = (OptionalInt) source; - return optional.isPresent() ? OptionalInt.of(optional.getAsInt()) : OptionalInt.empty(); - } - - return null; - } - - public Cloner getWrapped() { - return wrapped; - } -} +/** + * (c) 2018 dmulloy2 + */ +package com.comphenix.protocol.reflect.cloning; + +import java.util.Optional; +import java.util.OptionalInt; + +/** + * A cloner that can clone Java Optional objects + * @author dmulloy2 + */ +public class JavaOptionalCloner implements Cloner { + protected Cloner wrapped; + + public JavaOptionalCloner(Cloner wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean canClone(Object source) { + return source instanceof Optional || source instanceof OptionalInt; + } + + @Override + public Object clone(Object source) { + if (source instanceof Optional) { + Optional optional = (Optional) source; + return optional.map(o -> wrapped.clone(o)); + } else if (source instanceof OptionalInt) { + // why Java felt the need to make each optional class distinct is beyond me + // like why couldn't they have given us at least a common interface or something + OptionalInt optional = (OptionalInt) source; + return optional.isPresent() ? OptionalInt.of(optional.getAsInt()) : OptionalInt.empty(); + } + + return null; + } + + public Cloner getWrapped() { + return wrapped; + } +} diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 73ec4d63..afeb99af 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1,1657 +1,1657 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * 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.utility; - -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLogger; -import com.comphenix.protocol.injector.BukkitUnwrapper; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; -import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.wrappers.EnumWrappers; -import io.netty.buffer.Unpooled; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -/** - * Methods and constants specifically used in conjuction with reflecting Minecraft object. - * - * @author Kristian - */ -public final class MinecraftReflection { - - private static final ClassSource CLASS_SOURCE = ClassSource.fromClassLoader(); - - /** - * Regular expression that matches a canonical Java class. - */ - private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; - private static final String MINECRAFT_CLASS_NAME_REGEX = "net\\.minecraft\\." + CANONICAL_REGEX; - - /** - * Represents a regular expression that will match the version string in a package: org.bukkit.craftbukkit.v1_6_R2 -> - * v1_6_R2 - */ - private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)"); - - // Cache of getBukkitEntity - private static final Map, MethodAccessor> BUKKIT_ENTITY_CACHE = new HashMap<>(); - - /** - * The Entity package in Forge 1.5.2 - */ - private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity"; - - // Package private for the purpose of unit testing - static CachedPackage minecraftPackage; - static CachedPackage craftbukkitPackage; - static CachedPackage libraryPackage; - - /** - * Regular expression computed dynamically. - */ - private static String DYNAMIC_PACKAGE_MATCHER = null; - /** - * The package name of all the classes that belongs to the native code in Minecraft. - */ - private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; - private static String MINECRAFT_FULL_PACKAGE = null; - private static String CRAFTBUKKIT_PACKAGE = null; - - // fuzzy matcher for minecraft class objects - private static AbstractFuzzyMatcher> fuzzyMatcher; - // The NMS version - private static String packageVersion; - // net.minecraft.server - private static Class itemStackArrayClass; - // Whether we are using netty - private static Boolean cachedWatcherObject; - - // ---- ItemStack conversions - private static Object itemStackAir = null; - private static Boolean nullEnforced = null; - - private static MethodAccessor asNMSCopy = null; - private static MethodAccessor asCraftMirror = null; - private static MethodAccessor isEmpty = null; - - private MinecraftReflection() { - // No need to make this constructable. - } - - /** - * Retrieve a regular expression that can match Minecraft package objects. - * - * @return Minecraft package matcher. - */ - public static String getMinecraftObjectRegex() { - if (DYNAMIC_PACKAGE_MATCHER == null) { - getMinecraftPackage(); - } - return DYNAMIC_PACKAGE_MATCHER; - } - - /** - * Retrieve a abstract fuzzy class matcher for Minecraft objects. - * - * @return A matcher for Minecraft objects. - */ - public static AbstractFuzzyMatcher> getMinecraftObjectMatcher() { - if (fuzzyMatcher == null) { - fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex()); - } - return fuzzyMatcher; - } - - /** - * Retrieve the name of the Minecraft server package. - * - * @return Full canonical name of the Minecraft server package. - */ - public static String getMinecraftPackage() { - // Speed things up - if (MINECRAFT_FULL_PACKAGE != null) { - return MINECRAFT_FULL_PACKAGE; - } - - try { - // get the bukkit version we're running on - Server craftServer = Bukkit.getServer(); - CRAFTBUKKIT_PACKAGE = craftServer.getClass().getPackage().getName(); - - // Parse the package version - Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); - if (packageMatcher.matches()) { - packageVersion = packageMatcher.group(1); - } else { - MinecraftVersion version = new MinecraftVersion(craftServer); - - // Just assume R1 - it's probably fine (warn anyway) - packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; - ProtocolLogger.log(Level.SEVERE, "Assuming package version: " + packageVersion); - } - - if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { - // total rework of the NMS structure in 1.17 (at least there's no versioning) - MINECRAFT_FULL_PACKAGE = MINECRAFT_PREFIX_PACKAGE = "net.minecraft"; - setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); - } else { - // extract the server version from the return type of "getHandle" in CraftEntity - Method getHandle = getCraftEntityClass().getMethod("getHandle"); - MINECRAFT_FULL_PACKAGE = getHandle.getReturnType().getPackage().getName(); - - // Pretty important invariant - if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { - // See if we got the Forge entity package - if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) { - // Use the standard NMS versioned package - MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion); - } else { - // Assume they're the same instead - MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE; - } - - // The package is usually flat, so go with that assumption - String matcher = - (MINECRAFT_PREFIX_PACKAGE.length() > 0 ? Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX; - - // We'll still accept the default location, however - setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_CLASS_NAME_REGEX + ")"); - - } else { - // Use the standard matcher - setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); - } - } - - return MINECRAFT_FULL_PACKAGE; - } catch (NoSuchMethodException exception) { - throw new IllegalStateException("Cannot find getHandle() in CraftEntity", exception); - } - } - - /** - * Retrieve the package version of the underlying CraftBukkit server. - * - * @return The craftbukkit package version. - */ - public static String getPackageVersion() { - getMinecraftPackage(); - return packageVersion; - } - - /** - * Update the dynamic package matcher. - * - * @param regex - the Minecraft package regex. - */ - private static void setDynamicPackageMatcher(String regex) { - DYNAMIC_PACKAGE_MATCHER = regex; - // Ensure that the matcher is regenerated - fuzzyMatcher = null; - } - - /** - * Used during debugging and testing. - * - * @param minecraftPackage - the current Minecraft package. - * @param craftBukkitPackage - the current CraftBukkit package. - */ - static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { - MINECRAFT_FULL_PACKAGE = minecraftPackage; - CRAFTBUKKIT_PACKAGE = craftBukkitPackage; - - // Make sure it exists - if (getMinecraftServerClass() == null) { - throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage); - } - - // Standard matcher - setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); - } - - /** - * Retrieve the name of the root CraftBukkit package. - * - * @return Full canonical name of the root CraftBukkit package. - */ - public static String getCraftBukkitPackage() { - // Ensure it has been initialized - if (CRAFTBUKKIT_PACKAGE == null) { - getMinecraftPackage(); - } - - return CRAFTBUKKIT_PACKAGE; - } - - /** - * Dynamically retrieve the Bukkit entity from a given entity. - * - * @param nmsObject - the NMS entity. - * @return A bukkit entity. - * @throws RuntimeException If we were unable to retrieve the Bukkit entity. - */ - public static Object getBukkitEntity(Object nmsObject) { - if (nmsObject == null) { - return null; - } - - // We will have to do this dynamically, unfortunately - try { - Class clazz = nmsObject.getClass(); - MethodAccessor accessor = BUKKIT_ENTITY_CACHE.get(clazz); - - if (accessor == null) { - MethodAccessor created = Accessors.getMethodAccessor(clazz, "getBukkitEntity"); - accessor = BUKKIT_ENTITY_CACHE.putIfAbsent(clazz, created); - - // We won the race - if (accessor == null) { - accessor = created; - } - } - - return accessor.invoke(nmsObject); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot get Bukkit entity from " + nmsObject, e); - } - } - - /** - * Retrieve the Bukkit player from a given PlayerConnection. - * - * @param playerConnection The PlayerConnection. - * @return A bukkit player. - * @throws RuntimeException If we were unable to retrieve the Bukkit player. - */ - public static Player getBukkitPlayerFromConnection(Object playerConnection) { - try { - return (Player) getBukkitEntity(MinecraftFields.getPlayerFromConnection(playerConnection)); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot get Bukkit entity from connection " + playerConnection, e); - } - } - - /** - * Determine if a given object can be found within the package net.minecraft.server. - * - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isMinecraftObject(Object obj) { - if (obj == null) { - return false; - } - - // Doesn't matter if we don't check for the version here - return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); - } - - /** - * Determine if the given class is found within the package net.minecraft.server, or any equivalent package. - * - * @param clazz - the class to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isMinecraftClass(Class clazz) { - if (clazz == null) { - throw new IllegalArgumentException("clazz cannot be NULL."); - } - - return getMinecraftObjectMatcher().isMatch(clazz, null); - } - - /** - * Determine if a given object is found in net.minecraft.server, and has the given name. - * - * @param obj - the object to test. - * @param className - the class name to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isMinecraftObject(Object obj, String className) { - if (obj == null) { - return false; - } - - String javaName = obj.getClass().getName(); - return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); - } - - /** - * Determine if a given Object is compatible with a given Class. That is, whether or not the Object is an instance of - * that Class or one of its subclasses. If either is null, false is returned. - * - * @param clazz Class to test for, may be null - * @param object the Object to test, may be null - * @return True if it is, false if not - * @see Class#isAssignableFrom(Class) - */ - public static boolean is(Class clazz, Object object) { - if (clazz == null || object == null) { - return false; - } - - return clazz.isAssignableFrom(object.getClass()); - } - - /** - * Equivalent to {@link #is(Class, Object)} but we don't call getClass again - */ - public static boolean is(Class clazz, Class test) { - if (clazz == null || test == null) { - return false; - } - - return clazz.isAssignableFrom(test); - } - - /** - * Determine if a given object is a BlockPosition. - * - * @param obj - the object to test. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isBlockPosition(Object obj) { - return is(getBlockPositionClass(), obj); - } - - /** - * Determine if the given object is an NMS ChunkCoordIntPar. - * - * @param obj - the object. - * @return TRUE if it can, FALSE otherwise. - */ - public static boolean isChunkCoordIntPair(Object obj) { - return is(getChunkCoordIntPair(), obj); - } - - /** - * Determine if the given object is actually a Minecraft packet. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isPacketClass(Object obj) { - return is(getPacketClass(), obj); - } - - /** - * Determine if the given object is assignable to a NetServerHandler (PlayerConnection) - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isServerHandler(Object obj) { - return is(getPlayerConnectionClass(), obj); - } - - /** - * Determine if the given object is actually a Minecraft packet. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isMinecraftEntity(Object obj) { - return is(getEntityClass(), obj); - } - - /** - * Determine if the given object is a NMS ItemStack. - * - * @param value - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isItemStack(Object value) { - return is(getItemStackClass(), value); - } - - /** - * Determine if the given object is a CraftPlayer class. - * - * @param value - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isCraftPlayer(Object value) { - return is(getCraftPlayerClass(), value); - } - - /** - * Determine if the given object is a Minecraft player entity. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isMinecraftPlayer(Object obj) { - return is(getEntityPlayerClass(), obj); - } - - /** - * Determine if the given object is a data watcher object. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isDataWatcher(Object obj) { - return is(getDataWatcherClass(), obj); - } - - /** - * Determine if the given object is an IntHashMap object. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isIntHashMap(Object obj) { - return is(getIntHashMapClass(), obj); - } - - /** - * Determine if the given object is a CraftItemStack instancey. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isCraftItemStack(Object obj) { - return is(getCraftItemStackClass(), obj); - } - - /** - * Retrieve the EntityPlayer (NMS) class. - * - * @return The entity class. - */ - public static Class getEntityPlayerClass() { - try { - return getMinecraftClass("server.level.EntityPlayer", "server.level.ServerPlayer", "EntityPlayer"); - } catch (RuntimeException e) { - try { - // Grab CraftPlayer's handle - Method getHandle = FuzzyReflection - .fromClass(getCraftBukkitClass("entity.CraftPlayer")) - .getMethodByName("getHandle"); - - // EntityPlayer is the return type - return setMinecraftClass("EntityPlayer", getHandle.getReturnType()); - } catch (IllegalArgumentException e1) { - throw new RuntimeException("Could not find EntityPlayer class.", e1); - } - } - } - - /** - * Retrieve the EntityHuman class. - * - * @return The entity human class. - */ - public static Class getEntityHumanClass() { - // Assume its the direct superclass - return getEntityPlayerClass().getSuperclass(); - } - - /** - * Retrieve the GameProfile class. - * - * @return The game profile class. - */ - public static Class getGameProfileClass() { - return getClass("com.mojang.authlib.GameProfile"); - } - - /** - * Retrieve the entity (NMS) class. - * - * @return The entity class. - */ - public static Class getEntityClass() { - try { - return getMinecraftClass("world.entity.Entity", "Entity"); - } catch (RuntimeException e) { - return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle"); - } - } - - /** - * Retrieve the CraftChatMessage. - * - * @return The CraftChatMessage class. - */ - public static Class getCraftChatMessage() { - return getCraftBukkitClass("util.CraftChatMessage"); - } - - /** - * Retrieve the WorldServer (NMS) class. - * - * @return The WorldServer class. - */ - public static Class getWorldServerClass() { - try { - return getMinecraftClass("server.level.WorldServer", "server.level.ServerLevel", "WorldServer"); - } catch (RuntimeException e) { - return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle"); - } - } - - /** - * Retrieve the World (NMS) class. - * - * @return The world class. - */ - public static Class getNmsWorldClass() { - try { - return getMinecraftClass("world.level.World", "world.level.Level", "World"); - } catch (RuntimeException e) { - return setMinecraftClass("World", getWorldServerClass().getSuperclass()); - } - } - - /** - * Fallback on the return value of a named method in order to get a NMS class. - * - * @param nmsClass - the expected name of the Minecraft class. - * @param craftClass - a CraftBukkit class to look at. - * @param methodName - the method we will use. - * @return The return value of this method, which will be saved to the package cache. - */ - private static Class fallbackMethodReturn(String nmsClass, String craftClass, String methodName) { - Class result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)) - .getMethodByName(methodName) - .getReturnType(); - // Save the result - return setMinecraftClass(nmsClass, result); - } - - /** - * Retrieve the packet class. - * - * @return The packet class. - */ - public static Class getPacketClass() { - return getMinecraftClass("network.protocol.Packet", "Packet"); - } - - public static Class getByteBufClass() { - return getClass("io.netty.buffer.ByteBuf"); - } - - /** - * Retrieve the EnumProtocol class. - * - * @return The Enum protocol class. - */ - public static Class getEnumProtocolClass() { - return getMinecraftClass("network.EnumProtocol", "network.ConnectionProtocol", "EnumProtocol"); - } - - /** - * Retrieve the IChatBaseComponent class. - * - * @return The IChatBaseComponent. - */ - public static Class getIChatBaseComponentClass() { - return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "network.chat.Component", "IChatBaseComponent"); - } - - public static Class getIChatBaseComponentArrayClass() { - return getArrayClass(getIChatBaseComponentClass()); - } - - /** - * Retrieve the NMS chat component text class. - * - * @return The chat component class. - */ - public static Class getChatComponentTextClass() { - return getMinecraftClass("network.chat.ChatComponentText", "network.chat.TextComponent", "ChatComponentText"); - } - - /** - * Attempt to find the ChatSerializer class. - * - * @return The serializer class. - * @throws IllegalStateException If the class could not be found or deduced. - */ - public static Class getChatSerializerClass() { - return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer"); - } - - /** - * Retrieve the ServerPing class. - * - * @return The ServerPing class. - */ - public static Class getServerPingClass() { - return getMinecraftClass("network.protocol.status.ServerPing", "network.protocol.status.ServerStatus", "ServerPing"); - } - - /** - * Retrieve the ServerPingServerData class. - * - * @return The ServerPingServerData class. - */ - public static Class getServerPingServerDataClass() { - return getMinecraftClass("network.protocol.status.ServerPing$ServerData", "network.protocol.status.ServerStatus$Version", "ServerPing$ServerData"); - } - - /** - * Retrieve the ServerPingPlayerSample class. - * - * @return The ServerPingPlayerSample class. - */ - public static Class getServerPingPlayerSampleClass() { - return getMinecraftClass( - "network.protocol.status.ServerPing$ServerPingPlayerSample", - "network.protocol.status.ServerStatus$Players", - "ServerPing$ServerPingPlayerSample"); - } - - /** - * Retrieve the MinecraftServer class. - * - * @return MinecraftServer class. - */ - public static Class getMinecraftServerClass() { - try { - return getMinecraftClass("server.MinecraftServer", "MinecraftServer"); - } catch (RuntimeException e) { - // Reset cache and try again - setMinecraftClass("MinecraftServer", null); - - useFallbackServer(); - return getMinecraftClass("MinecraftServer"); - } - } - - /** - * Retrieve the NMS statistics class. - * - * @return The statistics class. - */ - public static Class getStatisticClass() { - return getMinecraftClass("stats.Statistic", "stats.Stat", "Statistic"); - } - - /** - * Retrieve the NMS statistic list class. - * - * @return The statistic list class. - */ - public static Class getStatisticListClass() { - return getMinecraftClass("stats.StatisticList", "stats.Stats", "StatisticList"); - } - - /** - * Retrieve the player list class (or ServerConfigurationManager), - * - * @return The player list class. - */ - public static Class getPlayerListClass() { - try { - return getMinecraftClass("server.players.PlayerList", "PlayerList"); - } catch (RuntimeException e) { - // Reset cache and try again - setMinecraftClass("PlayerList", null); - - useFallbackServer(); - return getMinecraftClass("PlayerList"); - } - } - - /** - * Retrieve the PlayerConnection class. - * - * @return The PlayerConnection class. - */ - public static Class getPlayerConnectionClass() { - return getMinecraftClass("server.network.PlayerConnection", "server.network.ServerGamePacketListenerImpl", "PlayerConnection"); - } - - /** - * Retrieve the NetworkManager class. - * - * @return The NetworkManager class. - */ - public static Class getNetworkManagerClass() { - return getMinecraftClass("network.NetworkManager", "network.Connection", "NetworkManager"); - } - - /** - * Retrieve the NMS ItemStack class. - * - * @return The ItemStack class. - */ - public static Class getItemStackClass() { - try { - return getMinecraftClass("world.item.ItemStack", "ItemStack"); - } catch (RuntimeException e) { - // Use the handle reference - return setMinecraftClass("ItemStack", FuzzyReflection.fromClass(getCraftItemStackClass(), true) - .getFieldByName("handle") - .getType()); - } - } - - /** - * Retrieve the Block (NMS) class. - * - * @return Block (NMS) class. - */ - public static Class getBlockClass() { - return getMinecraftClass("world.level.block.Block", "Block"); - } - - public static Class getItemClass() { - return getNullableNMS("world.item.Item", "Item"); - } - - public static Class getFluidTypeClass() { - return getNullableNMS("world.level.material.FluidType", "world.level.material.Fluid", "FluidType"); - } - - public static Class getParticleTypeClass() { - return getNullableNMS("core.particles.ParticleType", "core.particles.SimpleParticleType", "ParticleType"); - } - - /** - * Retrieve the WorldType class. - * - * @return The WorldType class. - */ - public static Class getWorldTypeClass() { - return getMinecraftClass("WorldType"); - } - - /** - * Retrieve the DataWatcher class. - * - * @return The DataWatcher class. - */ - public static Class getDataWatcherClass() { - return getMinecraftClass("network.syncher.DataWatcher", "network.syncher.SynchedEntityData", "DataWatcher"); - } - - /** - * Retrieves the BlockPosition class. - * - * @return The BlockPosition class. - */ - public static Class getBlockPositionClass() { - return getMinecraftClass("core.BlockPosition", "core.BlockPos", "BlockPosition"); - } - - /** - * Retrieves the Vec3D class. - * - * @return The Vec3D class. - */ - public static Class getVec3DClass() { - return getMinecraftClass("world.phys.Vec3D", "world.phys.Vec3", "Vec3D"); - } - - /** - * Retrieve the ChunkCoordIntPair class. - * - * @return The ChunkCoordIntPair class. - */ - public static Class getChunkCoordIntPair() { - return getMinecraftClass("world.level.ChunkCoordIntPair", "world.level.ChunkPos", "ChunkCoordIntPair"); - } - - /** - * Retrieve the DataWatcher Item class. - * - * @return The class - */ - public static Class getDataWatcherItemClass() { - return getMinecraftClass("network.syncher.DataWatcher$Item", "network.syncher.SynchedEntityData$DataItem", "DataWatcher$Item", "DataWatcher$WatchableObject"); - } - - public static Class getDataWatcherObjectClass() { - return getNullableNMS("network.syncher.DataWatcherObject", "network.syncher.EntityDataAccessor", "DataWatcherObject"); - } - - public static boolean watcherObjectExists() { - if (cachedWatcherObject == null) { - cachedWatcherObject = getDataWatcherObjectClass() != null; - } - - return cachedWatcherObject; - } - - public static Class getDataWatcherSerializerClass() { - return getNullableNMS("network.syncher.DataWatcherSerializer", "network.syncher.EntityDataSerializer", "DataWatcherSerializer"); - } - - public static Class getDataWatcherRegistryClass() { - return getMinecraftClass("network.syncher.DataWatcherRegistry", "network.syncher.EntityDataSerializers", "DataWatcherRegistry"); - } - - public static Class getMinecraftKeyClass() { - return getMinecraftClass("resources.MinecraftKey", "resources.ResourceLocation", "MinecraftKey"); - } - - public static Class getMobEffectListClass() { - return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect"); - } - - public static Class getSoundEffectClass() { - return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect"); - } - - /** - * Retrieve the ServerConnection abstract class. - * - * @return The ServerConnection class. - */ - public static Class getServerConnectionClass() { - return getMinecraftClass("server.network.ServerConnection", "server.network.ServerConnectionListener", "ServerConnection"); - } - - /** - * Retrieve the NBT base class. - * - * @return The NBT base class. - */ - public static Class getNBTBaseClass() { - return getMinecraftClass("nbt.NBTBase", "nbt.Tag", "NBTBase"); - } - - /** - * Retrieve the NBT read limiter class. - * - * @return The NBT read limiter. - */ - public static Class getNBTReadLimiterClass() { - return getMinecraftClass("nbt.NBTReadLimiter", "nbt.NbtAccounter", "NBTReadLimiter"); - } - - /** - * Retrieve the NBT Compound class. - * - * @return The NBT Compond class. - */ - public static Class getNBTCompoundClass() { - return getMinecraftClass("nbt.NBTTagCompound", "nbt.CompoundTag", "NBTTagCompound"); - } - - /** - * Retrieve the EntityTracker (NMS) class. - * - * @return EntityTracker class. - */ - public static Class getEntityTrackerClass() { - return getMinecraftClass("server.level.PlayerChunkMap$EntityTracker", "server.level.ChunkMap$TrackedEntity", "EntityTracker"); - } - - /** - * Retrieve the attribute snapshot class. - *

- * This stores the final value of an attribute, along with all the associated computational steps. - * - * @return The attribute snapshot class. - */ - public static Class getAttributeSnapshotClass() { - return getMinecraftClass( - "network.protocol.game.PacketPlayOutUpdateAttributes$AttributeSnapshot", - "network.protocol.game.ClientboundUpdateAttributesPacket$AttributeSnapshot", - "AttributeSnapshot", - "PacketPlayOutUpdateAttributes$AttributeSnapshot"); - } - - /** - * Retrieve the IntHashMap class. - * - * @return IntHashMap class. - */ - public static Class getIntHashMapClass() { - return getNullableNMS("IntHashMap"); - } - - /** - * Retrieve the attribute modifier class. - * - * @return Attribute modifier class. - */ - public static Class getAttributeModifierClass() { - return getMinecraftClass("world.entity.ai.attributes.AttributeModifier", "AttributeModifier"); - } - - /** - * Retrieve the net.minecraft.server.MobEffect class. - * - * @return The mob effect class. - */ - public static Class getMobEffectClass() { - return getMinecraftClass("world.effect.MobEffect", "world.effect.MobEffectInstance", "MobEffect"); - } - - /** - * Retrieve the packet data serializer class that overrides ByteBuf. - * - * @return The data serializer class. - */ - public static Class getPacketDataSerializerClass() { - return getMinecraftClass("network.PacketDataSerializer", "network.FriendlyByteBuf", "PacketDataSerializer"); - } - - /** - * Retrieve the NBTCompressedStreamTools class. - * - * @return The NBTCompressedStreamTools class. - */ - public static Class getNbtCompressedStreamToolsClass() { - return getMinecraftClass("nbt.NBTCompressedStreamTools", "nbt.NbtIo", "NBTCompressedStreamTools"); - } - - /** - * Retrieve the NMS tile entity class. - * - * @return The tile entity class. - */ - public static Class getTileEntityClass() { - return getMinecraftClass("world.level.block.entity.TileEntity", "world.level.block.entity.BlockEntity", "TileEntity"); - } - - /** - * Retrieve the Gson class used by Minecraft. - * - * @return The Gson class. - */ - public static Class getMinecraftGsonClass() { - return getMinecraftLibraryClass("com.google.gson.Gson"); - } - - /** - * Retrieve the ItemStack[] class. - * - * @return The ItemStack[] class. - */ - public static Class getItemStackArrayClass() { - if (itemStackArrayClass == null) { - itemStackArrayClass = getArrayClass(getItemStackClass()); - } - return itemStackArrayClass; - } - - /** - * Retrieve the array class of a given component type. - * - * @param componentType - type of each element in the array. - * @return The class of the array. - */ - public static Class getArrayClass(Class componentType) { - // A bit of a hack, but it works - return Array.newInstance(componentType, 0).getClass(); - } - - /** - * Retrieve the CraftItemStack class. - * - * @return The CraftItemStack class. - */ - public static Class getCraftItemStackClass() { - return getCraftBukkitClass("inventory.CraftItemStack"); - } - - /** - * Retrieve the CraftPlayer class. - * - * @return CraftPlayer class. - */ - public static Class getCraftPlayerClass() { - return getCraftBukkitClass("entity.CraftPlayer"); - } - - /** - * Retrieve the CraftWorld class. - * - * @return The CraftWorld class. - */ - public static Class getCraftWorldClass() { - return getCraftBukkitClass("CraftWorld"); - } - - /** - * Retrieve the CraftEntity class. - * - * @return CraftEntity class. - */ - public static Class getCraftEntityClass() { - return getCraftBukkitClass("entity.CraftEntity"); - } - - /** - * Retrieve the CraftChatMessage introduced in 1.7.2 - * - * @return The CraftChatMessage class. - */ - public static Class getCraftMessageClass() { - return getCraftBukkitClass("util.CraftChatMessage"); - } - - /** - * Retrieve the PlayerInfoData class in 1.8. - * - * @return The PlayerInfoData class - */ - public static Class getPlayerInfoDataClass() { - return getMinecraftClass( - "network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", - "network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", - "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); - } - - /** - * Retrieves the entity use action class in 1.17. - * - * @return The EntityUseAction class - */ - public static Class getEnumEntityUseActionClass() { - Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); - FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true); - try { - return fuzzyReflection.getFieldByType("^.*(EnumEntityUseAction)").getType(); - } catch (IllegalArgumentException ignored) { - return fuzzyReflection.getFieldByType("^.*(Action)").getType(); - } - } - - /** - * Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17. - * - * @return a method accessor to get the actual use action - */ - public static MethodAccessor getEntityUseActionEnumMethodAccessor() { - FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true); - return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() - .returnTypeExact(EnumWrappers.getEntityUseActionClass()) - .build())); - } - - /** - * Get a field accessor for the hand in the wrapping EnumEntityUseAction in 1.17. - * - * @param enumEntityUseAction the object instance of the action, the field is not present in attack. - * @return a field accessor for the hand in the wrapping EnumEntityUseAction - */ - public static FieldAccessor getHandEntityUseActionEnumFieldAccessor(Object enumEntityUseAction) { - FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true); - return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder() - .typeExact(EnumWrappers.getHandClass()) - .build())); - } - - /** - * Get a field accessor for the vec3d in the wrapping EnumEntityUseAction in 1.17. - * - * @param enumEntityUseAction the object instance of the action, the field is not present in attack. - * @return a field accessor for the hand in the wrapping EnumEntityUseAction - */ - public static FieldAccessor getVec3EntityUseActionEnumFieldAccessor(Object enumEntityUseAction) { - FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true); - return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder() - .typeExact(MinecraftReflection.getVec3DClass()) - .build())); - } - - /** - * Determine if the given object is a PlayerInfoData. - * - * @param obj - the given object. - * @return TRUE if it is, FALSE otherwise. - */ - public static boolean isPlayerInfoData(Object obj) { - return is(getPlayerInfoDataClass(), obj); - } - - /** - * Retrieve the IBlockData class in 1.8. - * - * @return The IBlockData class - */ - public static Class getIBlockDataClass() { - return getMinecraftClass("world.level.block.state.IBlockData", "world.level.block.state.BlockState", "IBlockData"); - } - - /** - * Retrieve the MultiBlockChangeInfo class in 1.8. - * - * @return The MultiBlockChangeInfo class - */ - public static Class getMultiBlockChangeInfoClass() { - return getMinecraftClass("MultiBlockChangeInfo", "PacketPlayOutMultiBlockChange$MultiBlockChangeInfo"); - } - - /** - * Retrieve the MultiBlockChangeInfo array class in 1.8. - * - * @return The MultiBlockChangeInfo array class - */ - public static Class getMultiBlockChangeInfoArrayClass() { - return getArrayClass(getMultiBlockChangeInfoClass()); - } - - /** - * Retrieve the PacketPlayOutGameStateChange.a class, aka GameState in 1.16 - * - * @return The GameState class - */ - public static Class getGameStateClass() { - // it's called "a" so there's not a lot we can do to identify it - Class packetClass = PacketType.Play.Server.GAME_STATE_CHANGE.getPacketClass(); - return packetClass.getClasses()[0]; - } - - public static boolean signUpdateExists() { - return getNullableNMS("PacketPlayOutUpdateSign") != null; - } - - public static Class getNonNullListClass() { - return getMinecraftClass("core.NonNullList", "NonNullList"); - } - - public static MethodAccessor getNonNullListCreateAccessor() { - try { - Class nonNullListType = MinecraftReflection.getNonNullListClass(); - Method method = FuzzyReflection.fromClass(nonNullListType).getMethod(FuzzyMethodContract.newBuilder() - .returnTypeExact(nonNullListType) - .requireModifier(Modifier.STATIC) - .build()); - return Accessors.getMethodAccessor(method); - } catch (Exception ex) { - return null; - } - } - - public static Class getCraftSoundClass() { - return getCraftBukkitClass("CraftSound"); - } - - public static Class getSectionPositionClass() { - return getMinecraftClass("core.SectionPosition", "core.SectionPos", "SectionPosition"); - } - - /** - * Retrieves the Bukkit equivalent of a NMS ItemStack. This method should preserve NBT data and will never return null - * when supplied with a valid ItemStack. Empty ItemStacks are treated as AIR. - * - * @param generic NMS ItemStack - * @return The Bukkit equivalent - */ - public static ItemStack getBukkitItemStack(Object generic) { - if (generic == null) { - // Convert null to AIR - 1.11 behavior - return new ItemStack(Material.AIR); - } - - if (generic instanceof ItemStack) { - ItemStack bukkit = (ItemStack) generic; - - // They're probably looking for the CraftItemStack - // If it's one already our work is done - if (is(getCraftItemStackClass(), generic)) { - return bukkit; - } - - // If not, convert it to one - Object nmsStack = getMinecraftItemStack(bukkit); - return getBukkitItemStack(nmsStack); - } - - if (!is(getItemStackClass(), generic)) { - // We can't do anything with non-ItemStacks - throw new IllegalArgumentException(generic + " is not an ItemStack!"); - } - - try { - // Check null enforcement - 1.11 behavior - if (nullEnforced == null) { - isEmpty = Accessors.getMethodAccessor(getItemStackClass().getMethod("isEmpty")); - nullEnforced = true; - } - - if (nullEnforced) { - if ((boolean) isEmpty.invoke(generic)) { - return new ItemStack(Material.AIR); - } - } - } catch (ReflectiveOperationException ex) { - nullEnforced = false; - } - - if (asCraftMirror == null) { - try { - Method asMirrorMethod = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass()); - asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex); - } - } - - try { - // Convert to a craft mirror to preserve NBT data - return (ItemStack) asCraftMirror.invoke(nullEnforced, generic); - } catch (IllegalStateException ex) { - throw new RuntimeException("Failed to obtain craft mirror of " + generic, ex); - } - } - - /** - * Retrieves the NMS equivalent of a Bukkit ItemStack. This method will never return null and should preserve NBT - * data. Null inputs are treated as empty (AIR) ItemStacks. - * - * @param specific Bukkit ItemStack - * @return The NMS equivalent - */ - public static Object getMinecraftItemStack(ItemStack specific) { - if (asNMSCopy == null) { - try { - Method asNmsCopyMethod = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); - asNMSCopy = Accessors.getMethodAccessor(asNmsCopyMethod); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to obtain CraftItemStack.asNMSCopy", ex); - } - } - - if (is(getCraftItemStackClass(), specific)) { - // If it's already a CraftItemStack, use its handle - Object unwrapped = BukkitUnwrapper.getInstance().unwrapItem(specific); - if (unwrapped != null) { - return unwrapped; - } else { - if (itemStackAir == null) { - // Easiest way to get the Material.AIR ItemStack? - itemStackAir = getMinecraftItemStack(new ItemStack(Material.AIR)); - } - return itemStackAir; - } - } - - try { - // If not, grab a NMS copy - return asNMSCopy.invoke(null, specific); - } catch (IllegalStateException ex) { - throw new RuntimeException("Failed to make NMS copy of " + specific, ex); - } - } - - /** - * Retrieve the given class by name. - * - * @param className - name of the class. - * @return The class. - */ - private static Class getClass(String className) { - try { - return getClassSource().loadClass(className); - } catch (ClassNotFoundException e) { - throw new RuntimeException("Cannot find class " + className, e); - } - } - - /** - * Retrieve the class object of a specific CraftBukkit class. - * - * @param className - the specific CraftBukkit class. - * @return Class object. - * @throws RuntimeException If we are unable to find the given class. - */ - public static Class getCraftBukkitClass(String className) { - if (craftbukkitPackage == null) { - craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource()); - } - - return craftbukkitPackage.getPackageClass(className) - .orElseThrow(() -> new RuntimeException("Failed to find CraftBukkit class: " + className)); - } - - /** - * Retrieve the class object of a specific Minecraft class. - * - * @param className - the specific Minecraft class. - * @return Class object. - * @throws RuntimeException If we are unable to find the given class. - */ - public static Class getMinecraftClass(String className) { - if (minecraftPackage == null) { - minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); - } - - return minecraftPackage.getPackageClass(className) - .orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className)); - } - - public static Class getNullableNMS(String className, String... aliases) { - try { - return getMinecraftClass(className, aliases); - } catch (RuntimeException ex) { - return null; - } - } - - /** - * Set the class object for the specific Minecraft class. - * - * @param className - name of the Minecraft class. - * @param clazz - the new class object. - * @return The provided clazz object. - */ - private static Class setMinecraftClass(String className, Class clazz) { - if (minecraftPackage == null) { - minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); - } - - minecraftPackage.setPackageClass(className, clazz); - return clazz; - } - - /** - * Retrieve the current class source. - * - * @return The class source. - */ - private static ClassSource getClassSource() { - return CLASS_SOURCE; - } - - /** - * Retrieve the first class that matches a specified Minecraft name. - * - * @param className - the specific Minecraft class. - * @param aliases - alternative names for this Minecraft class. - * @return Class object. - * @throws RuntimeException If we are unable to find any of the given classes. - */ - public static Class getMinecraftClass(String className, String... aliases) { - if (minecraftPackage == null) { - minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); - } - - return minecraftPackage.getPackageClass(className).orElseGet(() -> { - Class resolved = null; - for (String alias : aliases) { - // try to resolve the class and stop searching if we found it - resolved = minecraftPackage.getPackageClass(alias).orElse(null); - if (resolved != null) { - break; - } - } - - // if we resolved the class cache it and return the result - if (resolved != null) { - minecraftPackage.setPackageClass(className, resolved); - return resolved; - } - - // unable to find the class - throw new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases))); - }); - } - - /** - * Retrieve the class object of a specific Minecraft library class. - * - * @param className - the specific library Minecraft class. - * @return Class object. - * @throws RuntimeException If we are unable to find the given class. - */ - public static Class getMinecraftLibraryClass(String className) { - if (libraryPackage == null) { - libraryPackage = new CachedPackage("", getClassSource()); - } - - return libraryPackage.getPackageClass(className) - .orElseThrow(() -> new RuntimeException("Failed to find class: " + className)); - } - - /** - * Set the class object for the specific library class. - * - * @param className - name of the Minecraft library class. - * @param clazz - the new class object. - * @return The provided clazz object. - */ - private static Class setMinecraftLibraryClass(String className, Class clazz) { - if (libraryPackage == null) { - libraryPackage = new CachedPackage("", getClassSource()); - } - - libraryPackage.setPackageClass(className, clazz); - return clazz; - } - - /** - * Dynamically retrieve the NetworkManager name. - * - * @return Name of the NetworkManager class. - */ - public static String getNetworkManagerName() { - return getNetworkManagerClass().getSimpleName(); - } - - /** - * Retrieve an instance of the packet data serializer wrapper. - * - * @param buffer - the buffer. - * @return The instance. - */ - public static Object getPacketDataSerializer(Object buffer) { - try { - // TODO: move this to MinecraftMethods, or at least, cache the constructor accessor - Class packetSerializer = getPacketDataSerializerClass(); - return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer); - } catch (Exception e) { - throw new RuntimeException("Cannot construct packet serializer.", e); - } - } - - public static Object createPacketDataSerializer(int initialSize) { - // validate the initial size - if (initialSize <= 0) { - initialSize = 256; - } - - Object buffer = Unpooled.buffer(initialSize); - return getPacketDataSerializer(buffer); - } - - public static Class getNbtTagTypes() { - return getMinecraftClass("nbt.NBTTagTypes", "nbt.TagTypes", "NBTTagTypes"); - } - - public static Class getChatDeserializer() { - return getMinecraftClass("util.ChatDeserializer", "util.GsonHelper", "ChatDeserializer"); - } - - public static Class getChatMutableComponentClass() { - return getMinecraftClass("network.chat.IChatMutableComponent", "network.chat.MutableComponent"); - } - - public static Class getDimensionManager() { - return getMinecraftClass("world.level.dimension.DimensionManager", "world.level.dimension.DimensionType", "DimensionManager"); - } - - public static Class getMerchantRecipeList() { - return getMinecraftClass("world.item.trading.MerchantRecipeList", "world.item.trading.MerchantOffers", "MerchantRecipeList"); - } - - public static Class getResourceKey() { - return getMinecraftClass("resources.ResourceKey", "ResourceKey"); - } - - public static Class getEntityTypes() { - return getMinecraftClass("world.entity.EntityTypes", "world.entity.EntityType", "EntityTypes"); - } - - public static Class getParticleParam() { - return getMinecraftClass("core.particles.ParticleParam", "core.particles.ParticleOptions", "ParticleParam"); - } - - public static Class getSectionPosition() { - return getMinecraftClass("core.SectionPosition", "core.SectionPos", "SectionPosition"); - } - - public static Class getChunkProviderServer() { - return getMinecraftClass("server.level.ChunkProviderServer", "server.level.ServerChunkCache", "ChunkProviderServer"); - } - - public static Class getPlayerChunkMap() { - return getMinecraftClass("server.level.PlayerChunkMap", "server.level.ChunkMap", "PlayerChunkMap"); - } - - public static Class getIRegistry() { - return getNullableNMS("core.IRegistry", "core.Registry", "IRegistry"); - } - - public static Class getAttributeBase() { - return getMinecraftClass("world.entity.ai.attributes.AttributeBase", "world.entity.ai.attributes.Attribute", "AttributeBase"); - } - - public static Class getProfilePublicKeyClass() { - return getMinecraftClass("world.entity.player.ProfilePublicKey"); - } - - public static Class getSaltedSignatureClass() { - try { - return getMinecraftClass("SaltedSignature"); - } catch (RuntimeException runtimeException) { - Class minecraftEncryption = getMinecraftClass("util.MinecraftEncryption", "util.Crypt", "MinecraftEncryption"); - FuzzyMethodContract constructorContract = FuzzyMethodContract.newBuilder() - .parameterCount(2) - .parameterExactType(Long.TYPE, 0) - .parameterExactType(byte[].class, 1) - .build(); - - for (Class subclass : minecraftEncryption.getClasses()) { - FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(subclass, true); - List> constructors = fuzzyReflection.getConstructorList(constructorContract); - - if (!constructors.isEmpty()) { - return setMinecraftClass("SaltedSignature", subclass); - } - } - - Class messageSigClass = getMinecraftClass("network.chat.MessageSignature", "MessageSignature"); - FuzzyClassContract signatureContract = FuzzyClassContract.newBuilder() - .constructor(FuzzyMethodContract.newBuilder() - .parameterCount(2) - .parameterSuperOf(Long.TYPE, 0) - .parameterSuperOf(byte[].class, 1) - .build()) - .build(); - - FuzzyFieldContract fuzzyFieldContract = FuzzyFieldContract.newBuilder() - .typeMatches(getMinecraftObjectMatcher().and(signatureContract)) - .build(); - - Class signatureClass = FuzzyReflection.fromClass(messageSigClass, true) - .getField(fuzzyFieldContract) - .getType(); - return setMinecraftClass("SaltedSignature", signatureClass); - } - } - - public static Class getProfilePublicKeyDataClass() { - return getProfilePublicKeyClass().getClasses()[0]; - } - - public static Class getFastUtilClass(String className) { - return getLibraryClass("it.unimi.dsi.fastutil." + className); - } - - public static Class getInt2ObjectMapClass() { - return getFastUtilClass("ints.Int2ObjectMap"); - } - - public static Class getIntArrayListClass() { - return getFastUtilClass("ints.IntArrayList"); - } - - public static Class getLibraryClass(String classname) { - try { - return getMinecraftLibraryClass(classname); - } catch (RuntimeException ex) { - Class clazz = getMinecraftLibraryClass("org.bukkit.craftbukkit.libs." + classname); - setMinecraftLibraryClass(classname, clazz); - return clazz; - } - } - - /** - * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. - */ - private static void useFallbackServer() { - // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) - Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")) - .getConstructor(FuzzyMethodContract.newBuilder() - .parameterMatches(getMinecraftObjectMatcher(), 0) - .parameterCount(2) - .build()); - Class[] params = selected.getParameterTypes(); - - // Jackpot - two classes at the same time! - setMinecraftClass("MinecraftServer", params[0]); - setMinecraftClass("PlayerList", params[1]); - } - - public static Class getLevelChunkPacketDataClass() { - return getNullableNMS("network.protocol.game.ClientboundLevelChunkPacketData"); - } - - public static Class getLightUpdatePacketDataClass() { - return getNullableNMS("network.protocol.game.ClientboundLightUpdatePacketData"); - } - - public static Class getBlockEntityTypeClass() { - return getMinecraftClass("world.level.block.entity.BlockEntityType", "world.level.block.entity.TileEntityTypes", "TileEntityTypes"); - } - - public static Class getBlockEntityInfoClass() { - try { - return getMinecraftClass("BlockEntityInfo"); - } catch (RuntimeException expected) { - Class infoClass = (Class) ((ParameterizedType) FuzzyReflection.fromClass(getLevelChunkPacketDataClass(), - true).getFieldListByType(List.class).get(0).getGenericType()).getActualTypeArguments()[0]; - - setMinecraftClass("BlockEntityInfo", infoClass); - - return infoClass; - } - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * 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.utility; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLogger; +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; +import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.wrappers.EnumWrappers; +import io.netty.buffer.Unpooled; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +/** + * Methods and constants specifically used in conjuction with reflecting Minecraft object. + * + * @author Kristian + */ +public final class MinecraftReflection { + + private static final ClassSource CLASS_SOURCE = ClassSource.fromClassLoader(); + + /** + * Regular expression that matches a canonical Java class. + */ + private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; + private static final String MINECRAFT_CLASS_NAME_REGEX = "net\\.minecraft\\." + CANONICAL_REGEX; + + /** + * Represents a regular expression that will match the version string in a package: org.bukkit.craftbukkit.v1_6_R2 -> + * v1_6_R2 + */ + private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)"); + + // Cache of getBukkitEntity + private static final Map, MethodAccessor> BUKKIT_ENTITY_CACHE = new HashMap<>(); + + /** + * The Entity package in Forge 1.5.2 + */ + private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity"; + + // Package private for the purpose of unit testing + static CachedPackage minecraftPackage; + static CachedPackage craftbukkitPackage; + static CachedPackage libraryPackage; + + /** + * Regular expression computed dynamically. + */ + private static String DYNAMIC_PACKAGE_MATCHER = null; + /** + * The package name of all the classes that belongs to the native code in Minecraft. + */ + private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server"; + private static String MINECRAFT_FULL_PACKAGE = null; + private static String CRAFTBUKKIT_PACKAGE = null; + + // fuzzy matcher for minecraft class objects + private static AbstractFuzzyMatcher> fuzzyMatcher; + // The NMS version + private static String packageVersion; + // net.minecraft.server + private static Class itemStackArrayClass; + // Whether we are using netty + private static Boolean cachedWatcherObject; + + // ---- ItemStack conversions + private static Object itemStackAir = null; + private static Boolean nullEnforced = null; + + private static MethodAccessor asNMSCopy = null; + private static MethodAccessor asCraftMirror = null; + private static MethodAccessor isEmpty = null; + + private MinecraftReflection() { + // No need to make this constructable. + } + + /** + * Retrieve a regular expression that can match Minecraft package objects. + * + * @return Minecraft package matcher. + */ + public static String getMinecraftObjectRegex() { + if (DYNAMIC_PACKAGE_MATCHER == null) { + getMinecraftPackage(); + } + return DYNAMIC_PACKAGE_MATCHER; + } + + /** + * Retrieve a abstract fuzzy class matcher for Minecraft objects. + * + * @return A matcher for Minecraft objects. + */ + public static AbstractFuzzyMatcher> getMinecraftObjectMatcher() { + if (fuzzyMatcher == null) { + fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex()); + } + return fuzzyMatcher; + } + + /** + * Retrieve the name of the Minecraft server package. + * + * @return Full canonical name of the Minecraft server package. + */ + public static String getMinecraftPackage() { + // Speed things up + if (MINECRAFT_FULL_PACKAGE != null) { + return MINECRAFT_FULL_PACKAGE; + } + + try { + // get the bukkit version we're running on + Server craftServer = Bukkit.getServer(); + CRAFTBUKKIT_PACKAGE = craftServer.getClass().getPackage().getName(); + + // Parse the package version + Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE); + if (packageMatcher.matches()) { + packageVersion = packageMatcher.group(1); + } else { + MinecraftVersion version = new MinecraftVersion(craftServer); + + // Just assume R1 - it's probably fine (warn anyway) + packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1"; + ProtocolLogger.log(Level.SEVERE, "Assuming package version: " + packageVersion); + } + + if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { + // total rework of the NMS structure in 1.17 (at least there's no versioning) + MINECRAFT_FULL_PACKAGE = MINECRAFT_PREFIX_PACKAGE = "net.minecraft"; + setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); + } else { + // extract the server version from the return type of "getHandle" in CraftEntity + Method getHandle = getCraftEntityClass().getMethod("getHandle"); + MINECRAFT_FULL_PACKAGE = getHandle.getReturnType().getPackage().getName(); + + // Pretty important invariant + if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) { + // See if we got the Forge entity package + if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) { + // Use the standard NMS versioned package + MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion); + } else { + // Assume they're the same instead + MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE; + } + + // The package is usually flat, so go with that assumption + String matcher = + (MINECRAFT_PREFIX_PACKAGE.length() > 0 ? Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX; + + // We'll still accept the default location, however + setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_CLASS_NAME_REGEX + ")"); + + } else { + // Use the standard matcher + setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); + } + } + + return MINECRAFT_FULL_PACKAGE; + } catch (NoSuchMethodException exception) { + throw new IllegalStateException("Cannot find getHandle() in CraftEntity", exception); + } + } + + /** + * Retrieve the package version of the underlying CraftBukkit server. + * + * @return The craftbukkit package version. + */ + public static String getPackageVersion() { + getMinecraftPackage(); + return packageVersion; + } + + /** + * Update the dynamic package matcher. + * + * @param regex - the Minecraft package regex. + */ + private static void setDynamicPackageMatcher(String regex) { + DYNAMIC_PACKAGE_MATCHER = regex; + // Ensure that the matcher is regenerated + fuzzyMatcher = null; + } + + /** + * Used during debugging and testing. + * + * @param minecraftPackage - the current Minecraft package. + * @param craftBukkitPackage - the current CraftBukkit package. + */ + static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) { + MINECRAFT_FULL_PACKAGE = minecraftPackage; + CRAFTBUKKIT_PACKAGE = craftBukkitPackage; + + // Make sure it exists + if (getMinecraftServerClass() == null) { + throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage); + } + + // Standard matcher + setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX); + } + + /** + * Retrieve the name of the root CraftBukkit package. + * + * @return Full canonical name of the root CraftBukkit package. + */ + public static String getCraftBukkitPackage() { + // Ensure it has been initialized + if (CRAFTBUKKIT_PACKAGE == null) { + getMinecraftPackage(); + } + + return CRAFTBUKKIT_PACKAGE; + } + + /** + * Dynamically retrieve the Bukkit entity from a given entity. + * + * @param nmsObject - the NMS entity. + * @return A bukkit entity. + * @throws RuntimeException If we were unable to retrieve the Bukkit entity. + */ + public static Object getBukkitEntity(Object nmsObject) { + if (nmsObject == null) { + return null; + } + + // We will have to do this dynamically, unfortunately + try { + Class clazz = nmsObject.getClass(); + MethodAccessor accessor = BUKKIT_ENTITY_CACHE.get(clazz); + + if (accessor == null) { + MethodAccessor created = Accessors.getMethodAccessor(clazz, "getBukkitEntity"); + accessor = BUKKIT_ENTITY_CACHE.putIfAbsent(clazz, created); + + // We won the race + if (accessor == null) { + accessor = created; + } + } + + return accessor.invoke(nmsObject); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get Bukkit entity from " + nmsObject, e); + } + } + + /** + * Retrieve the Bukkit player from a given PlayerConnection. + * + * @param playerConnection The PlayerConnection. + * @return A bukkit player. + * @throws RuntimeException If we were unable to retrieve the Bukkit player. + */ + public static Player getBukkitPlayerFromConnection(Object playerConnection) { + try { + return (Player) getBukkitEntity(MinecraftFields.getPlayerFromConnection(playerConnection)); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get Bukkit entity from connection " + playerConnection, e); + } + } + + /** + * Determine if a given object can be found within the package net.minecraft.server. + * + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isMinecraftObject(Object obj) { + if (obj == null) { + return false; + } + + // Doesn't matter if we don't check for the version here + return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE); + } + + /** + * Determine if the given class is found within the package net.minecraft.server, or any equivalent package. + * + * @param clazz - the class to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isMinecraftClass(Class clazz) { + if (clazz == null) { + throw new IllegalArgumentException("clazz cannot be NULL."); + } + + return getMinecraftObjectMatcher().isMatch(clazz, null); + } + + /** + * Determine if a given object is found in net.minecraft.server, and has the given name. + * + * @param obj - the object to test. + * @param className - the class name to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isMinecraftObject(Object obj, String className) { + if (obj == null) { + return false; + } + + String javaName = obj.getClass().getName(); + return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className); + } + + /** + * Determine if a given Object is compatible with a given Class. That is, whether or not the Object is an instance of + * that Class or one of its subclasses. If either is null, false is returned. + * + * @param clazz Class to test for, may be null + * @param object the Object to test, may be null + * @return True if it is, false if not + * @see Class#isAssignableFrom(Class) + */ + public static boolean is(Class clazz, Object object) { + if (clazz == null || object == null) { + return false; + } + + return clazz.isAssignableFrom(object.getClass()); + } + + /** + * Equivalent to {@link #is(Class, Object)} but we don't call getClass again + */ + public static boolean is(Class clazz, Class test) { + if (clazz == null || test == null) { + return false; + } + + return clazz.isAssignableFrom(test); + } + + /** + * Determine if a given object is a BlockPosition. + * + * @param obj - the object to test. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isBlockPosition(Object obj) { + return is(getBlockPositionClass(), obj); + } + + /** + * Determine if the given object is an NMS ChunkCoordIntPar. + * + * @param obj - the object. + * @return TRUE if it can, FALSE otherwise. + */ + public static boolean isChunkCoordIntPair(Object obj) { + return is(getChunkCoordIntPair(), obj); + } + + /** + * Determine if the given object is actually a Minecraft packet. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isPacketClass(Object obj) { + return is(getPacketClass(), obj); + } + + /** + * Determine if the given object is assignable to a NetServerHandler (PlayerConnection) + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isServerHandler(Object obj) { + return is(getPlayerConnectionClass(), obj); + } + + /** + * Determine if the given object is actually a Minecraft packet. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isMinecraftEntity(Object obj) { + return is(getEntityClass(), obj); + } + + /** + * Determine if the given object is a NMS ItemStack. + * + * @param value - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isItemStack(Object value) { + return is(getItemStackClass(), value); + } + + /** + * Determine if the given object is a CraftPlayer class. + * + * @param value - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isCraftPlayer(Object value) { + return is(getCraftPlayerClass(), value); + } + + /** + * Determine if the given object is a Minecraft player entity. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isMinecraftPlayer(Object obj) { + return is(getEntityPlayerClass(), obj); + } + + /** + * Determine if the given object is a data watcher object. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isDataWatcher(Object obj) { + return is(getDataWatcherClass(), obj); + } + + /** + * Determine if the given object is an IntHashMap object. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isIntHashMap(Object obj) { + return is(getIntHashMapClass(), obj); + } + + /** + * Determine if the given object is a CraftItemStack instancey. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isCraftItemStack(Object obj) { + return is(getCraftItemStackClass(), obj); + } + + /** + * Retrieve the EntityPlayer (NMS) class. + * + * @return The entity class. + */ + public static Class getEntityPlayerClass() { + try { + return getMinecraftClass("server.level.EntityPlayer", "server.level.ServerPlayer", "EntityPlayer"); + } catch (RuntimeException e) { + try { + // Grab CraftPlayer's handle + Method getHandle = FuzzyReflection + .fromClass(getCraftBukkitClass("entity.CraftPlayer")) + .getMethodByName("getHandle"); + + // EntityPlayer is the return type + return setMinecraftClass("EntityPlayer", getHandle.getReturnType()); + } catch (IllegalArgumentException e1) { + throw new RuntimeException("Could not find EntityPlayer class.", e1); + } + } + } + + /** + * Retrieve the EntityHuman class. + * + * @return The entity human class. + */ + public static Class getEntityHumanClass() { + // Assume its the direct superclass + return getEntityPlayerClass().getSuperclass(); + } + + /** + * Retrieve the GameProfile class. + * + * @return The game profile class. + */ + public static Class getGameProfileClass() { + return getClass("com.mojang.authlib.GameProfile"); + } + + /** + * Retrieve the entity (NMS) class. + * + * @return The entity class. + */ + public static Class getEntityClass() { + try { + return getMinecraftClass("world.entity.Entity", "Entity"); + } catch (RuntimeException e) { + return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle"); + } + } + + /** + * Retrieve the CraftChatMessage. + * + * @return The CraftChatMessage class. + */ + public static Class getCraftChatMessage() { + return getCraftBukkitClass("util.CraftChatMessage"); + } + + /** + * Retrieve the WorldServer (NMS) class. + * + * @return The WorldServer class. + */ + public static Class getWorldServerClass() { + try { + return getMinecraftClass("server.level.WorldServer", "server.level.ServerLevel", "WorldServer"); + } catch (RuntimeException e) { + return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle"); + } + } + + /** + * Retrieve the World (NMS) class. + * + * @return The world class. + */ + public static Class getNmsWorldClass() { + try { + return getMinecraftClass("world.level.World", "world.level.Level", "World"); + } catch (RuntimeException e) { + return setMinecraftClass("World", getWorldServerClass().getSuperclass()); + } + } + + /** + * Fallback on the return value of a named method in order to get a NMS class. + * + * @param nmsClass - the expected name of the Minecraft class. + * @param craftClass - a CraftBukkit class to look at. + * @param methodName - the method we will use. + * @return The return value of this method, which will be saved to the package cache. + */ + private static Class fallbackMethodReturn(String nmsClass, String craftClass, String methodName) { + Class result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass)) + .getMethodByName(methodName) + .getReturnType(); + // Save the result + return setMinecraftClass(nmsClass, result); + } + + /** + * Retrieve the packet class. + * + * @return The packet class. + */ + public static Class getPacketClass() { + return getMinecraftClass("network.protocol.Packet", "Packet"); + } + + public static Class getByteBufClass() { + return getClass("io.netty.buffer.ByteBuf"); + } + + /** + * Retrieve the EnumProtocol class. + * + * @return The Enum protocol class. + */ + public static Class getEnumProtocolClass() { + return getMinecraftClass("network.EnumProtocol", "network.ConnectionProtocol", "EnumProtocol"); + } + + /** + * Retrieve the IChatBaseComponent class. + * + * @return The IChatBaseComponent. + */ + public static Class getIChatBaseComponentClass() { + return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "network.chat.Component", "IChatBaseComponent"); + } + + public static Class getIChatBaseComponentArrayClass() { + return getArrayClass(getIChatBaseComponentClass()); + } + + /** + * Retrieve the NMS chat component text class. + * + * @return The chat component class. + */ + public static Class getChatComponentTextClass() { + return getMinecraftClass("network.chat.ChatComponentText", "network.chat.TextComponent", "ChatComponentText"); + } + + /** + * Attempt to find the ChatSerializer class. + * + * @return The serializer class. + * @throws IllegalStateException If the class could not be found or deduced. + */ + public static Class getChatSerializerClass() { + return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer"); + } + + /** + * Retrieve the ServerPing class. + * + * @return The ServerPing class. + */ + public static Class getServerPingClass() { + return getMinecraftClass("network.protocol.status.ServerPing", "network.protocol.status.ServerStatus", "ServerPing"); + } + + /** + * Retrieve the ServerPingServerData class. + * + * @return The ServerPingServerData class. + */ + public static Class getServerPingServerDataClass() { + return getMinecraftClass("network.protocol.status.ServerPing$ServerData", "network.protocol.status.ServerStatus$Version", "ServerPing$ServerData"); + } + + /** + * Retrieve the ServerPingPlayerSample class. + * + * @return The ServerPingPlayerSample class. + */ + public static Class getServerPingPlayerSampleClass() { + return getMinecraftClass( + "network.protocol.status.ServerPing$ServerPingPlayerSample", + "network.protocol.status.ServerStatus$Players", + "ServerPing$ServerPingPlayerSample"); + } + + /** + * Retrieve the MinecraftServer class. + * + * @return MinecraftServer class. + */ + public static Class getMinecraftServerClass() { + try { + return getMinecraftClass("server.MinecraftServer", "MinecraftServer"); + } catch (RuntimeException e) { + // Reset cache and try again + setMinecraftClass("MinecraftServer", null); + + useFallbackServer(); + return getMinecraftClass("MinecraftServer"); + } + } + + /** + * Retrieve the NMS statistics class. + * + * @return The statistics class. + */ + public static Class getStatisticClass() { + return getMinecraftClass("stats.Statistic", "stats.Stat", "Statistic"); + } + + /** + * Retrieve the NMS statistic list class. + * + * @return The statistic list class. + */ + public static Class getStatisticListClass() { + return getMinecraftClass("stats.StatisticList", "stats.Stats", "StatisticList"); + } + + /** + * Retrieve the player list class (or ServerConfigurationManager), + * + * @return The player list class. + */ + public static Class getPlayerListClass() { + try { + return getMinecraftClass("server.players.PlayerList", "PlayerList"); + } catch (RuntimeException e) { + // Reset cache and try again + setMinecraftClass("PlayerList", null); + + useFallbackServer(); + return getMinecraftClass("PlayerList"); + } + } + + /** + * Retrieve the PlayerConnection class. + * + * @return The PlayerConnection class. + */ + public static Class getPlayerConnectionClass() { + return getMinecraftClass("server.network.PlayerConnection", "server.network.ServerGamePacketListenerImpl", "PlayerConnection"); + } + + /** + * Retrieve the NetworkManager class. + * + * @return The NetworkManager class. + */ + public static Class getNetworkManagerClass() { + return getMinecraftClass("network.NetworkManager", "network.Connection", "NetworkManager"); + } + + /** + * Retrieve the NMS ItemStack class. + * + * @return The ItemStack class. + */ + public static Class getItemStackClass() { + try { + return getMinecraftClass("world.item.ItemStack", "ItemStack"); + } catch (RuntimeException e) { + // Use the handle reference + return setMinecraftClass("ItemStack", FuzzyReflection.fromClass(getCraftItemStackClass(), true) + .getFieldByName("handle") + .getType()); + } + } + + /** + * Retrieve the Block (NMS) class. + * + * @return Block (NMS) class. + */ + public static Class getBlockClass() { + return getMinecraftClass("world.level.block.Block", "Block"); + } + + public static Class getItemClass() { + return getNullableNMS("world.item.Item", "Item"); + } + + public static Class getFluidTypeClass() { + return getNullableNMS("world.level.material.FluidType", "world.level.material.Fluid", "FluidType"); + } + + public static Class getParticleTypeClass() { + return getNullableNMS("core.particles.ParticleType", "core.particles.SimpleParticleType", "ParticleType"); + } + + /** + * Retrieve the WorldType class. + * + * @return The WorldType class. + */ + public static Class getWorldTypeClass() { + return getMinecraftClass("WorldType"); + } + + /** + * Retrieve the DataWatcher class. + * + * @return The DataWatcher class. + */ + public static Class getDataWatcherClass() { + return getMinecraftClass("network.syncher.DataWatcher", "network.syncher.SynchedEntityData", "DataWatcher"); + } + + /** + * Retrieves the BlockPosition class. + * + * @return The BlockPosition class. + */ + public static Class getBlockPositionClass() { + return getMinecraftClass("core.BlockPosition", "core.BlockPos", "BlockPosition"); + } + + /** + * Retrieves the Vec3D class. + * + * @return The Vec3D class. + */ + public static Class getVec3DClass() { + return getMinecraftClass("world.phys.Vec3D", "world.phys.Vec3", "Vec3D"); + } + + /** + * Retrieve the ChunkCoordIntPair class. + * + * @return The ChunkCoordIntPair class. + */ + public static Class getChunkCoordIntPair() { + return getMinecraftClass("world.level.ChunkCoordIntPair", "world.level.ChunkPos", "ChunkCoordIntPair"); + } + + /** + * Retrieve the DataWatcher Item class. + * + * @return The class + */ + public static Class getDataWatcherItemClass() { + return getMinecraftClass("network.syncher.DataWatcher$Item", "network.syncher.SynchedEntityData$DataItem", "DataWatcher$Item", "DataWatcher$WatchableObject"); + } + + public static Class getDataWatcherObjectClass() { + return getNullableNMS("network.syncher.DataWatcherObject", "network.syncher.EntityDataAccessor", "DataWatcherObject"); + } + + public static boolean watcherObjectExists() { + if (cachedWatcherObject == null) { + cachedWatcherObject = getDataWatcherObjectClass() != null; + } + + return cachedWatcherObject; + } + + public static Class getDataWatcherSerializerClass() { + return getNullableNMS("network.syncher.DataWatcherSerializer", "network.syncher.EntityDataSerializer", "DataWatcherSerializer"); + } + + public static Class getDataWatcherRegistryClass() { + return getMinecraftClass("network.syncher.DataWatcherRegistry", "network.syncher.EntityDataSerializers", "DataWatcherRegistry"); + } + + public static Class getMinecraftKeyClass() { + return getMinecraftClass("resources.MinecraftKey", "resources.ResourceLocation", "MinecraftKey"); + } + + public static Class getMobEffectListClass() { + return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect"); + } + + public static Class getSoundEffectClass() { + return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect"); + } + + /** + * Retrieve the ServerConnection abstract class. + * + * @return The ServerConnection class. + */ + public static Class getServerConnectionClass() { + return getMinecraftClass("server.network.ServerConnection", "server.network.ServerConnectionListener", "ServerConnection"); + } + + /** + * Retrieve the NBT base class. + * + * @return The NBT base class. + */ + public static Class getNBTBaseClass() { + return getMinecraftClass("nbt.NBTBase", "nbt.Tag", "NBTBase"); + } + + /** + * Retrieve the NBT read limiter class. + * + * @return The NBT read limiter. + */ + public static Class getNBTReadLimiterClass() { + return getMinecraftClass("nbt.NBTReadLimiter", "nbt.NbtAccounter", "NBTReadLimiter"); + } + + /** + * Retrieve the NBT Compound class. + * + * @return The NBT Compond class. + */ + public static Class getNBTCompoundClass() { + return getMinecraftClass("nbt.NBTTagCompound", "nbt.CompoundTag", "NBTTagCompound"); + } + + /** + * Retrieve the EntityTracker (NMS) class. + * + * @return EntityTracker class. + */ + public static Class getEntityTrackerClass() { + return getMinecraftClass("server.level.PlayerChunkMap$EntityTracker", "server.level.ChunkMap$TrackedEntity", "EntityTracker"); + } + + /** + * Retrieve the attribute snapshot class. + *

+ * This stores the final value of an attribute, along with all the associated computational steps. + * + * @return The attribute snapshot class. + */ + public static Class getAttributeSnapshotClass() { + return getMinecraftClass( + "network.protocol.game.PacketPlayOutUpdateAttributes$AttributeSnapshot", + "network.protocol.game.ClientboundUpdateAttributesPacket$AttributeSnapshot", + "AttributeSnapshot", + "PacketPlayOutUpdateAttributes$AttributeSnapshot"); + } + + /** + * Retrieve the IntHashMap class. + * + * @return IntHashMap class. + */ + public static Class getIntHashMapClass() { + return getNullableNMS("IntHashMap"); + } + + /** + * Retrieve the attribute modifier class. + * + * @return Attribute modifier class. + */ + public static Class getAttributeModifierClass() { + return getMinecraftClass("world.entity.ai.attributes.AttributeModifier", "AttributeModifier"); + } + + /** + * Retrieve the net.minecraft.server.MobEffect class. + * + * @return The mob effect class. + */ + public static Class getMobEffectClass() { + return getMinecraftClass("world.effect.MobEffect", "world.effect.MobEffectInstance", "MobEffect"); + } + + /** + * Retrieve the packet data serializer class that overrides ByteBuf. + * + * @return The data serializer class. + */ + public static Class getPacketDataSerializerClass() { + return getMinecraftClass("network.PacketDataSerializer", "network.FriendlyByteBuf", "PacketDataSerializer"); + } + + /** + * Retrieve the NBTCompressedStreamTools class. + * + * @return The NBTCompressedStreamTools class. + */ + public static Class getNbtCompressedStreamToolsClass() { + return getMinecraftClass("nbt.NBTCompressedStreamTools", "nbt.NbtIo", "NBTCompressedStreamTools"); + } + + /** + * Retrieve the NMS tile entity class. + * + * @return The tile entity class. + */ + public static Class getTileEntityClass() { + return getMinecraftClass("world.level.block.entity.TileEntity", "world.level.block.entity.BlockEntity", "TileEntity"); + } + + /** + * Retrieve the Gson class used by Minecraft. + * + * @return The Gson class. + */ + public static Class getMinecraftGsonClass() { + return getMinecraftLibraryClass("com.google.gson.Gson"); + } + + /** + * Retrieve the ItemStack[] class. + * + * @return The ItemStack[] class. + */ + public static Class getItemStackArrayClass() { + if (itemStackArrayClass == null) { + itemStackArrayClass = getArrayClass(getItemStackClass()); + } + return itemStackArrayClass; + } + + /** + * Retrieve the array class of a given component type. + * + * @param componentType - type of each element in the array. + * @return The class of the array. + */ + public static Class getArrayClass(Class componentType) { + // A bit of a hack, but it works + return Array.newInstance(componentType, 0).getClass(); + } + + /** + * Retrieve the CraftItemStack class. + * + * @return The CraftItemStack class. + */ + public static Class getCraftItemStackClass() { + return getCraftBukkitClass("inventory.CraftItemStack"); + } + + /** + * Retrieve the CraftPlayer class. + * + * @return CraftPlayer class. + */ + public static Class getCraftPlayerClass() { + return getCraftBukkitClass("entity.CraftPlayer"); + } + + /** + * Retrieve the CraftWorld class. + * + * @return The CraftWorld class. + */ + public static Class getCraftWorldClass() { + return getCraftBukkitClass("CraftWorld"); + } + + /** + * Retrieve the CraftEntity class. + * + * @return CraftEntity class. + */ + public static Class getCraftEntityClass() { + return getCraftBukkitClass("entity.CraftEntity"); + } + + /** + * Retrieve the CraftChatMessage introduced in 1.7.2 + * + * @return The CraftChatMessage class. + */ + public static Class getCraftMessageClass() { + return getCraftBukkitClass("util.CraftChatMessage"); + } + + /** + * Retrieve the PlayerInfoData class in 1.8. + * + * @return The PlayerInfoData class + */ + public static Class getPlayerInfoDataClass() { + return getMinecraftClass( + "network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", + "network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", + "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); + } + + /** + * Retrieves the entity use action class in 1.17. + * + * @return The EntityUseAction class + */ + public static Class getEnumEntityUseActionClass() { + Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); + FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true); + try { + return fuzzyReflection.getFieldByType("^.*(EnumEntityUseAction)").getType(); + } catch (IllegalArgumentException ignored) { + return fuzzyReflection.getFieldByType("^.*(Action)").getType(); + } + } + + /** + * Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17. + * + * @return a method accessor to get the actual use action + */ + public static MethodAccessor getEntityUseActionEnumMethodAccessor() { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true); + return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(EnumWrappers.getEntityUseActionClass()) + .build())); + } + + /** + * Get a field accessor for the hand in the wrapping EnumEntityUseAction in 1.17. + * + * @param enumEntityUseAction the object instance of the action, the field is not present in attack. + * @return a field accessor for the hand in the wrapping EnumEntityUseAction + */ + public static FieldAccessor getHandEntityUseActionEnumFieldAccessor(Object enumEntityUseAction) { + FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true); + return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder() + .typeExact(EnumWrappers.getHandClass()) + .build())); + } + + /** + * Get a field accessor for the vec3d in the wrapping EnumEntityUseAction in 1.17. + * + * @param enumEntityUseAction the object instance of the action, the field is not present in attack. + * @return a field accessor for the hand in the wrapping EnumEntityUseAction + */ + public static FieldAccessor getVec3EntityUseActionEnumFieldAccessor(Object enumEntityUseAction) { + FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true); + return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder() + .typeExact(MinecraftReflection.getVec3DClass()) + .build())); + } + + /** + * Determine if the given object is a PlayerInfoData. + * + * @param obj - the given object. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isPlayerInfoData(Object obj) { + return is(getPlayerInfoDataClass(), obj); + } + + /** + * Retrieve the IBlockData class in 1.8. + * + * @return The IBlockData class + */ + public static Class getIBlockDataClass() { + return getMinecraftClass("world.level.block.state.IBlockData", "world.level.block.state.BlockState", "IBlockData"); + } + + /** + * Retrieve the MultiBlockChangeInfo class in 1.8. + * + * @return The MultiBlockChangeInfo class + */ + public static Class getMultiBlockChangeInfoClass() { + return getMinecraftClass("MultiBlockChangeInfo", "PacketPlayOutMultiBlockChange$MultiBlockChangeInfo"); + } + + /** + * Retrieve the MultiBlockChangeInfo array class in 1.8. + * + * @return The MultiBlockChangeInfo array class + */ + public static Class getMultiBlockChangeInfoArrayClass() { + return getArrayClass(getMultiBlockChangeInfoClass()); + } + + /** + * Retrieve the PacketPlayOutGameStateChange.a class, aka GameState in 1.16 + * + * @return The GameState class + */ + public static Class getGameStateClass() { + // it's called "a" so there's not a lot we can do to identify it + Class packetClass = PacketType.Play.Server.GAME_STATE_CHANGE.getPacketClass(); + return packetClass.getClasses()[0]; + } + + public static boolean signUpdateExists() { + return getNullableNMS("PacketPlayOutUpdateSign") != null; + } + + public static Class getNonNullListClass() { + return getMinecraftClass("core.NonNullList", "NonNullList"); + } + + public static MethodAccessor getNonNullListCreateAccessor() { + try { + Class nonNullListType = MinecraftReflection.getNonNullListClass(); + Method method = FuzzyReflection.fromClass(nonNullListType).getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(nonNullListType) + .requireModifier(Modifier.STATIC) + .build()); + return Accessors.getMethodAccessor(method); + } catch (Exception ex) { + return null; + } + } + + public static Class getCraftSoundClass() { + return getCraftBukkitClass("CraftSound"); + } + + public static Class getSectionPositionClass() { + return getMinecraftClass("core.SectionPosition", "core.SectionPos", "SectionPosition"); + } + + /** + * Retrieves the Bukkit equivalent of a NMS ItemStack. This method should preserve NBT data and will never return null + * when supplied with a valid ItemStack. Empty ItemStacks are treated as AIR. + * + * @param generic NMS ItemStack + * @return The Bukkit equivalent + */ + public static ItemStack getBukkitItemStack(Object generic) { + if (generic == null) { + // Convert null to AIR - 1.11 behavior + return new ItemStack(Material.AIR); + } + + if (generic instanceof ItemStack) { + ItemStack bukkit = (ItemStack) generic; + + // They're probably looking for the CraftItemStack + // If it's one already our work is done + if (is(getCraftItemStackClass(), generic)) { + return bukkit; + } + + // If not, convert it to one + Object nmsStack = getMinecraftItemStack(bukkit); + return getBukkitItemStack(nmsStack); + } + + if (!is(getItemStackClass(), generic)) { + // We can't do anything with non-ItemStacks + throw new IllegalArgumentException(generic + " is not an ItemStack!"); + } + + try { + // Check null enforcement - 1.11 behavior + if (nullEnforced == null) { + isEmpty = Accessors.getMethodAccessor(getItemStackClass().getMethod("isEmpty")); + nullEnforced = true; + } + + if (nullEnforced) { + if ((boolean) isEmpty.invoke(generic)) { + return new ItemStack(Material.AIR); + } + } + } catch (ReflectiveOperationException ex) { + nullEnforced = false; + } + + if (asCraftMirror == null) { + try { + Method asMirrorMethod = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass()); + asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex); + } + } + + try { + // Convert to a craft mirror to preserve NBT data + return (ItemStack) asCraftMirror.invoke(nullEnforced, generic); + } catch (IllegalStateException ex) { + throw new RuntimeException("Failed to obtain craft mirror of " + generic, ex); + } + } + + /** + * Retrieves the NMS equivalent of a Bukkit ItemStack. This method will never return null and should preserve NBT + * data. Null inputs are treated as empty (AIR) ItemStacks. + * + * @param specific Bukkit ItemStack + * @return The NMS equivalent + */ + public static Object getMinecraftItemStack(ItemStack specific) { + if (asNMSCopy == null) { + try { + Method asNmsCopyMethod = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); + asNMSCopy = Accessors.getMethodAccessor(asNmsCopyMethod); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to obtain CraftItemStack.asNMSCopy", ex); + } + } + + if (is(getCraftItemStackClass(), specific)) { + // If it's already a CraftItemStack, use its handle + Object unwrapped = BukkitUnwrapper.getInstance().unwrapItem(specific); + if (unwrapped != null) { + return unwrapped; + } else { + if (itemStackAir == null) { + // Easiest way to get the Material.AIR ItemStack? + itemStackAir = getMinecraftItemStack(new ItemStack(Material.AIR)); + } + return itemStackAir; + } + } + + try { + // If not, grab a NMS copy + return asNMSCopy.invoke(null, specific); + } catch (IllegalStateException ex) { + throw new RuntimeException("Failed to make NMS copy of " + specific, ex); + } + } + + /** + * Retrieve the given class by name. + * + * @param className - name of the class. + * @return The class. + */ + private static Class getClass(String className) { + try { + return getClassSource().loadClass(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Cannot find class " + className, e); + } + } + + /** + * Retrieve the class object of a specific CraftBukkit class. + * + * @param className - the specific CraftBukkit class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getCraftBukkitClass(String className) { + if (craftbukkitPackage == null) { + craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource()); + } + + return craftbukkitPackage.getPackageClass(className) + .orElseThrow(() -> new RuntimeException("Failed to find CraftBukkit class: " + className)); + } + + /** + * Retrieve the class object of a specific Minecraft class. + * + * @param className - the specific Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getMinecraftClass(String className) { + if (minecraftPackage == null) { + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } + + return minecraftPackage.getPackageClass(className) + .orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className)); + } + + public static Class getNullableNMS(String className, String... aliases) { + try { + return getMinecraftClass(className, aliases); + } catch (RuntimeException ex) { + return null; + } + } + + /** + * Set the class object for the specific Minecraft class. + * + * @param className - name of the Minecraft class. + * @param clazz - the new class object. + * @return The provided clazz object. + */ + private static Class setMinecraftClass(String className, Class clazz) { + if (minecraftPackage == null) { + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } + + minecraftPackage.setPackageClass(className, clazz); + return clazz; + } + + /** + * Retrieve the current class source. + * + * @return The class source. + */ + private static ClassSource getClassSource() { + return CLASS_SOURCE; + } + + /** + * Retrieve the first class that matches a specified Minecraft name. + * + * @param className - the specific Minecraft class. + * @param aliases - alternative names for this Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find any of the given classes. + */ + public static Class getMinecraftClass(String className, String... aliases) { + if (minecraftPackage == null) { + minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource()); + } + + return minecraftPackage.getPackageClass(className).orElseGet(() -> { + Class resolved = null; + for (String alias : aliases) { + // try to resolve the class and stop searching if we found it + resolved = minecraftPackage.getPackageClass(alias).orElse(null); + if (resolved != null) { + break; + } + } + + // if we resolved the class cache it and return the result + if (resolved != null) { + minecraftPackage.setPackageClass(className, resolved); + return resolved; + } + + // unable to find the class + throw new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases))); + }); + } + + /** + * Retrieve the class object of a specific Minecraft library class. + * + * @param className - the specific library Minecraft class. + * @return Class object. + * @throws RuntimeException If we are unable to find the given class. + */ + public static Class getMinecraftLibraryClass(String className) { + if (libraryPackage == null) { + libraryPackage = new CachedPackage("", getClassSource()); + } + + return libraryPackage.getPackageClass(className) + .orElseThrow(() -> new RuntimeException("Failed to find class: " + className)); + } + + /** + * Set the class object for the specific library class. + * + * @param className - name of the Minecraft library class. + * @param clazz - the new class object. + * @return The provided clazz object. + */ + private static Class setMinecraftLibraryClass(String className, Class clazz) { + if (libraryPackage == null) { + libraryPackage = new CachedPackage("", getClassSource()); + } + + libraryPackage.setPackageClass(className, clazz); + return clazz; + } + + /** + * Dynamically retrieve the NetworkManager name. + * + * @return Name of the NetworkManager class. + */ + public static String getNetworkManagerName() { + return getNetworkManagerClass().getSimpleName(); + } + + /** + * Retrieve an instance of the packet data serializer wrapper. + * + * @param buffer - the buffer. + * @return The instance. + */ + public static Object getPacketDataSerializer(Object buffer) { + try { + // TODO: move this to MinecraftMethods, or at least, cache the constructor accessor + Class packetSerializer = getPacketDataSerializerClass(); + return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer); + } catch (Exception e) { + throw new RuntimeException("Cannot construct packet serializer.", e); + } + } + + public static Object createPacketDataSerializer(int initialSize) { + // validate the initial size + if (initialSize <= 0) { + initialSize = 256; + } + + Object buffer = Unpooled.buffer(initialSize); + return getPacketDataSerializer(buffer); + } + + public static Class getNbtTagTypes() { + return getMinecraftClass("nbt.NBTTagTypes", "nbt.TagTypes", "NBTTagTypes"); + } + + public static Class getChatDeserializer() { + return getMinecraftClass("util.ChatDeserializer", "util.GsonHelper", "ChatDeserializer"); + } + + public static Class getChatMutableComponentClass() { + return getMinecraftClass("network.chat.IChatMutableComponent", "network.chat.MutableComponent"); + } + + public static Class getDimensionManager() { + return getMinecraftClass("world.level.dimension.DimensionManager", "world.level.dimension.DimensionType", "DimensionManager"); + } + + public static Class getMerchantRecipeList() { + return getMinecraftClass("world.item.trading.MerchantRecipeList", "world.item.trading.MerchantOffers", "MerchantRecipeList"); + } + + public static Class getResourceKey() { + return getMinecraftClass("resources.ResourceKey", "ResourceKey"); + } + + public static Class getEntityTypes() { + return getMinecraftClass("world.entity.EntityTypes", "world.entity.EntityType", "EntityTypes"); + } + + public static Class getParticleParam() { + return getMinecraftClass("core.particles.ParticleParam", "core.particles.ParticleOptions", "ParticleParam"); + } + + public static Class getSectionPosition() { + return getMinecraftClass("core.SectionPosition", "core.SectionPos", "SectionPosition"); + } + + public static Class getChunkProviderServer() { + return getMinecraftClass("server.level.ChunkProviderServer", "server.level.ServerChunkCache", "ChunkProviderServer"); + } + + public static Class getPlayerChunkMap() { + return getMinecraftClass("server.level.PlayerChunkMap", "server.level.ChunkMap", "PlayerChunkMap"); + } + + public static Class getIRegistry() { + return getNullableNMS("core.IRegistry", "core.Registry", "IRegistry"); + } + + public static Class getAttributeBase() { + return getMinecraftClass("world.entity.ai.attributes.AttributeBase", "world.entity.ai.attributes.Attribute", "AttributeBase"); + } + + public static Class getProfilePublicKeyClass() { + return getMinecraftClass("world.entity.player.ProfilePublicKey"); + } + + public static Class getSaltedSignatureClass() { + try { + return getMinecraftClass("SaltedSignature"); + } catch (RuntimeException runtimeException) { + Class minecraftEncryption = getMinecraftClass("util.MinecraftEncryption", "util.Crypt", "MinecraftEncryption"); + FuzzyMethodContract constructorContract = FuzzyMethodContract.newBuilder() + .parameterCount(2) + .parameterExactType(Long.TYPE, 0) + .parameterExactType(byte[].class, 1) + .build(); + + for (Class subclass : minecraftEncryption.getClasses()) { + FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(subclass, true); + List> constructors = fuzzyReflection.getConstructorList(constructorContract); + + if (!constructors.isEmpty()) { + return setMinecraftClass("SaltedSignature", subclass); + } + } + + Class messageSigClass = getMinecraftClass("network.chat.MessageSignature", "MessageSignature"); + FuzzyClassContract signatureContract = FuzzyClassContract.newBuilder() + .constructor(FuzzyMethodContract.newBuilder() + .parameterCount(2) + .parameterSuperOf(Long.TYPE, 0) + .parameterSuperOf(byte[].class, 1) + .build()) + .build(); + + FuzzyFieldContract fuzzyFieldContract = FuzzyFieldContract.newBuilder() + .typeMatches(getMinecraftObjectMatcher().and(signatureContract)) + .build(); + + Class signatureClass = FuzzyReflection.fromClass(messageSigClass, true) + .getField(fuzzyFieldContract) + .getType(); + return setMinecraftClass("SaltedSignature", signatureClass); + } + } + + public static Class getProfilePublicKeyDataClass() { + return getProfilePublicKeyClass().getClasses()[0]; + } + + public static Class getFastUtilClass(String className) { + return getLibraryClass("it.unimi.dsi.fastutil." + className); + } + + public static Class getInt2ObjectMapClass() { + return getFastUtilClass("ints.Int2ObjectMap"); + } + + public static Class getIntArrayListClass() { + return getFastUtilClass("ints.IntArrayList"); + } + + public static Class getLibraryClass(String classname) { + try { + return getMinecraftLibraryClass(classname); + } catch (RuntimeException ex) { + Class clazz = getMinecraftLibraryClass("org.bukkit.craftbukkit.libs." + classname); + setMinecraftLibraryClass(classname, clazz); + return clazz; + } + } + + /** + * Fallback method that can determine the MinecraftServer and the ServerConfigurationManager. + */ + private static void useFallbackServer() { + // Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY) + Constructor selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer")) + .getConstructor(FuzzyMethodContract.newBuilder() + .parameterMatches(getMinecraftObjectMatcher(), 0) + .parameterCount(2) + .build()); + Class[] params = selected.getParameterTypes(); + + // Jackpot - two classes at the same time! + setMinecraftClass("MinecraftServer", params[0]); + setMinecraftClass("PlayerList", params[1]); + } + + public static Class getLevelChunkPacketDataClass() { + return getNullableNMS("network.protocol.game.ClientboundLevelChunkPacketData"); + } + + public static Class getLightUpdatePacketDataClass() { + return getNullableNMS("network.protocol.game.ClientboundLightUpdatePacketData"); + } + + public static Class getBlockEntityTypeClass() { + return getMinecraftClass("world.level.block.entity.BlockEntityType", "world.level.block.entity.TileEntityTypes", "TileEntityTypes"); + } + + public static Class getBlockEntityInfoClass() { + try { + return getMinecraftClass("BlockEntityInfo"); + } catch (RuntimeException expected) { + Class infoClass = (Class) ((ParameterizedType) FuzzyReflection.fromClass(getLevelChunkPacketDataClass(), + true).getFieldListByType(List.class).get(0).getGenericType()).getActualTypeArguments()[0]; + + setMinecraftClass("BlockEntityInfo", infoClass); + + return infoClass; + } + } +} diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java b/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java index 398f5b68..bc7c8503 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java @@ -1,421 +1,421 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * 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.utility; - -import com.comphenix.protocol.ProtocolLibrary; -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.Ordering; -import java.io.Serializable; -import java.text.SimpleDateFormat; -import java.util.Locale; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.bukkit.Bukkit; -import org.bukkit.Server; - -/** - * Determine the current Minecraft version. - * - * @author Kristian - */ -public final class MinecraftVersion implements Comparable, Serializable { - - /** - * Version 1.19 - the wild update - */ - public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19"); - /** - * Version 1.18 - caves and cliffs part 2 - */ - public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18"); - /** - * Version 1.17 - caves and cliffs part 1 - */ - public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17"); - /** - * Version 1.16.2 - breaking change to the nether update - */ - public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2"); - /** - * Version 1.16.0 - the nether update - */ - public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16"); - /** - * Version 1.15 - the bee update - */ - public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15"); - /** - * Version 1.14 - village and pillage update. - */ - public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14"); - /** - * Version 1.13 - update aquatic. - */ - public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13"); - /** - * Version 1.12 - the world of color update. - */ - public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12"); - /** - * Version 1.11 - the exploration update. - */ - public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11"); - /** - * Version 1.10 - the frostburn update. - */ - public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10"); - /** - * Version 1.9 - the combat update. - */ - public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9"); - /** - * Version 1.8 - the "bountiful" update. - */ - public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8"); - /** - * Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise) - */ - public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8"); - /** - * Version 1.7.2 - the update that changed the world. - */ - public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2"); - /** - * Version 1.6.1 - the horse update. - */ - public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1"); - /** - * Version 1.5.0 - the redstone update. - */ - public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0"); - /** - * Version 1.4.2 - the scary update (Wither Boss). - */ - public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2"); - - /** - * The latest release version of minecraft. - */ - public static final MinecraftVersion LATEST = WILD_UPDATE; - - // used when serializing - private static final long serialVersionUID = -8695133558996459770L; - - /** - * Regular expression used to parse version strings. - */ - private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*"); - - /** - * The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion() - */ - private static MinecraftVersion currentVersion; - - private final int major; - private final int minor; - private final int build; - // The development stage - private final String development; - - // Snapshot? - private final SnapshotVersion snapshot; - private volatile Boolean atCurrentOrAbove; - - /** - * Determine the current Minecraft version. - * - * @param server - the Bukkit server that will be used to examine the MC version. - */ - public MinecraftVersion(Server server) { - this(extractVersion(server.getVersion())); - } - - /** - * Construct a version object from the format major.minor.build, or the snapshot format. - * - * @param versionOnly - the version in text form. - */ - public MinecraftVersion(String versionOnly) { - this(versionOnly, true); - } - - /** - * Construct a version format from the standard release version or the snapshot verison. - * - * @param versionOnly - the version. - * @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise. - */ - private MinecraftVersion(String versionOnly, boolean parseSnapshot) { - String[] section = versionOnly.split("-"); - SnapshotVersion snapshot = null; - int[] numbers = new int[3]; - - try { - numbers = this.parseVersion(section[0]); - } catch (NumberFormatException cause) { - // Skip snapshot parsing - if (!parseSnapshot) { - throw cause; - } - - try { - // Determine if the snapshot is newer than the current release version - snapshot = new SnapshotVersion(section[0]); - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - - MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false); - boolean newer = snapshot.getSnapshotDate().compareTo( - format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0; - - numbers[0] = latest.getMajor(); - numbers[1] = latest.getMinor() + (newer ? 1 : -1); - } catch (Exception e) { - throw new IllegalStateException("Cannot parse " + section[0], e); - } - } - - this.major = numbers[0]; - this.minor = numbers[1]; - this.build = numbers[2]; - this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null); - this.snapshot = snapshot; - } - - /** - * Construct a version object directly. - * - * @param major - major version number. - * @param minor - minor version number. - * @param build - build version number. - */ - public MinecraftVersion(int major, int minor, int build) { - this(major, minor, build, null); - } - - /** - * Construct a version object directly. - * - * @param major - major version number. - * @param minor - minor version number. - * @param build - build version number. - * @param development - development stage. - */ - public MinecraftVersion(int major, int minor, int build, String development) { - this.major = major; - this.minor = minor; - this.build = build; - this.development = development; - this.snapshot = null; - } - - /** - * Extract the Minecraft version from CraftBukkit itself. - * - * @param text - the server version in text form. - * @return The underlying MC version. - * @throws IllegalStateException If we could not parse the version string. - */ - public static String extractVersion(String text) { - Matcher version = VERSION_PATTERN.matcher(text); - - if (version.matches() && version.group(1) != null) { - return version.group(1); - } else { - throw new IllegalStateException("Cannot parse version String '" + text + "'"); - } - } - - /** - * Parse the given server version into a Minecraft version. - * - * @param serverVersion - the server version. - * @return The resulting Minecraft version. - */ - public static MinecraftVersion fromServerVersion(String serverVersion) { - return new MinecraftVersion(extractVersion(serverVersion)); - } - - public static MinecraftVersion getCurrentVersion() { - if (currentVersion == null) { - currentVersion = fromServerVersion(Bukkit.getVersion()); - } - - return currentVersion; - } - - public static void setCurrentVersion(MinecraftVersion version) { - currentVersion = version; - } - - public static boolean atOrAbove(MinecraftVersion version) { - return getCurrentVersion().isAtLeast(version); - } - - private int[] parseVersion(String version) { - String[] elements = version.split("\\."); - int[] numbers = new int[3]; - - // Make sure it's even a valid version - if (elements.length < 1) { - throw new IllegalStateException("Corrupt MC version: " + version); - } - - // The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively. - for (int i = 0; i < Math.min(numbers.length, elements.length); i++) { - numbers[i] = Integer.parseInt(elements[i].trim()); - } - return numbers; - } - - /** - * Major version number - * - * @return Current major version number. - */ - public int getMajor() { - return this.major; - } - - /** - * Minor version number - * - * @return Current minor version number. - */ - public int getMinor() { - return this.minor; - } - - /** - * Build version number - * - * @return Current build version number. - */ - public int getBuild() { - return this.build; - } - - /** - * Retrieve the development stage. - * - * @return Development stage, or NULL if this is a release. - */ - public String getDevelopmentStage() { - return this.development; - } - - /** - * Retrieve the snapshot version, or NULL if this is a release. - * - * @return The snapshot version. - */ - public SnapshotVersion getSnapshot() { - return this.snapshot; - } - - /** - * Determine if this version is a snapshot. - * - * @return The snapshot version. - */ - public boolean isSnapshot() { - return this.snapshot != null; - } - - /** - * Checks if this version is at or above the current version the server is running. - * - * @return true if this version is equal or newer than the server version, false otherwise. - */ - public boolean atOrAbove() { - if (this.atCurrentOrAbove == null) { - this.atCurrentOrAbove = MinecraftVersion.atOrAbove(this); - } - - return this.atCurrentOrAbove; - } - - /** - * Retrieve the version String (major.minor.build) only. - * - * @return A normal version string. - */ - public String getVersion() { - if (this.getDevelopmentStage() == null) { - return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild()); - } else { - return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(), - this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : ""); - } - } - - @Override - public int compareTo(MinecraftVersion o) { - if (o == null) { - return 1; - } - - return ComparisonChain.start() - .compare(this.getMajor(), o.getMajor()) - .compare(this.getMinor(), o.getMinor()) - .compare(this.getBuild(), o.getBuild()) - .compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast()) - .compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst()) - .result(); - } - - public boolean isAtLeast(MinecraftVersion other) { - if (other == null) { - return false; - } - - return this.compareTo(other) >= 0; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (obj == this) { - return true; - } - - if (obj instanceof MinecraftVersion) { - MinecraftVersion other = (MinecraftVersion) obj; - - return this.getMajor() == other.getMajor() && - this.getMinor() == other.getMinor() && - this.getBuild() == other.getBuild() && - Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage()); - } - - return false; - } - - @Override - public int hashCode() { - return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild()); - } - - @Override - public String toString() { - // Convert to a String that we can parse back again - return String.format("(MC: %s)", this.getVersion()); - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * 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.utility; + +import com.comphenix.protocol.ProtocolLibrary; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.bukkit.Bukkit; +import org.bukkit.Server; + +/** + * Determine the current Minecraft version. + * + * @author Kristian + */ +public final class MinecraftVersion implements Comparable, Serializable { + + /** + * Version 1.19 - the wild update + */ + public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19"); + /** + * Version 1.18 - caves and cliffs part 2 + */ + public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18"); + /** + * Version 1.17 - caves and cliffs part 1 + */ + public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17"); + /** + * Version 1.16.2 - breaking change to the nether update + */ + public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2"); + /** + * Version 1.16.0 - the nether update + */ + public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16"); + /** + * Version 1.15 - the bee update + */ + public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15"); + /** + * Version 1.14 - village and pillage update. + */ + public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14"); + /** + * Version 1.13 - update aquatic. + */ + public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13"); + /** + * Version 1.12 - the world of color update. + */ + public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12"); + /** + * Version 1.11 - the exploration update. + */ + public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11"); + /** + * Version 1.10 - the frostburn update. + */ + public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10"); + /** + * Version 1.9 - the combat update. + */ + public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9"); + /** + * Version 1.8 - the "bountiful" update. + */ + public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8"); + /** + * Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise) + */ + public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8"); + /** + * Version 1.7.2 - the update that changed the world. + */ + public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2"); + /** + * Version 1.6.1 - the horse update. + */ + public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1"); + /** + * Version 1.5.0 - the redstone update. + */ + public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0"); + /** + * Version 1.4.2 - the scary update (Wither Boss). + */ + public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2"); + + /** + * The latest release version of minecraft. + */ + public static final MinecraftVersion LATEST = WILD_UPDATE; + + // used when serializing + private static final long serialVersionUID = -8695133558996459770L; + + /** + * Regular expression used to parse version strings. + */ + private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*"); + + /** + * The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion() + */ + private static MinecraftVersion currentVersion; + + private final int major; + private final int minor; + private final int build; + // The development stage + private final String development; + + // Snapshot? + private final SnapshotVersion snapshot; + private volatile Boolean atCurrentOrAbove; + + /** + * Determine the current Minecraft version. + * + * @param server - the Bukkit server that will be used to examine the MC version. + */ + public MinecraftVersion(Server server) { + this(extractVersion(server.getVersion())); + } + + /** + * Construct a version object from the format major.minor.build, or the snapshot format. + * + * @param versionOnly - the version in text form. + */ + public MinecraftVersion(String versionOnly) { + this(versionOnly, true); + } + + /** + * Construct a version format from the standard release version or the snapshot verison. + * + * @param versionOnly - the version. + * @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise. + */ + private MinecraftVersion(String versionOnly, boolean parseSnapshot) { + String[] section = versionOnly.split("-"); + SnapshotVersion snapshot = null; + int[] numbers = new int[3]; + + try { + numbers = this.parseVersion(section[0]); + } catch (NumberFormatException cause) { + // Skip snapshot parsing + if (!parseSnapshot) { + throw cause; + } + + try { + // Determine if the snapshot is newer than the current release version + snapshot = new SnapshotVersion(section[0]); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + + MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false); + boolean newer = snapshot.getSnapshotDate().compareTo( + format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0; + + numbers[0] = latest.getMajor(); + numbers[1] = latest.getMinor() + (newer ? 1 : -1); + } catch (Exception e) { + throw new IllegalStateException("Cannot parse " + section[0], e); + } + } + + this.major = numbers[0]; + this.minor = numbers[1]; + this.build = numbers[2]; + this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null); + this.snapshot = snapshot; + } + + /** + * Construct a version object directly. + * + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + */ + public MinecraftVersion(int major, int minor, int build) { + this(major, minor, build, null); + } + + /** + * Construct a version object directly. + * + * @param major - major version number. + * @param minor - minor version number. + * @param build - build version number. + * @param development - development stage. + */ + public MinecraftVersion(int major, int minor, int build, String development) { + this.major = major; + this.minor = minor; + this.build = build; + this.development = development; + this.snapshot = null; + } + + /** + * Extract the Minecraft version from CraftBukkit itself. + * + * @param text - the server version in text form. + * @return The underlying MC version. + * @throws IllegalStateException If we could not parse the version string. + */ + public static String extractVersion(String text) { + Matcher version = VERSION_PATTERN.matcher(text); + + if (version.matches() && version.group(1) != null) { + return version.group(1); + } else { + throw new IllegalStateException("Cannot parse version String '" + text + "'"); + } + } + + /** + * Parse the given server version into a Minecraft version. + * + * @param serverVersion - the server version. + * @return The resulting Minecraft version. + */ + public static MinecraftVersion fromServerVersion(String serverVersion) { + return new MinecraftVersion(extractVersion(serverVersion)); + } + + public static MinecraftVersion getCurrentVersion() { + if (currentVersion == null) { + currentVersion = fromServerVersion(Bukkit.getVersion()); + } + + return currentVersion; + } + + public static void setCurrentVersion(MinecraftVersion version) { + currentVersion = version; + } + + public static boolean atOrAbove(MinecraftVersion version) { + return getCurrentVersion().isAtLeast(version); + } + + private int[] parseVersion(String version) { + String[] elements = version.split("\\."); + int[] numbers = new int[3]; + + // Make sure it's even a valid version + if (elements.length < 1) { + throw new IllegalStateException("Corrupt MC version: " + version); + } + + // The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively. + for (int i = 0; i < Math.min(numbers.length, elements.length); i++) { + numbers[i] = Integer.parseInt(elements[i].trim()); + } + return numbers; + } + + /** + * Major version number + * + * @return Current major version number. + */ + public int getMajor() { + return this.major; + } + + /** + * Minor version number + * + * @return Current minor version number. + */ + public int getMinor() { + return this.minor; + } + + /** + * Build version number + * + * @return Current build version number. + */ + public int getBuild() { + return this.build; + } + + /** + * Retrieve the development stage. + * + * @return Development stage, or NULL if this is a release. + */ + public String getDevelopmentStage() { + return this.development; + } + + /** + * Retrieve the snapshot version, or NULL if this is a release. + * + * @return The snapshot version. + */ + public SnapshotVersion getSnapshot() { + return this.snapshot; + } + + /** + * Determine if this version is a snapshot. + * + * @return The snapshot version. + */ + public boolean isSnapshot() { + return this.snapshot != null; + } + + /** + * Checks if this version is at or above the current version the server is running. + * + * @return true if this version is equal or newer than the server version, false otherwise. + */ + public boolean atOrAbove() { + if (this.atCurrentOrAbove == null) { + this.atCurrentOrAbove = MinecraftVersion.atOrAbove(this); + } + + return this.atCurrentOrAbove; + } + + /** + * Retrieve the version String (major.minor.build) only. + * + * @return A normal version string. + */ + public String getVersion() { + if (this.getDevelopmentStage() == null) { + return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild()); + } else { + return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(), + this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : ""); + } + } + + @Override + public int compareTo(MinecraftVersion o) { + if (o == null) { + return 1; + } + + return ComparisonChain.start() + .compare(this.getMajor(), o.getMajor()) + .compare(this.getMinor(), o.getMinor()) + .compare(this.getBuild(), o.getBuild()) + .compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast()) + .compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst()) + .result(); + } + + public boolean isAtLeast(MinecraftVersion other) { + if (other == null) { + return false; + } + + return this.compareTo(other) >= 0; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + + if (obj instanceof MinecraftVersion) { + MinecraftVersion other = (MinecraftVersion) obj; + + return this.getMajor() == other.getMajor() && + this.getMinor() == other.getMinor() && + this.getBuild() == other.getBuild() && + Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage()); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild()); + } + + @Override + public String toString() { + // Convert to a String that we can parse back again + return String.format("(MC: %s)", this.getVersion()); + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java b/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java index 5bfa0a03..647b3b19 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java +++ b/src/main/java/com/comphenix/protocol/wrappers/AutoWrapper.java @@ -1,194 +1,194 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2017 Dan Mulloy - * - * 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.wrappers; - -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.google.common.base.Defaults; - -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.utility.MinecraftReflection; - -/** - * Automatically wraps an internal NMS class to a non-versioned, deofbuscated class. - * Requirements: - *

    - *
  • The wrapper must be public
  • - *
  • If the wrapper is an internal class, it must be static
  • - *
  • The wrapper must have one public constructor with no arguments (the default constructor is acceptable)
  • - *
  • The wrapper must have the the same number of fields as the NMS class
  • - *
  • Each field should correspond, in order, to its NMS counterpart
  • - *
  • Non-generic fields must have a converter
  • - *
- * - * @author dmulloy2 - */ -public class AutoWrapper implements EquivalentConverter { - private static final Object[] NO_ARGS = new Object[0]; - - private Map> wrappers = new HashMap<>(); - private Map> unwrappers = new HashMap<>(); - - // lazy - private FieldAccessor[] nmsAccessors; - private FieldAccessor[] wrapperAccessors; - - private Object[] nmsDefaultArgs; - private ConstructorAccessor nmsInstanceCreator; - - private Class wrapperClass; - private Class nmsClass; - - private AutoWrapper(Class wrapperClass, Class nmsClass) { - this.wrapperClass = wrapperClass; - this.nmsClass = nmsClass; - } - - public static AutoWrapper wrap(Class wrapperClass, Class nmsClass) { - return new AutoWrapper<>(wrapperClass, nmsClass); - } - - public static AutoWrapper wrap(Class wrapperClass, String nmsClassName) { - return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName)); - } - - public static AutoWrapper wrap(Class wrapperClass, String nmsClassName, String... aliases) { - return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName, aliases)); - } - - public AutoWrapper field(int index, Function wrapper, Function unwrapper) { - wrappers.put(index, wrapper); - unwrappers.put(index, unwrapper); - return this; - } - - public AutoWrapper field(int index, EquivalentConverter converter) { - return field(index, converter::getSpecific, specific -> converter.getGeneric(specific)); - } - - public T wrap(Object nmsObject) { - T instance; - - try { - instance = wrapperClass.newInstance(); - } catch (ReflectiveOperationException ex) { - throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex); - } - - // ensures that all accessors are present - computeFieldAccessors(); - - for (int i = 0; i < wrapperAccessors.length; i++) { - FieldAccessor source = nmsAccessors[i]; - FieldAccessor target = wrapperAccessors[i]; - - Object value = source.get(nmsObject); - if (wrappers.containsKey(i)) - value = wrappers.get(i).apply(value); - - target.set(instance, value); - } - - return instance; - } - - public Object unwrap(Object wrapper) { - // ensures that all accessors are present - computeFieldAccessors(); - computeNmsConstructorAccess(); - - Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs); - - for (int i = 0; i < wrapperAccessors.length; i++) { - FieldAccessor source = wrapperAccessors[i]; - FieldAccessor target = nmsAccessors[i]; - - Object value = source.get(wrapper); - if (unwrappers.containsKey(i)) - value = unwrappers.get(i).apply(value); - - target.set(instance, value); - } - - return instance; - } - - private void computeFieldAccessors() { - if (nmsAccessors == null) { - nmsAccessors = Arrays - .stream(nmsClass.getDeclaredFields()) - .filter(field -> !Modifier.isStatic(field.getModifiers())) - .map(field -> Accessors.getFieldAccessor(field)) - .toArray(FieldAccessor[]::new); - } - - if (wrapperAccessors == null) { - wrapperAccessors = Arrays - .stream(wrapperClass.getDeclaredFields()) - .map(field -> Accessors.getFieldAccessor(field)) - .toArray(FieldAccessor[]::new); - } - } - - private void computeNmsConstructorAccess() { - if (nmsInstanceCreator == null) { - ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass); - if (noArgs != null) { - // no args constructor is available - use it - nmsInstanceCreator = noArgs; - nmsDefaultArgs = NO_ARGS; - } else { - // use the first constructor of the class - nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]); - nmsDefaultArgs = Arrays - .stream(nmsInstanceCreator.getConstructor().getParameterTypes()) - .map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null) - .toArray(Object[]::new); - } - } - } - - // ---- Equivalent conversion - - @Override - public T getSpecific(Object generic) { - return wrap(generic); - } - - @Override - public Object getGeneric(Object specific) { - return unwrap(specific); - } - - @Override - public Class getSpecificType() { - return wrapperClass; - } - - public static class InvalidWrapperException extends RuntimeException { - private InvalidWrapperException(String message, Throwable cause) { - super(message, cause); - } - } -} +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2017 Dan Mulloy + * + * 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.wrappers; + +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.google.common.base.Defaults; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.utility.MinecraftReflection; + +/** + * Automatically wraps an internal NMS class to a non-versioned, deofbuscated class. + * Requirements: + *
    + *
  • The wrapper must be public
  • + *
  • If the wrapper is an internal class, it must be static
  • + *
  • The wrapper must have one public constructor with no arguments (the default constructor is acceptable)
  • + *
  • The wrapper must have the the same number of fields as the NMS class
  • + *
  • Each field should correspond, in order, to its NMS counterpart
  • + *
  • Non-generic fields must have a converter
  • + *
+ * + * @author dmulloy2 + */ +public class AutoWrapper implements EquivalentConverter { + private static final Object[] NO_ARGS = new Object[0]; + + private Map> wrappers = new HashMap<>(); + private Map> unwrappers = new HashMap<>(); + + // lazy + private FieldAccessor[] nmsAccessors; + private FieldAccessor[] wrapperAccessors; + + private Object[] nmsDefaultArgs; + private ConstructorAccessor nmsInstanceCreator; + + private Class wrapperClass; + private Class nmsClass; + + private AutoWrapper(Class wrapperClass, Class nmsClass) { + this.wrapperClass = wrapperClass; + this.nmsClass = nmsClass; + } + + public static AutoWrapper wrap(Class wrapperClass, Class nmsClass) { + return new AutoWrapper<>(wrapperClass, nmsClass); + } + + public static AutoWrapper wrap(Class wrapperClass, String nmsClassName) { + return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName)); + } + + public static AutoWrapper wrap(Class wrapperClass, String nmsClassName, String... aliases) { + return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName, aliases)); + } + + public AutoWrapper field(int index, Function wrapper, Function unwrapper) { + wrappers.put(index, wrapper); + unwrappers.put(index, unwrapper); + return this; + } + + public AutoWrapper field(int index, EquivalentConverter converter) { + return field(index, converter::getSpecific, specific -> converter.getGeneric(specific)); + } + + public T wrap(Object nmsObject) { + T instance; + + try { + instance = wrapperClass.newInstance(); + } catch (ReflectiveOperationException ex) { + throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex); + } + + // ensures that all accessors are present + computeFieldAccessors(); + + for (int i = 0; i < wrapperAccessors.length; i++) { + FieldAccessor source = nmsAccessors[i]; + FieldAccessor target = wrapperAccessors[i]; + + Object value = source.get(nmsObject); + if (wrappers.containsKey(i)) + value = wrappers.get(i).apply(value); + + target.set(instance, value); + } + + return instance; + } + + public Object unwrap(Object wrapper) { + // ensures that all accessors are present + computeFieldAccessors(); + computeNmsConstructorAccess(); + + Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs); + + for (int i = 0; i < wrapperAccessors.length; i++) { + FieldAccessor source = wrapperAccessors[i]; + FieldAccessor target = nmsAccessors[i]; + + Object value = source.get(wrapper); + if (unwrappers.containsKey(i)) + value = unwrappers.get(i).apply(value); + + target.set(instance, value); + } + + return instance; + } + + private void computeFieldAccessors() { + if (nmsAccessors == null) { + nmsAccessors = Arrays + .stream(nmsClass.getDeclaredFields()) + .filter(field -> !Modifier.isStatic(field.getModifiers())) + .map(field -> Accessors.getFieldAccessor(field)) + .toArray(FieldAccessor[]::new); + } + + if (wrapperAccessors == null) { + wrapperAccessors = Arrays + .stream(wrapperClass.getDeclaredFields()) + .map(field -> Accessors.getFieldAccessor(field)) + .toArray(FieldAccessor[]::new); + } + } + + private void computeNmsConstructorAccess() { + if (nmsInstanceCreator == null) { + ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass); + if (noArgs != null) { + // no args constructor is available - use it + nmsInstanceCreator = noArgs; + nmsDefaultArgs = NO_ARGS; + } else { + // use the first constructor of the class + nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]); + nmsDefaultArgs = Arrays + .stream(nmsInstanceCreator.getConstructor().getParameterTypes()) + .map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null) + .toArray(Object[]::new); + } + } + } + + // ---- Equivalent conversion + + @Override + public T getSpecific(Object generic) { + return wrap(generic); + } + + @Override + public Object getGeneric(Object specific) { + return unwrap(specific); + } + + @Override + public Class getSpecificType() { + return wrapperClass; + } + + public static class InvalidWrapperException extends RuntimeException { + private InvalidWrapperException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/ClonableWrapper.java b/src/main/java/com/comphenix/protocol/wrappers/ClonableWrapper.java index ec671081..5a80b39e 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/ClonableWrapper.java +++ b/src/main/java/com/comphenix/protocol/wrappers/ClonableWrapper.java @@ -1,7 +1,7 @@ -package com.comphenix.protocol.wrappers; - -public interface ClonableWrapper { - Object getHandle(); - ClonableWrapper deepClone(); - -} +package com.comphenix.protocol.wrappers; + +public interface ClonableWrapper { + Object getHandle(); + ClonableWrapper deepClone(); + +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java b/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java index 97a57923..8f7ec892 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java +++ b/src/main/java/com/comphenix/protocol/wrappers/ComponentParser.java @@ -1,75 +1,75 @@ -/** - * 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.wrappers; - -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.lang.reflect.Method; -import java.lang.reflect.Constructor; - -/** - * Handles component parsing in 1.8 - * @author dmulloy2 - */ -public class ComponentParser { - - private static Constructor readerConstructor; - private static Method setLenient; - private static Method getAdapter; - private static Method read; - - private ComponentParser() { - } - - public static Object deserialize(Object gson, Class component, StringReader str) { - try { - com.google.gson.stream.JsonReader reader = new com.google.gson.stream.JsonReader(str); - reader.setLenient(true); - return ((com.google.gson.Gson) gson).getAdapter(component).read(reader); - } catch (IOException ex) { - throw new RuntimeException("Failed to read JSON", ex); - } catch (LinkageError er) { - return deserializeLegacy(gson, component, str); - } - } - - // Should only be needed on 1.8. - private static Object deserializeLegacy(Object gson, Class component, StringReader str) { - try { - if (readerConstructor == null) { - - Class readerClass = Class.forName("org.bukkit.craftbukkit.libs.com.google.gson.stream.JsonReader"); - readerConstructor = readerClass.getDeclaredConstructor(Reader.class); - readerConstructor.setAccessible(true); - setLenient = readerClass.getDeclaredMethod("setLenient", boolean.class); - setLenient.setAccessible(true); - getAdapter = gson.getClass().getDeclaredMethod("getAdapter", Class.class); - getAdapter.setAccessible(true); - Object adapter = getAdapter.invoke(gson, component); - read = adapter.getClass().getDeclaredMethod("read", readerClass); - read.setAccessible(true); - } - Object reader = readerConstructor.newInstance(str); - setLenient.invoke(reader, true); - Object adapter = getAdapter.invoke(gson, component); - return read.invoke(adapter, reader); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to read JSON", ex); - } - } -} +/** + * 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.wrappers; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; + +/** + * Handles component parsing in 1.8 + * @author dmulloy2 + */ +public class ComponentParser { + + private static Constructor readerConstructor; + private static Method setLenient; + private static Method getAdapter; + private static Method read; + + private ComponentParser() { + } + + public static Object deserialize(Object gson, Class component, StringReader str) { + try { + com.google.gson.stream.JsonReader reader = new com.google.gson.stream.JsonReader(str); + reader.setLenient(true); + return ((com.google.gson.Gson) gson).getAdapter(component).read(reader); + } catch (IOException ex) { + throw new RuntimeException("Failed to read JSON", ex); + } catch (LinkageError er) { + return deserializeLegacy(gson, component, str); + } + } + + // Should only be needed on 1.8. + private static Object deserializeLegacy(Object gson, Class component, StringReader str) { + try { + if (readerConstructor == null) { + + Class readerClass = Class.forName("org.bukkit.craftbukkit.libs.com.google.gson.stream.JsonReader"); + readerConstructor = readerClass.getDeclaredConstructor(Reader.class); + readerConstructor.setAccessible(true); + setLenient = readerClass.getDeclaredMethod("setLenient", boolean.class); + setLenient.setAccessible(true); + getAdapter = gson.getClass().getDeclaredMethod("getAdapter", Class.class); + getAdapter.setAccessible(true); + Object adapter = getAdapter.invoke(gson, component); + read = adapter.getClass().getDeclaredMethod("read", readerClass); + read.setAccessible(true); + } + Object reader = readerConstructor.newInstance(str); + setLenient.invoke(reader, true); + Object adapter = getAdapter.invoke(gson, component); + return read.invoke(adapter, reader); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to read JSON", ex); + } + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/Converters.java b/src/main/java/com/comphenix/protocol/wrappers/Converters.java index 870a2226..e4ad486e 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/Converters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/Converters.java @@ -1,174 +1,174 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2017 Dan Mulloy - * - * 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.wrappers; - -import java.lang.reflect.Array; -import java.util.Optional; -import java.util.function.Function; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.utility.MinecraftReflection; - -/** - * Utility class for converters - * @author dmulloy2 - */ -@SuppressWarnings("unchecked") -public class Converters { - - /** - * Returns a converter that ignores null elements, so that the underlying converter doesn't have to worry about them. - * @param converter Underlying converter - * @param Element type - * @return An ignore null converter - */ - public static EquivalentConverter ignoreNull(final EquivalentConverter converter) { - return new EquivalentConverter() { - @Override - public T getSpecific(Object generic) { - return generic != null ? converter.getSpecific(generic) : null; - } - - @Override - public Object getGeneric(T specific) { - return specific != null ? converter.getGeneric(specific) : null; - } - - @Override - public Class getSpecificType() { - return converter.getSpecificType(); - } - }; - } - - /** - * Returns a converter that passes generic and specific values through without converting. - * @param clazz Element class - * @param Element type - * @return A passthrough converter - */ - public static EquivalentConverter passthrough(final Class clazz) { - return ignoreNull(new EquivalentConverter() { - @Override - public T getSpecific(Object generic) { - return (T) generic; - } - - @Override - public Object getGeneric(T specific) { - return specific; - } - - @Override - public Class getSpecificType() { - return clazz; - } - }); - } - - /** - * Creates a simple converter for wrappers with {@code getHandle()} and {@code fromHandle(...)} methods. With Java 8, - * converters can be reduced to a single line (see {@link BukkitConverters#getWrappedGameProfileConverter()}). - * @param toHandle Function from wrapper to handle (i.e. {@code getHandle()}) - * @param fromHandle Function from handle to wrapper (i.e. {@code fromHandle(Object)}) - * @param Wrapper type - * @return A handle converter - */ - public static EquivalentConverter handle(final Function toHandle, - final Function fromHandle, final Class specificType) { - return new EquivalentConverter() { - @Override - public T getSpecific(Object generic) { - return fromHandle.apply(generic); - } - - @Override - public Object getGeneric(T specific) { - return toHandle.apply(specific); - } - - @Override - public Class getSpecificType() { - return specificType; - } - }; - } - - /** - * Creates a generic array converter. Converts a NMS object array to and from a wrapper array by converting - * each element individually. - * - * @param nmsClass NMS class - * @param converter Underlying converter - * @param Generic type - * @return An array converter - */ - public static EquivalentConverter array(final Class nmsClass, final EquivalentConverter converter) { - return new EquivalentConverter() { - @Override - public T[] getSpecific(Object generic) { - Object[] array = (Object[]) generic; - Class clazz = getSpecificType(); - T[] result = clazz.cast(Array.newInstance(clazz.getComponentType(), array.length)); - - // Unwrap every item - for (int i = 0; i < result.length; i++) { - result[i] = converter.getSpecific(array[i]); - } - - return result; - } - - @Override - public Object getGeneric(T[] specific) { - Object[] result = (Object[]) Array.newInstance(nmsClass, specific.length); - - // Wrap every item - for (int i = 0; i < result.length; i++) { - result[i] = converter.getGeneric(specific[i]); - } - - return result; - } - - @Override - public Class getSpecificType() { - return (Class) MinecraftReflection.getArrayClass(converter.getSpecificType()); - } - }; - } - - public static EquivalentConverter> optional(final EquivalentConverter converter) { - return new EquivalentConverter>() { - @Override - public Object getGeneric(Optional specific) { - return specific.map(converter::getGeneric); - } - - @Override - public Optional getSpecific(Object generic) { - Optional optional = (Optional) generic; - return optional.map(converter::getSpecific); - } - - @Override - public Class> getSpecificType() { - return (Class>) Optional.empty().getClass(); - } - }; - } -} +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2017 Dan Mulloy + * + * 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.wrappers; + +import java.lang.reflect.Array; +import java.util.Optional; +import java.util.function.Function; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.utility.MinecraftReflection; + +/** + * Utility class for converters + * @author dmulloy2 + */ +@SuppressWarnings("unchecked") +public class Converters { + + /** + * Returns a converter that ignores null elements, so that the underlying converter doesn't have to worry about them. + * @param converter Underlying converter + * @param Element type + * @return An ignore null converter + */ + public static EquivalentConverter ignoreNull(final EquivalentConverter converter) { + return new EquivalentConverter() { + @Override + public T getSpecific(Object generic) { + return generic != null ? converter.getSpecific(generic) : null; + } + + @Override + public Object getGeneric(T specific) { + return specific != null ? converter.getGeneric(specific) : null; + } + + @Override + public Class getSpecificType() { + return converter.getSpecificType(); + } + }; + } + + /** + * Returns a converter that passes generic and specific values through without converting. + * @param clazz Element class + * @param Element type + * @return A passthrough converter + */ + public static EquivalentConverter passthrough(final Class clazz) { + return ignoreNull(new EquivalentConverter() { + @Override + public T getSpecific(Object generic) { + return (T) generic; + } + + @Override + public Object getGeneric(T specific) { + return specific; + } + + @Override + public Class getSpecificType() { + return clazz; + } + }); + } + + /** + * Creates a simple converter for wrappers with {@code getHandle()} and {@code fromHandle(...)} methods. With Java 8, + * converters can be reduced to a single line (see {@link BukkitConverters#getWrappedGameProfileConverter()}). + * @param toHandle Function from wrapper to handle (i.e. {@code getHandle()}) + * @param fromHandle Function from handle to wrapper (i.e. {@code fromHandle(Object)}) + * @param Wrapper type + * @return A handle converter + */ + public static EquivalentConverter handle(final Function toHandle, + final Function fromHandle, final Class specificType) { + return new EquivalentConverter() { + @Override + public T getSpecific(Object generic) { + return fromHandle.apply(generic); + } + + @Override + public Object getGeneric(T specific) { + return toHandle.apply(specific); + } + + @Override + public Class getSpecificType() { + return specificType; + } + }; + } + + /** + * Creates a generic array converter. Converts a NMS object array to and from a wrapper array by converting + * each element individually. + * + * @param nmsClass NMS class + * @param converter Underlying converter + * @param Generic type + * @return An array converter + */ + public static EquivalentConverter array(final Class nmsClass, final EquivalentConverter converter) { + return new EquivalentConverter() { + @Override + public T[] getSpecific(Object generic) { + Object[] array = (Object[]) generic; + Class clazz = getSpecificType(); + T[] result = clazz.cast(Array.newInstance(clazz.getComponentType(), array.length)); + + // Unwrap every item + for (int i = 0; i < result.length; i++) { + result[i] = converter.getSpecific(array[i]); + } + + return result; + } + + @Override + public Object getGeneric(T[] specific) { + Object[] result = (Object[]) Array.newInstance(nmsClass, specific.length); + + // Wrap every item + for (int i = 0; i < result.length; i++) { + result[i] = converter.getGeneric(specific[i]); + } + + return result; + } + + @Override + public Class getSpecificType() { + return (Class) MinecraftReflection.getArrayClass(converter.getSpecificType()); + } + }; + } + + public static EquivalentConverter> optional(final EquivalentConverter converter) { + return new EquivalentConverter>() { + @Override + public Object getGeneric(Optional specific) { + return specific.map(converter::getGeneric); + } + + @Override + public Optional getSpecific(Object generic) { + Optional optional = (Optional) generic; + return optional.map(converter::getSpecific); + } + + @Override + public Class> getSpecificType() { + return (Class>) Optional.empty().getClass(); + } + }; + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java b/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java index 9ff4f0cd..79af6b5d 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java +++ b/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java @@ -1,103 +1,103 @@ -package com.comphenix.protocol.wrappers; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.EnumWrappers.Direction; - -import org.bukkit.util.Vector; - -public class MovingObjectPositionBlock implements Cloneable { - private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS( - "world.phys.MovingObjectPositionBlock", "world.phys.BlockHitResult", "MovingObjectPositionBlock"); - - private BlockPosition position; - private Vector posVector; - private Direction direction; - private boolean insideBlock; - - public MovingObjectPositionBlock() { } - - public MovingObjectPositionBlock(BlockPosition position, Vector posVector, Direction direction, boolean insideBlock) { - this.position = position; - this.posVector = posVector; - this.direction = direction; - this.insideBlock = insideBlock; - } - - public static Class getNmsClass() { - return NMS_CLASS; - } - - public BlockPosition getBlockPosition() { - return position; - } - - public void setBlockPosition(BlockPosition position) { - this.position = position; - } - - public Vector getPosVector() { - return posVector; - } - - public void setPosVector(Vector vector) { - this.posVector = vector; - } - - public Direction getDirection() { - return direction; - } - - public void setDirection(Direction direction) { - this.direction = direction; - } - - public boolean isInsideBlock() { - return insideBlock; - } - - public void setInsideBlock(boolean insideBlock) { - this.insideBlock = insideBlock; - } - - private static ConstructorAccessor constructor; - - public static EquivalentConverter getConverter() { - return Converters.ignoreNull(new EquivalentConverter() { - @Override - public Object getGeneric(MovingObjectPositionBlock specific) { - if (constructor == null) { - constructor = Accessors.getConstructorAccessor(NMS_CLASS, - MinecraftReflection.getVec3DClass(), - EnumWrappers.getDirectionClass(), - MinecraftReflection.getBlockPositionClass(), - boolean.class); - } - - Object nmsVector = BukkitConverters.getVectorConverter().getGeneric(specific.posVector); - Object nmsDirection = EnumWrappers.getDirectionConverter().getGeneric(specific.direction); - Object nmsBlockPos = BlockPosition.getConverter().getGeneric(specific.position); - - return constructor.invoke(nmsVector, nmsDirection, nmsBlockPos, specific.insideBlock); - } - - @Override - public MovingObjectPositionBlock getSpecific(Object generic) { - StructureModifier modifier = new StructureModifier<>(generic.getClass()).withTarget(generic); - Direction direction = modifier.withType(EnumWrappers.getDirectionClass(), EnumWrappers.getDirectionConverter()).read(0); - BlockPosition blockPos = modifier.withType(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()).read(0); - Vector posVector = modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0); - boolean insideBlock = (boolean) modifier.withType(boolean.class).read(1); - return new MovingObjectPositionBlock(blockPos, posVector, direction, insideBlock); - } - - @Override - public Class getSpecificType() { - return MovingObjectPositionBlock.class; - } - }); - } -} +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.EnumWrappers.Direction; + +import org.bukkit.util.Vector; + +public class MovingObjectPositionBlock implements Cloneable { + private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS( + "world.phys.MovingObjectPositionBlock", "world.phys.BlockHitResult", "MovingObjectPositionBlock"); + + private BlockPosition position; + private Vector posVector; + private Direction direction; + private boolean insideBlock; + + public MovingObjectPositionBlock() { } + + public MovingObjectPositionBlock(BlockPosition position, Vector posVector, Direction direction, boolean insideBlock) { + this.position = position; + this.posVector = posVector; + this.direction = direction; + this.insideBlock = insideBlock; + } + + public static Class getNmsClass() { + return NMS_CLASS; + } + + public BlockPosition getBlockPosition() { + return position; + } + + public void setBlockPosition(BlockPosition position) { + this.position = position; + } + + public Vector getPosVector() { + return posVector; + } + + public void setPosVector(Vector vector) { + this.posVector = vector; + } + + public Direction getDirection() { + return direction; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + public boolean isInsideBlock() { + return insideBlock; + } + + public void setInsideBlock(boolean insideBlock) { + this.insideBlock = insideBlock; + } + + private static ConstructorAccessor constructor; + + public static EquivalentConverter getConverter() { + return Converters.ignoreNull(new EquivalentConverter() { + @Override + public Object getGeneric(MovingObjectPositionBlock specific) { + if (constructor == null) { + constructor = Accessors.getConstructorAccessor(NMS_CLASS, + MinecraftReflection.getVec3DClass(), + EnumWrappers.getDirectionClass(), + MinecraftReflection.getBlockPositionClass(), + boolean.class); + } + + Object nmsVector = BukkitConverters.getVectorConverter().getGeneric(specific.posVector); + Object nmsDirection = EnumWrappers.getDirectionConverter().getGeneric(specific.direction); + Object nmsBlockPos = BlockPosition.getConverter().getGeneric(specific.position); + + return constructor.invoke(nmsVector, nmsDirection, nmsBlockPos, specific.insideBlock); + } + + @Override + public MovingObjectPositionBlock getSpecific(Object generic) { + StructureModifier modifier = new StructureModifier<>(generic.getClass()).withTarget(generic); + Direction direction = modifier.withType(EnumWrappers.getDirectionClass(), EnumWrappers.getDirectionConverter()).read(0); + BlockPosition blockPos = modifier.withType(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()).read(0); + Vector posVector = modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0); + boolean insideBlock = (boolean) modifier.withType(boolean.class).read(1); + return new MovingObjectPositionBlock(blockPos, posVector, direction, insideBlock); + } + + @Override + public Class getSpecificType() { + return MovingObjectPositionBlock.class; + } + }); + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/Pair.java b/src/main/java/com/comphenix/protocol/wrappers/Pair.java index 6a466fca..060a00eb 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/Pair.java +++ b/src/main/java/com/comphenix/protocol/wrappers/Pair.java @@ -1,43 +1,43 @@ -package com.comphenix.protocol.wrappers; - -import java.util.Objects; - -public class Pair { - private A first; - private B second; - - public Pair(A first, B second) { - this.first = first; - this.second = second; - } - - public A getFirst() { - return first; - } - - public B getSecond() { - return second; - } - - public void setFirst(A first) { - this.first = first; - } - - public void setSecond(B second) { - this.second = second; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Pair pair = (Pair) o; - return Objects.equals(first, pair.first) && - Objects.equals(second, pair.second); - } - - @Override - public int hashCode() { - return Objects.hash(first, second); - } -} +package com.comphenix.protocol.wrappers; + +import java.util.Objects; + +public class Pair { + private A first; + private B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { + return first; + } + + public B getSecond() { + return second; + } + + public void setFirst(A first) { + this.first = first; + } + + public void setSecond(B second) { + this.second = second; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && + Objects.equals(second, pair.second); + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/Vector3F.java b/src/main/java/com/comphenix/protocol/wrappers/Vector3F.java index d02b9b5c..6402956e 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/Vector3F.java +++ b/src/main/java/com/comphenix/protocol/wrappers/Vector3F.java @@ -1,127 +1,127 @@ -/** - * (c) 2016 dmulloy2 - */ -package com.comphenix.protocol.wrappers; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.utility.MinecraftReflection; - -import java.lang.reflect.Constructor; - -/** - * @author dmulloy2 - */ -public class Vector3F { - protected float x; - protected float y; - protected float z; - - public Vector3F() { - this(0, 0, 0); - } - - public Vector3F(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - } - - public float getX() { - return x; - } - - public Vector3F setX(float x) { - this.x = x; - return this; - } - - public float getY() { - return y; - } - - public Vector3F setY(float y) { - this.y = y; - return this; - } - - public float getZ() { - return z; - } - - public Vector3F setZ(float z) { - this.z = z; - return this; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Float.floatToIntBits(x); - result = prime * result + Float.floatToIntBits(y); - result = prime * result + Float.floatToIntBits(z); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - - if (obj instanceof Vector3F) { - Vector3F that = (Vector3F) obj; - if (Float.floatToIntBits(x) != Float.floatToIntBits(that.x)) - return false; - if (Float.floatToIntBits(y) != Float.floatToIntBits(that.y)) - return false; - if (Float.floatToIntBits(z) != Float.floatToIntBits(that.z)) - return false; - return true; - } - - return false; - } - - private static Constructor constructor = null; - private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS("core.Vector3f", "core.Rotations", "Vector3f"); - - public static Class getMinecraftClass() { - return NMS_CLASS; - } - - public static EquivalentConverter getConverter() { - return Converters.ignoreNull(new EquivalentConverter() { - @Override - public Class getSpecificType() { - return Vector3F.class; - } - - @Override - public Object getGeneric(Vector3F specific) { - if (constructor == null) { - try { - constructor = NMS_CLASS.getConstructor(float.class, float.class, float.class); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to find constructor for Vector3f", ex); - } - } - - try { - return constructor.newInstance(specific.x, specific.y, specific.z); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException("Failed to create new instance of Vector3f", ex); - } - } - - @Override - public Vector3F getSpecific(Object generic) { - StructureModifier modifier = new StructureModifier(generic.getClass()) - .withTarget(generic).withType(float.class); - float x = modifier.read(0); - float y = modifier.read(1); - float z = modifier.read(2); - return new Vector3F(x, y, z); - } - }); - } +/** + * (c) 2016 dmulloy2 + */ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; + +import java.lang.reflect.Constructor; + +/** + * @author dmulloy2 + */ +public class Vector3F { + protected float x; + protected float y; + protected float z; + + public Vector3F() { + this(0, 0, 0); + } + + public Vector3F(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public float getX() { + return x; + } + + public Vector3F setX(float x) { + this.x = x; + return this; + } + + public float getY() { + return y; + } + + public Vector3F setY(float y) { + this.y = y; + return this; + } + + public float getZ() { + return z; + } + + public Vector3F setZ(float z) { + this.z = z; + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(x); + result = prime * result + Float.floatToIntBits(y); + result = prime * result + Float.floatToIntBits(z); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + + if (obj instanceof Vector3F) { + Vector3F that = (Vector3F) obj; + if (Float.floatToIntBits(x) != Float.floatToIntBits(that.x)) + return false; + if (Float.floatToIntBits(y) != Float.floatToIntBits(that.y)) + return false; + if (Float.floatToIntBits(z) != Float.floatToIntBits(that.z)) + return false; + return true; + } + + return false; + } + + private static Constructor constructor = null; + private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS("core.Vector3f", "core.Rotations", "Vector3f"); + + public static Class getMinecraftClass() { + return NMS_CLASS; + } + + public static EquivalentConverter getConverter() { + return Converters.ignoreNull(new EquivalentConverter() { + @Override + public Class getSpecificType() { + return Vector3F.class; + } + + @Override + public Object getGeneric(Vector3F specific) { + if (constructor == null) { + try { + constructor = NMS_CLASS.getConstructor(float.class, float.class, float.class); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to find constructor for Vector3f", ex); + } + } + + try { + return constructor.newInstance(specific.x, specific.y, specific.z); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException("Failed to create new instance of Vector3f", ex); + } + } + + @Override + public Vector3F getSpecific(Object generic) { + StructureModifier modifier = new StructureModifier(generic.getClass()) + .withTarget(generic).withType(float.class); + float x = modifier.read(0); + float y = modifier.read(1); + float z = modifier.read(2); + return new Vector3F(x, y, z); + } + }); + } } \ No newline at end of file diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java index 3a663778..04f9e19c 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedParticle.java @@ -1,169 +1,169 @@ -package com.comphenix.protocol.wrappers; - -import java.lang.reflect.Modifier; - -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.MethodAccessor; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.utility.MinecraftReflection; - -import com.comphenix.protocol.utility.MinecraftVersion; -import com.mojang.math.Vector3fa; -import org.bukkit.Color; -import org.bukkit.Particle; -import org.bukkit.inventory.ItemStack; - -/** - * Represents an immutable wrapped ParticleParam in 1.13 - */ -public class WrappedParticle { - private static MethodAccessor toBukkit; - private static MethodAccessor toNMS; - private static MethodAccessor toCraftData; - - private static void ensureMethods() { - if (toBukkit != null && toNMS != null) { - return; - } - - FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("CraftParticle")); - FuzzyMethodContract contract = FuzzyMethodContract - .newBuilder() - .requireModifier(Modifier.STATIC) - .returnTypeExact(Particle.class) - .parameterExactType(MinecraftReflection.getParticleParam()) - .build(); - toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); - - contract = FuzzyMethodContract - .newBuilder() - .requireModifier(Modifier.STATIC) - .returnTypeExact(MinecraftReflection.getParticleParam()) - .parameterCount(2) - .build(); - toNMS = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); - - Class cbData = MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData"); - fuzzy = FuzzyReflection.fromClass(cbData); - contract = FuzzyMethodContract - .newBuilder() - .requireModifier(Modifier.STATIC) - .returnTypeExact(cbData) - .parameterExactArray(MinecraftReflection.getIBlockDataClass()) - .build(); - toCraftData = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); - } - - private final Particle particle; - private final T data; - private final Object handle; - - private WrappedParticle(Object handle, Particle particle, T data) { - this.handle = handle; - this.particle = particle; - this.data = data; - } - - /** - * @return This particle's Bukkit type - */ - public Particle getParticle() { - return particle; - } - - /** - * Gets this Particle's Bukkit/ProtocolLib data. The type of this data depends on the - * {@link #getParticle() Particle type}. For Block particles it will be {@link WrappedBlockData}, - * for Item crack particles, it will be an {@link ItemStack}, and for redstone particles it will - * be {@link Particle.DustOptions} - * - * @return The particle data - */ - public T getData() { - return data; - } - - /** - * @return NMS handle - */ - public Object getHandle() { - return handle; - } - - public static WrappedParticle fromHandle(Object handle) { - ensureMethods(); - - Particle bukkit = (Particle) toBukkit.invoke(null, handle); - Object data = null; - - switch (bukkit) { - case BLOCK_CRACK: - case BLOCK_DUST: - case FALLING_DUST: - data = getBlockData(handle); - break; - case ITEM_CRACK: - data = getItem(handle); - break; - case REDSTONE: - data = getRedstone(handle); - break; - default: - break; - } - - return new WrappedParticle<>(handle, bukkit, data); - } - - private static WrappedBlockData getBlockData(Object handle) { - return new StructureModifier<>(handle.getClass()) - .withTarget(handle) - .withType(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter()) - .read(0); - } - - private static Object getItem(Object handle) { - return new StructureModifier<>(handle.getClass()) - .withTarget(handle) - .withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter()) - .read(0); - } - - private static Object getRedstone(Object handle) { - int r, g, b; - float alpha; - - if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { - StructureModifier modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); - Vector3fa rgb = (Vector3fa) modifier.withType(Vector3fa.class).read(0); - - r = (int) (rgb.a() * 255); - g = (int) (rgb.b() * 255); - b = (int) (rgb.c() * 255); - alpha = (float) modifier.withType(float.class).read(0); - } else { - StructureModifier modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class); - r = (int) (modifier.read(0) * 255); - g = (int) (modifier.read(1) * 255); - b = (int) (modifier.read(2) * 255); - alpha = modifier.read(3); - } - - return new Particle.DustOptions(Color.fromRGB(r, g, b), alpha); - } - - public static WrappedParticle create(Particle particle, T data) { - ensureMethods(); - - Object bukkitData = data; - if (data instanceof WrappedBlockData) { - WrappedBlockData blockData = (WrappedBlockData) data; - bukkitData = toCraftData.invoke(null, blockData.getHandle()); - } - - Object handle = toNMS.invoke(null, particle, bukkitData); - return new WrappedParticle<>(handle, particle, data); - } -} +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.Modifier; + +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.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; + +import com.comphenix.protocol.utility.MinecraftVersion; +import com.mojang.math.Vector3fa; +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.inventory.ItemStack; + +/** + * Represents an immutable wrapped ParticleParam in 1.13 + */ +public class WrappedParticle { + private static MethodAccessor toBukkit; + private static MethodAccessor toNMS; + private static MethodAccessor toCraftData; + + private static void ensureMethods() { + if (toBukkit != null && toNMS != null) { + return; + } + + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("CraftParticle")); + FuzzyMethodContract contract = FuzzyMethodContract + .newBuilder() + .requireModifier(Modifier.STATIC) + .returnTypeExact(Particle.class) + .parameterExactType(MinecraftReflection.getParticleParam()) + .build(); + toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); + + contract = FuzzyMethodContract + .newBuilder() + .requireModifier(Modifier.STATIC) + .returnTypeExact(MinecraftReflection.getParticleParam()) + .parameterCount(2) + .build(); + toNMS = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); + + Class cbData = MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData"); + fuzzy = FuzzyReflection.fromClass(cbData); + contract = FuzzyMethodContract + .newBuilder() + .requireModifier(Modifier.STATIC) + .returnTypeExact(cbData) + .parameterExactArray(MinecraftReflection.getIBlockDataClass()) + .build(); + toCraftData = Accessors.getMethodAccessor(fuzzy.getMethod(contract)); + } + + private final Particle particle; + private final T data; + private final Object handle; + + private WrappedParticle(Object handle, Particle particle, T data) { + this.handle = handle; + this.particle = particle; + this.data = data; + } + + /** + * @return This particle's Bukkit type + */ + public Particle getParticle() { + return particle; + } + + /** + * Gets this Particle's Bukkit/ProtocolLib data. The type of this data depends on the + * {@link #getParticle() Particle type}. For Block particles it will be {@link WrappedBlockData}, + * for Item crack particles, it will be an {@link ItemStack}, and for redstone particles it will + * be {@link Particle.DustOptions} + * + * @return The particle data + */ + public T getData() { + return data; + } + + /** + * @return NMS handle + */ + public Object getHandle() { + return handle; + } + + public static WrappedParticle fromHandle(Object handle) { + ensureMethods(); + + Particle bukkit = (Particle) toBukkit.invoke(null, handle); + Object data = null; + + switch (bukkit) { + case BLOCK_CRACK: + case BLOCK_DUST: + case FALLING_DUST: + data = getBlockData(handle); + break; + case ITEM_CRACK: + data = getItem(handle); + break; + case REDSTONE: + data = getRedstone(handle); + break; + default: + break; + } + + return new WrappedParticle<>(handle, bukkit, data); + } + + private static WrappedBlockData getBlockData(Object handle) { + return new StructureModifier<>(handle.getClass()) + .withTarget(handle) + .withType(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter()) + .read(0); + } + + private static Object getItem(Object handle) { + return new StructureModifier<>(handle.getClass()) + .withTarget(handle) + .withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter()) + .read(0); + } + + private static Object getRedstone(Object handle) { + int r, g, b; + float alpha; + + if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { + StructureModifier modifier = new StructureModifier<>(handle.getClass()).withTarget(handle); + Vector3fa rgb = (Vector3fa) modifier.withType(Vector3fa.class).read(0); + + r = (int) (rgb.a() * 255); + g = (int) (rgb.b() * 255); + b = (int) (rgb.c() * 255); + alpha = (float) modifier.withType(float.class).read(0); + } else { + StructureModifier modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class); + r = (int) (modifier.read(0) * 255); + g = (int) (modifier.read(1) * 255); + b = (int) (modifier.read(2) * 255); + alpha = modifier.read(3); + } + + return new Particle.DustOptions(Color.fromRGB(r, g, b), alpha); + } + + public static WrappedParticle create(Particle particle, T data) { + ensureMethods(); + + Object bukkitData = data; + if (data instanceof WrappedBlockData) { + WrappedBlockData blockData = (WrappedBlockData) data; + bukkitData = toCraftData.invoke(null, blockData.getHandle()); + } + + Object handle = toNMS.invoke(null, particle, bukkitData); + return new WrappedParticle<>(handle, particle, data); + } +} diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedVillagerData.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedVillagerData.java index e41ed1f4..bb7579d7 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedVillagerData.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedVillagerData.java @@ -1,84 +1,84 @@ -package com.comphenix.protocol.wrappers; - -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; - -public class WrappedVillagerData extends AbstractWrapper implements ClonableWrapper { - private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS( - "world.entity.npc.VillagerData","VillagerData"); - private static final Class TYPE_CLASS = MinecraftReflection.getNullableNMS( - "world.entity.npc.VillagerType", "VillagerType"); - private static final Class PROF_CLASS = MinecraftReflection.getNullableNMS( - "world.entity.npc.VillagerProfession", "VillagerProfession"); - - private static EquivalentConverter TYPE_CONVERTER; - private static EquivalentConverter PROF_CONVERTER; - - static { - if (NMS_CLASS != null) { - TYPE_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Type.class, TYPE_CLASS); - PROF_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Profession.class, PROF_CLASS); - } - } - - public enum Type { - DESERT, JUNGLE, PLAINS, SAVANNA, SNOW, SWAMP, TAIGA - } - - public enum Profession { - NONE, ARMORER, BUTCHER, CARTOGRAPHER, CLERIC, FARMER, FISHERMAN, - FLETCHER, LEATHERWORKER, LIBRARIAN, MASON, NITWIT, SHEPHERD, - TOOLSMITH, WEAPONSMITH - } - - private StructureModifier modifier; - - private WrappedVillagerData(Object handle) { - super(NMS_CLASS); - setHandle(handle); - - modifier = new StructureModifier<>(NMS_CLASS).withTarget(handle); - } - - public static WrappedVillagerData fromHandle(Object handle) { - return new WrappedVillagerData(handle); - } - - private static ConstructorAccessor CONSTRUCTOR; - - public static WrappedVillagerData fromValues(Type type, Profession profession, int level) { - Object genericType = TYPE_CONVERTER.getGeneric(type); - Object genericProf = PROF_CONVERTER.getGeneric(profession); - - if (CONSTRUCTOR == null) { - CONSTRUCTOR = Accessors.getConstructorAccessor(NMS_CLASS, TYPE_CLASS, PROF_CLASS, int.class); - } - - Object handle = CONSTRUCTOR.invoke(genericType, genericProf, level); - return fromHandle(handle); - } - - public static Class getNmsClass() { - return NMS_CLASS; - } - - public int getLevel() { - return modifier.withType(int.class).read(0); - } - - public Type getType() { - return modifier.withType(TYPE_CLASS, TYPE_CONVERTER).read(0); - } - - public Profession getProfession() { - return modifier.withType(PROF_CLASS, PROF_CONVERTER).read(0); - } - - @Override - public WrappedVillagerData deepClone() { - return WrappedVillagerData.fromValues(getType(), getProfession(), getLevel()); - } -} +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; + +public class WrappedVillagerData extends AbstractWrapper implements ClonableWrapper { + private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS( + "world.entity.npc.VillagerData","VillagerData"); + private static final Class TYPE_CLASS = MinecraftReflection.getNullableNMS( + "world.entity.npc.VillagerType", "VillagerType"); + private static final Class PROF_CLASS = MinecraftReflection.getNullableNMS( + "world.entity.npc.VillagerProfession", "VillagerProfession"); + + private static EquivalentConverter TYPE_CONVERTER; + private static EquivalentConverter PROF_CONVERTER; + + static { + if (NMS_CLASS != null) { + TYPE_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Type.class, TYPE_CLASS); + PROF_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Profession.class, PROF_CLASS); + } + } + + public enum Type { + DESERT, JUNGLE, PLAINS, SAVANNA, SNOW, SWAMP, TAIGA + } + + public enum Profession { + NONE, ARMORER, BUTCHER, CARTOGRAPHER, CLERIC, FARMER, FISHERMAN, + FLETCHER, LEATHERWORKER, LIBRARIAN, MASON, NITWIT, SHEPHERD, + TOOLSMITH, WEAPONSMITH + } + + private StructureModifier modifier; + + private WrappedVillagerData(Object handle) { + super(NMS_CLASS); + setHandle(handle); + + modifier = new StructureModifier<>(NMS_CLASS).withTarget(handle); + } + + public static WrappedVillagerData fromHandle(Object handle) { + return new WrappedVillagerData(handle); + } + + private static ConstructorAccessor CONSTRUCTOR; + + public static WrappedVillagerData fromValues(Type type, Profession profession, int level) { + Object genericType = TYPE_CONVERTER.getGeneric(type); + Object genericProf = PROF_CONVERTER.getGeneric(profession); + + if (CONSTRUCTOR == null) { + CONSTRUCTOR = Accessors.getConstructorAccessor(NMS_CLASS, TYPE_CLASS, PROF_CLASS, int.class); + } + + Object handle = CONSTRUCTOR.invoke(genericType, genericProf, level); + return fromHandle(handle); + } + + public static Class getNmsClass() { + return NMS_CLASS; + } + + public int getLevel() { + return modifier.withType(int.class).read(0); + } + + public Type getType() { + return modifier.withType(TYPE_CLASS, TYPE_CONVERTER).read(0); + } + + public Profession getProfession() { + return modifier.withType(PROF_CLASS, PROF_CONVERTER).read(0); + } + + @Override + public WrappedVillagerData deepClone() { + return WrappedVillagerData.fromValues(getType(), getProfession(), getLevel()); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3ab3c97a..4de8994d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,45 +1,45 @@ -name: ProtocolLib -version: ${project.fullVersion} -description: Provides read/write access to the Minecraft protocol. -authors: [dmulloy2, comphenix] - -main: com.comphenix.protocol.ProtocolLib -load: STARTUP -database: false -api-version: "1.13" - -commands: - protocol: - description: Performs administrative tasks regarding ProtocolLib. - usage: / config|check|update|timings|listeners|version|dump - permission: protocol.admin - permission-message: You don't have - packet: - description: Add or remove a simple packet listener. - usage: / add|remove|names client|server [ID start]-[ID stop] [detailed] - permission: protocol.admin - permission-message: You don't have - filter: - description: Add or remove programmable filters to the packet listeners. - usage: / add|remove name [ID start]-[ID stop] - aliases: [packet_filter] - permission: protocol.admin - permission-message: You don't have - packetlog: - description: Logs hex representations of packets to a file or console - usage: / [location] - permission: protocol.admin - permission-message: You don't have - -permissions: - protocol.*: - description: Gives access to everything. - children: - protocol.admin: true - protocol.info: true - protocol.admin: - description: Able to initiate the update process, and can configure debug mode. - default: op - protocol.info: - description: Can read update notifications and error reports. +name: ProtocolLib +version: ${project.fullVersion} +description: Provides read/write access to the Minecraft protocol. +authors: [dmulloy2, comphenix] + +main: com.comphenix.protocol.ProtocolLib +load: STARTUP +database: false +api-version: "1.13" + +commands: + protocol: + description: Performs administrative tasks regarding ProtocolLib. + usage: / config|check|update|timings|listeners|version|dump + permission: protocol.admin + permission-message: You don't have + packet: + description: Add or remove a simple packet listener. + usage: / add|remove|names client|server [ID start]-[ID stop] [detailed] + permission: protocol.admin + permission-message: You don't have + filter: + description: Add or remove programmable filters to the packet listeners. + usage: / add|remove name [ID start]-[ID stop] + aliases: [packet_filter] + permission: protocol.admin + permission-message: You don't have + packetlog: + description: Logs hex representations of packets to a file or console + usage: / [location] + permission: protocol.admin + permission-message: You don't have + +permissions: + protocol.*: + description: Gives access to everything. + children: + protocol.admin: true + protocol.info: true + protocol.admin: + description: Able to initiate the update process, and can configure debug mode. + default: op + protocol.info: + description: Can read update notifications and error reports. default: op \ No newline at end of file diff --git a/src/test/java/com/comphenix/protocol/BukkitInitialization.java b/src/test/java/com/comphenix/protocol/BukkitInitialization.java index cf9e31ab..8811d818 100644 --- a/src/test/java/com/comphenix/protocol/BukkitInitialization.java +++ b/src/test/java/com/comphenix/protocol/BukkitInitialization.java @@ -1,126 +1,126 @@ -package com.comphenix.protocol; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.utility.MinecraftReflectionTestUtil; -import java.util.Collections; -import java.util.List; -import net.minecraft.SharedConstants; -import net.minecraft.core.IRegistry; -import net.minecraft.server.DispenserRegistry; -import net.minecraft.server.level.WorldServer; -import org.apache.logging.log4j.LogManager; -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.craftbukkit.v1_19_R1.CraftServer; -import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemFactory; -import org.bukkit.craftbukkit.v1_19_R1.util.Versioning; -import org.spigotmc.SpigotWorldConfig; - -/** - * Used to ensure that ProtocolLib and Bukkit is prepared to be tested. - * - * @author Kristian - */ -public class BukkitInitialization { - - private static final BukkitInitialization instance = new BukkitInitialization(); - private boolean initialized; - private boolean packaged; - - private BukkitInitialization() { - System.out.println("Created new BukkitInitialization on " + Thread.currentThread().getName()); - } - - /** - * Statically initializes the mock server for unit testing - */ - public static synchronized void initializeAll() { - instance.initialize(); - } - - /** - * Initialize Bukkit and ProtocolLib such that we can perfrom unit testing - */ - private void initialize() { - if (!this.initialized) { - // Denote that we're done - this.initialized = true; - - try { - LogManager.getLogger(); - } catch (Throwable ex) { - // Happens only on my Jenkins, but if it errors here it works when it matters - ex.printStackTrace(); - } - - instance.setPackage(); - - SharedConstants.a(); - DispenserRegistry.a(); - - try { - IRegistry.class.getName(); - } catch (Throwable ex) { - ex.printStackTrace(); - } - - String releaseTarget = SharedConstants.b().getReleaseTarget(); - String serverVersion = CraftServer.class.getPackage().getImplementationVersion(); - - // Mock the server object - Server mockedServer = mock(Server.class); - - when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft")); - when(mockedServer.getName()).thenReturn("Mock Server"); - when(mockedServer.getVersion()).thenReturn(serverVersion + " (MC: " + releaseTarget + ")"); - when(mockedServer.getBukkitVersion()).thenReturn(Versioning.getBukkitVersion()); - - when(mockedServer.getItemFactory()).thenReturn(CraftItemFactory.instance()); - when(mockedServer.isPrimaryThread()).thenReturn(true); - - WorldServer nmsWorld = mock(WorldServer.class); - - SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class); - - try { - FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig")); - spigotConfig.set(nmsWorld, mockWorldConfig); - } catch (ReflectiveOperationException ex) { - throw new RuntimeException(ex); - } - - CraftWorld world = mock(CraftWorld.class); - when(world.getHandle()).thenReturn(nmsWorld); - - List worlds = Collections.singletonList(world); - when(mockedServer.getWorlds()).thenReturn(worlds); - - // Inject this fake server - Bukkit.setServer(mockedServer); - } - } - - /** - * Ensure that package names are correctly set up. - */ - private void setPackage() { - if (!this.packaged) { - this.packaged = true; - - try { - LogManager.getLogger(); - } catch (Throwable ex) { - // Happens only on my Jenkins, but if it errors here it works when it matters - ex.printStackTrace(); - } - - MinecraftReflectionTestUtil.init(); - } - } +package com.comphenix.protocol; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.utility.MinecraftReflectionTestUtil; +import java.util.Collections; +import java.util.List; +import net.minecraft.SharedConstants; +import net.minecraft.core.IRegistry; +import net.minecraft.server.DispenserRegistry; +import net.minecraft.server.level.WorldServer; +import org.apache.logging.log4j.LogManager; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R1.CraftServer; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_19_R1.util.Versioning; +import org.spigotmc.SpigotWorldConfig; + +/** + * Used to ensure that ProtocolLib and Bukkit is prepared to be tested. + * + * @author Kristian + */ +public class BukkitInitialization { + + private static final BukkitInitialization instance = new BukkitInitialization(); + private boolean initialized; + private boolean packaged; + + private BukkitInitialization() { + System.out.println("Created new BukkitInitialization on " + Thread.currentThread().getName()); + } + + /** + * Statically initializes the mock server for unit testing + */ + public static synchronized void initializeAll() { + instance.initialize(); + } + + /** + * Initialize Bukkit and ProtocolLib such that we can perfrom unit testing + */ + private void initialize() { + if (!this.initialized) { + // Denote that we're done + this.initialized = true; + + try { + LogManager.getLogger(); + } catch (Throwable ex) { + // Happens only on my Jenkins, but if it errors here it works when it matters + ex.printStackTrace(); + } + + instance.setPackage(); + + SharedConstants.a(); + DispenserRegistry.a(); + + try { + IRegistry.class.getName(); + } catch (Throwable ex) { + ex.printStackTrace(); + } + + String releaseTarget = SharedConstants.b().getReleaseTarget(); + String serverVersion = CraftServer.class.getPackage().getImplementationVersion(); + + // Mock the server object + Server mockedServer = mock(Server.class); + + when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft")); + when(mockedServer.getName()).thenReturn("Mock Server"); + when(mockedServer.getVersion()).thenReturn(serverVersion + " (MC: " + releaseTarget + ")"); + when(mockedServer.getBukkitVersion()).thenReturn(Versioning.getBukkitVersion()); + + when(mockedServer.getItemFactory()).thenReturn(CraftItemFactory.instance()); + when(mockedServer.isPrimaryThread()).thenReturn(true); + + WorldServer nmsWorld = mock(WorldServer.class); + + SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class); + + try { + FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig")); + spigotConfig.set(nmsWorld, mockWorldConfig); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + + CraftWorld world = mock(CraftWorld.class); + when(world.getHandle()).thenReturn(nmsWorld); + + List worlds = Collections.singletonList(world); + when(mockedServer.getWorlds()).thenReturn(worlds); + + // Inject this fake server + Bukkit.setServer(mockedServer); + } + } + + /** + * Ensure that package names are correctly set up. + */ + private void setPackage() { + if (!this.packaged) { + this.packaged = true; + + try { + LogManager.getLogger(); + } catch (Throwable ex) { + // Happens only on my Jenkins, but if it errors here it works when it matters + ex.printStackTrace(); + } + + MinecraftReflectionTestUtil.init(); + } + } } \ No newline at end of file diff --git a/src/test/java/com/comphenix/protocol/PacketTypeTest.java b/src/test/java/com/comphenix/protocol/PacketTypeTest.java index ace2adaf..2fa60808 100644 --- a/src/test/java/com/comphenix/protocol/PacketTypeTest.java +++ b/src/test/java/com/comphenix/protocol/PacketTypeTest.java @@ -1,334 +1,334 @@ -/** - * 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.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 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") - public static void main(String[] args) 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("j"); - 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); - - // Format it like SET_PROTOCOL - StringBuilder fieldName = new StringBuilder(); - char[] chars = className.toCharArray(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; - if (i != 0 && Character.isUpperCase(c)) { - fieldName.append("_"); - } - fieldName.append(Character.toUpperCase(c)); - } - - builder.append(fieldName.toString().replace("N_B_T", "NBT")); - 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(); - } - - @BeforeAll - public static void initializeReflection() { - BukkitInitialization.initializeAll(); - } - - @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 { - boolean fail = false; - - EnumProtocol[] protocols = EnumProtocol.values(); - for (EnumProtocol protocol : protocols) { - Field field = EnumProtocol.class.getDeclaredField("j"); - 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) { - ex.printStackTrace(); - fail = true; - } - } - } - } - - assertFalse(fail, "Packet type(s) were incorrect!"); - } - - @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"); - } -} +/** + * 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.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 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") + public static void main(String[] args) 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("j"); + 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); + + // Format it like SET_PROTOCOL + StringBuilder fieldName = new StringBuilder(); + char[] chars = className.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (i != 0 && Character.isUpperCase(c)) { + fieldName.append("_"); + } + fieldName.append(Character.toUpperCase(c)); + } + + builder.append(fieldName.toString().replace("N_B_T", "NBT")); + 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(); + } + + @BeforeAll + public static void initializeReflection() { + BukkitInitialization.initializeAll(); + } + + @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 { + boolean fail = false; + + EnumProtocol[] protocols = EnumProtocol.values(); + for (EnumProtocol protocol : protocols) { + Field field = EnumProtocol.class.getDeclaredField("j"); + 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) { + ex.printStackTrace(); + fail = true; + } + } + } + } + + assertFalse(fail, "Packet type(s) were incorrect!"); + } + + @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"); + } +} diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 4f7bd019..68e77732 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -1,1029 +1,1029 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S. - * Stangeland - *

- * 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.events; - -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -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.BlockPosition; -import com.comphenix.protocol.wrappers.BukkitConverters; -import com.comphenix.protocol.wrappers.ComponentConverter; -import com.comphenix.protocol.wrappers.Either; -import com.comphenix.protocol.wrappers.EnumWrappers; -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.SoundCategory; -import com.comphenix.protocol.wrappers.MovingObjectPositionBlock; -import com.comphenix.protocol.wrappers.Pair; -import com.comphenix.protocol.wrappers.WrappedBlockData; -import com.comphenix.protocol.wrappers.WrappedChatComponent; -import com.comphenix.protocol.wrappers.WrappedDataWatcher; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; -import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction; -import com.comphenix.protocol.wrappers.WrappedGameProfile; -import com.comphenix.protocol.wrappers.WrappedRegistry; -import com.comphenix.protocol.wrappers.WrappedSaltedSignature; -import com.comphenix.protocol.wrappers.WrappedWatchableObject; -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.IRegistry; -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.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.assertNull; -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 watchConvert = BukkitConverters.getDataWatcherConverter(); - private final EquivalentConverter 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 void testPrimitive(StructureModifier 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 void testObjectArray(StructureModifier 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 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 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 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> itemAccess = windowItems.getItemListModifier(); - - List 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 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 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> positionAccessor = explosionPacket.getBlockPositionCollectionModifier(); - - assertEquals(positionAccessor.read(0).size(), 0); - - List 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 cloned = positionAccessor.read(0); - - assertEquals(positions, cloned); - } - - @Test - public void testGetWatchableCollectionModifier() { - PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); - StructureModifier> watchableAccessor = - entityMetadata.getWatchableCollectionModifier(); - - assertNull(watchableAccessor.read(0)); - - WrappedDataWatcher watcher = new WrappedDataWatcher(); - watcher.setObject(0, Registry.get(String.class), "Test"); - watcher.setObject(1, Registry.get(Byte.class), (byte) 21); - - List list = watcher.getWatchableObjects(); - - // Insert and read back - watchableAccessor.write(0, list); - assertEquals(list, watchableAccessor.read(0)); - - // Put it into a new data watcher - WrappedDataWatcher newWatcher = new WrappedDataWatcher(watchableAccessor.read(0)); - assertEquals(newWatcher.getWatchableObjects(), list); - } - - @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() {{ - this.add(420); - this.add(69); - }}); - List 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 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 = IRegistry.ak.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().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 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> 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> 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 testLoginSignatureNonce() { - PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN); - encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3}); - - byte[] nonce = {4, 5, 6}; - encryptionStart.getLoginSignatures().write(0, Either.left(nonce)); - - byte[] read = encryptionStart.getLoginSignatures().read(0).left().get(); - assertArrayEquals(nonce, read); - } - - @Test - public void testLoginSignatureSigned() { - PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN); - encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3}); - - byte[] signature = new byte[512]; - long salt = 124L; - encryptionStart.getLoginSignatures().write(0, Either.right(new WrappedSaltedSignature(salt, signature))); - - WrappedSaltedSignature read = encryptionStart.getLoginSignatures().read(0).right().get(); - assertEquals(salt, read.getSalt()); - assertArrayEquals(signature, read.getSignature()); - } - - // 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 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")) { - 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.getWatchableCollectionModifier().write(0, Lists.newArrayList( - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(Byte.class)), - (byte) 1), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(Integer.class)), - 1), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(Float.class)), - 1F), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(String.class)), - "String"), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(Boolean.class)), - true), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)), - Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.getItemStackSerializer(false)), - BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.WOODEN_AXE))), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(CatVariant.class)), - CatVariant.a), - new WrappedWatchableObject( - new WrappedDataWatcherObject(0, Registry.get(FrogVariant.class)), - FrogVariant.c) - )); - } 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 - System.out.println("Cloning " + type); - - // 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 (Exception ex) { - Assertions.fail("Unable to clone " + type, ex); - } - } - } - - // 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 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 - } -} +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S. + * Stangeland + *

+ * 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.events; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +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.BlockPosition; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.ComponentConverter; +import com.comphenix.protocol.wrappers.Either; +import com.comphenix.protocol.wrappers.EnumWrappers; +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.SoundCategory; +import com.comphenix.protocol.wrappers.MovingObjectPositionBlock; +import com.comphenix.protocol.wrappers.Pair; +import com.comphenix.protocol.wrappers.WrappedBlockData; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; +import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction; +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.comphenix.protocol.wrappers.WrappedRegistry; +import com.comphenix.protocol.wrappers.WrappedSaltedSignature; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; +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.IRegistry; +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.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.assertNull; +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 watchConvert = BukkitConverters.getDataWatcherConverter(); + private final EquivalentConverter 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 void testPrimitive(StructureModifier 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 void testObjectArray(StructureModifier 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 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 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 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> itemAccess = windowItems.getItemListModifier(); + + List 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 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 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> positionAccessor = explosionPacket.getBlockPositionCollectionModifier(); + + assertEquals(positionAccessor.read(0).size(), 0); + + List 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 cloned = positionAccessor.read(0); + + assertEquals(positions, cloned); + } + + @Test + public void testGetWatchableCollectionModifier() { + PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA); + StructureModifier> watchableAccessor = + entityMetadata.getWatchableCollectionModifier(); + + assertNull(watchableAccessor.read(0)); + + WrappedDataWatcher watcher = new WrappedDataWatcher(); + watcher.setObject(0, Registry.get(String.class), "Test"); + watcher.setObject(1, Registry.get(Byte.class), (byte) 21); + + List list = watcher.getWatchableObjects(); + + // Insert and read back + watchableAccessor.write(0, list); + assertEquals(list, watchableAccessor.read(0)); + + // Put it into a new data watcher + WrappedDataWatcher newWatcher = new WrappedDataWatcher(watchableAccessor.read(0)); + assertEquals(newWatcher.getWatchableObjects(), list); + } + + @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() {{ + this.add(420); + this.add(69); + }}); + List 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 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 = IRegistry.ak.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().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 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> 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> 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 testLoginSignatureNonce() { + PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN); + encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3}); + + byte[] nonce = {4, 5, 6}; + encryptionStart.getLoginSignatures().write(0, Either.left(nonce)); + + byte[] read = encryptionStart.getLoginSignatures().read(0).left().get(); + assertArrayEquals(nonce, read); + } + + @Test + public void testLoginSignatureSigned() { + PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN); + encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3}); + + byte[] signature = new byte[512]; + long salt = 124L; + encryptionStart.getLoginSignatures().write(0, Either.right(new WrappedSaltedSignature(salt, signature))); + + WrappedSaltedSignature read = encryptionStart.getLoginSignatures().read(0).right().get(); + assertEquals(salt, read.getSalt()); + assertArrayEquals(signature, read.getSignature()); + } + + // 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 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")) { + 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.getWatchableCollectionModifier().write(0, Lists.newArrayList( + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(Byte.class)), + (byte) 1), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(Integer.class)), + 1), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(Float.class)), + 1F), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(String.class)), + "String"), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(Boolean.class)), + true), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)), + Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.getItemStackSerializer(false)), + BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.WOODEN_AXE))), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(CatVariant.class)), + CatVariant.a), + new WrappedWatchableObject( + new WrappedDataWatcherObject(0, Registry.get(FrogVariant.class)), + FrogVariant.c) + )); + } 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 + System.out.println("Cloning " + type); + + // 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 (Exception ex) { + Assertions.fail("Unable to clone " + type, ex); + } + } + } + + // 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 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 + } +} diff --git a/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java b/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java index 2325ee5a..63680094 100644 --- a/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java +++ b/src/test/java/com/comphenix/protocol/injector/EntityUtilitiesTest.java @@ -1,59 +1,59 @@ -package com.comphenix.protocol.injector; - -import static com.comphenix.protocol.utility.TestUtils.setFinalField; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.lang.reflect.Field; -import net.minecraft.server.level.ChunkProviderServer; -import net.minecraft.server.level.PlayerChunkMap; -import net.minecraft.server.level.PlayerChunkMap.EntityTracker; -import net.minecraft.server.level.WorldServer; -import net.minecraft.world.entity.Entity; -import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class EntityUtilitiesTest { - - @BeforeAll - public static void beforeClass() { - BukkitInitialization.initializeAll(); - } - - @Test - public void testReflection() { - CraftWorld bukkit = mock(CraftWorld.class); - WorldServer world = mock(WorldServer.class); - when(bukkit.getHandle()).thenReturn(world); - - ChunkProviderServer provider = mock(ChunkProviderServer.class); - when(world.k()).thenReturn(provider); - - PlayerChunkMap chunkMap = mock(PlayerChunkMap.class); - Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true) - .getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build()); - setFinalField(provider, chunkMapField, chunkMap); - - CraftEntity bukkitEntity = mock(CraftEntity.class); - Entity fakeEntity = mock(Entity.class); - when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity); - - EntityTracker tracker = mock(EntityTracker.class); - Field trackerField = FuzzyReflection.fromClass(EntityTracker.class, true) - .getField(FuzzyFieldContract.newBuilder().typeExact(Entity.class).build()); - setFinalField(tracker, trackerField, fakeEntity); - - Int2ObjectMap trackerMap = new Int2ObjectOpenHashMap<>(); - trackerMap.put(1, tracker); - Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true) - .getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build()); - setFinalField(chunkMap, trackedEntitiesField, trackerMap); - } -} +package com.comphenix.protocol.injector; + +import static com.comphenix.protocol.utility.TestUtils.setFinalField; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.lang.reflect.Field; +import net.minecraft.server.level.ChunkProviderServer; +import net.minecraft.server.level.PlayerChunkMap; +import net.minecraft.server.level.PlayerChunkMap.EntityTracker; +import net.minecraft.server.level.WorldServer; +import net.minecraft.world.entity.Entity; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class EntityUtilitiesTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testReflection() { + CraftWorld bukkit = mock(CraftWorld.class); + WorldServer world = mock(WorldServer.class); + when(bukkit.getHandle()).thenReturn(world); + + ChunkProviderServer provider = mock(ChunkProviderServer.class); + when(world.k()).thenReturn(provider); + + PlayerChunkMap chunkMap = mock(PlayerChunkMap.class); + Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true) + .getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build()); + setFinalField(provider, chunkMapField, chunkMap); + + CraftEntity bukkitEntity = mock(CraftEntity.class); + Entity fakeEntity = mock(Entity.class); + when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity); + + EntityTracker tracker = mock(EntityTracker.class); + Field trackerField = FuzzyReflection.fromClass(EntityTracker.class, true) + .getField(FuzzyFieldContract.newBuilder().typeExact(Entity.class).build()); + setFinalField(tracker, trackerField, fakeEntity); + + Int2ObjectMap trackerMap = new Int2ObjectOpenHashMap<>(); + trackerMap.put(1, tracker); + Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true) + .getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build()); + setFinalField(chunkMap, trackedEntitiesField, trackerMap); + } +} diff --git a/src/test/java/com/comphenix/protocol/injector/WirePacketTest.java b/src/test/java/com/comphenix/protocol/injector/WirePacketTest.java index e660fd5d..f5315544 100644 --- a/src/test/java/com/comphenix/protocol/injector/WirePacketTest.java +++ b/src/test/java/com/comphenix/protocol/injector/WirePacketTest.java @@ -1,69 +1,69 @@ -/** - * (c) 2016 dmulloy2 - */ -package com.comphenix.protocol.injector; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.injector.netty.WirePacket; -import io.netty.buffer.ByteBuf; -import java.util.ArrayList; -import java.util.List; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * @author dmulloy2 - */ -public class WirePacketTest { - - @BeforeAll - public static void beforeClass() { - BukkitInitialization.initializeAll(); - } - - // @Test - public void testPackets() { - List failures = new ArrayList<>(); - - for (PacketType type : PacketType.values()) { - if (type.isDeprecated()) { - continue; - } - - try { - PacketContainer packet = new PacketContainer(type); - WirePacket wire = WirePacket.fromPacket(packet); - WirePacket handle = WirePacket.fromPacket(packet.getHandle()); - assertEquals(wire, handle); - } catch (Exception ex) { - failures.add(type + " :: " + ex.getMessage()); - System.out.println(type); - ex.printStackTrace(); - } - } - - assertEquals(failures, new ArrayList<>()); - } - - @Test - public void testSerialization() { - int id = 42; - byte[] array = {1, 3, 7, 21, 88, 67, 8}; - - WirePacket packet = new WirePacket(id, array); - - ByteBuf buf = packet.serialize(); - - int backId = WirePacket.readVarInt(buf); - byte[] backArray = new byte[buf.readableBytes()]; - buf.readBytes(backArray); - - assertEquals(id, backId); - assertArrayEquals(array, backArray); - } +/** + * (c) 2016 dmulloy2 + */ +package com.comphenix.protocol.injector; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.injector.netty.WirePacket; +import io.netty.buffer.ByteBuf; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author dmulloy2 + */ +public class WirePacketTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + // @Test + public void testPackets() { + List failures = new ArrayList<>(); + + for (PacketType type : PacketType.values()) { + if (type.isDeprecated()) { + continue; + } + + try { + PacketContainer packet = new PacketContainer(type); + WirePacket wire = WirePacket.fromPacket(packet); + WirePacket handle = WirePacket.fromPacket(packet.getHandle()); + assertEquals(wire, handle); + } catch (Exception ex) { + failures.add(type + " :: " + ex.getMessage()); + System.out.println(type); + ex.printStackTrace(); + } + } + + assertEquals(failures, new ArrayList<>()); + } + + @Test + public void testSerialization() { + int id = 42; + byte[] array = {1, 3, 7, 21, 88, 67, 8}; + + WirePacket packet = new WirePacket(id, array); + + ByteBuf buf = packet.serialize(); + + int backId = WirePacket.readVarInt(buf); + byte[] backArray = new byte[buf.readableBytes()]; + buf.readBytes(backArray); + + assertEquals(id, backId); + assertArrayEquals(array, backArray); + } } \ No newline at end of file diff --git a/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java b/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java index 9c541c8b..babecc10 100644 --- a/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java +++ b/src/test/java/com/comphenix/protocol/reflect/cloning/AggregateClonerTest.java @@ -1,45 +1,45 @@ -package com.comphenix.protocol.reflect.cloning; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import java.util.Arrays; -import java.util.List; -import net.minecraft.core.NonNullList; -import net.minecraft.world.item.ItemStack; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class AggregateClonerTest { - - @BeforeAll - public static void initializeBukkit() { - BukkitInitialization.initializeAll(); - } - - @Test - public void testArrays() { - List input = Arrays.asList(1, 2, 3); - assertEquals(input, AggregateCloner.DEFAULT.clone(input)); - } - - // @Test - // Usages of NonNullList were removed in 1.17.1 - public void testNonNullList() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS); - - NonNullList list = NonNullList.a(16, ItemStack.b); - packet.getModifier().write(1, list); - - PacketContainer cloned = packet.deepClone(); - - @SuppressWarnings("unchecked") - NonNullList list1 = (NonNullList) cloned.getModifier().read(1); - - assertEquals(list.size(), list1.size()); - Assertions.assertArrayEquals(list.toArray(), list1.toArray()); - } -} +package com.comphenix.protocol.reflect.cloning; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import java.util.Arrays; +import java.util.List; +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class AggregateClonerTest { + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testArrays() { + List input = Arrays.asList(1, 2, 3); + assertEquals(input, AggregateCloner.DEFAULT.clone(input)); + } + + // @Test + // Usages of NonNullList were removed in 1.17.1 + public void testNonNullList() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS); + + NonNullList list = NonNullList.a(16, ItemStack.b); + packet.getModifier().write(1, list); + + PacketContainer cloned = packet.deepClone(); + + @SuppressWarnings("unchecked") + NonNullList list1 = (NonNullList) cloned.getModifier().read(1); + + assertEquals(list.size(), list1.size()); + Assertions.assertArrayEquals(list.toArray(), list1.toArray()); + } +} diff --git a/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java b/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java index 598b5508..64d40806 100644 --- a/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java +++ b/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java @@ -1,159 +1,159 @@ -package com.comphenix.protocol.utility; - -import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import com.comphenix.protocol.BukkitInitialization; -import com.mojang.authlib.GameProfile; -import net.minecraft.nbt.NBTCompressedStreamTools; -import net.minecraft.network.chat.IChatBaseComponent; -import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes; -import net.minecraft.network.protocol.status.ServerPing; -import net.minecraft.network.syncher.DataWatcher; -import net.minecraft.server.network.PlayerConnection; -import net.minecraft.util.MinecraftEncryption; -import net.minecraft.world.level.ChunkCoordIntPair; -import net.minecraft.world.level.block.state.IBlockData; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; -import org.bukkit.entity.Entity; -import org.bukkit.inventory.ItemStack; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class MinecraftReflectionTest { - - @BeforeAll - public static void initializeBukkit() { - BukkitInitialization.initializeAll(); - } - - @AfterAll - public static void undoMocking() { - // NOP - MinecraftReflection.minecraftPackage = null; - } - - @Test - public void testBukkitMethod() { - FakeEntity entity = mock(FakeEntity.class); - FakeBlock block = mock(FakeBlock.class); - - MinecraftReflection.getBukkitEntity(entity); - MinecraftReflection.getBukkitEntity(block); - - verify(entity, times(1)).getBukkitEntity(); - verify(block, times(1)).getBukkitEntity(); - } - - @Test - public void testIllegalClass() { - assertThrows(IllegalArgumentException.class, () -> MinecraftReflection.getBukkitEntity("Hello")); - } - - @Test - public void testNullable() { - assertNull(MinecraftReflection.getNullableNMS("ProtocolLib")); - } - - @Test - public void testAttributeSnapshot() { - assertEquals(PacketPlayOutUpdateAttributes.AttributeSnapshot.class, - MinecraftReflection.getAttributeSnapshotClass()); - } - - @Test - public void testChatComponent() { - assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass()); - } - - @Test - public void testChatSerializer() { - assertEquals(IChatBaseComponent.ChatSerializer.class, MinecraftReflection.getChatSerializerClass()); - } - - @Test - public void testChunkCoordIntPair() { - assertEquals(ChunkCoordIntPair.class, MinecraftReflection.getChunkCoordIntPair()); - } - - @Test - public void testIBlockData() { - assertEquals(IBlockData.class, MinecraftReflection.getIBlockDataClass()); - } - - @Test - public void testPlayerConnection() { - assertEquals(PlayerConnection.class, MinecraftReflection.getPlayerConnectionClass()); - } - - @Test - public void testServerPing() { - assertEquals(ServerPing.class, MinecraftReflection.getServerPingClass()); - } - - @Test - public void testServerPingPlayerSample() { - assertEquals(ServerPing.ServerPingPlayerSample.class, MinecraftReflection.getServerPingPlayerSampleClass()); - } - - @Test - public void testServerPingServerData() { - assertEquals(ServerPing.ServerData.class, MinecraftReflection.getServerPingServerDataClass()); - } - - @Test - public void testNbtStreamTools() { - assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass()); - } - - @Test - public void testDataWatcherItem() { - assertEquals(DataWatcher.Item.class, MinecraftReflection.getDataWatcherItemClass()); - } - - @Test - public void testLoginSignature() { - assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass()); - } - - @Test - public void testItemStacks() { - ItemStack stack = new ItemStack(Material.GOLDEN_SWORD); - Object nmsStack = MinecraftReflection.getMinecraftItemStack(stack); - assertItemsEqual(stack, MinecraftReflection.getBukkitItemStack(nmsStack)); - - // The NMS handle for CraftItemStack is null with Material.AIR, make sure it is handled correctly - assertNotNull(MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR)))); - } - - @Test - public void testGameProfile() { - assertEquals(GameProfile.class, MinecraftReflection.getGameProfileClass()); - } - - @Test - public void testEnumEntityUseAction() { - // this class is package-private in PacketPlayInUseEntity, so we can only check if no exception is thrown during retrieval - MinecraftReflection.getEnumEntityUseActionClass(); - } - - // Mocking objects - private interface FakeEntity { - - Entity getBukkitEntity(); - } - - private interface FakeBlock { - - Block getBukkitEntity(); - } -} +package com.comphenix.protocol.utility; + +import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.comphenix.protocol.BukkitInitialization; +import com.mojang.authlib.GameProfile; +import net.minecraft.nbt.NBTCompressedStreamTools; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes; +import net.minecraft.network.protocol.status.ServerPing; +import net.minecraft.network.syncher.DataWatcher; +import net.minecraft.server.network.PlayerConnection; +import net.minecraft.util.MinecraftEncryption; +import net.minecraft.world.level.ChunkCoordIntPair; +import net.minecraft.world.level.block.state.IBlockData; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; +import org.bukkit.entity.Entity; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class MinecraftReflectionTest { + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @AfterAll + public static void undoMocking() { + // NOP + MinecraftReflection.minecraftPackage = null; + } + + @Test + public void testBukkitMethod() { + FakeEntity entity = mock(FakeEntity.class); + FakeBlock block = mock(FakeBlock.class); + + MinecraftReflection.getBukkitEntity(entity); + MinecraftReflection.getBukkitEntity(block); + + verify(entity, times(1)).getBukkitEntity(); + verify(block, times(1)).getBukkitEntity(); + } + + @Test + public void testIllegalClass() { + assertThrows(IllegalArgumentException.class, () -> MinecraftReflection.getBukkitEntity("Hello")); + } + + @Test + public void testNullable() { + assertNull(MinecraftReflection.getNullableNMS("ProtocolLib")); + } + + @Test + public void testAttributeSnapshot() { + assertEquals(PacketPlayOutUpdateAttributes.AttributeSnapshot.class, + MinecraftReflection.getAttributeSnapshotClass()); + } + + @Test + public void testChatComponent() { + assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass()); + } + + @Test + public void testChatSerializer() { + assertEquals(IChatBaseComponent.ChatSerializer.class, MinecraftReflection.getChatSerializerClass()); + } + + @Test + public void testChunkCoordIntPair() { + assertEquals(ChunkCoordIntPair.class, MinecraftReflection.getChunkCoordIntPair()); + } + + @Test + public void testIBlockData() { + assertEquals(IBlockData.class, MinecraftReflection.getIBlockDataClass()); + } + + @Test + public void testPlayerConnection() { + assertEquals(PlayerConnection.class, MinecraftReflection.getPlayerConnectionClass()); + } + + @Test + public void testServerPing() { + assertEquals(ServerPing.class, MinecraftReflection.getServerPingClass()); + } + + @Test + public void testServerPingPlayerSample() { + assertEquals(ServerPing.ServerPingPlayerSample.class, MinecraftReflection.getServerPingPlayerSampleClass()); + } + + @Test + public void testServerPingServerData() { + assertEquals(ServerPing.ServerData.class, MinecraftReflection.getServerPingServerDataClass()); + } + + @Test + public void testNbtStreamTools() { + assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass()); + } + + @Test + public void testDataWatcherItem() { + assertEquals(DataWatcher.Item.class, MinecraftReflection.getDataWatcherItemClass()); + } + + @Test + public void testLoginSignature() { + assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass()); + } + + @Test + public void testItemStacks() { + ItemStack stack = new ItemStack(Material.GOLDEN_SWORD); + Object nmsStack = MinecraftReflection.getMinecraftItemStack(stack); + assertItemsEqual(stack, MinecraftReflection.getBukkitItemStack(nmsStack)); + + // The NMS handle for CraftItemStack is null with Material.AIR, make sure it is handled correctly + assertNotNull(MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR)))); + } + + @Test + public void testGameProfile() { + assertEquals(GameProfile.class, MinecraftReflection.getGameProfileClass()); + } + + @Test + public void testEnumEntityUseAction() { + // this class is package-private in PacketPlayInUseEntity, so we can only check if no exception is thrown during retrieval + MinecraftReflection.getEnumEntityUseActionClass(); + } + + // Mocking objects + private interface FakeEntity { + + Entity getBukkitEntity(); + } + + private interface FakeBlock { + + Block getBukkitEntity(); + } +} diff --git a/src/test/java/com/comphenix/protocol/utility/TestUtils.java b/src/test/java/com/comphenix/protocol/utility/TestUtils.java index c2865597..6211fb4d 100644 --- a/src/test/java/com/comphenix/protocol/utility/TestUtils.java +++ b/src/test/java/com/comphenix/protocol/utility/TestUtils.java @@ -1,52 +1,52 @@ -package com.comphenix.protocol.utility; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.comphenix.protocol.reflect.accessors.Accessors; -import java.lang.reflect.Field; -import java.util.List; -import org.bukkit.Bukkit; -import org.bukkit.inventory.ItemStack; - -public class TestUtils { - - public static void assertItemCollectionsEqual(List first, List second) { - assertEquals(first.size(), second.size()); - for (int i = 0; i < first.size(); i++) { - assertItemsEqual(first.get(i), second.get(i)); - } - } - - public static void assertItemsEqual(ItemStack first, ItemStack second) { - if (first == null) { - assertNull(second); - } else { - assertNotNull(first); - - // The legacy check in ItemStack#isSimilar causes a null pointer - assertEquals(first.getType(), second.getType()); - assertEquals(first.getDurability(), second.getDurability()); - assertEquals(first.hasItemMeta(), second.hasItemMeta()); - if (first.hasItemMeta()) { - assertTrue(Bukkit.getItemFactory().equals(first.getItemMeta(), second.getItemMeta())); - } - } - } - - public static boolean equivalentItem(ItemStack first, ItemStack second) { - if (first == null) { - return second == null; - } else if (second == null) { - return false; - } else { - return first.getType().equals(second.getType()); - } - } - - public static void setFinalField(Object obj, Field field, Object newValue) { - Accessors.getFieldAccessor(field).set(obj, newValue); - } -} +package com.comphenix.protocol.utility; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.comphenix.protocol.reflect.accessors.Accessors; +import java.lang.reflect.Field; +import java.util.List; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; + +public class TestUtils { + + public static void assertItemCollectionsEqual(List first, List second) { + assertEquals(first.size(), second.size()); + for (int i = 0; i < first.size(); i++) { + assertItemsEqual(first.get(i), second.get(i)); + } + } + + public static void assertItemsEqual(ItemStack first, ItemStack second) { + if (first == null) { + assertNull(second); + } else { + assertNotNull(first); + + // The legacy check in ItemStack#isSimilar causes a null pointer + assertEquals(first.getType(), second.getType()); + assertEquals(first.getDurability(), second.getDurability()); + assertEquals(first.hasItemMeta(), second.hasItemMeta()); + if (first.hasItemMeta()) { + assertTrue(Bukkit.getItemFactory().equals(first.getItemMeta(), second.getItemMeta())); + } + } + } + + public static boolean equivalentItem(ItemStack first, ItemStack second) { + if (first == null) { + return second == null; + } else if (second == null) { + return false; + } else { + return first.getType().equals(second.getType()); + } + } + + public static void setFinalField(Object obj, Field field, Object newValue) { + Accessors.getFieldAccessor(field).set(obj, newValue); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/BukkitConvertersTest.java b/src/test/java/com/comphenix/protocol/wrappers/BukkitConvertersTest.java index 1ac1dd57..b7220b98 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/BukkitConvertersTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/BukkitConvertersTest.java @@ -1,58 +1,58 @@ -package com.comphenix.protocol.wrappers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.wrappers.Either.Left; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class BukkitConvertersTest { - - @BeforeAll - public static void beforeClass() { - BukkitInitialization.initializeAll(); - } - - @Test - public void testItemStacks() { - ItemStack item = new ItemStack(Material.DIAMOND_SWORD, 16); - item.addEnchantment(Enchantment.DAMAGE_ALL, 4); - ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(ChatColor.GREEN + "Diamond Sword"); - item.setItemMeta(meta); - - EquivalentConverter converter = BukkitConverters.getItemStackConverter(); - Object nmsStack = converter.getGeneric(item); - ItemStack back = converter.getSpecific(nmsStack); - - assertEquals(item.getType(), back.getType()); - assertEquals(item.getDurability(), back.getDurability()); - assertEquals(item.hasItemMeta(), back.hasItemMeta()); - assertTrue(Bukkit.getItemFactory().equals(item.getItemMeta(), back.getItemMeta())); - } - - @Test - public void testEither() { - Either test = new Left<>("bla"); - - EquivalentConverter> converter = BukkitConverters.getEitherConverter( - Converters.passthrough(String.class), Converters.passthrough(String.class) - ); - - com.mojang.datafixers.util.Either nmsEither = (com.mojang.datafixers.util.Either) converter.getGeneric(test); - Either wrapped = converter.getSpecific(nmsEither); - - assertEquals(wrapped.left(), nmsEither.left()); - assertEquals(wrapped.right(), nmsEither.right()); - } -} +package com.comphenix.protocol.wrappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.wrappers.Either.Left; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class BukkitConvertersTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testItemStacks() { + ItemStack item = new ItemStack(Material.DIAMOND_SWORD, 16); + item.addEnchantment(Enchantment.DAMAGE_ALL, 4); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.GREEN + "Diamond Sword"); + item.setItemMeta(meta); + + EquivalentConverter converter = BukkitConverters.getItemStackConverter(); + Object nmsStack = converter.getGeneric(item); + ItemStack back = converter.getSpecific(nmsStack); + + assertEquals(item.getType(), back.getType()); + assertEquals(item.getDurability(), back.getDurability()); + assertEquals(item.hasItemMeta(), back.hasItemMeta()); + assertTrue(Bukkit.getItemFactory().equals(item.getItemMeta(), back.getItemMeta())); + } + + @Test + public void testEither() { + Either test = new Left<>("bla"); + + EquivalentConverter> converter = BukkitConverters.getEitherConverter( + Converters.passthrough(String.class), Converters.passthrough(String.class) + ); + + com.mojang.datafixers.util.Either nmsEither = (com.mojang.datafixers.util.Either) converter.getGeneric(test); + Either wrapped = converter.getSpecific(nmsEither); + + assertEquals(wrapped.left(), nmsEither.left()); + assertEquals(wrapped.right(), nmsEither.right()); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/ChunkCoordIntPairTest.java b/src/test/java/com/comphenix/protocol/wrappers/ChunkCoordIntPairTest.java index 4f970c19..2b8e26b2 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/ChunkCoordIntPairTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/ChunkCoordIntPairTest.java @@ -1,31 +1,31 @@ -package com.comphenix.protocol.wrappers; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.comphenix.protocol.BukkitInitialization; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class ChunkCoordIntPairTest { - - @BeforeAll - public static void initializeBukkit() { - BukkitInitialization.initializeAll(); - } - - @Test - public void test() { - net.minecraft.world.level.ChunkCoordIntPair pair = new net.minecraft.world.level.ChunkCoordIntPair(1, 2); - ChunkCoordIntPair specific = ChunkCoordIntPair.getConverter().getSpecific(pair); - - assertEquals(1, specific.getChunkX()); - assertEquals(2, specific.getChunkZ()); - - net.minecraft.world.level.ChunkCoordIntPair roundtrip = - (net.minecraft.world.level.ChunkCoordIntPair) ChunkCoordIntPair.getConverter(). - getGeneric(specific); - - assertEquals(1, roundtrip.e); - assertEquals(2, roundtrip.f); - } -} +package com.comphenix.protocol.wrappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.comphenix.protocol.BukkitInitialization; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class ChunkCoordIntPairTest { + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + public void test() { + net.minecraft.world.level.ChunkCoordIntPair pair = new net.minecraft.world.level.ChunkCoordIntPair(1, 2); + ChunkCoordIntPair specific = ChunkCoordIntPair.getConverter().getSpecific(pair); + + assertEquals(1, specific.getChunkX()); + assertEquals(2, specific.getChunkZ()); + + net.minecraft.world.level.ChunkCoordIntPair roundtrip = + (net.minecraft.world.level.ChunkCoordIntPair) ChunkCoordIntPair.getConverter(). + getGeneric(specific); + + assertEquals(1, roundtrip.e); + assertEquals(2, roundtrip.f); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java index 249de783..23efffc3 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java @@ -1,57 +1,57 @@ -package com.comphenix.protocol.wrappers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.google.common.collect.Sets; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class EnumWrappersTest { - - private static final Set KNOWN_INVALID = Sets.newHashSet( - "Particle", "WorldBorderAction", "CombatEventType", "TitleAction", "ChatType" - ); - - @BeforeAll - public static void initializeBukkit() { - BukkitInitialization.initializeAll(); - EnumWrappers.getPlayerInfoActionClass(); // just to initialize the classes and converters - } - - @Test - @SuppressWarnings("unchecked") - public void validateAllEnumFieldsAreWrapped() { - Map, EquivalentConverter> nativeEnums = EnumWrappers.getFromNativeMap(); - for (Entry, EquivalentConverter> entry : nativeEnums.entrySet()) { - for (Object nativeConstant : entry.getKey().getEnumConstants()) { - try { - // yay, generics - EquivalentConverter converter = (EquivalentConverter) entry.getValue(); - - // try to convert the native constant to a wrapper and back - Object wrappedValue = converter.getSpecific(nativeConstant); - assertNotNull(wrappedValue); - - Object unwrappedValue = converter.getGeneric(wrappedValue); - assertNotNull(unwrappedValue); - - assertEquals(nativeConstant, unwrappedValue); - } catch (Exception exception) { - fail(exception); - } - } - } - } - - @Test - public void testValidity() { - assertEquals(EnumWrappers.INVALID, KNOWN_INVALID); - } +package com.comphenix.protocol.wrappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.google.common.collect.Sets; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class EnumWrappersTest { + + private static final Set KNOWN_INVALID = Sets.newHashSet( + "Particle", "WorldBorderAction", "CombatEventType", "TitleAction", "ChatType" + ); + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + EnumWrappers.getPlayerInfoActionClass(); // just to initialize the classes and converters + } + + @Test + @SuppressWarnings("unchecked") + public void validateAllEnumFieldsAreWrapped() { + Map, EquivalentConverter> nativeEnums = EnumWrappers.getFromNativeMap(); + for (Entry, EquivalentConverter> entry : nativeEnums.entrySet()) { + for (Object nativeConstant : entry.getKey().getEnumConstants()) { + try { + // yay, generics + EquivalentConverter converter = (EquivalentConverter) entry.getValue(); + + // try to convert the native constant to a wrapper and back + Object wrappedValue = converter.getSpecific(nativeConstant); + assertNotNull(wrappedValue); + + Object unwrappedValue = converter.getGeneric(wrappedValue); + assertNotNull(unwrappedValue); + + assertEquals(nativeConstant, unwrappedValue); + } catch (Exception exception) { + fail(exception); + } + } + } + } + + @Test + public void testValidity() { + assertEquals(EnumWrappers.INVALID, KNOWN_INVALID); + } } \ No newline at end of file diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java index 52abd7d3..8e5c3b42 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java @@ -1,104 +1,104 @@ -package com.comphenix.protocol.wrappers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.WrappedAttributeModifier.Operation; -import com.google.common.collect.Lists; - -import java.util.ArrayList; -import java.util.List; -import net.minecraft.core.IRegistry; -import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot; -import net.minecraft.resources.MinecraftKey; -import net.minecraft.world.entity.ai.attributes.AttributeBase; -import net.minecraft.world.entity.ai.attributes.AttributeModifier; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class WrappedAttributeTest { - - private WrappedAttributeModifier doubleModifier; - private WrappedAttributeModifier constantModifier; - private WrappedAttribute attribute; - - @BeforeAll - public static void initializeBukkit() { - BukkitInitialization.initializeAll(); - } - - @BeforeEach - public void setUp() { - // Create a couple of modifiers - this.doubleModifier = - WrappedAttributeModifier.newBuilder(). - name("Double Damage"). - amount(1). - operation(Operation.ADD_PERCENTAGE). - build(); - this.constantModifier = - WrappedAttributeModifier.newBuilder(). - name("Damage Bonus"). - amount(5). - operation(Operation.ADD_NUMBER). - build(); - - // Create attribute - this.attribute = WrappedAttribute.newBuilder(). - attributeKey("generic.attackDamage"). - baseValue(2). - packet(new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES)). - modifiers(Lists.newArrayList(this.constantModifier, this.doubleModifier)). - build(); - } - - @Test - public void testEquality() { - // Check wrapped equality - assertEquals(this.doubleModifier, this.doubleModifier); - assertNotSame(this.constantModifier, this.doubleModifier); - - assertEquals(this.doubleModifier.getHandle(), this.getModifierCopy(this.doubleModifier)); - assertEquals(this.constantModifier.getHandle(), this.getModifierCopy(this.constantModifier)); - } - - @Test - public void testAttribute() { - assertEquals(this.attribute, WrappedAttribute.fromHandle(this.getAttributeCopy(this.attribute))); - - assertTrue(this.attribute.hasModifier(this.doubleModifier.getUUID())); - assertTrue(this.attribute.hasModifier(this.constantModifier.getUUID())); - } - - @Test - public void testFromTemplate() { - assertEquals(this.attribute, WrappedAttribute.newBuilder(this.attribute).build()); - } - - /** - * Retrieve the equivalent NMS attribute. - * - * @param attribute - the wrapped attribute. - * @return The equivalent NMS attribute. - */ - private AttributeSnapshot getAttributeCopy(WrappedAttribute attribute) { - List modifiers = new ArrayList<>(); - - for (WrappedAttributeModifier wrapper : attribute.getModifiers()) { - modifiers.add((AttributeModifier) wrapper.getHandle()); - } - - AttributeBase base = IRegistry.ak.a(MinecraftKey.a(attribute.getAttributeKey())); - return new AttributeSnapshot(base, attribute.getBaseValue(), modifiers); - } - - private AttributeModifier getModifierCopy(WrappedAttributeModifier modifier) { - AttributeModifier.Operation operation = AttributeModifier.Operation.values()[modifier.getOperation().getId()]; - return new AttributeModifier(modifier.getUUID(), modifier.getName(), modifier.getAmount(), operation); - } -} +package com.comphenix.protocol.wrappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.wrappers.WrappedAttributeModifier.Operation; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.core.IRegistry; +import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot; +import net.minecraft.resources.MinecraftKey; +import net.minecraft.world.entity.ai.attributes.AttributeBase; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class WrappedAttributeTest { + + private WrappedAttributeModifier doubleModifier; + private WrappedAttributeModifier constantModifier; + private WrappedAttribute attribute; + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @BeforeEach + public void setUp() { + // Create a couple of modifiers + this.doubleModifier = + WrappedAttributeModifier.newBuilder(). + name("Double Damage"). + amount(1). + operation(Operation.ADD_PERCENTAGE). + build(); + this.constantModifier = + WrappedAttributeModifier.newBuilder(). + name("Damage Bonus"). + amount(5). + operation(Operation.ADD_NUMBER). + build(); + + // Create attribute + this.attribute = WrappedAttribute.newBuilder(). + attributeKey("generic.attackDamage"). + baseValue(2). + packet(new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES)). + modifiers(Lists.newArrayList(this.constantModifier, this.doubleModifier)). + build(); + } + + @Test + public void testEquality() { + // Check wrapped equality + assertEquals(this.doubleModifier, this.doubleModifier); + assertNotSame(this.constantModifier, this.doubleModifier); + + assertEquals(this.doubleModifier.getHandle(), this.getModifierCopy(this.doubleModifier)); + assertEquals(this.constantModifier.getHandle(), this.getModifierCopy(this.constantModifier)); + } + + @Test + public void testAttribute() { + assertEquals(this.attribute, WrappedAttribute.fromHandle(this.getAttributeCopy(this.attribute))); + + assertTrue(this.attribute.hasModifier(this.doubleModifier.getUUID())); + assertTrue(this.attribute.hasModifier(this.constantModifier.getUUID())); + } + + @Test + public void testFromTemplate() { + assertEquals(this.attribute, WrappedAttribute.newBuilder(this.attribute).build()); + } + + /** + * Retrieve the equivalent NMS attribute. + * + * @param attribute - the wrapped attribute. + * @return The equivalent NMS attribute. + */ + private AttributeSnapshot getAttributeCopy(WrappedAttribute attribute) { + List modifiers = new ArrayList<>(); + + for (WrappedAttributeModifier wrapper : attribute.getModifiers()) { + modifiers.add((AttributeModifier) wrapper.getHandle()); + } + + AttributeBase base = IRegistry.ak.a(MinecraftKey.a(attribute.getAttributeKey())); + return new AttributeSnapshot(base, attribute.getBaseValue(), modifiers); + } + + private AttributeModifier getModifierCopy(WrappedAttributeModifier modifier) { + AttributeModifier.Operation operation = AttributeModifier.Operation.values()[modifier.getOperation().getId()]; + return new AttributeModifier(modifier.getUUID(), modifier.getName(), modifier.getAmount(), operation); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedBlockDataTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedBlockDataTest.java index cc953a1c..80cf855f 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/WrappedBlockDataTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedBlockDataTest.java @@ -1,70 +1,70 @@ -/** - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 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.wrappers; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.comphenix.protocol.BukkitInitialization; -import net.minecraft.world.level.block.state.IBlockData; -import org.bukkit.Material; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.type.GlassPane; -import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData; -import org.bukkit.craftbukkit.v1_19_R1.block.impl.CraftStainedGlassPane; -import org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * @author dmulloy2 - */ - -public class WrappedBlockDataTest { - - @BeforeAll - public static void initializeBukkit() { - BukkitInitialization.initializeAll(); - } - - @Test - public void testMaterialCreation() { - Material type = Material.BLUE_WOOL; - - WrappedBlockData wrapper = WrappedBlockData.createData(type); - - assertEquals(wrapper.getType(), type); - //assertEquals(wrapper.getData(), data); - - Object generic = BukkitConverters.getWrappedBlockDataConverter().getGeneric(wrapper); - WrappedBlockData back = BukkitConverters.getWrappedBlockDataConverter().getSpecific(generic); - - assertEquals(wrapper.getType(), back.getType()); - assertEquals(wrapper.getData(), back.getData()); - } - - @Test - public void testDataCreation() { - IBlockData nmsData = CraftMagicNumbers.getBlock(Material.CYAN_STAINED_GLASS_PANE).m(); - GlassPane data = (GlassPane) CraftBlockData.fromData(nmsData); - data.setFace(BlockFace.EAST, true); - - WrappedBlockData wrapper = WrappedBlockData.createData(data); - assertEquals(wrapper.getType(), Material.CYAN_STAINED_GLASS_PANE); - - GlassPane back = new CraftStainedGlassPane((IBlockData) wrapper.getHandle()); - assertEquals(back.hasFace(BlockFace.EAST), data.hasFace(BlockFace.EAST)); - assertEquals(back.hasFace(BlockFace.SOUTH), data.hasFace(BlockFace.SOUTH)); - } -} +/** + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 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.wrappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.comphenix.protocol.BukkitInitialization; +import net.minecraft.world.level.block.state.IBlockData; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.type.GlassPane; +import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_19_R1.block.impl.CraftStainedGlassPane; +import org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author dmulloy2 + */ + +public class WrappedBlockDataTest { + + @BeforeAll + public static void initializeBukkit() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testMaterialCreation() { + Material type = Material.BLUE_WOOL; + + WrappedBlockData wrapper = WrappedBlockData.createData(type); + + assertEquals(wrapper.getType(), type); + //assertEquals(wrapper.getData(), data); + + Object generic = BukkitConverters.getWrappedBlockDataConverter().getGeneric(wrapper); + WrappedBlockData back = BukkitConverters.getWrappedBlockDataConverter().getSpecific(generic); + + assertEquals(wrapper.getType(), back.getType()); + assertEquals(wrapper.getData(), back.getData()); + } + + @Test + public void testDataCreation() { + IBlockData nmsData = CraftMagicNumbers.getBlock(Material.CYAN_STAINED_GLASS_PANE).m(); + GlassPane data = (GlassPane) CraftBlockData.fromData(nmsData); + data.setFace(BlockFace.EAST, true); + + WrappedBlockData wrapper = WrappedBlockData.createData(data); + assertEquals(wrapper.getType(), Material.CYAN_STAINED_GLASS_PANE); + + GlassPane back = new CraftStainedGlassPane((IBlockData) wrapper.getHandle()); + assertEquals(back.hasFace(BlockFace.EAST), data.hasFace(BlockFace.EAST)); + assertEquals(back.hasFace(BlockFace.SOUTH), data.hasFace(BlockFace.SOUTH)); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java index 24936547..9e41bc9b 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedDataWatcherTest.java @@ -1,115 +1,115 @@ -/** - * 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.wrappers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer; -import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; -import java.util.UUID; -import net.minecraft.world.entity.projectile.EntityEgg; -import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEgg; -import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * @author dmulloy2 - */ -public class WrappedDataWatcherTest { - - @BeforeAll - public static void prepare() { - BukkitInitialization.initializeAll(); - } - - @Test - public void testBytes() { - // Create a fake lightning strike and get its watcher - EntityEgg nmsEgg = new EntityEgg(null, 0, 0, 0); - CraftEntity craftEgg = new CraftEgg(null, nmsEgg); - WrappedDataWatcher wrapper = WrappedDataWatcher.getEntityWatcher(craftEgg); - - WrappedWatchableObject watchable = wrapper.getWatchableObject(0); - WrappedDataWatcherObject object = watchable.getWatcherObject(); - - // Make sure the serializers work - assertEquals(object.getSerializer(), Registry.get(Byte.class)); - - // Make sure we can set existing objects - wrapper.setObject(0, (byte) 21); - assertEquals(21, (byte) wrapper.getByte(0)); - } - - @Test - public void testStrings() { - WrappedDataWatcher wrapper = new WrappedDataWatcher(); - - // Make sure we can create watcher objects - Serializer serializer = Registry.get(String.class); - WrappedDataWatcherObject object = new WrappedDataWatcherObject(3, serializer); - wrapper.setObject(object, "Test"); - - assertEquals(wrapper.getString(3), "Test"); - } - - @Test - public void testFloats() { - WrappedDataWatcher wrapper = new WrappedDataWatcher(); - - // Make sure we can add new entries - Serializer serializer = Registry.get(Float.class); - WrappedDataWatcherObject object = new WrappedDataWatcherObject(10, serializer); - wrapper.setObject(object, 21.0F); - - assertTrue(wrapper.hasIndex(10)); - } - - @Test - public void testSerializers() { - Serializer blockPos = Registry.get(net.minecraft.core.BlockPosition.class, false); - Serializer optionalBlockPos = Registry.get(net.minecraft.core.BlockPosition.class, true); - assertNotSame(blockPos, optionalBlockPos); - - // assertNull(Registry.get(ItemStack.class, false)); - assertNotNull(Registry.get(UUID.class, true)); - } - - @Test - public void testHasIndex() { - WrappedDataWatcher watcher = new WrappedDataWatcher(); - Serializer serializer = Registry.get(Integer.class); - - assertFalse(watcher.hasIndex(0)); - watcher.setObject(0, serializer, 1); - assertTrue(watcher.hasIndex(0)); - } - - @Test - public void testDeepClone() { - WrappedDataWatcher watcher = new WrappedDataWatcher(); - watcher.setObject(0, Registry.get(Integer.class), 1); - - WrappedDataWatcher cloned = watcher.deepClone(); - assertEquals(1, cloned.asMap().size()); - assertEquals(1, (Object) cloned.getInteger(0)); - } -} +/** + * 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.wrappers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer; +import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; +import java.util.UUID; +import net.minecraft.world.entity.projectile.EntityEgg; +import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEgg; +import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author dmulloy2 + */ +public class WrappedDataWatcherTest { + + @BeforeAll + public static void prepare() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testBytes() { + // Create a fake lightning strike and get its watcher + EntityEgg nmsEgg = new EntityEgg(null, 0, 0, 0); + CraftEntity craftEgg = new CraftEgg(null, nmsEgg); + WrappedDataWatcher wrapper = WrappedDataWatcher.getEntityWatcher(craftEgg); + + WrappedWatchableObject watchable = wrapper.getWatchableObject(0); + WrappedDataWatcherObject object = watchable.getWatcherObject(); + + // Make sure the serializers work + assertEquals(object.getSerializer(), Registry.get(Byte.class)); + + // Make sure we can set existing objects + wrapper.setObject(0, (byte) 21); + assertEquals(21, (byte) wrapper.getByte(0)); + } + + @Test + public void testStrings() { + WrappedDataWatcher wrapper = new WrappedDataWatcher(); + + // Make sure we can create watcher objects + Serializer serializer = Registry.get(String.class); + WrappedDataWatcherObject object = new WrappedDataWatcherObject(3, serializer); + wrapper.setObject(object, "Test"); + + assertEquals(wrapper.getString(3), "Test"); + } + + @Test + public void testFloats() { + WrappedDataWatcher wrapper = new WrappedDataWatcher(); + + // Make sure we can add new entries + Serializer serializer = Registry.get(Float.class); + WrappedDataWatcherObject object = new WrappedDataWatcherObject(10, serializer); + wrapper.setObject(object, 21.0F); + + assertTrue(wrapper.hasIndex(10)); + } + + @Test + public void testSerializers() { + Serializer blockPos = Registry.get(net.minecraft.core.BlockPosition.class, false); + Serializer optionalBlockPos = Registry.get(net.minecraft.core.BlockPosition.class, true); + assertNotSame(blockPos, optionalBlockPos); + + // assertNull(Registry.get(ItemStack.class, false)); + assertNotNull(Registry.get(UUID.class, true)); + } + + @Test + public void testHasIndex() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + Serializer serializer = Registry.get(Integer.class); + + assertFalse(watcher.hasIndex(0)); + watcher.setObject(0, serializer, 1); + assertTrue(watcher.hasIndex(0)); + } + + @Test + public void testDeepClone() { + WrappedDataWatcher watcher = new WrappedDataWatcher(); + watcher.setObject(0, Registry.get(Integer.class), 1); + + WrappedDataWatcher cloned = watcher.deepClone(); + assertEquals(1, cloned.asMap().size()); + assertEquals(1, (Object) cloned.getInteger(0)); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java b/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java index 02ca84f5..e207994f 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/WrappedParticleTest.java @@ -1,62 +1,62 @@ -package com.comphenix.protocol.wrappers; - -import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Particle.DustOptions; -import org.bukkit.inventory.ItemStack; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class WrappedParticleTest { - - @BeforeAll - public static void beforeClass() { - BukkitInitialization.initializeAll(); - } - - @Test - public void testBlockData() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); - - WrappedParticle before = WrappedParticle.create(Particle.BLOCK_CRACK, - WrappedBlockData.createData(Material.LAPIS_BLOCK)); - packet.getNewParticles().write(0, before); - - WrappedParticle after = packet.getNewParticles().read(0); - assertEquals(before.getParticle(), after.getParticle()); - assertEquals(before.getData(), after.getData()); - } - - @Test - public void testItemStacks() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); - WrappedParticle before = WrappedParticle.create(Particle.ITEM_CRACK, new ItemStack(Material.FLINT_AND_STEEL)); - packet.getNewParticles().write(0, before); - - WrappedParticle after = packet.getNewParticles().read(0); - assertEquals(before.getParticle(), after.getParticle()); - assertItemsEqual((ItemStack) before.getData(), (ItemStack) after.getData()); - } - - @Test - public void testRedstone() { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); - WrappedParticle before = WrappedParticle.create(Particle.REDSTONE, new DustOptions(Color.BLUE, 1)); - packet.getNewParticles().write(0, before); - - WrappedParticle after = packet.getNewParticles().read(0); - assertEquals(before.getParticle(), after.getParticle()); - - DustOptions beforeDust = (DustOptions) before.getData(); - DustOptions afterDust = (DustOptions) after.getData(); - assertEquals(beforeDust.getColor(), afterDust.getColor()); - assertEquals(beforeDust.getSize(), afterDust.getSize(), 0); - } -} +package com.comphenix.protocol.wrappers; + +import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Particle.DustOptions; +import org.bukkit.inventory.ItemStack; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class WrappedParticleTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testBlockData() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); + + WrappedParticle before = WrappedParticle.create(Particle.BLOCK_CRACK, + WrappedBlockData.createData(Material.LAPIS_BLOCK)); + packet.getNewParticles().write(0, before); + + WrappedParticle after = packet.getNewParticles().read(0); + assertEquals(before.getParticle(), after.getParticle()); + assertEquals(before.getData(), after.getData()); + } + + @Test + public void testItemStacks() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); + WrappedParticle before = WrappedParticle.create(Particle.ITEM_CRACK, new ItemStack(Material.FLINT_AND_STEEL)); + packet.getNewParticles().write(0, before); + + WrappedParticle after = packet.getNewParticles().read(0); + assertEquals(before.getParticle(), after.getParticle()); + assertItemsEqual((ItemStack) before.getData(), (ItemStack) after.getData()); + } + + @Test + public void testRedstone() { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.WORLD_PARTICLES); + WrappedParticle before = WrappedParticle.create(Particle.REDSTONE, new DustOptions(Color.BLUE, 1)); + packet.getNewParticles().write(0, before); + + WrappedParticle after = packet.getNewParticles().read(0); + assertEquals(before.getParticle(), after.getParticle()); + + DustOptions beforeDust = (DustOptions) before.getData(); + DustOptions afterDust = (DustOptions) after.getData(); + assertEquals(beforeDust.getColor(), afterDust.getColor()); + assertEquals(beforeDust.getSize(), afterDust.getSize(), 0); + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java b/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java index d5e2ccd7..3ae9414c 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java @@ -1,87 +1,87 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * 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.wrappers.nbt; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.comphenix.protocol.BukkitInitialization; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; -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 net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class NbtFactoryTest { - - @BeforeAll - public static void initializeBukkit() throws IllegalAccessException { - BukkitInitialization.initializeAll(); - } - - @Test - public void testFromStream() { - WrappedCompound compound = WrappedCompound.fromName("tag"); - compound.put("name", "Test Testerson"); - compound.put("age", 42); - - compound.put(NbtFactory.ofList("nicknames", "a", "b", "c")); - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - DataOutput test = new DataOutputStream(buffer); - compound.write(test); - - ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray()); - DataInput input = new DataInputStream(source); - - NbtCompound cloned = NbtBinarySerializer.DEFAULT.deserializeCompound(input); - - assertEquals(compound.getString("name"), cloned.getString("name")); - assertEquals(compound.getInteger("age"), cloned.getInteger("age")); - assertEquals(compound.getList("nicknames"), cloned.getList("nicknames")); - } - - @Test - public void testItemTag() { - ItemStack test = new ItemStack(Items.L); - org.bukkit.inventory.ItemStack craftTest = MinecraftReflection.getBukkitItemStack(test); - - NbtCompound compound = NbtFactory.ofCompound("tag"); - compound.put("name", "Test Testerson"); - compound.put("age", 42); - - NbtFactory.setItemTag(craftTest, compound); - - assertEquals(compound, NbtFactory.fromItemTag(craftTest)); - } - - @Test - public void testCreateTags() { - for (NbtType type : NbtType.values()) { - if (type != NbtType.TAG_END) { - NbtFactory.ofWrapper(type, ""); - } - } - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * 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.wrappers.nbt; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer; +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 net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class NbtFactoryTest { + + @BeforeAll + public static void initializeBukkit() throws IllegalAccessException { + BukkitInitialization.initializeAll(); + } + + @Test + public void testFromStream() { + WrappedCompound compound = WrappedCompound.fromName("tag"); + compound.put("name", "Test Testerson"); + compound.put("age", 42); + + compound.put(NbtFactory.ofList("nicknames", "a", "b", "c")); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + DataOutput test = new DataOutputStream(buffer); + compound.write(test); + + ByteArrayInputStream source = new ByteArrayInputStream(buffer.toByteArray()); + DataInput input = new DataInputStream(source); + + NbtCompound cloned = NbtBinarySerializer.DEFAULT.deserializeCompound(input); + + assertEquals(compound.getString("name"), cloned.getString("name")); + assertEquals(compound.getInteger("age"), cloned.getInteger("age")); + assertEquals(compound.getList("nicknames"), cloned.getList("nicknames")); + } + + @Test + public void testItemTag() { + ItemStack test = new ItemStack(Items.L); + org.bukkit.inventory.ItemStack craftTest = MinecraftReflection.getBukkitItemStack(test); + + NbtCompound compound = NbtFactory.ofCompound("tag"); + compound.put("name", "Test Testerson"); + compound.put("age", 42); + + NbtFactory.setItemTag(craftTest, compound); + + assertEquals(compound, NbtFactory.fromItemTag(craftTest)); + } + + @Test + public void testCreateTags() { + for (NbtType type : NbtType.values()) { + if (type != NbtType.TAG_END) { + NbtFactory.ofWrapper(type, ""); + } + } + } +} diff --git a/src/test/java/com/comphenix/protocol/wrappers/nbt/TileEntityTest.java b/src/test/java/com/comphenix/protocol/wrappers/nbt/TileEntityTest.java index 86514ddc..c694e4d6 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/nbt/TileEntityTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/nbt/TileEntityTest.java @@ -1,38 +1,38 @@ -/** - * 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.wrappers.nbt; - -import com.comphenix.protocol.BukkitInitialization; -import org.bukkit.block.BlockState; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** - * @author dmulloy2 - */ -public class TileEntityTest { - - @BeforeAll - public static void beforeClass() { - BukkitInitialization.initializeAll(); - } - - @Test - public void test() { - // Ensure the read and write methods exist - TileEntityAccessor accessor = new TileEntityAccessor<>(); - accessor.findMethods(null, null); - } -} +/** + * 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.wrappers.nbt; + +import com.comphenix.protocol.BukkitInitialization; +import org.bukkit.block.BlockState; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author dmulloy2 + */ +public class TileEntityTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void test() { + // Ensure the read and write methods exist + TileEntityAccessor accessor = new TileEntityAccessor<>(); + accessor.findMethods(null, null); + } +}