Compare commits

..
22 Commits
Author SHA1 Message Date
NotAlexNoyle a783b380a2 Exclude CODEOWNERS from language statistics 2026-10-27 08:25:13 -04:00
NotAlexNoyle 9065ec1848 Add CODEOWNERS (one staff approval) 2026-10-27 08:25:13 -04:00
NotAlexNoyle caeaab7fba Disable debugging 2026-06-04 11:54:57 -04:00
NotAlexNoyle 3e9af0bf7e Fix building with TrueOG Bootstrap 2026-04-08 12:44:43 -04:00
NotAlexNoyle adc02cfce4 Fix config file interpretation 2026-04-01 15:18:10 -04:00
NotAlexNoyle 47661a5746 Include version in jarfile 2026-04-01 13:22:41 -04:00
NotAlexNoyle 8c8a39f423 Fix build 2026-04-01 13:20:28 -04:00
NotAlexNoyle 0396dbe917 Update config file 2026-04-01 13:16:04 -04:00
NotAlexNoyle ff46d7dbe5 Remove jitpack.yml 2025-11-14 11:47:47 -05:00
NotAlexNoyle ab0d75004e Update shadow to 8.3.8 2025-09-24 10:52:21 -04:00
NotAlexNoyle d818379207 Update gradle to 8.14.3 2025-09-01 12:34:30 -04:00
NotAlexNoyle 9b37cbc72c Update gradle to 8.14.1, use kotlin instead of groovy to build 2025-06-13 11:52:52 -04:00
NotAlexNoyle 077c9691ac Update gradle 2025-06-01 15:57:00 -04:00
NotAlexNoyle c3019d5f01 Properly support TrueOG Bootstrap 2025-01-01 17:48:59 -05:00
NotAlexNoyle 315370b985 Fix build with TrueOG Bootstrap 2025-01-01 17:19:18 -05:00
Dan Mulloy 16f4870714 Update version to 5.0.0 for release 2023-05-12 10:10:49 -04:00
Jinyu Yu e77ed96957 Add 1.17 support to TinyProtocol (#194) 2023-05-06 17:26:49 -05:00
Hasan Demirtaş 46f6e76f91 Add BlockEntityType support. (#2111) 2023-05-06 17:18:09 -05:00
Lukas Alt 38bbd764cc Fixed creation of packet bundles (#2383) 2023-05-06 17:12:47 -05:00
Lukas Alt 2931af58db Added wrappers for MessageSignature (#2362) 2023-04-30 22:28:19 -05:00
Lukas Alt 08ea2da642 Improved Wrapping of PlayerInfoData and support chat session data (#2361)
* Improved Wrapping of PlayerInfoData and support chat session data
* added constructor for unambiguous creation of playerinfodata without signature
2023-04-29 19:49:51 +00:00
Lukas Alt 448e9369de JSON Parsing for WrappedServerPing and fixed modifying favicon (#2265)
* Fix WrappedServerPing access and ensure legacy compatability for JSON parsing
* added wrappers for mojang codecs and allow serializing server pings
2023-04-29 14:45:47 -05:00
42 changed files with 1192 additions and 1838 deletions

No files matched your search

+1
View File
@@ -1 +1,2 @@
* text=auto eol=lf
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+2 -4
View File
@@ -10,8 +10,6 @@ Critically, different plugins that use this approach may _hook_ into the same cl
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/)
@@ -55,7 +53,7 @@ You can also add ProtocolLib as a Maven dependency:
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -69,7 +67,7 @@ repositories {
}
dependencies {
compileOnly 'com.comphenix.protocol:ProtocolLib:5.0.0-SNAPSHOT'
compileOnly 'com.comphenix.protocol:ProtocolLib:5.0.0'
}
```
-137
View File
@@ -1,137 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.tinyprotocol</groupId>
<artifactId>TinyProtocol</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>TinyProtocol</name>
<description>Intercept packets without ProtocolLib.</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
<finalName>TinyProtocol</finalName>
<outputDirectory>../../target/</outputDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<phase>package</phase>
<id>attach-javadocs</id>
</execution>
</executions>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -1,117 +0,0 @@
package com.comphenix.tinyprotocol;
import io.netty.channel.Channel;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.tinyprotocol.Reflection.ConstructorInvoker;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
/**
* Represents an example plugin utilizing TinyProtocol
*/
public class ExamplePlugin extends JavaPlugin {
// Chat packets
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField("{nms}.PacketPlayInChat", String.class, 0);
// Explosion packet
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
private FieldAccessor<Float> particleX = Reflection.getField(particleClass, float.class, 0);
private FieldAccessor<Float> particleY = Reflection.getField(particleClass, float.class, 1);
private FieldAccessor<Float> particleZ = Reflection.getField(particleClass, float.class, 2);
private FieldAccessor<Integer> particleCount = Reflection.getField(particleClass, int.class, 0);
// Server info packet
private Class<?> serverInfoClass = Reflection.getClass("{nms}.PacketStatusOutServerInfo");
private Class<Object> serverPingClass = Reflection.getUntypedClass("{nms}.ServerPing");
private Class<Object> playerSampleClass = Reflection.getUntypedClass("{nms}.ServerPingPlayerSample");
private FieldAccessor<Object> serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0);
private FieldAccessor<Object> playerSample = Reflection.getField(serverPingClass, playerSampleClass, 0);
private ConstructorInvoker playerSampleInvoker = Reflection.getConstructor(playerSampleClass, int.class, int.class);
private TinyProtocol protocol;
@Override
public void onEnable() {
protocol = new TinyProtocol(this) {
@Override
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
// Cancel chat packets
if (CHAT_MESSAGE.hasField(packet)) {
if (CHAT_MESSAGE.get(packet).contains("dirty")) {
sendExplosion(sender);
return null;
}
}
if (particleName.hasField(packet)) {
System.out.println("Sending particle field:" + packet);
}
return super.onPacketInAsync(sender, channel, packet);
}
@Override
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
if (serverInfoClass.isInstance(packet)) {
Object ping = serverPing.get(packet);
playerSample.set(ping, playerSampleInvoker.invoke(1000, 0));
// Which is equivalent to:
// serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
return packet;
}
return super.onPacketOutAsync(reciever, channel, packet);
}
};
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
// Toggle injection
if (protocol.hasInjected(player)) {
protocol.uninjectPlayer(player);
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
} else {
protocol.injectPlayer(player);
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
}
return true;
} else {
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
}
return false;
}
private void sendExplosion(Player player) {
try {
// Only visible for the client
Object explosionPacket = particleClass.newInstance();
Location loc = player.getLocation();
particleName.set(explosionPacket, "hugeexplosion");
particleX.set(explosionPacket, (float) loc.getX());
particleY.set(explosionPacket, (float) loc.getY());
particleZ.set(explosionPacket, (float) loc.getZ());
particleCount.set(explosionPacket, 1);
// Send the packet to the player
protocol.sendPacket(player, explosionPacket);
} catch (Exception e) {
throw new RuntimeException("Cannot send packet.", e);
}
}
}
@@ -1,403 +0,0 @@
package com.comphenix.tinyprotocol;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
/**
* An utility class that simplifies reflection in Bukkit plugins.
*
* @author Kristian
*/
public final class Reflection {
/**
* An interface for invoking a specific constructor.
*/
public interface ConstructorInvoker {
/**
* Invoke a constructor for a specific class.
*
* @param arguments - the arguments to pass to the constructor.
* @return The constructed object.
*/
public Object invoke(Object... arguments);
}
/**
* An interface for invoking a specific method.
*/
public interface MethodInvoker {
/**
* Invoke a method on a specific target object.
*
* @param target - the target object, or NULL for a static method.
* @param arguments - the arguments to pass to the method.
* @return The return value, or NULL if is void.
*/
public Object invoke(Object target, Object... arguments);
}
/**
* An interface for retrieving the field content.
*
* @param <T> - field type.
*/
public interface FieldAccessor<T> {
/**
* Retrieve the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @return The value of the field.
*/
public T get(Object target);
/**
* Set the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @param value - the new value of the field.
*/
public void set(Object target, Object value);
/**
* Determine if the given object has this field.
*
* @param target - the object to test.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasField(Object target);
}
// Deduce the net.minecraft.server.v* package
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
// Variable replacement
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
private Reflection() {
// Seal class
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
return getField(getClass(className), name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
// Common method
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
for (final Field field : target.getDeclaredFields()) {
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
field.setAccessible(true);
// A function for retrieving a specific field value
return new FieldAccessor<T>() {
@Override
@SuppressWarnings("unchecked")
public T get(Object target) {
try {
return (T) field.get(target);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public void set(Object target, Object value) {
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public boolean hasField(Object target) {
// target instanceof DeclaringClass
return field.getDeclaringClass().isAssignableFrom(target.getClass());
}
};
}
}
// Search in parent classes
if (target.getSuperclass() != null)
return getField(target.getSuperclass(), name, fieldType, index);
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
return getTypedMethod(getClass(className), methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
return getTypedMethod(clazz, methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param returnType - the expected return type, or NULL to ignore.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
for (final Method method : clazz.getDeclaredMethods()) {
if ((methodName == null || method.getName().equals(methodName))
&& (returnType == null || method.getReturnType().equals(returnType))
&& Arrays.equals(method.getParameterTypes(), params)) {
method.setAccessible(true);
return new MethodInvoker() {
@Override
public Object invoke(Object target, Object... arguments) {
try {
return method.invoke(target, arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke method " + method, e);
}
}
};
}
}
// Search in every superclass
if (clazz.getSuperclass() != null)
return getMethod(clazz.getSuperclass(), methodName, params);
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
return getConstructor(getClass(className), params);
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Arrays.equals(constructor.getParameterTypes(), params)) {
constructor.setAccessible(true);
return new ConstructorInvoker() {
@Override
public Object invoke(Object... arguments) {
try {
return constructor.newInstance(arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
}
}
};
}
}
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
}
/**
* Retrieve a class from its full name, without knowing its type on compile time.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @see {@link #getClass()} for more information.
* @param lookupName - the class name with variables.
* @return The class.
*/
public static Class<Object> getUntypedClass(String lookupName) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* <p>
* <table border="1">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @param lookupName - the class name with variables.
* @return The looked up class.
* @throws IllegalArgumentException If a variable or class could not be found.
*/
public static Class<?> getClass(String lookupName) {
return getCanonicalClass(expandVariables(lookupName));
}
/**
* Retrieve a class in the net.minecraft.server.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getMinecraftClass(String name) {
return getCanonicalClass(NMS_PREFIX + "." + name);
}
/**
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getCraftBukkitClass(String name) {
return getCanonicalClass(OBC_PREFIX + "." + name);
}
/**
* Retrieve a class by its canonical name.
*
* @param canonicalName - the canonical name.
* @return The class.
*/
private static Class<?> getCanonicalClass(String canonicalName) {
try {
return Class.forName(canonicalName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
}
}
/**
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
*
* @param name - the full name of the class.
* @return The expanded string.
*/
private static String expandVariables(String name) {
StringBuffer output = new StringBuffer();
Matcher matcher = MATCH_VARIABLE.matcher(name);
while (matcher.find()) {
String variable = matcher.group(1);
String replacement = "";
// Expand all detected variables
if ("nms".equalsIgnoreCase(variable))
replacement = NMS_PREFIX;
else if ("obc".equalsIgnoreCase(variable))
replacement = OBC_PREFIX;
else if ("version".equalsIgnoreCase(variable))
replacement = VERSION;
else
throw new IllegalArgumentException("Unknown variable: " + variable);
// Assume the expanded variables are all packages, and append a dot
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.')
replacement += ".";
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(output);
return output.toString();
}
}
@@ -1,522 +0,0 @@
package com.comphenix.tinyprotocol;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
import com.comphenix.tinyprotocol.Reflection.MethodInvoker;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.mojang.authlib.GameProfile;
/**
* Represents a very tiny alternative to ProtocolLib.
* <p>
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
*
* @author Kristian
*/
public abstract class TinyProtocol {
private static final AtomicInteger ID = new AtomicInteger(0);
// Used in order to lookup a channel
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
private static final FieldAccessor<Object> getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
private static final FieldAccessor<Channel> getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0);
// Looking up ServerConnection
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
private static final FieldAccessor<Object> getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass);
// Packets we have to intercept
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart");
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
// Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
private Listener listener;
// Channels that have already been removed
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap());
// List of network markers
private List<Object> networkManagers;
// Injected channel handlers
private List<Channel> serverChannels = new ArrayList<>();
private ChannelInboundHandlerAdapter serverChannelHandler;
private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol;
// Current handler name
private String handlerName;
protected volatile boolean closed;
protected Plugin plugin;
/**
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
* <p>
* You can construct multiple instances per plugin.
*
* @param plugin - the plugin.
*/
public TinyProtocol(final Plugin plugin) {
this.plugin = plugin;
// Compute handler name
this.handlerName = getHandlerName();
// Prepare existing players
registerBukkitEvents();
try {
registerChannelHandler();
registerPlayers(plugin);
} catch (IllegalArgumentException ex) {
// Damn you, late bind
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind.");
new BukkitRunnable() {
@Override
public void run() {
registerChannelHandler();
registerPlayers(plugin);
plugin.getLogger().info("[TinyProtocol] Late bind injection successful.");
}
}.runTask(plugin);
}
}
private void createServerChannelHandler() {
// Handle connected channels
endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
try {
// This can take a while, so we need to stop the main thread from interfering
synchronized (networkManagers) {
// Stop injecting channels
if (!closed) {
channel.eventLoop().submit(() -> injectChannelInternal(channel));
}
}
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
}
}
};
// This is executed before Minecraft's channel handler
beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(endInitProtocol);
}
};
serverChannelHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
}
/**
* Register bukkit events.
*/
private void registerBukkitEvents() {
listener = new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public final void onPlayerLogin(PlayerLoginEvent e) {
if (closed)
return;
Channel channel = getChannel(e.getPlayer());
// Don't inject players that have been explicitly uninjected
if (!uninjectedChannels.contains(channel)) {
injectPlayer(e.getPlayer());
}
}
@EventHandler
public final void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) {
close();
}
}
};
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
@SuppressWarnings("unchecked")
private void registerChannelHandler() {
Object mcServer = getMinecraftServer.get(Bukkit.getServer());
Object serverConnection = getServerConnection.get(mcServer);
boolean looking = true;
// We need to synchronize against this list
networkManagers = (List<Object>) getNetworkMarkers.invoke(null, serverConnection);
createServerChannelHandler();
// Find the correct list, or implicitly throw an exception
for (int i = 0; looking; i++) {
List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
for (Object item : list) {
if (!ChannelFuture.class.isInstance(item))
break;
// Channel future that contains the server connection
Channel serverChannel = ((ChannelFuture) item).channel();
serverChannels.add(serverChannel);
serverChannel.pipeline().addFirst(serverChannelHandler);
looking = false;
}
}
}
private void unregisterChannelHandler() {
if (serverChannelHandler == null)
return;
for (Channel serverChannel : serverChannels) {
final ChannelPipeline pipeline = serverChannel.pipeline();
// Remove channel handler
serverChannel.eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
pipeline.remove(serverChannelHandler);
} catch (NoSuchElementException e) {
// That's fine
}
}
});
}
}
private void registerPlayers(Plugin plugin) {
for (Player player : plugin.getServer().getOnlinePlayers()) {
injectPlayer(player);
}
}
/**
* Invoked when the server is starting to send a packet to a player.
* <p>
* Note that this is not executed on the main thread.
*
* @param receiver - the receiving player, NULL for early login/status packets.
* @param channel - the channel that received the packet. Never NULL.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
return packet;
}
/**
* Invoked when the server has received a packet from a given player.
* <p>
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
*
* @param sender - the player that sent the packet, NULL for early login/status packets.
* @param channel - channel that received the packet. Never NULL.
* @param packet - the packet being received.
* @return The packet to recieve instead, or NULL to cancel.
*/
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
return packet;
}
/**
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
*/
public void sendPacket(Player player, Object packet) {
sendPacket(getChannel(player), packet);
}
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet to send.
*/
public void sendPacket(Channel channel, Object packet) {
channel.pipeline().writeAndFlush(packet);
}
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the player that sent the packet.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Player player, Object packet) {
receivePacket(getChannel(player), packet);
}
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Channel channel, Object packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
/**
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
* <p>
* Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
*
* @return A unique channel handler name.
*/
protected String getHandlerName() {
return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
}
/**
* Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
* <p>
* This will automatically be called when a player has logged in.
*
* @param player - the player to inject.
*/
public void injectPlayer(Player player) {
injectChannelInternal(getChannel(player)).player = player;
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/
public void injectChannel(Channel channel) {
injectChannelInternal(channel);
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) {
try {
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
// Inject our packet interceptor
if (interceptor == null) {
interceptor = new PacketInterceptor();
channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
uninjectedChannels.remove(channel);
}
return interceptor;
} catch (IllegalArgumentException e) {
// Try again
return (PacketInterceptor) channel.pipeline().get(handlerName);
}
}
/**
* Retrieve the Netty channel associated with a player. This is cached.
*
* @param player - the player.
* @return The Netty channel.
*/
public Channel getChannel(Player player) {
Channel channel = channelLookup.get(player.getName());
// Lookup channel again
if (channel == null) {
Object connection = getConnection.get(getPlayerHandle.invoke(player));
Object manager = getManager.get(connection);
channelLookup.put(player.getName(), channel = getChannel.get(manager));
}
return channel;
}
/**
* Uninject a specific player.
*
* @param player - the injected player.
*/
public void uninjectPlayer(Player player) {
uninjectChannel(getChannel(player));
}
/**
* Uninject a specific channel.
* <p>
* This will also disable the automatic channel injection that occurs when a player has properly logged in.
*
* @param channel - the injected channel.
*/
public void uninjectChannel(final Channel channel) {
// No need to guard against this if we're closing
if (!closed) {
uninjectedChannels.add(channel);
}
// See ChannelInjector in ProtocolLib, line 590
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.pipeline().remove(handlerName);
}
});
}
/**
* Determine if the given player has been injected by TinyProtocol.
*
* @param player - the player.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Player player) {
return hasInjected(getChannel(player));
}
/**
* Determine if the given channel has been injected by TinyProtocol.
*
* @param channel - the channel.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Channel channel) {
return channel.pipeline().get(handlerName) != null;
}
/**
* Cease listening for packets. This is called automatically when your plugin is disabled.
*/
public final void close() {
if (!closed) {
closed = true;
// Remove our handlers
for (Player player : plugin.getServer().getOnlinePlayers()) {
uninjectPlayer(player);
}
// Clean up Bukkit
HandlerList.unregisterAll(listener);
unregisterChannelHandler();
}
}
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
* @author Kristian
*/
private final class PacketInterceptor extends ChannelDuplexHandler {
// Updated by the login event
public volatile Player player;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Intercept channel
final Channel channel = ctx.channel();
handleLoginStart(channel, msg);
try {
msg = onPacketInAsync(player, channel, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
}
if (msg != null) {
super.channelRead(ctx, msg);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = onPacketOutAsync(player, ctx.channel(), msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
GameProfile profile = getGameProfile.get(packet);
channelLookup.put(profile.getName(), channel);
}
}
}
}
@@ -1,14 +0,0 @@
name: TinyProtocol
version: 0.1.0
description: Intercept packets without ProtocolLib
author: Comphenix
load: startup
main: com.comphenix.tinyprotocol.ExamplePlugin
database: false
commands:
toggleinjection:
description: Toggle the TinyProtocol injection of the current player.
usage: /<command>
-112
View File
@@ -1,112 +0,0 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
group = 'com.comphenix.protocol'
version = '5.0.0-SNAPSHOT'
description = 'Provides access to the Minecraft protocol'
def isSnapshot = version.endsWith('-SNAPSHOT')
repositories {
// mavenLocal() // can speed up build, but may fail in CI
mavenCentral()
maven {
url 'https://repo.dmulloy2.net/repository/public/'
}
maven {
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
}
maven {
url 'https://libraries.minecraft.net/'
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.14.3'
compileOnly 'org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT'
compileOnly 'io.netty:netty-all:4.0.23.Final'
compileOnly 'net.kyori:adventure-text-serializer-gson:4.13.0'
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation 'org.mockito:mockito-inline:4.11.0'
testImplementation 'io.netty:netty-common:4.1.77.Final'
testImplementation 'io.netty:netty-transport:4.1.77.Final'
testImplementation 'org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withJavadocJar()
withSourcesJar()
}
shadowJar {
dependencies {
include(dependency('net.bytebuddy:byte-buddy:.*'))
}
relocate 'net.bytebuddy', 'com.comphenix.net.bytebuddy'
archiveFileName = 'ProtocolLib.jar'
}
test {
useJUnitPlatform()
}
processResources {
def includeBuild = isSnapshot && System.getenv('BUILD_NUMBER')
def fullVersion = includeBuild
? version + '-' + System.getenv('BUILD_NUMBER')
: version
eachFile { expand(['version': fullVersion]) }
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
afterEvaluate {
artifactId = 'ProtocolLib'
}
}
}
repositories {
maven {
url isSnapshot
? 'https://repo.dmulloy2.net/repository/snapshots/'
: 'https://repo.dmulloy2.net/repository/releases/'
credentials {
username project.nexusUsername
password project.nexusPassword
}
}
}
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
tasks.withType(Javadoc) {
options.encoding = 'UTF-8'
}
+185
View File
@@ -0,0 +1,185 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources
plugins {
id("java-library")
id("eclipse")
id("com.gradleup.shadow") version "8.3.9"
}
group = "com.comphenix.protocol"
version = "5.0.0-dev"
description = "Provides access to the Minecraft protocol"
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
repositories {
val bootstrapRepo = System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let { // TrueOG Bootstrap mavenLocal().
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
dir
} else {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
null
}
} ?: run {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
null
}
// Prefer the bootstrap-local Spigot artifacts when present. If ~/.m2 contains
// partial module metadata, resolving there first can block Gradle from falling
// through to the bootstrap repo for the actual jar.
maven {
url = uri("file://${(bootstrapRepo ?: file("${System.getProperty("user.home")}/.m2/repository")).absolutePath}")
content {
includeGroup("org.spigotmc")
}
}
if (bootstrapRepo != null) {
maven {
url = uri("file://${System.getProperty("user.home")}/.m2/repository")
content {
includeGroup("org.spigotmc")
}
}
}
mavenCentral()
maven {
name = "Rosewood Public"
url = uri("https://repo.rosewooddev.io/repository/public/")
content {
includeGroup("org.spigotmc")
}
}
maven {
name = "ElMakers Mirror"
url = uri("https://maven.elmakers.com/repository/")
content {
includeGroup("org.spigotmc")
}
}
maven {
name = "dmulloy2"
url = uri("https://repo.dmulloy2.net/repository/public/")
content {
excludeGroup("org.spigotmc")
}
}
maven {
name = "Spigot Snapshots"
url = uri("https://hub.spigotmc.org/nexus/content/groups/public/")
content {
includeGroup("org.spigotmc")
}
}
maven {
url = uri("https://libraries.minecraft.net/")
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation("net.bytebuddy:byte-buddy:1.14.3")
compileOnly("org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT") {
isTransitive = false
}
compileOnly("com.google.guava:guava:31.1-jre")
compileOnly("org.apache.commons:commons-lang3:3.17.0")
compileOnly("commons-lang:commons-lang:2.6")
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("com.mojang:datafixerupper:6.0.6")
compileOnly("net.md-5:bungeecord-chat:1.16-R0.4")
compileOnly("org.yaml:snakeyaml:1.33")
compileOnly("org.joml:joml:1.10.5")
compileOnly("io.netty:netty-all:4.0.23.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.13.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1") {
isTransitive = false
}
testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
testImplementation("org.mockito:mockito-core:4.11.0")
testImplementation("org.mockito:mockito-inline:4.11.0")
testImplementation("io.netty:netty-common:4.1.77.Final")
testImplementation("io.netty:netty-transport:4.1.77.Final")
testImplementation("org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT")
testImplementation("org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT")
testImplementation("com.google.guava:guava:31.1-jre")
testImplementation("org.apache.commons:commons-lang3:3.17.0")
testImplementation("commons-lang:commons-lang:2.6")
testImplementation("com.google.code.findbugs:jsr305:3.0.2")
testImplementation("com.mojang:authlib:3.17.30")
testImplementation("com.mojang:brigadier:1.0.18")
testImplementation("com.mojang:datafixerupper:6.0.6")
testImplementation("com.mojang:logging:1.1.1")
testImplementation("net.md-5:bungeecord-chat:1.16-R0.4")
testImplementation("org.apache.logging.log4j:log4j-api:2.17.1")
testRuntimeOnly("org.slf4j:slf4j-api:2.0.0")
testImplementation("org.yaml:snakeyaml:1.33")
testImplementation("org.joml:joml:1.10.5")
testImplementation("net.kyori:adventure-text-serializer-gson:4.13.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.13.1")
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.named<ShadowJar>("shadowJar") {
dependencies {
include(dependency("net.bytebuddy:byte-buddy:.*"))
}
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
archiveFileName.set("ProtocolLib-${project.version}.jar")
}
tasks.named<Jar>("jar") {
enabled = false
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
tasks.named<ProcessResources>("processResources") {
val includeBuild = isSnapshot && System.getenv("BUILD_NUMBER") != null
val fullVersion = if (includeBuild) "$version-${System.getenv("BUILD_NUMBER")}" else version
filesMatching("**/*") {
expand("version" to fullVersion)
}
}
tasks.named("build") {
dependsOn("shadowJar")
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
tasks.withType<Javadoc> {
options.encoding = "UTF-8"
}
testing {
suites {
val test by getting(JvmTestSuite::class) {
useJUnitJupiter()
}
}
}
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+22 -15
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,10 +85,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -133,10 +133,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -197,16 +200,20 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+14 -12
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
-5
View File
@@ -1,5 +0,0 @@
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 17.0.4-tem
- sdk use java 17.0.4-tem
-379
View File
@@ -1,379 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>5.0.0-SNAPSHOT</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>https://github.com/dmulloy2/ProtocolLib</url>
<packaging>jar</packaging>
<inceptionYear>2012</inceptionYear>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
<junit.version>5.9.2</junit.version>
<mockito.version>4.11.0</mockito.version>
<netty.version>4.1.77.Final</netty.version>
<spigot.version>1.19.4-R0.1-SNAPSHOT</spigot.version>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
<exclude>io.netty:*</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.fullVersion}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>${project.name}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
<!-- We need this as we use reflection in tests to ensure equality -->
<argLine>--add-opens java.base/java.util=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<failOnError>false</failOnError>
<encoding>ISO-8859-1</encoding>
<doctitle>ProtocolLib JavaDocs</doctitle>
<windowtitle>ProtocolLib JavaDocs</windowtitle>
<bottom>Copyright &#169; {inceptionYear}&#x2013;{currentYear}
Comphenix and dmulloy2. Licensed under the GNU GPL v2.
</bottom>
<additionalOptions>
<additionalOption>-Xdoclint:none</additionalOption>
</additionalOptions>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
<!--<project.fullVersion>${project.version}</project.fullVersion>-->
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<properties>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- TODO: Look into signing releases
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
</profile>
</profiles>
<scm>
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
<url>https://github.com/dmulloy2/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>https://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>https://dmulloy2.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
</developer>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>https://comphenix.net/</url>
<roles>
<role>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<distributionManagement>
<repository>
<id>dmulloy2-releases</id>
<url>https://repo.dmulloy2.net/repository/releases/</url>
</repository>
<snapshotRepository>
<id>dmulloy2-snapshots</id>
<url>https://repo.dmulloy2.net/repository/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<!-- core minecraft dependencies -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Compile with the old version of Netty for backwards compat -->
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-gson -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.13.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.3</version>
</dependency>
<!-- Testing dependencies -->
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-transport -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
-5
View File
@@ -1,5 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*/
rootProject.name = 'ProtocolLib'
+5
View File
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
rootProject.name = "ProtocolLib"
@@ -25,6 +25,9 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Represents the configuration of ProtocolLib.
@@ -275,7 +278,24 @@ public class ProtocolConfig {
* @return Every suppressed report type.
*/
public ImmutableList<String> getSuppressedReports() {
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
Object value = getGlobalValue(SUPPRESSED_REPORTS, Collections.emptyList());
if (value instanceof Collection<?>) {
List<String> reports = new ArrayList<>();
for (Object entry : (Collection<?>) value) {
if (entry != null) {
reports.add(String.valueOf(entry));
}
}
return ImmutableList.copyOf(reports);
}
if (value instanceof String) {
String report = ((String) value).trim();
return report.isEmpty() ? ImmutableList.of() : ImmutableList.of(report);
}
return ImmutableList.of();
}
/**
@@ -294,7 +314,16 @@ public class ProtocolConfig {
* @return The version to ignore ProtocolLib's satefy.
*/
public String getIgnoreVersionCheck() {
return getGlobalValue(IGNORE_VERSION_CHECK, "");
Object value = getGlobalValue(IGNORE_VERSION_CHECK, "");
if (value instanceof String) {
return (String) value;
}
if (value instanceof Boolean) {
return (Boolean) value ? "true" : "";
}
return value != null ? String.valueOf(value) : "";
}
/**
@@ -29,6 +29,7 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
public abstract class AbstractStructure {
protected transient Object handle;
@@ -308,6 +309,31 @@ public abstract class AbstractStructure {
BlockPosition.getConverter());
}
/**
* Retrieves a read/write structure for registrable objects.
*
* @param registrableClass The registrable object's class.
* @return A modifier for a registrable objects.
* @see MinecraftReflection#getBlockEntityTypeClass()
*/
public StructureModifier<WrappedRegistrable> getRegistrableModifier(
@NotNull final Class<?> registrableClass
) {
// Convert to and from the Bukkit wrapper
return structureModifier.withType(
registrableClass,
BukkitConverters.getWrappedRegistrable(registrableClass));
}
/**
* Retrieves a read/write structure for BlockEntityType.
* @return A modifier for a BlockEntityType.
*/
public StructureModifier<WrappedRegistrable> getBlockEntityTypeModifier() {
// Convert to and from the Bukkit wrapper
return getRegistrableModifier(MinecraftReflection.getBlockEntityTypeClass());
}
/**
* Retrieves a read/write structure for wrapped ChunkCoordIntPairs.
* @return A modifier for ChunkCoordIntPair.
@@ -961,7 +987,7 @@ public abstract class AbstractStructure {
}
/**
* Retrieve a read/write structure for profile public keys in 1.9
* Retrieve a read/write structure for profile public keys in 1.19
* @return The Structure Modifier
*/
public StructureModifier<WrappedProfilePublicKey> getProfilePublicKeys() {
@@ -971,13 +997,24 @@ public abstract class AbstractStructure {
}
/**
* Retrieve a read/write structure for profile public key data in 1.9
* Retrieve a read/write structure for profile public key data in 1.19
* @return The Structure Modifier
*/
public StructureModifier<WrappedProfileKeyData> getProfilePublicKeyData() {
return structureModifier.withType(
MinecraftReflection.getProfilePublicKeyDataClass(),
BukkitConverters.getWrappedPublicKeyDataConverter());
MinecraftReflection.getProfilePublicKeyDataClass(),
BukkitConverters.getWrappedPublicKeyDataConverter());
}
/**
* Retrieves read/write structure for remote chat session data in 1.19.3
* @return The Structure Modifier
*/
public StructureModifier<WrappedRemoteChatSessionData> getRemoteChatSessionData() {
return structureModifier.withType(
MinecraftReflection.getRemoteChatSessionDataClass(),
BukkitConverters.getWrappedRemoteChatSessionDataConverter()
);
}
/**
@@ -1006,7 +1043,7 @@ public abstract class AbstractStructure {
}
/**
* @return read/writer structure direct access to signature data like chat messages
* @return read/writer structure direct access to salted signature data like chat messages
*/
public StructureModifier<WrappedSaltedSignature> getSignatures() {
return structureModifier.withType(
@@ -1015,6 +1052,16 @@ public abstract class AbstractStructure {
);
}
/**
* @return read/writer structure direct access to unsalted signature data for example in chat message (since 1.19.3)
*/
public StructureModifier<WrappedMessageSignature> getMessageSignatures() {
return structureModifier.withType(
MinecraftReflection.getMessageSignatureClass(),
BukkitConverters.getWrappedMessageSignatureConverter()
);
}
/**
* @param leftConverter converter for left values
* @param rightConverter converter for right values
@@ -363,6 +363,9 @@ public class PacketRegistry {
// Try the lookup first (may be null, so check contains)
Optional<Class<?>> res = REGISTER.typeToClass.get(type);
if (res != null) {
if(res.isPresent() && MinecraftReflection.isBundleDelimiter(res.get())) {
return MinecraftReflection.getPackedBundlePacketClass();
}
return res;
}
@@ -375,6 +378,9 @@ public class PacketRegistry {
// cache it for next time
associate(type, clazz);
if(clazz != null && MinecraftReflection.isBundleDelimiter(clazz)) {
clazz = MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet bundle class not found."));
}
return Optional.ofNullable(clazz);
}
@@ -1573,6 +1573,10 @@ public final class MinecraftReflection {
return getMinecraftClass("world.entity.player.ProfilePublicKey");
}
public static Class<?> getMessageSignatureClass() {
return getMinecraftClass("network.chat.MessageSignature");
}
public static Class<?> getSaltedSignatureClass() {
try {
return getMinecraftClass("SaltedSignature");
@@ -1621,6 +1625,10 @@ public final class MinecraftReflection {
return getMinecraftClass("network.chat.RemoteChatSession");
}
public static Class<?> getRemoteChatSessionDataClass() {
return getRemoteChatSessionClass().getClasses()[0];
}
public static Class<?> getFastUtilClass(String className) {
return getLibraryClass("it.unimi.dsi.fastutil." + className);
}
@@ -1685,6 +1693,22 @@ public final class MinecraftReflection {
}
}
public static Class<?> getDynamicOpsClass() {
return getLibraryClass("com.mojang.serialization.DynamicOps");
}
public static Class<?> getJsonOpsClass() {
return getLibraryClass("com.mojang.serialization.JsonOps");
}
public static Class<?> getNbtOpsClass() {
return getMinecraftClass("nbt.DynamicOpsNBT" /* Spigot Mappings */, "nbt.NbtOps" /* Mojang Mappings */);
}
public static Class<?> getCodecClass() {
return getLibraryClass("com.mojang.serialization.Codec");
}
public static Class<?> getHolderClass() {
return getMinecraftClass("core.Holder");
}
@@ -77,6 +77,7 @@ import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import static com.comphenix.protocol.utility.MinecraftReflection.getCraftBukkitClass;
import static com.comphenix.protocol.utility.MinecraftReflection.getMinecraftClass;
@@ -587,7 +588,23 @@ public class BukkitConverters {
public static EquivalentConverter<WrappedBlockData> getWrappedBlockDataConverter() {
return ignoreNull(handle(WrappedBlockData::getHandle, WrappedBlockData::fromHandle, WrappedBlockData.class));
}
/**
* Retrieve a converter for wrapped block entity type.
* @return Wrapped block entity type.
*/
public static EquivalentConverter<WrappedRegistrable> getWrappedRegistrable(
@NotNull final Class<?> registrableClass
) {
return ignoreNull(
handle(
WrappedRegistrable::getHandle,
handle -> WrappedRegistrable.fromHandle(registrableClass, handle),
WrappedRegistrable.class
)
);
}
/**
* Retrieve a converter for wrapped attribute snapshots.
* @return Wrapped attribute snapshot converter.
@@ -604,6 +621,10 @@ public class BukkitConverters {
return ignoreNull(handle(WrappedProfileKeyData::getHandle, WrappedProfileKeyData::new, WrappedProfileKeyData.class));
}
public static EquivalentConverter<WrappedRemoteChatSessionData> getWrappedRemoteChatSessionDataConverter() {
return ignoreNull(handle(WrappedRemoteChatSessionData::getHandle, WrappedRemoteChatSessionData::new, WrappedRemoteChatSessionData.class));
}
/**
* @return converter for cryptographic signature data that are used in login and chat packets
*/
@@ -611,6 +632,13 @@ public class BukkitConverters {
return ignoreNull(handle(WrappedSaltedSignature::getHandle, WrappedSaltedSignature::new, WrappedSaltedSignature.class));
}
/**
* @return converter for an encoded cryptographic message signature
*/
public static EquivalentConverter<WrappedMessageSignature> getWrappedMessageSignatureConverter() {
return ignoreNull(handle(WrappedMessageSignature::getHandle, WrappedMessageSignature::new, WrappedMessageSignature.class));
}
public static EquivalentConverter<WrappedLevelChunkData.ChunkData> getWrappedChunkDataConverter() {
return ignoreNull(handle(WrappedLevelChunkData.ChunkData::getHandle, WrappedLevelChunkData.ChunkData::new, WrappedLevelChunkData.ChunkData.class));
}
@@ -30,6 +30,8 @@ import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import javax.annotation.Nullable;
/**
* Represents an immutable PlayerInfoData in the PLAYER_INFO packet.
* @author dmulloy2
@@ -43,6 +45,9 @@ public class PlayerInfoData {
private final NativeGameMode gameMode;
private final WrappedGameProfile profile;
private final WrappedChatComponent displayName;
@Nullable
private final WrappedRemoteChatSessionData remoteChatSessionData;
@Nullable
private final WrappedProfileKeyData profileKeyData;
// This is the same order as the NMS class, minus the packet (which isn't a field)
@@ -54,7 +59,35 @@ public class PlayerInfoData {
this(profile.getUUID(), latency, true, gameMode, profile, displayName, keyData);
}
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, WrappedProfileKeyData profileKeyData) {
/**
* Constructs a new PlayerInfoData for Minecraft 1.19 or later without signature data
* @see PlayerInfoData#PlayerInfoData(UUID, int, boolean, NativeGameMode, WrappedGameProfile, WrappedChatComponent, WrappedRemoteChatSessionData)
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
* @param listed whether the player is listed in the tab list
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
*/
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName) {
this(profileId, latency, listed, gameMode, profile, displayName, (WrappedRemoteChatSessionData) null);
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.19. This is incompatible on 1.19.3.
* @see PlayerInfoData#PlayerInfoData(UUID, int, boolean, NativeGameMode, WrappedGameProfile, WrappedChatComponent, WrappedRemoteChatSessionData)
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
* @param listed whether the player is listed in the tab list
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
* @param profileKeyData the public key for the profile or null
*/
@Deprecated
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, @Nullable WrappedProfileKeyData profileKeyData) {
this.profileId = profileId;
this.latency = latency;
this.listed = listed;
@@ -62,6 +95,29 @@ public class PlayerInfoData {
this.profile = profile;
this.displayName = displayName;
this.profileKeyData = profileKeyData;
this.remoteChatSessionData = null;
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.19.3 or later.
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
* @param listed whether the player is listed in the tab list
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
* @param remoteChatSession the remote chat session for this profile or null
*/
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
this.profileId = profileId;
this.latency = latency;
this.listed = listed;
this.gameMode = gameMode;
this.profile = profile;
this.displayName = displayName;
this.profileKeyData = null;
this.remoteChatSessionData = remoteChatSession;
}
/**
@@ -69,6 +125,9 @@ public class PlayerInfoData {
* @return the id of the profile
*/
public UUID getProfileId() {
if(profileId == null && profile != null) {
return profile.getUUID(); // Ensure forward compatability
}
return profileId;
}
@@ -97,7 +156,7 @@ public class PlayerInfoData {
}
/**
* Gets if the player is listed on the client.
* Gets if the player is listed on the client (since 1.19.3)
* @return if the player is listed
*/
public boolean isListed() {
@@ -121,11 +180,21 @@ public class PlayerInfoData {
}
/**
* Gets the profile key data of the player represented by this data, null if not present.
* @return The profile key data
* Returns the public key of the profile (since 1.19). Returns the public key of the remote chat session since 1.19.3
* @return The public key of the profile.
*/
@Nullable
public WrappedProfileKeyData getProfileKeyData() {
return this.profileKeyData;
return this.profileKeyData != null ? this.profileKeyData : (this.remoteChatSessionData != null ? this.remoteChatSessionData.getProfilePublicKey() : null);
}
/**
* Returns the remoteChatSessionData (since 1.19.3)
* @return The remote chat sesion data or null
*/
@Nullable
public WrappedRemoteChatSessionData getRemoteChatSessionData() {
return this.remoteChatSessionData;
}
/**
@@ -157,8 +226,7 @@ public class PlayerInfoData {
args.add(MinecraftReflection.getIChatBaseComponentClass());
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
// RemoteChatSession$a...
args.add(MinecraftReflection.getRemoteChatSessionClass().getClasses()[0]);
args.add(MinecraftReflection.getRemoteChatSessionDataClass());
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
args.add(MinecraftReflection.getProfilePublicKeyDataClass());
}
@@ -175,26 +243,28 @@ public class PlayerInfoData {
Object gameMode = EnumWrappers.getGameModeConverter().getGeneric(specific.gameMode);
Object displayName = specific.displayName != null ? specific.displayName.handle : null;
Object profile = specific.profile != null ? specific.profile.handle : null;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return constructor.newInstance(
specific.profileId,
specific.profile.handle,
profile,
specific.listed,
specific.latency,
gameMode,
displayName,
null); // TODO: do we want to support this?
specific.remoteChatSessionData != null ? BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(specific.remoteChatSessionData) : null
);
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
return constructor.newInstance(
specific.profile.handle,
profile,
specific.latency,
gameMode,
displayName,
specific.profileKeyData == null ? null : specific.profileKeyData.handle);
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
return constructor.newInstance(specific.profile.handle, specific.latency, gameMode, displayName);
return constructor.newInstance(profile, specific.latency, gameMode, displayName);
} else {
return constructor.newInstance(null, specific.profile.handle, specific.latency, gameMode, displayName);
return constructor.newInstance(null, profile, specific.latency, gameMode, displayName);
}
} catch (Exception e) {
throw new RuntimeException("Failed to construct PlayerInfoData.", e);
@@ -222,8 +292,18 @@ public class PlayerInfoData {
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
WrappedChatComponent displayName = displayNames.read(0);
if(MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
latency,
modifier.<Boolean>withType(boolean.class).read(0),
gameMode,
gameProfile,
displayName,
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
);
}
WrappedProfileKeyData key = null;
if (MinecraftVersion.WILD_UPDATE.atOrAbove() && !MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
StructureModifier<WrappedProfileKeyData> keyData = modifier.withType(
MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter());
key = keyData.read(0);
@@ -253,20 +333,34 @@ public class PlayerInfoData {
// Only compare objects of similar type
if (obj instanceof PlayerInfoData) {
PlayerInfoData other = (PlayerInfoData) obj;
return profile.equals(other.profile) && latency == other.latency && gameMode == other.gameMode
&& Objects.equals(displayName, other.displayName);
return Objects.equals(profile, other.profile)
&& Objects.equals(profileId, other.profileId)
&& latency == other.latency
&& gameMode == other.gameMode
&& Objects.equals(displayName, other.displayName)
&& listed == other.listed
&& Objects.equals(remoteChatSessionData, other.remoteChatSessionData)
&& Objects.equals(profileKeyData, other.profileKeyData);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(latency, gameMode, profile, displayName);
return Objects.hash(latency, gameMode, profile, displayName, profileKeyData, remoteChatSessionData, listed);
}
@Override
public String toString() {
if(MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return String.format("PlayerInfoData[latency=%s, listed=%b, gameMode=%s, profile=%s, displayName=%s, remoteChatSession=%s]",
latency, listed, gameMode, profile, displayName, remoteChatSessionData);
}
if(MinecraftVersion.WILD_UPDATE.atOrAbove()) {
return String.format("PlayerInfoData[latency=%s, listed=%b, gameMode=%s, profile=%s, displayName=%s, profilePublicKey=%s]",
latency, listed, gameMode, profile, displayName, profileKeyData);
}
return String.format("PlayerInfoData[latency=%s, gameMode=%s, profile=%s, displayName=%s]",
latency, gameMode, profile, displayName);
latency, gameMode, profile, displayName);
}
}
@@ -0,0 +1,48 @@
package com.comphenix.protocol.wrappers;
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;
/**
* @author Lukas Alt
* @since 24.04.2023
*/
public class WrappedMessageSignature extends AbstractWrapper {
private final static Class<?> HANDLE_TYPE = MinecraftReflection.getMessageSignatureClass();
private static ConstructorAccessor CONSTRUCTOR;
private StructureModifier<Object> modifier;
/**
* Construct a new NMS wrapper.
*
* @param handle - the NMS handle
*/
public WrappedMessageSignature(Object handle) {
super(HANDLE_TYPE);
this.setHandle(handle);
}
public WrappedMessageSignature(byte[] bytes) {
super(HANDLE_TYPE);
if(CONSTRUCTOR == null) {
CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, byte[].class);
}
this.setHandle(CONSTRUCTOR.invoke(bytes));
}
@Override
protected void setHandle(Object handle) {
super.setHandle(handle);
this.modifier = new StructureModifier<>(HANDLE_TYPE).withTarget(handle);
}
public byte[] getBytes() {
return modifier.<byte[]>withType(byte[].class).read(0);
}
public void setBytes(byte[] bytes) {
modifier.<byte[]>withType(byte[].class).write(0, bytes);
}
}
@@ -0,0 +1,185 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
* <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.wrappers;
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.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Represents a wrapper around registrable objects.
*
* @author portlek
*/
public final class WrappedRegistrable extends AbstractWrapper implements ClonableWrapper {
@NotNull
private final Factory factory;
private WrappedRegistrable(
@NotNull final Factory factory,
@NotNull final Object handle
) {
super(factory.registrableClass);
this.factory = factory;
setHandle(handle);
}
@NotNull
public static WrappedRegistrable fromHandle(
@NotNull final Factory factory,
@NotNull final Object handle
) {
return new WrappedRegistrable(factory, handle);
}
@NotNull
public static WrappedRegistrable fromHandle(
@NotNull final Class<?> registrableClass,
@NotNull final Object handle
) {
return fromHandle(Factory.getOrCreate(registrableClass), handle);
}
@NotNull
public static WrappedRegistrable fromClassAndKey(
@NotNull final Class<?> registrableClass,
@NotNull final MinecraftKey key
) {
final Factory factory = Factory.getOrCreate(registrableClass);
return fromHandle(factory, factory.getHandle(key));
}
@NotNull
public static WrappedRegistrable fromClassAndKey(
@NotNull final Class<?> registrableClass,
@NotNull final String key
) {
return fromClassAndKey(registrableClass, new MinecraftKey(key));
}
@NotNull
public static WrappedRegistrable blockEntityType(
@NotNull final MinecraftKey key
) {
return fromClassAndKey(MinecraftReflection.getBlockEntityTypeClass(), key);
}
@NotNull
public static WrappedRegistrable blockEntityType(
@NotNull final String key
) {
return blockEntityType(new MinecraftKey(key));
}
/**
* Gets this registrable object's Minecraft key
*
* @return The Minecraft key
*/
public MinecraftKey getKey() {
return factory.getKey(handle);
}
/**
* Sets this registrable object's Minecraft key
*
* @param key Minecraft key
*/
public void setKey(MinecraftKey key) {
setHandle(factory.getHandle(key));
}
public WrappedRegistrable deepClone() {
return fromHandle(factory, handle);
}
@Override
public String toString() {
return "WrappedRegistrable[handle=" + handle + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getKey().hashCode();
return result;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof WrappedRegistrable)) {
return false;
}
final WrappedRegistrable that = (WrappedRegistrable) o;
return handle.equals(that.handle) || (getKey() == that.getKey());
}
private static final class Factory {
private static final Map<Class<?>, Factory> CACHE = new ConcurrentHashMap<>();
@NotNull
private final Class<?> registrableClass;
@NotNull
private final WrappedRegistry registry;
@NotNull
private final FieldAccessor fieldAccessor;
private Factory(@NotNull final Class<?> registrableClass) {
this.registrableClass = registrableClass;
this.registry = WrappedRegistry.getRegistry(registrableClass);
this.fieldAccessor = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(registrableClass, true)
.getField(
FuzzyFieldContract.newBuilder()
.typeExact(registrableClass)
.build()
)
);
}
@NotNull
public static Factory getOrCreate(
@NotNull final Class<?> registrableClass
) {
return CACHE.computeIfAbsent(registrableClass, Factory::new);
}
@NotNull
public MinecraftKey getKey(@NotNull final Object handle) {
return registry.getKey(fieldAccessor.get(handle));
}
@NotNull
public Object getHandle(MinecraftKey key) {
return registry.get(key);
}
}
}
@@ -0,0 +1,96 @@
package com.comphenix.protocol.wrappers;
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 java.util.UUID;
/**
* A wrapper around the remote chat session.
*
* @since 1.19.3
*/
public class WrappedRemoteChatSessionData extends AbstractWrapper {
private final static Class<?> HANDLE_TYPE = MinecraftReflection.getRemoteChatSessionDataClass();
private static ConstructorAccessor CONSTRUCTOR;
private StructureModifier<Object> modifier;
/**
* Constructs a new profile public key wrapper directly from a nms RemoteChatSession.Data/RemoteChatSession.a object.
*
* @param handle the handle to create the wrapper from.
*/
public WrappedRemoteChatSessionData(Object handle) {
super(HANDLE_TYPE);
this.setHandle(handle);
}
public WrappedRemoteChatSessionData(UUID sessionId, WrappedProfilePublicKey.WrappedProfileKeyData profilePublicKey) {
super(HANDLE_TYPE);
if (CONSTRUCTOR == null) {
CONSTRUCTOR = Accessors.getConstructorAccessor(
this.getHandleType(),
UUID.class, MinecraftReflection.getProfilePublicKeyDataClass());
}
this.setHandle(CONSTRUCTOR.invoke(sessionId, profilePublicKey.getHandle()));
}
/**
* Retrieves the id of the current session
* @return session id
*/
public UUID getSessionID() {
return this.modifier.<UUID>withType(UUID.class).read(0);
}
/**
* Set the id for this session
* @param sessionId new session id
*/
public void setSessionID(UUID sessionId) {
this.modifier.<UUID>withType(UUID.class).write(0, sessionId);
}
/**
* Retrieves the ProfileKeyData
* @return the public key data for this session
*/
public WrappedProfilePublicKey.WrappedProfileKeyData getProfilePublicKey() {
return this.modifier.withType(MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter()).read(0);
}
/**
* Set the profile key data for this session
* @param data ProfileKeyData
*/
public void setProfilePublicKey(WrappedProfilePublicKey.WrappedProfileKeyData data) {
this.modifier.withType(MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter()).write(0, data);
}
@Override
protected void setHandle(Object handle) {
super.setHandle(handle);
this.modifier = new StructureModifier<>(HANDLE_TYPE).withTarget(handle);
}
@Override
public boolean equals(Object obj) {
if(obj instanceof WrappedRemoteChatSessionData) {
return handle.equals(((WrappedRemoteChatSessionData) obj).getHandle());
}
return false;
}
@Override
public String toString() {
return handle.toString();
}
@Override
public int hashCode() {
return handle.hashCode();
}
}
@@ -1,11 +1,8 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.EquivalentConverter;
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.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.ping.LegacyServerPing;
@@ -15,7 +12,6 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
@@ -103,8 +99,10 @@ public class WrappedServerPing implements ClonableWrapper {
* @return The wrapped server ping.
*/
public static WrappedServerPing fromJson(String json) {
// return fromHandle(GSON_FROM_JSON.invoke(PING_GSON.get(null), json, SERVER_PING));
return null;
if(MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove()) {
return new WrappedServerPing(ServerPingRecord.fromJson(json).getHandle());
}
return new WrappedServerPing(LegacyServerPing.fromJson(json));
}
/**
@@ -350,8 +348,7 @@ public class WrappedServerPing implements ClonableWrapper {
* @return The JSON representation.
*/
public String toJson() {
return null;
// return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), getHandle());
return impl.getJson();
}
@Override
@@ -546,4 +543,17 @@ public class WrappedServerPing implements ClonableWrapper {
return encoded;
}
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof WrappedServerPing)) {
return false;
}
return getHandle().equals(((WrappedServerPing) obj).getHandle());
}
@Override
public int hashCode() {
return getHandle().hashCode();
}
}
@@ -0,0 +1,31 @@
package com.comphenix.protocol.wrappers.codecs;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
public class WrappedCodec extends AbstractWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getCodecClass();
private static final Class<?> ENCODER_CLASS = MinecraftReflection.getLibraryClass("com.mojang.serialization.Encoder");
private static final Class<?> DECODER_CLASS = MinecraftReflection.getLibraryClass("com.mojang.serialization.Decoder");
private static final MethodAccessor ENCODE_START_ACCESSOR = Accessors.getMethodAccessor(ENCODER_CLASS, "encodeStart", MinecraftReflection.getDynamicOpsClass(), Object.class);
private static final MethodAccessor PARSE_ACCESSOR = Accessors.getMethodAccessor(DECODER_CLASS, "parse", MinecraftReflection.getDynamicOpsClass(), Object.class);
private WrappedCodec(Object handle) {
super(HANDLE_TYPE);
this.setHandle(handle);
}
public static WrappedCodec fromHandle(Object handle) {
return new WrappedCodec(handle);
}
public WrappedDataResult encode(Object object, WrappedDynamicOps ops) {
return WrappedDataResult.fromHandle(ENCODE_START_ACCESSOR.invoke(handle, ops.getHandle(), object));
}
public WrappedDataResult parse(Object value, WrappedDynamicOps ops) {
return WrappedDataResult.fromHandle(PARSE_ACCESSOR.invoke(handle, ops.getHandle(), value));
}
}
@@ -0,0 +1,50 @@
package com.comphenix.protocol.wrappers.codecs;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;
public class WrappedDataResult extends AbstractWrapper {
private final static Class<?> HANDLE_TYPE = MinecraftReflection.getLibraryClass("com.mojang.serialization.DataResult");
private final static Class<?> PARTIAL_DATA_RESULT_CLASS = MinecraftReflection.getLibraryClass("com.mojang.serialization.DataResult$PartialResult");
private final static MethodAccessor ERROR_ACCESSOR = Accessors.getMethodAccessor(HANDLE_TYPE, "error");
private final static MethodAccessor RESULT_ACCESSOR = Accessors.getMethodAccessor(HANDLE_TYPE, "result");
private final static MethodAccessor PARTIAL_RESULT_MESSAGE_ACCESSOR = Accessors.getMethodAccessor(PARTIAL_DATA_RESULT_CLASS, "message");
/**
* Construct a new NMS wrapper.
**/
public WrappedDataResult(Object handle) {
super(HANDLE_TYPE);
this.setHandle(handle);
}
public static WrappedDataResult fromHandle(Object handle) {
return new WrappedDataResult(handle);
}
public Optional<Object> getResult() {
return (Optional) RESULT_ACCESSOR.invoke(this.handle);
}
public Optional<String> getErrorMessage() {
return (Optional) ERROR_ACCESSOR.invoke(this.handle);
}
public Object getOrThrow(Function<String, Throwable> errorHandler) {
Optional<String> err = getErrorMessage();
if(err.isPresent()) {
return errorHandler.apply((String) PARTIAL_RESULT_MESSAGE_ACCESSOR.invoke(err.get()));
}
Optional<Object> result = getResult();
if(result.isPresent()) {
return result.get();
}
throw new NoSuchElementException();
}
}
@@ -0,0 +1,26 @@
package com.comphenix.protocol.wrappers.codecs;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
public class WrappedDynamicOps extends AbstractWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDynamicOpsClass();
public static final FieldAccessor NBT_ACCESSOR = Accessors.getFieldAccessor(MinecraftReflection.getNbtOpsClass(), MinecraftReflection.getNbtOpsClass(), false);
public static final FieldAccessor[] JSON_ACCESSORS = Accessors.getFieldAccessorArray(MinecraftReflection.getJsonOpsClass(), MinecraftReflection.getJsonOpsClass(), false);
private WrappedDynamicOps(Object handle) {
super(HANDLE_TYPE);
this.setHandle(handle);
}
public static WrappedDynamicOps fromHandle(Object handle) {
return new WrappedDynamicOps(handle);
}
public static WrappedDynamicOps json(boolean compressed) {
return fromHandle(JSON_ACCESSORS[compressed ? 1 : 0].get(null));
}
public static WrappedDynamicOps nbt() {
return fromHandle(NBT_ACCESSOR);
}
}
@@ -286,6 +286,11 @@ public final class LegacyServerPing extends AbstractWrapper implements ServerPin
}
}
@Override
public String getJson() {
return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), getHandle());
}
/**
* Determine if the player count and maximum is visible.
* <p>
@@ -1,10 +1,7 @@
package com.comphenix.protocol.wrappers.ping;
import java.util.Optional;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.collect.ImmutableList;
public interface ServerPingImpl extends Cloneable {
@@ -38,6 +35,7 @@ public interface ServerPingImpl extends Cloneable {
boolean arePlayersVisible();
void setPlayersVisible(boolean visible);
String getJson();
Object getHandle();
}
@@ -1,31 +1,39 @@
package com.comphenix.protocol.wrappers.ping;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Semaphore;
import com.comphenix.protocol.events.InternalStructure;
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.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.codecs.WrappedCodec;
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit;
import java.nio.charset.StandardCharsets;
import java.util.*;
public final class ServerPingRecord implements ServerPingImpl {
private static Class<?> SERVER_PING;
private static Class<?> PLAYER_SAMPLE_CLASS;
private static Class<?> SERVER_DATA_CLASS;
private static Class<?> GSON_CLASS;
private static MethodAccessor GSON_TO_JSON;
private static MethodAccessor GSON_FROM_JSON;
private static FieldAccessor DATA_SERIALIZER_GSON;
private static Class<?> JSON_ELEMENT_CLASS;
private static WrappedChatComponent DEFAULT_DESCRIPTION;
private static ConstructorAccessor PING_CTOR;
private static WrappedCodec CODEC;
private static EquivalentConverter<List<WrappedGameProfile>> PROFILE_LIST_CONVERTER;
@@ -57,6 +65,13 @@ public final class ServerPingRecord implements ServerPingImpl {
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
DEFAULT_DESCRIPTION = WrappedChatComponent.fromLegacyText("A Minecraft Server");
GSON_CLASS = MinecraftReflection.getMinecraftGsonClass();
GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class);
GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class);
DATA_SERIALIZER_GSON = Accessors.getFieldAccessor(MinecraftReflection.getPacketDataSerializerClass(), GSON_CLASS, true);
JSON_ELEMENT_CLASS = MinecraftReflection.getLibraryClass("com.google.gson.JsonElement");
CODEC = WrappedCodec.fromHandle(Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getCodecClass(), false).get(null));
} catch (Exception ex) {
throw new RuntimeException("Failed to initialize Server Ping", ex);
} finally {
@@ -133,15 +148,26 @@ public final class ServerPingRecord implements ServerPingImpl {
int max = Bukkit.getMaxPlayers();
int online = Bukkit.getOnlinePlayers().size();
return new PlayerSample(max, online, null);
return new PlayerSample(max, online, new ArrayList<>());
}
private static Favicon defaultFavicon() {
return new Favicon();
}
public static ServerPingRecord fromJson(String json) {
Object jsonElement = GSON_FROM_JSON.invoke(DATA_SERIALIZER_GSON.get(null), json, JSON_ELEMENT_CLASS);
Object decoded = CODEC.parse(jsonElement, WrappedDynamicOps.json(false)).getOrThrow(e -> new IllegalStateException("Failed to decode: " + e));
return new ServerPingRecord(decoded);
}
public ServerPingRecord(Object handle) {
initialize();
if(handle.getClass() != SERVER_PING) {
throw new IllegalArgumentException("Expected handle of type " + SERVER_PING.getName() + " but got " + handle.getClass().getName());
}
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
InternalStructure structure = new InternalStructure(handle, modifier);
@@ -286,15 +312,35 @@ public final class ServerPingRecord implements ServerPingImpl {
this.playersVisible = visible;
}
@Override
public String getJson() {
Object encoded = CODEC.encode(getHandle(), WrappedDynamicOps.json(false)).getOrThrow(e -> new IllegalStateException("Failed to encode: " + e));
return (String) GSON_TO_JSON.invoke(DATA_SERIALIZER_GSON.get(null), encoded);
}
@Override
public Object getHandle() {
WrappedChatComponent wrappedDescription = description != null ? description : DEFAULT_DESCRIPTION;
Object descHandle = wrappedDescription.getHandle();
Optional<Object> playersHandle = Optional.ofNullable(playerSample != null ? SAMPLE_WRAPPER.unwrap(playerSample) : null);
Optional<Object> playersHandle = Optional.ofNullable(SAMPLE_WRAPPER.unwrap(playerSample != null ? playerSample : new ArrayList<>())); // sample has to be non-null in handle
Optional<Object> versionHandle = Optional.ofNullable(serverData != null ? DATA_WRAPPER.unwrap(serverData) : null);
Optional<Object> favHandle = Optional.ofNullable(favicon != null ? FAVICON_WRAPPER.unwrap(favicon) : null);
return PING_CTOR.invoke(descHandle, playersHandle, versionHandle, favHandle, enforceSafeChat);
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof ServerPingRecord)) {
return false;
}
ServerPingRecord other = (ServerPingRecord) obj;
return Objects.equals(description, other.description)
&& Objects.equals(playerSample, other.playerSample)
&& Objects.equals(serverData, other.serverData)
&& ((favicon == null && other.favicon.iconBytes == null)
|| ((favicon != null) == (other.favicon != null) && Arrays.equals(favicon.iconBytes, other.favicon.iconBytes)));
}
}
+12 -12
View File
@@ -1,30 +1,30 @@
global:
# Settings for the automatic version updater
auto updater:
notify: true
notify: false
download: false
# Number of seconds to wait until a new update is downloaded
delay: 43200 # 12 hours
metrics: true
metrics: false
# Prints certain warnings to players with the protocol.info permission
chat warnings: true
# Automatically compile structure modifiers
# Automatically compile structure modifiers
background compiler: true
# Disable version checking for the given Minecraft version. Backup your world first!
ignore version check:
ignore version check: false
# Whether or not to enable the filter command
debug: false
# Whether or not to print a stack trace for every warning
detailed error: false
# The engine used by the filter command
script engine: JavaScript
suppressed reports:
suppressed reports: all
@@ -22,6 +22,7 @@ import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftReflectionTestUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -31,6 +32,9 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.minecraft.network.EnumProtocol;
import net.minecraft.network.protocol.EnumProtocolDirection;
import net.minecraft.network.protocol.login.PacketLoginInStart;
@@ -357,4 +361,17 @@ public class PacketTypeTest {
}
assertFalse(fail, "Packet type(s) failed to instantiate");
}
@Test
public void testPacketBundleWriting() {
PacketContainer bundlePacket = new PacketContainer(PacketType.Play.Server.BUNDLE);
assertEquals(MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet Bundle class is not present")), bundlePacket.getHandle().getClass());
List<PacketContainer> bundle = new ArrayList<>();
PacketContainer chatMessage = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT);
chatMessage.getStrings().write(0, WrappedChatComponent.fromText("Test").getJson());
chatMessage.getBooleans().write(0, false);
bundle.add(chatMessage);
bundlePacket.getPacketBundles().write(0, bundle);
}
}
@@ -0,0 +1,65 @@
package com.comphenix.protocol;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class ProtocolConfigTest {
@TempDir
Path tempDir;
@Test
void shouldTreatBooleanIgnoreVersionCheckAsDisabled() throws IOException {
ProtocolConfig config = createConfigWithGlobalValues(false, Collections.emptyList());
assertEquals("", config.getIgnoreVersionCheck());
}
@Test
void shouldAcceptScalarSuppressedReportsValue() throws IOException {
ProtocolConfig config = createConfigWithGlobalValues("", "all");
assertEquals(Collections.singletonList("all"), config.getSuppressedReports());
}
@Test
void shouldPreserveSuppressedReportsList() throws IOException {
ProtocolConfig config = createConfigWithGlobalValues("", Arrays.asList("REPORT_A", "REPORT_B"));
assertEquals(Arrays.asList("REPORT_A", "REPORT_B"), config.getSuppressedReports());
}
private ProtocolConfig createConfigWithGlobalValues(Object ignoreVersionCheck, Object suppressedReports) throws IOException {
YamlConfiguration configuration = new YamlConfiguration();
configuration.createSection("global");
configuration.createSection("global.auto updater");
configuration.set("global.ignore version check", ignoreVersionCheck);
configuration.set("global.suppressed reports", suppressedReports);
File dataFolder = tempDir.toFile();
File configFile = new File(dataFolder, "config.yml");
if (!configFile.createNewFile()) {
throw new IOException("Failed to create " + configFile);
}
Plugin plugin = mock(Plugin.class);
when(plugin.getConfig()).thenReturn(configuration);
when(plugin.getDataFolder()).thenReturn(dataFolder);
when(plugin.getLogger()).thenReturn(Logger.getLogger("ProtocolConfigTest"));
return new ProtocolConfig(plugin);
}
}
@@ -40,26 +40,13 @@ 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.EnumWrappers;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory;
import com.comphenix.protocol.wrappers.MovingObjectPositionBlock;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataValue;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Lists;
@@ -768,7 +755,7 @@ public class PacketContainerTest {
NativeGameMode.CREATIVE,
new WrappedGameProfile(new UUID(0, 0), "system"),
null,
null);
(WrappedRemoteChatSessionData) null);
updatePlayerInfoActions.getPlayerInfoDataLists().write(1, Collections.singletonList(data));
Set<EnumWrappers.PlayerInfoAction> readActions = updatePlayerInfoActions.getPlayerInfoActions().read(0);
@@ -787,21 +774,17 @@ public class PacketContainerTest {
Assertions.assertEquals(NativeGameMode.CREATIVE, firstData.getGameMode());
}
// TODO: fix this this at some point
/*
@Test
public void testSignedChatMessage() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Client.CHAT);
byte[] signature = new byte[512];
long salt = 124L;
WrappedSaltedSignature wrappedSignature = new WrappedSaltedSignature(salt, signature);
chatPacket.getSignatures().write(0, wrappedSignature);
byte[] signature = new byte[256];
WrappedMessageSignature wrappedSignature = new WrappedMessageSignature(signature);
chatPacket.getMessageSignatures().write(0, wrappedSignature);
WrappedSaltedSignature read = chatPacket.getSignatures().read(0);
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}*/
WrappedMessageSignature read = chatPacket.getMessageSignatures().read(0);
assertArrayEquals(signature, read.getBytes());
}
private void assertPacketsEqualAndSerializable(PacketContainer constructed, PacketContainer cloned) {
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
@@ -871,7 +854,6 @@ public class PacketContainerTest {
}
// 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();
@@ -886,8 +868,8 @@ public class PacketContainerTest {
serializedCloned.getLongs().write(0, 0L);
}
this.assertPacketsEqualAndSerializable(constructed, serializedCloned);
} catch (Exception ex) {
Assertions.fail("Unable to clone " + type, ex);
} catch (Throwable t) {
Assertions.fail("Unable to clone " + type, t);
}
}
}
@@ -7,7 +7,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import com.comphenix.protocol.reflect.accessors.Accessors;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.time.Instant;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
import com.comphenix.protocol.wrappers.WrappedRemoteChatSessionData;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
@@ -49,4 +57,17 @@ public class TestUtils {
public static void setFinalField(Object obj, Field field, Object newValue) {
Accessors.getFieldAccessor(field).set(obj, newValue);
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
return keyPairGenerator.generateKeyPair();
}
public static WrappedRemoteChatSessionData creteDummyRemoteChatSessionData() throws Exception {
byte[] signature = new byte[256];
new Random().nextBytes(signature);
return new WrappedRemoteChatSessionData(UUID.randomUUID(), new WrappedProfilePublicKey.WrappedProfileKeyData(Instant.now(), TestUtils.generateKeyPair().getPublic(), signature));
}
}
@@ -1,15 +1,11 @@
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.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.utility.TestUtils;
import com.comphenix.protocol.wrappers.Either.Left;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -20,37 +16,41 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
public class BukkitConvertersTest {
@BeforeAll
public static void beforeClass() {
BukkitInitialization.initializeAll();
}
@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);
@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<ItemStack> converter = BukkitConverters.getItemStackConverter();
Object nmsStack = converter.getGeneric(item);
ItemStack back = converter.getSpecific(nmsStack);
EquivalentConverter<ItemStack> 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()));
}
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<String, String> test = new Left<>("bla");
EquivalentConverter<Either<String, String>> converter = BukkitConverters.getEitherConverter(
Converters.passthrough(String.class), Converters.passthrough(String.class)
Converters.passthrough(String.class), Converters.passthrough(String.class)
);
com.mojang.datafixers.util.Either<String, String> nmsEither = (com.mojang.datafixers.util.Either<String, String>) converter.getGeneric(test);
@@ -72,4 +72,23 @@ public class BukkitConvertersTest {
assertTrue(EqualsBuilder.reflectionEquals(container, specific)); // PacketContainer does not properly implement equals(.)
}
}
@Test
void getWrappedMessageSignatureConverter() {
byte[] data = new byte[256];
new Random().nextBytes(data);
WrappedMessageSignature messageSignature = new WrappedMessageSignature(data);
assertArrayEquals(data, BukkitConverters.getWrappedMessageSignatureConverter().getSpecific(BukkitConverters.getWrappedMessageSignatureConverter().getGeneric(messageSignature)).getBytes());
}
@Test
public void testRemoteChatSessionDataConverter() throws Exception {
WrappedRemoteChatSessionData wrappedRemoteChatSessionData = TestUtils.creteDummyRemoteChatSessionData();
Object generic = BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(wrappedRemoteChatSessionData);
WrappedRemoteChatSessionData specific = BukkitConverters.getWrappedRemoteChatSessionDataConverter().getSpecific(generic);
assertEquals(wrappedRemoteChatSessionData, specific);
}
}
@@ -17,6 +17,7 @@ package com.comphenix.protocol.wrappers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.utility.TestUtils;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import java.util.UUID;
import org.junit.jupiter.api.BeforeAll;
@@ -33,14 +34,19 @@ public class PlayerInfoDataTest {
}
@Test
public void test() {
public void test() throws Exception {
WrappedGameProfile profile = new WrappedGameProfile(UUID.randomUUID(), "Name");
WrappedChatComponent displayName = WrappedChatComponent.fromText("Name's Name");
PlayerInfoData data = new PlayerInfoData(profile, 42, NativeGameMode.CREATIVE, displayName);
testWriteBack(new PlayerInfoData(profile, 42, NativeGameMode.CREATIVE, displayName));
testWriteBack(new PlayerInfoData(profile.getUUID(), 42, false, NativeGameMode.CREATIVE, profile, displayName, TestUtils.creteDummyRemoteChatSessionData()));
testWriteBack(new PlayerInfoData(profile.getUUID(), 42, false, NativeGameMode.CREATIVE, null, null, TestUtils.creteDummyRemoteChatSessionData()));
testWriteBack(new PlayerInfoData(profile.getUUID(), 42, true, NativeGameMode.CREATIVE, null, displayName));
}
private static void testWriteBack(PlayerInfoData data) {
Object generic = PlayerInfoData.getConverter().getGeneric(data);
PlayerInfoData back = PlayerInfoData.getConverter().getSpecific(generic);
assertEquals(data, back);
}
}
@@ -9,6 +9,7 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
import com.google.common.io.Resources;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
@@ -49,6 +50,18 @@ public class WrappedServerPingTest {
WrappedServerPing roundTrip = packet.getServerPings().read(0);
String asJson = serverPing.toJson();
WrappedServerPing deserialized = WrappedServerPing.fromJson(asJson);
deserialized.setMotD(serverPing.getMotD());
// Deserializing to JSON and parsing the JSON again can lead to a different object as the Mojang Datafixer optimizes server icons or reorders components in the server description
assertEquals(serverPing.getVersionName(), deserialized.getVersionName(), "Failed to serialize as JSON and deserialize afterwards (version name mismatch)");
assertEquals(serverPing.getVersionProtocol(), deserialized.getVersionProtocol(), "Failed to serialize as JSON and deserialize afterwards (version protocol mismatch)");
assertEquals(serverPing.getPlayersOnline(), deserialized.getPlayersOnline(), "Failed to serialize as JSON and deserialize afterwards (players online mismatch)");
assertEquals(serverPing.getPlayersMaximum(), deserialized.getPlayersMaximum(), "Failed to serialize as JSON and deserialize afterwards (players maximum mismatch)");
assertEquals(serverPing.getPlayers(), deserialized.getPlayers(), "Failed to serialize as JSON and deserialize afterwards (player sample mismatch)");
assertEquals(PlainTextComponentSerializer.plainText().serialize(AdventureComponentConverter.fromWrapper(serverPing.getMotD())), PlainTextComponentSerializer.plainText().serialize(AdventureComponentConverter.fromWrapper(deserialized.getMotD()))); // Check if plain text is equivalent
assertEquals(5, roundTrip.getPlayersOnline());
assertEquals(10, roundTrip.getPlayersMaximum());
assertEquals("Minecraft 123", roundTrip.getVersionName());