Compare commits

...
51 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
Nassim Jahnke ac6f911f15 Fix bundle packet processing errors (#2339) 2023-04-15 18:27:52 -05:00
Dan Mulloy c69bcc36f5 Migrate to Gradle (#2319) 2023-04-15 16:09:15 -05:00
Lukas Alt 0ee93acd65 Fixed null packet handles in Bundle (#2328) 2023-04-11 08:13:03 -05:00
LOOHP d83dd9ab8a Get around expensive validate calls (#2318) 2023-04-09 16:40:57 -05:00
Pasqual Koschmieder ab32f938d7 resolve packet instance construction concurrency issue (#2312) 2023-04-07 20:57:49 -05:00
Dan Mulloy c7753a9d5b Better server ping concurrency
Addresses #2289
2023-04-03 22:55:32 -04:00
libraryaddict b51812655a Correct the version from 1.19.4 to 1.19.3 (#2299) 2023-04-03 18:27:38 -05:00
libraryaddict a910edc16b Fix WrappedRegistry for versions older than 1.19.4 (#2298) 2023-04-03 11:27:12 -05:00
Dan Mulloy fb2075b774 Fix exception for null sounds
Fixes #2276
2023-03-30 17:07:34 -05:00
Dan Mulloy 4b78bf6a34 Update dependencies to support Java 20
Fixes #2274
Fixes #2270
Fixes #2251
2023-03-28 12:40:32 -05:00
libraryaddict edce5b6d0f Fix /packet arguments, fixes #1847 (#2271)
Pretty simple, it's deleting the argument twice. Which means you have to use `/packet add add <remaining>`
2023-03-27 22:03:27 -05:00
Dan Mulloy 7217b11ba7 Fix reading class aliases
Fixes #2263
2023-03-26 14:09:45 -05:00
Dan Mulloy 18c2b389a4 Improve unit tests for server ping 2023-03-26 13:58:46 -05:00
Dan Mulloy 1912a9c871 Fix setting ping player sample in 1.19.4 2023-03-26 13:31:39 -05:00
Dan Mulloy 0c6fa46871 Optimize class lookups 2023-03-25 23:16:04 -05:00
Dan Mulloy df3b68df4c Some cleanup
Fixes super critical issue #2255
2023-03-25 21:45:29 -05:00
Lukas Alt aebefded86 Packet filtering for bundled packets in 1.19.4 (#2258)
Since Minecraft 1.19.4, the protocol supports bundling consecutive packets to ensure the client processes them in one tick. However, Packet Events are not called for the individual packets in such a bundle in the current dev build of ProtocolLib. For example, no packet events are currently sent for the ENTITY_METADATA packet when an entity is first spawned as the packet is bundled with the ENTITY_SPAWN packet. However, if the entity metadata is changed later on, the event will be called.
This PR proposes to fix this by unpacking the bundled packets and invoking the packet filtering for each packet.

I also want to briefly explain how the bundling works. A bundle starts with a PACKET_DELIMITER (0x00, net.minecraft.network.protocol.BundleDelimiterPacket) packet followed by all packets that should be bundled and finished with another PACKET_DELIMITER (0x00). Within the Netty pipeline, this sequence is transformed into one synthesized packet found in net.minecraft.network.protocol.game.ClientboundBundlePacket, which is essentially just a list of packets. At the stage at which ProtocolLib injects into the clientbound netty pipeline, this packet has not been unpacked yet. Thus, we need to handle the ClientboundBundlePacket, which unfortunately is not registered in ProtocolLib. The fact that two different classes map to the same packet currently requires a dirty remapping in the packet structure modifier.
2023-03-25 21:08:31 -05:00
Dan Mulloy 64e1e7de24 Fix some issues with server ping in 1.19.4 2023-03-23 20:23:10 -05:00
Dan Mulloy 05fa147b48 WIP: 1.19.4 update 2023-03-23 13:43:22 -05:00
libraryaddict bba534d694 Fix listeners removing each other (#2236) 2023-03-16 16:52:10 -05:00
Aseeefandaseef 4f0fe72add Use ConcurrentHashMap to improve performance (#2226)
* modified hashset collections for small performance gains

* reverted load factor modification since no noticeable performance gain was seen

---------

Co-authored-by: aseef <contact@aseef.dev>
2023-03-12 13:05:19 -05:00
armagidon-exception 365bb66d2a Fixed backwards compatibility with WrappedDataValue (#2172) 2023-02-16 02:31:26 +00:00
Richy 0a41cf9353 Add a soft-cache to getFields method (#2152)
* Added a soft-cache to getFields method

* Added cache purge on cache miss
2023-02-15 21:28:02 -05:00
Lennart Lösche d0b274249c Print exception in MethodHandleHelper (#2097)
print exception caused by xyz
2023-01-14 01:00:56 +00:00
Nassim Jahnke bdbbb7cb9c Fix SynchedEntityData$DataValue mapping (#2101)
Add SynchedEntityData$DataValue mapping
2023-01-13 19:56:51 -05:00
Dan Mulloy 531f28cbaf Fix sounds in 1.19.3
Fixes #2049
2023-01-10 16:36:01 -05:00
Mathéo Cimbaro 30b69d3ecf Added PacketContainer#getUUIDLists() (#2072)
The 1.19.3 PlayerListRemove is the first one using List<UUID>

Fixes #2048
2023-01-10 03:43:39 +00:00
Elioby 9d183e85d7 Force login disconnect packet to be run async (#2021) 2022-12-18 15:32:45 +00:00
Dan MulloyandPasqual Koschmieder eebb99fa37 1.19.3 update
Co-authored-by: Pasqual Koschmieder <pasqual.koschmieder@gmail.com>
2022-12-13 20:38:37 -05:00
93 changed files with 3966 additions and 2518 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
+5 -5
View File
@@ -15,14 +15,14 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
java-version: '19'
cache: 'gradle'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Run gradle build lifecycle
run: ./gradlew build shadowJar --no-daemon
- name: Upload plugin file
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: target/ProtocolLib.jar
path: build/libs/ProtocolLib.jar
+4 -4
View File
@@ -26,16 +26,16 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
java-version: '19'
cache: 'gradle'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'java'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Run gradle build lifecycle
run: ./gradlew build -x test --no-daemon
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+6 -6
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/)
@@ -20,8 +18,10 @@ Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/
### Compilation
ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run
`mvn package` in the root project folder.
ProtocolLib is built with [Gradle](https://gradle.org/). If you have it installed, just run
`./gradlew build` in the root project folder. Other gradle targets you may be interested in
include `clean`, `test`, and `shadowJar`. `shadowJar` will create a jar with all dependencies
(ByteBuddy) included.
### A new API
@@ -53,7 +53,7 @@ You can also add ProtocolLib as a Maven dependency:
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.7.0</version>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -67,7 +67,7 @@ repositories {
}
dependencies {
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.7.0";
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>
+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()
}
}
}
+2
View File
@@ -0,0 +1,2 @@
nexusUsername=""
nexusPassword=""
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# 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/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# 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
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
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.
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=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# 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" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@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 ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
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
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
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=
@rem Execute Gradle
"%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
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-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.0</junit.version>
<mockito.version>4.6.1</mockito.version>
<netty.version>4.1.77.Final</netty.version>
<spigot.version>1.19.2-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.3.0</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.10.1</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.2.2</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-M7</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.4.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.11.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.12.12</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
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
rootProject.name = "ProtocolLib"
@@ -446,7 +446,7 @@ class CommandPacket extends CommandBase {
{
final String text = arguments.remove().toLowerCase();
switch (arguments.remove().toLowerCase()) {
switch (text) {
case "add":
return SubCommand.ADD;
case "remove":
@@ -41,19 +41,19 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Handshake {
private static final Protocol PROTOCOL = Protocol.HANDSHAKING;
static final Protocol PROTOCOL = Protocol.HANDSHAKING;
/**
* Incoming packets.
* @author Kristian
*/
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
private static final Sender SENDER = Sender.CLIENT;
@ForceAsync
public static final PacketType SET_PROTOCOL = new PacketType(PROTOCOL, SENDER, 0x00, "SetProtocol", "C00Handshake");
private final static Client INSTANCE = new Client();
private static final Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(); }
@@ -71,8 +71,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
private final static Server INSTANCE = new Server();
private static final Sender SENDER = Sender.CLIENT;
private static final Server INSTANCE = new Server();
private Server() { super(); }
public static Server getInstance() {
@@ -93,123 +93,126 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Play {
private static final Protocol PROTOCOL = Protocol.PLAY;
static final Protocol PROTOCOL = Protocol.PLAY;
/**
* Outgoing packets.
* @author Kristian
*/
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.SERVER;
private static final Sender SENDER = Sender.SERVER;
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x00, "SpawnEntity", "SPacketSpawnObject");
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x01, "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
public static final PacketType NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 0x02, "NamedEntitySpawn", "SPacketSpawnPlayer");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x03, "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x04, "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x09, "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0A, "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0B, "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x0C, "ChatPreview");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0D, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0E, "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x0F, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x10, "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x11, "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x12, "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x13, "SetSlot", "SPacketSetSlot");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x14, "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x15, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x16, "CustomPayload", "SPacketCustomPayload");
public static final PacketType CUSTOM_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x17, "CustomSoundEffect", "SPacketCustomSound");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x18, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x19, "KickDisconnect", "SPacketDisconnect");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1A, "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1B, "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1C, "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x1D, "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x1E, "OpenWindowHorse");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x1F, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x20, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, "MapChunk", "SPacketChunkData", "LevelChunkWithLight");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x22, "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x23, "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x24, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x25, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x26, "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x27, "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x28, "Entity$PacketPlayOutRelEntityMove", "Entity$RelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x29, "Entity$PacketPlayOutRelEntityMoveLook", "Entity$RelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x2A, "Entity$PacketPlayOutEntityLook", "Entity$EntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2B, "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x2C, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x2D, "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x2E, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x2F, "Ping");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x30, "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x31, "Abilities", "SPacketPlayerAbilities");
public static final PacketType PLAYER_CHAT_HEADER = new PacketType(PROTOCOL, SENDER, 0x32, "PlayerChatHeader");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x33, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x34, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x35, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x36, "PlayerCombatKill");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x37, "PlayerInfo", "SPacketPlayerListItem");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x38, "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x39, "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x3A, "Recipes", "SPacketRecipeBook");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x3B, "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x3C, "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 0x3D, "ResourcePackSend", "SPacketResourcePackSend");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x3E, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x3F, "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x40, "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x41, "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x42, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x43, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x44, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x45, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x46, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x47, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x48, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x49, "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x4A, "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x4B, "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4C, "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x4D, "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SET_DISPLAY_CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x4E, "SetDisplayChatPreview");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x4F, "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x50, "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x51, "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x52, "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x53, "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x54, "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x55, "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x56, "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x57, "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x58, "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x59, "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5A, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5B, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x5C, "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5D, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x5E, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x5F, "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x60, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x61, "StopSound");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x62, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x63, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x64, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x65, "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x66, "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x67, "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x68, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x69, "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x6A, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x6B, "Tags");
public static final PacketType BUNDLE = new PacketType(PROTOCOL, SENDER, 0x00, "Delimiter", "BundleDelimiterPacket");
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x01, "SpawnEntity", "SPacketSpawnObject");
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x02, "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
public static final PacketType NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 0x03, "NamedEntitySpawn", "SPacketSpawnPlayer");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x04, "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x05, "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x07, "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x08, "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x09, "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x0A, "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0B, "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0C, "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunksBiomes", "ClientboundChunksBiomesPacket");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0E, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x10, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x11, "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x12, "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x13, "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x14, "SetSlot", "SPacketSetSlot");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x15, "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x16, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x17, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x18, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x19, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1A, "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1B, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1C, "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1D, "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1E, "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x1F, "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x20, "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x21, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x22, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x23, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x24, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x25, "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x26, "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x27, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x28, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x29, "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2A, "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2B, "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2C, "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x2D, "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x30, "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x31, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x32, "Ping");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x33, "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x34, "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x35, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x36, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x37, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x38, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x3B, "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x3C, "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x3D, "Recipes", "SPacketRecipeBook");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x3E, "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x3F, "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 0x40, "ResourcePackSend", "SPacketResourcePackSend");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x41, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x42, "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x43, "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x44, "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x45, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x46, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x47, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x48, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x49, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x4A, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4B, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x4C, "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x4D, "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x4E, "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4F, "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x50, "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x51, "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x52, "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x53, "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x54, "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x55, "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x56, "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x57, "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x58, "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x59, "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x5A, "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x5B, "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5C, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5D, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x5E, "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x60, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x61, "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x62, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x63, "StopSound");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x64, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x65, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x66, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x67, "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x68, "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x69, "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x6A, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x6B, "UpdateEnabledFeatures");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x6C, "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x6D, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x6E, "Tags");
// ---- Removed in 1.9
@@ -340,7 +343,31 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType BLOCK_BREAK = new PacketType(PROTOCOL, SENDER, 241, "BlockBreak");
private final static Server INSTANCE = new Server();
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x0C, "ChatPreview");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType PLAYER_CHAT_HEADER = new PacketType(PROTOCOL, SENDER, 0x32, "PlayerChatHeader");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType SET_DISPLAY_CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x4E, "SetDisplayChatPreview");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CUSTOM_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x16, "CustomSoundEffect", "SPacketCustomSound");
private static final Server INSTANCE = new Server();
// Prevent accidental construction
private Server() { super(); }
@@ -358,7 +385,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "TeleportAccept", "CPacketConfirmTeleport");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "TileNBTQuery");
@@ -366,7 +393,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x03, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, "ChatCommand");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x05, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x06, "ChatPreview");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x06, "ChatSessionUpdate");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x07, "ClientCommand", "CPacketClientStatus");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x08, "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x09, "TabComplete", "CPacketTabComplete");
@@ -380,9 +407,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x11, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x12, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x13, "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x14, "Flying$PacketPlayInPosition", "Flying$Position", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x15, "Flying$PacketPlayInPositionLook", "Flying$PositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x16, "Flying$PacketPlayInLook", "Flying$Look", "CPacketPlayer$Rotation");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x14, "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x15, "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x16, "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x17, "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x18, "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x19, "BoatMove", "CPacketSteerBoat");
@@ -392,7 +419,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x1D, "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x1E, "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x1F, "SteerVehicle", "CPacketInput");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x20, "Pong", "ServerboundPongPacket");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x20, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x21, "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x22, "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x23, "ItemName");
@@ -424,7 +451,13 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType FLYING = new PacketType(PROTOCOL, SENDER, 254, "Flying", "CPacketPlayer");
private final static Client INSTANCE = new Client();
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x06, "ChatPreview");
private static final Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(); }
@@ -447,14 +480,14 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Status {
private static final Protocol PROTOCOL = Protocol.STATUS;
static final Protocol PROTOCOL = Protocol.STATUS;
/**
* Outgoing packets.
* @author Kristian
*/
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.SERVER;
private static final Sender SENDER = Sender.SERVER;
@ForceAsync
public static final PacketType SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, "ServerInfo", "SPacketServerInfo");
@@ -468,7 +501,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@ForceAsync
public static final PacketType OUT_SERVER_INFO = SERVER_INFO.clone();
private final static Server INSTANCE = new Server();
private static final Server INSTANCE = new Server();
// Prevent accidental construction
private Server() { super(); }
@@ -486,13 +519,13 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Start", "CPacketServerQuery");
@ForceAsync
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x01, "Ping", "CPacketPing");
private final static Client INSTANCE = new Client();
private static final Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(); }
@@ -515,22 +548,23 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Login {
private static final Protocol PROTOCOL = Protocol.LOGIN;
static final Protocol PROTOCOL = Protocol.LOGIN;
/**
* Outgoing packets.
* @author Kristian
*/
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.SERVER;
private static final Sender SENDER = Sender.SERVER;
@ForceAsync
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, "Disconnect", "SPacketDisconnect");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "EncryptionBegin", "SPacketEncryptionRequest");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "Success", "SPacketLoginSuccess");
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, "SetCompression", "SPacketEnableCompression");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, "CustomPayload", "SPacketCustomPayload");
private final static Server INSTANCE = new Server();
private static final Server INSTANCE = new Server();
// Prevent accidental construction
private Server() { super(); }
@@ -548,13 +582,13 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Start", "CPacketLoginStart");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "EncryptionBegin", "CPacketEncryptionResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x02, "CustomPayload", "CPacketCustomPayload");
private final static Client INSTANCE = new Client();
private static final Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(); }
@@ -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) : "";
}
/**
@@ -414,7 +414,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
} else if (methods.size() == 1) {
// We're in 1.2.5
alwaysSync = true;
} else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
} else if (MinecraftVersion.BOUNTIFUL_UPDATE.atOrAbove()) {
// The centralized async marker was removed in 1.8
// Incoming chat packets can be async
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
@@ -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.
@@ -397,6 +423,17 @@ public abstract class AbstractStructure {
BukkitConverters.getListConverter(BukkitConverters.getWatchableObjectConverter()));
}
/**
* Retrieves a read/write structure for collections of data values.
* @return A modifier for data values.
*/
public StructureModifier<List<WrappedDataValue>> getDataValueCollectionModifier() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(BukkitConverters.getDataValueConverter()));
}
/**
* Retrieves a read/write structure for block fields.
* <p>
@@ -625,6 +662,20 @@ public abstract class AbstractStructure {
EnumWrappers.getPlayerInfoActionConverter());
}
/**
* Retrieve a read/write structure for an EnumSet of PlayerInfos.
* @return A modifier for an EnumSet of PlayerInfo fields.
*/
public StructureModifier<Set<EnumWrappers.PlayerInfoAction>> getPlayerInfoActions() {
// Convert to and from the wrapper
return structureModifier.withType(
EnumSet.class,
Converters.collection(
EnumWrappers.getPlayerInfoActionConverter(),
generic -> EnumSet.noneOf(EnumWrappers.PlayerInfoAction.class),
specific -> EnumWrappers.createEmptyEnumSet(EnumWrappers.getPlayerInfoActionClass())));
}
/**
* Retrieve a read/write structure for the TitleAction enum in 1.8.
* @return A modifier for TitleAction enum fields.
@@ -736,11 +787,31 @@ public abstract class AbstractStructure {
EnumWrappers.getSoundCategoryConverter());
}
/**
* Retrieve a read/write structure for a Holder&lt;T&gt; in 1.19.3.
* @param genericType NMS type of T
* @param converter Converter from genericType to T
* @return A modifier for Holder fields
* @param <T> Bukkit type
*/
public <T> StructureModifier<T> getHolders(Class<?> genericType,
EquivalentConverter<T> converter) {
return structureModifier.withParamType(
MinecraftReflection.getHolderClass(),
Converters.holder(converter, WrappedRegistry.getRegistry(genericType)),
genericType
);
}
/**
* Retrieve a read/write structure for the SoundEffect enum in 1.9.
* @return A modifier for SoundEffect enum fields.
*/
public StructureModifier<Sound> getSoundEffects() {
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return getHolders(MinecraftReflection.getSoundEffectClass(), BukkitConverters.getSoundConverter());
}
// Convert to and from Bukkit
return structureModifier.withType(
MinecraftReflection.getSoundEffectClass(),
@@ -901,6 +972,12 @@ public abstract class AbstractStructure {
);
}
public StructureModifier<List<UUID>> getUUIDLists() {
return structureModifier.withType(
List.class,
BukkitConverters.getListConverter(Converters.passthrough(UUID.class)));
}
/**
* Retrieve a read/write structure for Instants in (mostly for use in 1.19+)
* @return The Structure Modifier
@@ -910,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() {
@@ -920,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()
);
}
/**
@@ -955,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(
@@ -964,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
@@ -1070,6 +1168,12 @@ public abstract class AbstractStructure {
return structureModifier.withType(Optional.class, Converters.optional(converter));
}
public StructureModifier<Iterable<PacketContainer>> getPacketBundles() {
return structureModifier.withType(Iterable.class, Converters.iterable(
BukkitConverters.getPacketContainerConverter(), ArrayList::new, ArrayList::new
));
}
/**
* Represents an equivalent converter for ItemStack arrays.
* @author Kristian
@@ -8,7 +8,7 @@ import com.comphenix.protocol.wrappers.Converters;
public class InternalStructure extends AbstractStructure {
protected InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
public InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
super(handle, structureModifier);
}
@@ -40,6 +40,8 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import javax.annotation.Nullable;
/**
* Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be
* sent or received. It is also possible to cancel an event.
@@ -70,6 +72,9 @@ public class PacketEvent extends EventObject implements Cancellable {
private boolean readOnly;
private boolean filtered;
@Nullable
private PacketEvent bundle;
/**
* Use the static constructors to create instances of this event.
*
@@ -81,27 +86,31 @@ public class PacketEvent extends EventObject implements Cancellable {
}
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
this(source, packet, null, player, serverPacket, true);
this(source, packet, null, player, serverPacket, true, null);
}
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
boolean filtered) {
boolean filtered, @Nullable PacketEvent bundleEvent) {
super(source);
if(packet == null) {
throw new IllegalArgumentException("packet cannot be null");
}
this.packet = packet;
this.playerReference = new WeakReference<>(player);
this.networkMarker = marker;
this.serverPacket = serverPacket;
this.filtered = filtered;
this.bundle = bundleEvent;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
this.playerReference = origial.getPlayerReference();
this.cancel = origial.cancel;
this.serverPacket = origial.serverPacket;
this.filtered = origial.filtered;
this.networkMarker = origial.networkMarker;
private PacketEvent(PacketEvent original, AsyncMarker asyncMarker) {
super(original.source);
this.packet = original.packet;
this.playerReference = original.getPlayerReference();
this.cancel = original.cancel;
this.serverPacket = original.serverPacket;
this.filtered = original.filtered;
this.networkMarker = original.networkMarker;
this.asyncMarker = asyncMarker;
this.asynchronous = true;
}
@@ -128,7 +137,7 @@ public class PacketEvent extends EventObject implements Cancellable {
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
return new PacketEvent(source, packet, marker, client, false, true);
return new PacketEvent(source, packet, marker, client, false, true, null);
}
/**
@@ -145,7 +154,7 @@ public class PacketEvent extends EventObject implements Cancellable {
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
boolean filtered) {
return new PacketEvent(source, packet, marker, client, false, filtered);
return new PacketEvent(source, packet, marker, client, false, filtered, null);
}
/**
@@ -170,7 +179,7 @@ public class PacketEvent extends EventObject implements Cancellable {
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
return new PacketEvent(source, packet, marker, recipient, true, true);
return new PacketEvent(source, packet, marker, recipient, true, true, null);
}
/**
@@ -187,7 +196,25 @@ public class PacketEvent extends EventObject implements Cancellable {
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
boolean filtered) {
return new PacketEvent(source, packet, marker, recipient, true, filtered);
return fromServer(source, packet, marker, recipient, filtered, null);
}
/**
* Creates an event representing a server packet transmission.
* <p>
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @param filtered - whether this packet event is processed by every packet listener.
* @param bundle - The corresponding packet event of the bundle if this packet is part of a bundle. Otherwise, null.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
boolean filtered, @Nullable PacketEvent bundle) {
return new PacketEvent(source, packet, marker, recipient, true, filtered, bundle);
}
/**
@@ -517,6 +544,15 @@ public class PacketEvent extends EventObject implements Cancellable {
}
}
/**
* Returns the packet event corresponding to the bundle if this packet is sent as a part of a bundle, t. Otherwise, null.
* @return Corresponding packet event or null.
*/
@Nullable
public PacketEvent getBundle() {
return bundle;
}
@Override
public String toString() {
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
@@ -28,7 +28,7 @@ public interface PacketOutputHandler {
* Note that the buffer is initially filled with the output from the default write method.
* <p>
* In Minecraft 1.6.4, the header is always excluded, whereas it MUST be included in Minecraft 1.7.2. Call
* {@link NetworkMarker#requireOutputHeader()} to determine this.
* NetworkMarker#requireOutputHeader() to determine this.
* @param event - the packet that will be outputted.
* @param buffer - the data that is currently scheduled to be outputted.
* @return The modified byte array to write. NULL is not permitted.
@@ -178,7 +178,9 @@ class EntityUtilities {
Validate.notNull(entity, "entity cannot be null");
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
Validate.notNull(trackerEntry, "Could not find entity trackers for " + entity);
if (trackerEntry == null) { //entity.toString() is too expensive to be called every time
throw new IllegalArgumentException("Could not find entity trackers for " + entity);
}
if (this.trackedPlayersField == null) {
this.trackedPlayersField = Accessors.getFieldAccessor(
@@ -3,6 +3,7 @@ package com.comphenix.protocol.injector;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
@@ -32,6 +33,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
@@ -306,6 +309,10 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
ListeningWhitelist outbound = listener.getSendingWhitelist();
ListeningWhitelist inbound = listener.getReceivingWhitelist();
// Remove packets from the lists if they are not supposed to be in those lists
inbound.getTypes().removeIf(type -> !type.isClient());
outbound.getTypes().removeIf(type -> !type.isServer());
// verify plugin if needed
if (this.shouldVerifyPlugin(outbound, inbound)) {
this.printPluginWarnings(listener.getPlugin());
@@ -537,25 +544,29 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
}
private void postPacketToListeners(SortedPacketListenerList listeners, PacketEvent event, boolean outbound) {
// append async marker if any async listener for the packet was registered
if (this.asyncFilterManager.hasAsynchronousListeners(event)) {
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker());
}
try {
// append async marker if any async listener for the packet was registered
if (this.asyncFilterManager.hasAsynchronousListeners(event)) {
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker());
}
// post to sync listeners
if (outbound) {
listeners.invokePacketSending(this.reporter, event);
} else {
listeners.invokePacketRecieving(this.reporter, event);
}
// post to sync listeners
if (outbound) {
listeners.invokePacketSending(this.reporter, event);
} else {
listeners.invokePacketRecieving(this.reporter, event);
}
// check if we need to post the packet to the async handler
if (!event.isCancelled() && event.getAsyncMarker() != null && !event.getAsyncMarker().isAsyncCancelled()) {
this.asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
// check if we need to post the packet to the async handler
if (!event.isCancelled() && event.getAsyncMarker() != null && !event.getAsyncMarker().isAsyncCancelled()) {
this.asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
// cancel the packet here for async processing (enqueueSyncPacket will create a copy of the event)
event.setReadOnly(false);
event.setCancelled(true);
// cancel the packet here for async processing (enqueueSyncPacket will create a copy of the event)
event.setReadOnly(false);
event.setCancelled(true);
}
} catch (Throwable t) {
plugin.getLogger().log(Level.WARNING, "Failed to process " + (outbound ? "outbound" : "inbound") + " packet event: " + event, t);
}
}
@@ -17,17 +17,25 @@
package com.comphenix.protocol.injector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.timing.TimedListenerManager;
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
import com.comphenix.protocol.timing.TimedTracker;
import javax.annotation.Nullable;
/**
* Registry of synchronous packet listeners.
*
@@ -108,14 +116,12 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
* @param event - the related packet event.
* @param element - the listener to invoke.
*/
private final void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
private void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
try {
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
element.getListener().onPacketReceiving(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
} catch (OutOfMemoryError | ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
@@ -130,73 +136,89 @@ public final class SortedPacketListenerList extends AbstractConcurrentListenerMu
* @param event - the packet event to invoke.
*/
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
if (list == null)
return;
if (timedManager.isTiming()) {
for (PrioritizedListener<PacketListener> element : list) {
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
long token = tracker.beginTracking();
// Measure and record the execution time
invokeSendingListener(reporter, event, element);
tracker.endTracking(token, event.getPacketType());
}
} else {
for (PrioritizedListener<PacketListener> element : list) {
invokeSendingListener(reporter, event, element);
}
}
invokePacketSending(reporter, event, null);
}
/**
* Invokes the given packet event for every registered listener of the given priority.
* @param reporter - the error reporter that will be used to inform about listener exceptions.
* @param event - the packet event to invoke.
* @param priorityFilter - the required priority for a listener to be invoked.
* @param priorityFilter - the priority for a listener to be invoked. If null is provided, every registered listener will be invoked
*/
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, @Nullable ListenerPriority priorityFilter) {
invokeUnpackedPacketSending(reporter, event, priorityFilter);
if (event.getPacketType() == PacketType.Play.Server.BUNDLE && !event.isCancelled()) {
// unpack the bundle and invoke for each packet in the bundle
Iterable<PacketContainer> packets = event.getPacket().getPacketBundles().read(0);
List<PacketContainer> outPackets = new ArrayList<>();
for (PacketContainer subPacket : packets) {
if(subPacket == null) {
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "Failed to invoke packet event " + (priorityFilter == null ? "" : ("with priority " + priorityFilter)) + " in bundle because bundle contains null packet: " + packets, new Throwable());
continue;
}
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(), event.getPlayer());
invokeUnpackedPacketSending(reporter, subPacketEvent, priorityFilter);
if (!subPacketEvent.isCancelled()) {
// if the packet event has been cancelled, the packet will be removed from the bundle
PacketContainer packet = subPacketEvent.getPacket();
if(packet == null) {
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet container returned for " + subPacketEvent, new Throwable());
} else if(packet.getHandle() == null) {
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet handle returned for " + subPacketEvent, new Throwable());
} else {
outPackets.add(packet);
}
}
}
if (packets.iterator().hasNext()) {
event.getPacket().getPacketBundles().write(0, outPackets);
} else {
// cancel entire packet if each individual packet has been cancelled
event.setCancelled(true);
}
}
}
private void invokeUnpackedPacketSending(ErrorReporter reporter, PacketEvent event, @org.jetbrains.annotations.Nullable ListenerPriority priorityFilter) {
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
if (list == null)
return;
if (timedManager.isTiming()) {
for (PrioritizedListener<PacketListener> element : list) {
if (element.getPriority() == priorityFilter) {
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
long token = tracker.beginTracking();
// Measure and record the execution time
invokeSendingListener(reporter, event, element);
tracker.endTracking(token, event.getPacketType());
if (priorityFilter == null || element.getPriority() == priorityFilter) {
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
long token = tracker.beginTracking();
// Measure and record the execution time
invokeSendingListener(reporter, event, element);
tracker.endTracking(token, event.getPacketType());
}
}
} else {
for (PrioritizedListener<PacketListener> element : list) {
if (element.getPriority() == priorityFilter) {
if (priorityFilter == null || element.getPriority() == priorityFilter) {
invokeSendingListener(reporter, event, element);
}
}
}
}
/**
* Invoke a particular sending listener.
* @param reporter - the error reporter.
* @param event - the related packet event.
* @param element - the listener to invoke.
*/
private final void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
private void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
try {
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
element.getListener().onPacketSending(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
} catch (OutOfMemoryError | ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
@@ -17,6 +17,7 @@
package com.comphenix.protocol.injector;
import java.security.PublicKey;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -36,7 +37,8 @@ import com.comphenix.protocol.utility.ZeroBuffer;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
@@ -52,8 +54,11 @@ public class StructureCache {
private static final Map<PacketType, StructureModifier<Object>> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>();
// packet data serializer which always returns an empty nbt tag compound
private static final Object TRICK_INIT_LOCK = new Object();
private static boolean TRICK_TRIED = false;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
public static Object newPacket(Class<?> packetClass) {
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
@@ -63,15 +68,28 @@ public class StructureCache {
clazz,
MinecraftReflection.getPacketDataSerializerClass());
if (serializerAccessor != null) {
return () -> {
// the tricked serializer should always be present... If not try using a normal buffer
Object dataSerializer = getTrickDataSerializerOrNull();
if (dataSerializer == null) {
dataSerializer = MinecraftReflection.getPacketDataSerializer(new ZeroBuffer());
}
// check if the method is possible
if (tryInitTrickDataSerializer()) {
try {
// first try with the base accessor
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
serializerAccessor.invoke(serializer); // throwaway instance, for testing
return serializerAccessor.invoke(dataSerializer);
};
// method is working
return () -> serializerAccessor.invoke(serializer);
} catch (Exception exception) {
try {
// try with the json accessor
Object serializer = TRICKED_DATA_SERIALIZER_JSON.invoke(new ZeroBuffer());
serializerAccessor.invoke(serializer); // throwaway instance, for testing
// method is working
return () -> serializerAccessor.invoke(serializer);
} catch (Exception ignored) {
// shrug, fall back to default behaviour
}
}
}
}
}
@@ -119,42 +137,79 @@ public class StructureCache {
return STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
// We need to map the Bundle Delimiter to the synthetic bundle packet which contains a list of all packets in a bundle
if (MinecraftReflection.isBundleDelimiter(packetClass)) {
packetClass = MinecraftReflection.getPackedBundlePacketClass().get();
}
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);
});
}
/**
* Returns a new mocked null data serializer instance, if possible.
*
* @return a new null data serializer instance.
*/
public static Object newNullDataSerializer() {
tryInitTrickDataSerializer();
return TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
}
/**
* Creates a packet data serializer sub-class if needed to allow the fixed read of a NbtTagCompound because of a
* null check in the MapChunk packet constructor.
*
* @return an accessor to a constructor which creates a data serializer.
*/
public static Object getTrickDataSerializerOrNull() {
if (TRICKED_DATA_SERIALIZER == null && !TRICK_TRIED) {
// ensure that we only try once to create the class
TRICK_TRIED = true;
public static boolean tryInitTrickDataSerializer() {
if (TRICK_TRIED) {
return TRICKED_DATA_SERIALIZER_BASE != null;
}
synchronized (TRICK_INIT_LOCK) {
if (TRICK_TRIED) {
return TRICKED_DATA_SERIALIZER_BASE != null;
}
try {
// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
Object textCompound = WrappedChatComponent.fromText("").getHandle();
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();
// create the method in the class to read an empty nbt tag compound (currently used for MAP_CHUNK because of null check)
Class<?> generatedClass = ByteBuddyFactory.getInstance()
// base builder which intercepts a few methods
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializer")
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
.method(ElementMatchers.returns(MinecraftReflection.getNBTCompoundClass())
.and(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())))
.intercept(FixedValue.value(compound))
.method(ElementMatchers.returns(MinecraftReflection.getIChatBaseComponentClass()))
.intercept(FixedValue.value(textCompound))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.method(ElementMatchers.returns(PublicKey.class).and(ElementMatchers.takesNoArguments()))
.intercept(FixedValue.nullValue());
Class<?> serializerBase = baseBuilder.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER = Accessors.getConstructorAccessor(generatedClass, ByteBuf.class);
} catch (Exception ignored) {
// can happen if unsupported
}
}
TRICKED_DATA_SERIALIZER_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
return TRICKED_DATA_SERIALIZER == null ? null : TRICKED_DATA_SERIALIZER.invoke(new ZeroBuffer());
// extended builder which intercepts the read string method as well
Class<?> withStringIntercept = baseBuilder
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
.intercept(FixedValue.value("{}"))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER_JSON = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
// worked
return true;
} catch (Exception ignored) {
} finally {
TRICK_TRIED = true;
}
// didn't work
return false;
}
}
}
@@ -27,12 +27,7 @@ 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.ByteBuddyGenerated;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.*;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
@@ -101,7 +96,7 @@ public class NettyChannelInjector implements Injector {
// packet marking
private final Map<Object, NetworkMarker> savedMarkers = new WeakHashMap<>(16, 0.9f);
private final Set<Object> skippedPackets = Collections.synchronizedSet(new HashSet<>());
private final Set<Object> skippedPackets = ConcurrentHashMap.newKeySet();
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
// status of this injector
@@ -553,7 +548,7 @@ public class NettyChannelInjector implements Injector {
}
// no listener and no marker - no magic :)
if (!this.channelListener.hasListener(packet.getClass()) && marker == null) {
if (!this.channelListener.hasListener(packet.getClass()) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
return action;
}
@@ -28,6 +28,7 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.Util;
import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.ChannelFuture;
import org.bukkit.Server;
@@ -90,7 +91,7 @@ public class NetworkManagerInjector implements ChannelListener {
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
// check if we need to intercept the packet
Class<?> packetClass = packet.getClass();
if (this.outboundListeners.contains(packetClass) || marker != null) {
if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || outboundListeners.contains(packetClass)) {
// wrap packet and construct the event
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
PacketEvent packetEvent = PacketEvent.fromServer(this, container, marker, injector.getPlayer());
@@ -108,7 +109,7 @@ public class NetworkManagerInjector implements ChannelListener {
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
// check if we need to intercept the packet
Class<?> packetClass = packet.getClass();
if (this.inboundListeners.contains(packetClass) || marker != null) {
if (marker != null || inboundListeners.contains(packetClass)) {
// wrap the packet and construct the event
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
PacketEvent packetEvent = PacketEvent.fromClient(this, container, marker, injector.getPlayer());
@@ -45,12 +45,12 @@ public class PacketRegistry {
*/
protected static class Register {
// The main lookup table
private final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
private final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
private volatile Set<PacketType> serverPackets = new HashSet<>();
private volatile Set<PacketType> clientPackets = new HashSet<>();
private final List<MapContainer> containers = new ArrayList<>();
volatile Set<PacketType> serverPackets = new HashSet<>();
volatile Set<PacketType> clientPackets = new HashSet<>();
final List<MapContainer> containers = new ArrayList<>();
public Register() {}
@@ -251,10 +251,13 @@ public class PacketRegistry {
protected static void associatePackets(Register register, Map<Integer, Class<?>> lookup, PacketType.Protocol protocol, Sender sender) {
for (Map.Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
int packetId = entry.getKey();
Class<?> packetClass = entry.getValue();
PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass);
try {
register.registerPacket(type, entry.getValue(), sender);
register.registerPacket(type, packetClass, sender);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
@@ -270,18 +273,28 @@ public class PacketRegistry {
}
}
private static final Object registryLock = new Object();
/**
* Initializes the packet registry.
*/
private static void initialize() {
if (!INITIALIZED) {
INITIALIZED = true;
if (INITIALIZED) {
return;
}
synchronized (registryLock) {
if (INITIALIZED) {
return;
}
if (MinecraftVersion.BEE_UPDATE.atOrAbove()) {
REGISTER = createNewRegister();
} else {
REGISTER = createOldRegister();
}
INITIALIZED = true;
}
}
@@ -350,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;
}
@@ -362,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);
}
@@ -383,6 +402,11 @@ public class PacketRegistry {
*/
public static PacketType getPacketType(Class<?> packet) {
initialize();
if (MinecraftReflection.isBundlePacket(packet)) {
return PacketType.Play.Server.BUNDLE;
}
return REGISTER.classToType.get(packet);
}
@@ -116,7 +116,9 @@ public class FuzzyReflection {
public static <T> Set<T> combineArrays(T[]... arrays) {
Set<T> result = new LinkedHashSet<>();
for (T[] elements : arrays) {
Collections.addAll(result, elements);
if (elements != null) {
Collections.addAll(result, elements);
}
}
return result;
@@ -23,6 +23,11 @@ import com.comphenix.protocol.reflect.instances.BannedGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.InstanceProvider;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -151,12 +156,34 @@ public class StructureModifier<T> {
return requireDefaults;
}
// Used to filter out irrelevant fields
private static final ThreadLocal<Table<Class<?>, Class<?>, Reference<List<FieldAccessor>>>>
fieldCacheLocal = ThreadLocal.withInitial(HashBasedTable::create);
private static final Class<?> NULL_CACHE_CLASS_REPLACEMENT = Void.class;
// Used to filter out irrelevant fields
private static List<FieldAccessor> getFields(Class<?> type, Class<?> superclassExclude) {
return FuzzyReflection.fromClass(type, true).getDeclaredFields(superclassExclude).stream()
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(Accessors::getFieldAccessor)
.collect(Collectors.toList());
if (type == null) {
throw new IllegalArgumentException("Type cannot be NULL.");
}
Table<Class<?>, Class<?>, Reference<List<FieldAccessor>>> fieldCache = fieldCacheLocal.get();
Class<?> superclassKey = superclassExclude == null ? NULL_CACHE_CLASS_REPLACEMENT : superclassExclude;
Reference<List<FieldAccessor>> cacheEntryReference = fieldCache.get(type, superclassKey);
if (cacheEntryReference != null) {
List<FieldAccessor> cacheEntry = cacheEntryReference.get();
if (cacheEntry != null) {
return cacheEntry;
}
}
List<FieldAccessor> accessors = FuzzyReflection.fromClass(type, true)
.getDeclaredFields(superclassExclude)
.stream()
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(Accessors::getFieldAccessor)
.collect(Collectors.toList());
fieldCache.put(type, superclassKey, new SoftReference<>(accessors));
fieldCache.cellSet().removeIf(entry -> entry.getValue().get() == null);
return accessors;
}
/**
@@ -4,6 +4,8 @@ import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import com.google.common.base.Preconditions;
final class DefaultFieldAccessor implements FieldAccessor {
private final Field field;
@@ -13,9 +15,9 @@ final class DefaultFieldAccessor implements FieldAccessor {
private final MethodHandle getter;
public DefaultFieldAccessor(Field field, MethodHandle setter, MethodHandle getter, boolean staticField) {
this.field = field;
this.setter = setter;
this.getter = getter;
this.field = Preconditions.checkNotNull(field, "field");
this.setter = Preconditions.checkNotNull(setter, "setter");
this.getter = Preconditions.checkNotNull(getter, "getter");
this.staticField = staticField;
}
@@ -11,6 +11,8 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
import com.google.common.base.Preconditions;
final class MethodHandleHelper {
private static final Lookup LOOKUP;
@@ -51,29 +53,35 @@ final class MethodHandleHelper {
}
public static MethodAccessor getMethodAccessor(Method method) {
Preconditions.checkNotNull(method, "method");
try {
MethodHandle unreflected = LOOKUP.unreflect(method);
boolean staticMethod = Modifier.isStatic(method.getModifiers());
MethodHandle generified = convertToGeneric(unreflected, staticMethod, false);
return new DefaultMethodAccessor(method, generified, staticMethod);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to access method " + method);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Unable to access method " + method, ex);
}
}
public static ConstructorAccessor getConstructorAccessor(Constructor<?> constructor) {
Preconditions.checkNotNull(constructor, "constructor");
try {
MethodHandle unreflected = LOOKUP.unreflectConstructor(constructor);
MethodHandle generified = convertToGeneric(unreflected, false, true);
return new DefaultConstrutorAccessor(constructor, generified);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to access constructor " + constructor);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Unable to access constructor " + constructor, ex);
}
}
public static FieldAccessor getFieldAccessor(Field field) {
Preconditions.checkNotNull(field, "field");
try {
boolean staticField = Modifier.isStatic(field.getModifiers());
@@ -97,10 +105,18 @@ final class MethodHandleHelper {
setter = setter.asType(VIRTUAL_FIELD_SETTER);
}
if (getter == null) {
throw new IllegalStateException("Unable to access field " + field + ". Could not find getter");
}
if (setter == null) {
throw new IllegalStateException("Unable to access field " + field + ". Could not find setter");
}
return new DefaultFieldAccessor(field, setter, getter, staticField);
} catch (IllegalAccessException | NoSuchFieldException exception) {
} catch (IllegalAccessException | NoSuchFieldException ex) {
// NoSuchFieldException can never happen, the field always exists
throw new IllegalStateException("Unable to access field " + field);
throw new IllegalStateException("Unable to access field " + field, ex);
}
}
@@ -36,7 +36,7 @@ final class CachedPackage {
* @param packageName - the name of the current package.
* @param source - the class source.
*/
public CachedPackage(String packageName, ClassSource source) {
CachedPackage(String packageName, ClassSource source) {
this.source = source;
this.packageName = packageName;
this.cache = new ConcurrentHashMap<>();
@@ -71,6 +71,10 @@ final class CachedPackage {
}
}
private Optional<Class<?>> resolveClass(String className) {
return source.loadClass(combine(packageName, className));
}
/**
* Retrieve the class object of a specific class in the current package.
*
@@ -78,13 +82,21 @@ final class CachedPackage {
* @return Class object.
* @throws RuntimeException If we are unable to find the given class.
*/
public Optional<Class<?>> getPackageClass(final String className) {
return this.cache.computeIfAbsent(className, x -> {
try {
return Optional.ofNullable(this.source.loadClass(combine(this.packageName, className)));
} catch (ClassNotFoundException ex) {
return Optional.empty();
public Optional<Class<?>> getPackageClass(String className, String... aliases) {
return cache.computeIfAbsent(className, x -> {
Optional<Class<?>> clazz = resolveClass(className);
if (clazz.isPresent()) {
return clazz;
}
for (String alias : aliases) {
clazz = resolveClass(alias);
if (clazz.isPresent()) {
return clazz;
}
}
return Optional.empty();
});
}
}
@@ -2,6 +2,8 @@ package com.comphenix.protocol.utility;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Represents an abstract class loader that can only retrieve classes by their canonical name.
@@ -37,27 +39,25 @@ public interface ClassSource {
* @return The corresponding class source.
*/
static ClassSource fromClassLoader(final ClassLoader loader) {
return loader::loadClass;
return canonicalName -> {
try {
return Optional.of(loader.loadClass(canonicalName));
} catch (ClassNotFoundException ignored) {
return Optional.empty();
}
};
}
/**
* Construct a class source from a mapping of canonical names and the corresponding classes. If the map is null, it
* will be interpreted as an empty map. If the map does not contain a Class with the specified name, or that string
* maps to NULL explicitly, a {@link ClassNotFoundException} will be thrown.
* maps to NULL explicitly, an empty optional will be returned
*
* @param map - map of class names and classes.
* @return The class source.
*/
static ClassSource fromMap(final Map<String, Class<?>> map) {
return canonicalName -> {
Class<?> loaded = map == null ? null : map.get(canonicalName);
if (loaded == null) {
// Throw the appropriate exception if we can't load the class
throw new ClassNotFoundException("The specified class could not be found by this ClassLoader.");
}
return loaded;
};
return canonicalName -> Optional.ofNullable(map.get(canonicalName));
}
/**
@@ -94,14 +94,8 @@ public interface ClassSource {
* @param other - the other class source.
* @return A new class source.
*/
default ClassSource retry(final ClassSource other) {
return canonicalName -> {
try {
return ClassSource.this.loadClass(canonicalName);
} catch (ClassNotFoundException e) {
return other.loadClass(canonicalName);
}
};
default ClassSource retry(ClassSource other) {
return canonicalName -> Optionals.or(loadClass(canonicalName), () -> other.loadClass(canonicalName));
}
/**
@@ -115,12 +109,9 @@ public interface ClassSource {
}
/**
* Retrieve a class by name.
*
* @param canonicalName - the full canonical name of the class.
* @return The corresponding class. If the class is not found, NULL should <b>not</b> be returned, instead a {@code
* ClassNotFoundException} exception should be thrown.
* @throws ClassNotFoundException If the class could not be found.
* Retrieve a class by its canonical name
* @param canonicalName The class's canonical name, i.e. java.lang.Object
* @return Optional that may contain a Class
*/
Class<?> loadClass(String canonicalName) throws ClassNotFoundException;
Optional<Class<?>> loadClass(String canonicalName);
}
@@ -84,6 +84,8 @@ public final class MinecraftProtocolVersion {
map.put(new MinecraftVersion(1, 19, 0), 759);
map.put(new MinecraftVersion(1, 19, 1), 760);
map.put(new MinecraftVersion(1, 19, 2), 760);
map.put(new MinecraftVersion(1, 19, 3), 761);
map.put(new MinecraftVersion(1, 19, 4), 762);
return map;
}
@@ -25,6 +25,7 @@ import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -628,6 +629,22 @@ public final class MinecraftReflection {
return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "network.chat.Component", "IChatBaseComponent");
}
public static Optional<Class<?>> getPackedBundlePacketClass() {
return getOptionalNMS("network.protocol.game.ClientboundBundlePacket", "ClientboundBundlePacket");
}
public static boolean isBundlePacket(Class<?> packetClass) {
return Optionals.Equals(getPackedBundlePacketClass(), packetClass);
}
public static boolean isBundleDelimiter(Class<?> packetClass) {
return Optionals.Equals(getBundleDelimiterClass(), packetClass);
}
public static Optional<Class<?>> getBundleDelimiterClass() {
return getOptionalNMS("network.protocol.BundleDelimiterPacket","BundleDelimiterPacket");
}
public static Class<?> getIChatBaseComponentArrayClass() {
return getArrayClass(getIChatBaseComponentClass());
}
@@ -1071,10 +1088,17 @@ public final class MinecraftReflection {
* @return The PlayerInfoData class
*/
public static Class<?> getPlayerInfoDataClass() {
return getMinecraftClass(
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData",
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate",
"PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
try {
return getMinecraftClass(
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData",
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate",
"PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
} catch (Exception ex) {
// todo: ClientboundPlayerInfoUpdatePacket$b, maybe get this via field type
return setMinecraftClass(
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData",
PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[0]);
}
}
/**
@@ -1318,11 +1342,8 @@ public final class MinecraftReflection {
* @return The class.
*/
private static Class<?> getClass(String className) {
try {
return getClassSource().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot find class " + className, e);
}
return getClassSource().loadClass(className)
.orElseThrow(() -> new RuntimeException("Cannot find class " + className));
}
/**
@@ -1357,12 +1378,34 @@ public final class MinecraftReflection {
.orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className));
}
public static Class<?> getNullableNMS(String className, String... aliases) {
try {
return getMinecraftClass(className, aliases);
} catch (RuntimeException ex) {
return null;
/**
* Optionally retrieve the class object of a NMS (net.minecraft.server) class.
* If the class does not exist, the optional will be empty
*
* @param className NMS class name
* @param aliases Potential aliases
* @return Optional that may contain the class
*/
private static Optional<Class<?>> getOptionalNMS(String className, String... aliases) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
return minecraftPackage.getPackageClass(className, aliases);
}
/**
* Retrieves a nullable NMS (net.minecraft.server) class. We will attempt to
* look up the class and its aliases, but will return null if none is found.
*
* @deprecated - Use getOptionalNMS where possible
* @param className NMS class name
* @param aliases Potential aliases
* @return The class, or null if not found
*/
@Deprecated
public static Class<?> getNullableNMS(String className, String... aliases) {
return getOptionalNMS(className, aliases).orElse(null);
}
/**
@@ -1399,29 +1442,8 @@ public final class MinecraftReflection {
* @throws RuntimeException If we are unable to find any of the given classes.
*/
public static Class<?> getMinecraftClass(String className, String... aliases) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
return minecraftPackage.getPackageClass(className).orElseGet(() -> {
Class<?> resolved = null;
for (String alias : aliases) {
// try to resolve the class and stop searching if we found it
resolved = minecraftPackage.getPackageClass(alias).orElse(null);
if (resolved != null) {
break;
}
}
// if we resolved the class cache it and return the result
if (resolved != null) {
minecraftPackage.setPackageClass(className, resolved);
return resolved;
}
// unable to find the class
throw new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases)));
});
return getOptionalNMS(className, aliases)
.orElseThrow(() -> new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases))));
}
/**
@@ -1539,6 +1561,10 @@ public final class MinecraftReflection {
return getNullableNMS("core.IRegistry", "core.Registry", "IRegistry");
}
public static Class<?> getBuiltInRegistries() {
return getNullableNMS("core.registries.BuiltInRegistries");
}
public static Class<?> getAttributeBase() {
return getMinecraftClass("world.entity.ai.attributes.AttributeBase", "world.entity.ai.attributes.Attribute", "AttributeBase");
}
@@ -1547,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");
@@ -1591,6 +1621,14 @@ public final class MinecraftReflection {
return getProfilePublicKeyClass().getClasses()[0];
}
public static Class<?> getRemoteChatSessionClass() {
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);
}
@@ -1654,4 +1692,24 @@ public final class MinecraftReflection {
return infoClass;
}
}
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");
}
}
@@ -17,15 +17,16 @@
package com.comphenix.protocol.utility;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;
import org.bukkit.Bukkit;
import org.bukkit.Server;
@@ -36,6 +37,15 @@ import org.bukkit.Server;
*/
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
/**
* Version 1.19.4 - the rest of the feature preview
*/
public static final MinecraftVersion FEATURE_PREVIEW_2 = new MinecraftVersion("1.19.4");
/**
* Version 1.19.3 - introducing feature preview
*/
public static final MinecraftVersion FEATURE_PREVIEW_UPDATE = new MinecraftVersion("1.19.3");
/**
* Version 1.19 - the wild update
*/
@@ -48,6 +58,10 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
* Version 1.17 - caves and cliffs part 1
*/
public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17");
/**
* Version 1.16.4
*/
public static final MinecraftVersion NETHER_UPDATE_4 = new MinecraftVersion("1.16.4");
/**
* Version 1.16.2 - breaking change to the nether update
*/
@@ -112,7 +126,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
/**
* The latest release version of minecraft.
*/
public static final MinecraftVersion LATEST = WILD_UPDATE;
public static final MinecraftVersion LATEST = FEATURE_PREVIEW_2;
// used when serializing
private static final long serialVersionUID = -8695133558996459770L;
@@ -263,7 +277,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
currentVersion = version;
}
public static boolean atOrAbove(MinecraftVersion version) {
private static boolean atOrAbove(MinecraftVersion version) {
return getCurrentVersion().isAtLeast(version);
}
@@ -344,7 +358,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
*/
public boolean atOrAbove() {
if (this.atCurrentOrAbove == null) {
this.atCurrentOrAbove = MinecraftVersion.atOrAbove(this);
this.atCurrentOrAbove = atOrAbove(this);
}
return this.atCurrentOrAbove;
@@ -0,0 +1,45 @@
package com.comphenix.protocol.utility;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* Utility methods for operating with Optionals
*/
public final class Optionals {
/**
* Chains two optionals together by returning the secondary
* optional if the primary does not contain a value
* @param primary Primary optional
* @param secondary Supplier of secondary optional
* @return The resulting optional
* @param <T> Type
*/
public static <T> Optional<T> or(Optional<T> primary, Supplier<Optional<T>> secondary) {
return primary.isPresent() ? primary : secondary.get();
}
/**
* Evaluates the provided predicate against the optional only if it is present
* @param optional Optional
* @param predicate Test to run against potential value
* @return True if the optional is present and the predicate passes
* @param <T> Type
*/
public static <T> boolean TestIfPresent(Optional<T> optional, Predicate<T> predicate) {
return optional.isPresent() && predicate.test(optional.get());
}
/**
* Check if the optional has a value and its value equals the provided value
* @param optional Optional
* @param contents Contents to test for
* @return True if the optional has a value and that value equals the parameter
* @param <T> Type
*/
public static <T> boolean Equals(Optional<T> optional, Class<?> contents) {
return optional.isPresent() && contents.equals(optional.get());
}
}
@@ -22,6 +22,7 @@ package com.comphenix.protocol.utility;
public final class Util {
private static final boolean SPIGOT = classExists("org.spigotmc.SpigotConfig");
private static Class<?> cachedBundleClass;
public static boolean classExists(String className) {
try {
@@ -40,7 +40,7 @@ public class AdventureComponentConverter {
/**
* Converts a {@link Component} into a ProtocolLib wrapper
* @param components Component
* @param component Component
* @return ProtocolLib wrapper
*/
public static WrappedChatComponent fromComponent(Component component) {
@@ -20,6 +20,7 @@ import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.google.common.base.Defaults;
import com.google.common.base.Preconditions;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -88,6 +89,8 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
}
public T wrap(Object nmsObject) {
Preconditions.checkNotNull(nmsObject);
T instance;
try {
@@ -114,6 +117,8 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
}
public Object unwrap(Object wrapper) {
Preconditions.checkNotNull(wrapper);
// ensures that all accessors are present
computeFieldAccessors();
computeNmsConstructorAccess();
@@ -139,14 +144,14 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
nmsAccessors = Arrays
.stream(nmsClass.getDeclaredFields())
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(field -> Accessors.getFieldAccessor(field))
.map(Accessors::getFieldAccessor)
.toArray(FieldAccessor[]::new);
}
if (wrapperAccessors == null) {
wrapperAccessors = Arrays
.stream(wrapperClass.getDeclaredFields())
.map(field -> Accessors.getFieldAccessor(field))
.map(Accessors::getFieldAccessor)
.toArray(FieldAccessor[]::new);
}
}
@@ -16,6 +16,7 @@
*/
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.Either.Left;
import com.comphenix.protocol.wrappers.Either.Right;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
@@ -76,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;
@@ -586,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.
@@ -603,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
*/
@@ -610,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));
}
@@ -618,6 +647,10 @@ public class BukkitConverters {
return ignoreNull(handle(WrappedLevelChunkData.LightData::getHandle, WrappedLevelChunkData.LightData::new, WrappedLevelChunkData.LightData.class));
}
public static EquivalentConverter<PacketContainer> getPacketContainerConverter() {
return ignoreNull(handle(PacketContainer::getHandle, PacketContainer::fromPacket, PacketContainer.class));
}
/**
* Retrieve a converter for watchable objects and the respective wrapper.
* @return A watchable object converter.
@@ -645,6 +678,29 @@ public class BukkitConverters {
}
});
}
/**
* Retrieve a converter for data values in 1.19.3+.
* @return A data value converter.
*/
public static EquivalentConverter<WrappedDataValue> getDataValueConverter() {
return ignoreNull(new EquivalentConverter<WrappedDataValue>() {
@Override
public Object getGeneric(WrappedDataValue specific) {
return specific.getHandle();
}
@Override
public WrappedDataValue getSpecific(Object generic) {
return new WrappedDataValue(generic);
}
@Override
public Class<WrappedDataValue> getSpecificType() {
return WrappedDataValue.class;
}
});
}
/**
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
@@ -1055,19 +1111,19 @@ public class BukkitConverters {
});
}
private static MethodAccessor getSound = null;
private static MethodAccessor getSoundEffect = null;
private static FieldAccessor soundKey = null;
static MethodAccessor getSound = null;
static MethodAccessor getSoundEffect = null;
static FieldAccessor soundKey = null;
private static MethodAccessor getSoundEffectByKey = null;
private static MethodAccessor getSoundEffectBySound = null;
private static MethodAccessor getSoundByEffect = null;
static MethodAccessor getSoundEffectByKey = null;
static MethodAccessor getSoundEffectBySound = null;
static MethodAccessor getSoundByEffect = null;
private static Map<String, Sound> soundIndex = null;
static Map<String, Sound> soundIndex = null;
public static EquivalentConverter<Sound> getSoundConverter() {
// Try to create sound converter for new versions greater 1.16.4
try {
if (MinecraftVersion.NETHER_UPDATE_4.atOrAbove()) {
if (getSoundEffectByKey == null || getSoundEffectBySound == null || getSoundByEffect == null) {
Class<?> craftSound = MinecraftReflection.getCraftSoundClass();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true);
@@ -1075,19 +1131,19 @@ public class BukkitConverters {
getSoundEffectByKey = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters(
"getSoundEffect",
MinecraftReflection.getSoundEffectClass(),
new Class<?>[]{String.class}
String.class
));
getSoundEffectBySound = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters(
"getSoundEffect",
MinecraftReflection.getSoundEffectClass(),
new Class<?>[]{Sound.class}
Sound.class
));
getSoundByEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters(
"getBukkit",
Sound.class,
new Class<?>[]{MinecraftReflection.getSoundEffectClass()}
MinecraftReflection.getSoundEffectClass()
));
}
@@ -1105,10 +1161,17 @@ public class BukkitConverters {
@Override
public Sound getSpecific(Object generic) {
return (Sound) getSoundByEffect.invoke(null, generic);
try {
return (Sound) getSoundByEffect.invoke(null, generic);
} catch (IllegalStateException ex) {
if (ex.getCause() instanceof NullPointerException) {
// "null" sounds cause NPEs inside getSoundByEffect
return null;
}
throw ex;
}
}
});
} catch (Exception e) {
}
// Fall back to sound converter from legacy versions before 1.16.4
@@ -1116,9 +1179,9 @@ public class BukkitConverters {
Class<?> craftSound = MinecraftReflection.getCraftSoundClass();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true);
getSound = Accessors.getMethodAccessor(
fuzzy.getMethodByReturnTypeAndParameters("getSound", String.class, new Class<?>[]{Sound.class}));
fuzzy.getMethodByReturnTypeAndParameters("getSound", String.class, Sound.class));
getSoundEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("getSoundEffect",
MinecraftReflection.getSoundEffectClass(), new Class<?>[]{String.class}));
MinecraftReflection.getSoundEffectClass(), String.class));
}
return ignoreNull(new EquivalentConverter<Sound>() {
@@ -1,27 +1,34 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2017 Dan Mulloy
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2017 Dan Mulloy
* <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.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.function.Supplier;
/**
* Utility class for converters
@@ -171,4 +178,151 @@ public class Converters {
}
};
}
public static <T, C extends Collection<T>> EquivalentConverter<C> collection(
final EquivalentConverter<T> elementConverter,
final Function<Collection<Object>, C> genericToSpecificCollectionFactory,
final Function<C, Collection<?>> specificToGenericCollectionFactory
) {
return ignoreNull(new EquivalentConverter<C>() {
@Override
public Object getGeneric(C specific) {
// generics are very cool, thank you java
Collection<Object> targetCollection = (Collection<Object>) specificToGenericCollectionFactory.apply(specific);
for (T element : specific) {
Object generic = elementConverter.getGeneric(element);
if (generic != null) {
targetCollection.add(generic);
}
}
return targetCollection;
}
@Override
public C getSpecific(Object generic) {
if (generic instanceof Collection<?>) {
Collection<Object> sourceCollection = (Collection<Object>) generic;
C targetCollection = genericToSpecificCollectionFactory.apply(sourceCollection);
// copy over all elements into a new collection
for (Object element : sourceCollection) {
T specific = elementConverter.getSpecific(element);
if (specific != null) {
targetCollection.add(specific);
}
}
return targetCollection;
}
// not valid
return null;
}
@Override
public Class<C> getSpecificType() {
return (Class) Collection.class;
}
});
}
public static <T> EquivalentConverter<Iterable<T>> iterable(
final EquivalentConverter<T> elementConverter,
final Supplier<List<T>> specificCollectionFactory,
final Supplier<List<?>> genericCollectionFactory
) {
return ignoreNull(new EquivalentConverter<Iterable<T>>() {
@Override
public Object getGeneric(Iterable<T> specific) {
// generics are very cool, thank you java
List<Object> targetCollection = (List<Object>) genericCollectionFactory.get();
for (T element : specific) {
Object generic = elementConverter.getGeneric(element);
if (generic != null) {
targetCollection.add(generic);
}
}
return targetCollection;
}
@Override
public Iterable<T> getSpecific(Object generic) {
if (generic instanceof Iterable<?>) {
Iterable<Object> sourceCollection = (Iterable<Object>) generic;
List<T> targetCollection = specificCollectionFactory.get();
// copy over all elements into a new collection
for (Object element : sourceCollection) {
T specific = elementConverter.getSpecific(element);
if (specific != null) {
targetCollection.add(specific);
}
}
return targetCollection;
}
// not valid
return null;
}
@Override
public Class<Iterable<T>> getSpecificType() {
return (Class) Iterable.class;
}
});
}
private static MethodAccessor holderGetValue;
public static <T> EquivalentConverter<T> holder(final EquivalentConverter<T> converter,
final WrappedRegistry registry) {
return new EquivalentConverter<T>() {
@Override
public Object getGeneric(T specific) {
Object generic = converter.getGeneric(specific);
return registry.getHolder(generic);
}
@Override
public T getSpecific(Object generic) {
if (holderGetValue == null) {
Class<?> holderClass = MinecraftReflection.getHolderClass();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(holderClass, false);
holderGetValue = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(0)
.banModifier(Modifier.STATIC)
.returnTypeExact(Object.class)
.build()));
}
Object value = holderGetValue.invoke(generic);
return converter.getSpecific(value);
}
@Override
public Class<T> getSpecificType() {
return converter.getSpecificType();
}
};
}
public static <T> List<T> toList(Iterable<? extends T> iterable) {
if (iterable instanceof List) {
return (List<T>) iterable;
}
List<T> result = new ArrayList<>();
if (iterable instanceof Collection) {
Collection<T> coll = (Collection<T>) iterable;
result.addAll(coll);
} else {
for (T elem : iterable) {
result.add(elem);
}
}
return result;
}
}
@@ -65,6 +65,7 @@ public abstract class EnumWrappers {
/**
* @deprecated Replaced by NOT_SET
*/
@Deprecated
NONE;
/**
@@ -120,9 +121,15 @@ public abstract class EnumWrappers {
public enum PlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
REMOVE_PLAYER
}
@@ -371,9 +378,9 @@ public abstract class EnumWrappers {
/**
* Wrapped EntityPose enum for use in Entity Metadata Packet.<br>
*
* @apiNote Remember to use {@link #toNms()} when adding to a {@link WrappedDataWatcher}. <br>
* Serializer is obtained using Registry.get(EnumWrappers.getEntityPoseClass())
* Remember to use {@link #toNms()} when adding to a {@link WrappedDataWatcher}. <br>
* Serializer is obtained using Registry.get(EnumWrappers.getEntityPoseClass())
*
* @since 1.13
* @author Lewys Davies (Lew_)
*/
@@ -388,6 +395,7 @@ public abstract class EnumWrappers {
DYING,
CROAKING,
USING_TONGUE,
SITTING,
ROARING,
SNIFFING,
EMERGING,
@@ -490,7 +498,6 @@ public abstract class EnumWrappers {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
@@ -498,7 +505,18 @@ public abstract class EnumWrappers {
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.CUSTOM_SOUND_EFFECT.getPacketClass(), 0);
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
try {
// TODO enum names are more stable than their packet associations
@@ -798,6 +816,17 @@ public abstract class EnumWrappers {
return new EnumConverter<>(genericClass, specificType);
}
/**
* Creates an enum set with no elements based off the given class. The given must be an enum.
*
* @param clazz the element type of the enum set
* @return a new enum set with the given class as its element type
* @throws ClassCastException if the given class is not an enum
*/
public static <E extends Enum<E>> EnumSet<E> createEmptyEnumSet(Class<?> clazz) {
return EnumSet.noneOf((Class<E>) clazz);
}
/**
* The common Enum converter
*/
@@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
@@ -29,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
@@ -36,10 +39,15 @@ import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
public class PlayerInfoData {
private static Constructor<?> constructor;
private final UUID profileId;
private final int latency;
private final boolean listed;
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)
@@ -48,11 +56,79 @@ public class PlayerInfoData {
}
public PlayerInfoData(WrappedGameProfile profile, int latency, NativeGameMode gameMode, WrappedChatComponent displayName, WrappedProfileKeyData keyData) {
this.profile = profile;
this(profile.getUUID(), latency, true, gameMode, profile, displayName, keyData);
}
/**
* 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;
this.gameMode = gameMode;
this.profile = profile;
this.displayName = displayName;
this.profileKeyData = keyData;
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;
}
/**
* Get the id of the affected profile (since 1.19.3)
* @return the id of the profile
*/
public UUID getProfileId() {
if(profileId == null && profile != null) {
return profile.getUUID(); // Ensure forward compatability
}
return profileId;
}
/**
@@ -79,6 +155,14 @@ public class PlayerInfoData {
return latency;
}
/**
* Gets if the player is listed on the client (since 1.19.3)
* @return if the player is listed
*/
public boolean isListed() {
return listed;
}
/**
* Gets the GameMode of the player represented by this data.
* @return The GameMode
@@ -96,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;
}
/**
@@ -118,12 +212,22 @@ public class PlayerInfoData {
args.add(PacketType.Play.Server.PLAYER_INFO.getPacketClass());
}
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
args.add(UUID.class);
}
args.add(MinecraftReflection.getGameProfileClass());
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
args.add(boolean.class);
}
args.add(int.class);
args.add(EnumWrappers.getGameModeClass());
args.add(MinecraftReflection.getIChatBaseComponentClass());
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
args.add(MinecraftReflection.getRemoteChatSessionDataClass());
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
args.add(MinecraftReflection.getProfilePublicKeyDataClass());
}
@@ -139,17 +243,28 @@ public class PlayerInfoData {
Object gameMode = EnumWrappers.getGameModeConverter().getGeneric(specific.gameMode);
Object displayName = specific.displayName != null ? specific.displayName.handle : null;
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
Object profile = specific.profile != null ? specific.profile.handle : null;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return constructor.newInstance(
specific.profile.handle,
specific.profileId,
profile,
specific.listed,
specific.latency,
gameMode,
displayName,
specific.remoteChatSessionData != null ? BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(specific.remoteChatSessionData) : null
);
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
return constructor.newInstance(
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);
@@ -177,6 +292,16 @@ 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()) {
StructureModifier<WrappedProfileKeyData> keyData = modifier.withType(
@@ -208,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);
}
}
@@ -4,6 +4,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
@@ -142,17 +143,11 @@ public class TroveWrapper {
private static Object getDecorated(@Nonnull Object trove) {
if (trove == null)
throw new IllegalArgumentException("trove instance cannot be non-null.");
AbstractFuzzyMatcher<Class<?>> match = FuzzyMatchers.matchSuper(trove.getClass());
Class<?> decorators = null;
try {
// Attempt to get decorator class
decorators = getClassSource(trove.getClass()).loadClass("TDecorators");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
Class<?> decorators = getClassSource(trove.getClass()).loadClass("TDecorator")
.orElseThrow(() -> new IllegalStateException("TDecorators class not found"));
// Find an appropriate wrapper method in TDecorators
for (Method method : decorators.getMethods()) {
Class<?>[] types = method.getParameterTypes();
@@ -168,6 +168,7 @@ public class WrappedAttribute extends AbstractWrapper {
* @deprecated Removed in 1.17
*/
@Nullable
@Deprecated
public PacketContainer getParentPacket() {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
return null;
@@ -22,7 +22,7 @@ import com.google.common.base.Preconditions;
* @author Kristian
*/
public class WrappedAttributeModifier extends AbstractWrapper {
private static final boolean OPERATION_ENUM = MinecraftVersion.atOrAbove(MinecraftVersion.VILLAGE_UPDATE);
private static final boolean OPERATION_ENUM = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private static final Class<?> OPERATION_CLASS;
private static final EquivalentConverter<Operation> OPERATION_CONVERTER;
@@ -0,0 +1,85 @@
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 com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer;
/**
* Represents a DataValue in 1.19.3+.
*/
public class WrappedDataValue extends AbstractWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getNullableNMS("network.syncher.DataWatcher$b", "network.syncher.SynchedEntityData$DataValue");
private static ConstructorAccessor constructor;
private final StructureModifier<Object> modifier;
/**
* Construct a new NMS wrapper.
*
* @param handle the wrapped data value.
*/
public WrappedDataValue(Object handle) {
super(HANDLE_TYPE);
this.setHandle(handle);
this.modifier = new StructureModifier<>(this.handleType).withTarget(handle);
}
public WrappedDataValue(int index, Serializer serializer, Object value) {
this(newHandle(index, serializer, value));
}
private static Object newHandle(int index, Serializer serializer, Object value) {
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE.getConstructors()[0]);
}
return constructor.invoke(index, serializer.getHandle(), value);
}
public int getIndex() {
return this.modifier.<Integer>withType(int.class).read(0);
}
public void setIndex(int index) {
this.modifier.withType(int.class).write(0, index);
}
public Serializer getSerializer() {
Object serializer = this.modifier.readSafely(1);
if (serializer != null) {
Serializer wrapper = Registry.fromHandle(serializer);
if (wrapper != null) {
return wrapper;
} else {
return new Serializer(null, serializer, false);
}
} else {
return null;
}
}
public void setSerializer(Serializer serializer) {
this.modifier.writeSafely(1, serializer == null ? null : serializer.getHandle());
}
public Object getValue() {
return WrappedWatchableObject.getWrapped(getRawValue());
}
public void setValue(Object value) {
setRawValue(WrappedWatchableObject.getUnwrapped(value));
}
public Object getRawValue() {
return this.modifier.readSafely(2);
}
public void setRawValue(Object value) {
this.modifier.writeSafely(2, value);
}
}
@@ -941,8 +941,8 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable<Wrap
/**
* Gets the first serializer associated with a given class and optional state.
*
* <p><b>Note</b>: If the serializer is optional, values <i>must<i> be
* <br/>
* <b>Note</b>: If the serializer is optional, values <i>must</i> be
* wrapped in an {@link Optional}
*
* @param clazz Class to find serializer for
@@ -16,6 +16,7 @@ import com.google.common.base.Preconditions;
* @author Kristian
* @deprecated IntHashMap no longer exists
*/
@Deprecated
public class WrappedIntHashMap extends AbstractWrapper {
private static Method PUT_METHOD;
private static Method GET_METHOD;
@@ -132,7 +132,7 @@ public final class WrappedLevelChunkData {
* @return a newly created wrapper
*/
public static ChunkData fromValues(NbtCompound heightmapsTag, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.getTrickDataSerializerOrNull(), 0, 0));
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
data.setHeightmapsTag(heightmapsTag);
data.setBuffer(buffer);
@@ -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);
}
}
@@ -8,9 +8,7 @@ import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.mojang.math.Vector3fa;
import org.bukkit.Color;
import org.bukkit.Particle;
import org.bukkit.inventory.ItemStack;
@@ -19,6 +17,8 @@ import org.bukkit.inventory.ItemStack;
* Represents an immutable wrapped ParticleParam in 1.13
*/
public class WrappedParticle<T> {
private static Class<?> VECTOR_3FA;
private static MethodAccessor toBukkit;
private static MethodAccessor toNMS;
private static MethodAccessor toCraftData;
@@ -135,13 +135,27 @@ public class WrappedParticle<T> {
int r, g, b;
float alpha;
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
Vector3fa rgb = (Vector3fa) modifier.withType(Vector3fa.class).read(0);
org.joml.Vector3f rgb = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(0);
r = (int) (rgb.a() * 255);
g = (int) (rgb.b() * 255);
b = (int) (rgb.c() * 255);
r = (int) (rgb.x() * 255);
g = (int) (rgb.y() * 255);
b = (int) (rgb.z() * 255);
alpha = (float) modifier.withType(float.class).read(0);
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
if (VECTOR_3FA == null) {
VECTOR_3FA = MinecraftReflection.getLibraryClass("com.mojang.math.Vector3fa");
}
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
Object rgb = modifier.withType(VECTOR_3FA).read(0);
StructureModifier<Object> rgbModifier = new StructureModifier<>(VECTOR_3FA).withTarget(rgb);
r = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
g = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
b = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
alpha = (float) modifier.withType(float.class).read(0);
} else {
StructureModifier<Float> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class);
@@ -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);
}
}
}
@@ -5,6 +5,7 @@ import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableMap;
import java.lang.reflect.Field;
@@ -14,6 +15,7 @@ import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class WrappedRegistry {
// map of NMS class to registry instance
@@ -23,15 +25,23 @@ public class WrappedRegistry {
private static final MethodAccessor GET_ID;
private static final MethodAccessor GET_KEY;
private static final MethodAccessor GET_HOLDER;
static {
Map<Class<?>, WrappedRegistry> regMap = new HashMap<>();
Class<?> regClass = MinecraftReflection.getIRegistry();
if (regClass != null) {
for (Field field : regClass.getFields()) {
// get the possible registry fields
Class<?> iRegistry = MinecraftReflection.getIRegistry();
Class<?> builtInRegistries = MinecraftReflection.getBuiltInRegistries();
Set<Field> registries = FuzzyReflection.combineArrays(
iRegistry == null ? null : iRegistry.getFields(),
builtInRegistries == null ? null : builtInRegistries.getFields());
if (iRegistry != null && !registries.isEmpty()) {
for (Field field : registries) {
try {
// make sure it's actually a registry
if (regClass.isAssignableFrom(field.getType())) {
if (iRegistry.isAssignableFrom(field.getType())) {
Type genType = field.getGenericType();
if (genType instanceof ParameterizedType) {
ParameterizedType par = (ParameterizedType) genType;
@@ -73,7 +83,7 @@ public class WrappedRegistry {
REGISTRY = ImmutableMap.copyOf(regMap);
FuzzyReflection fuzzy = FuzzyReflection.fromClass(regClass, false);
FuzzyReflection fuzzy = FuzzyReflection.fromClass(iRegistry, false);
GET = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
@@ -93,6 +103,23 @@ public class WrappedRegistry {
.parameterCount(1)
.returnTypeExact(MinecraftReflection.getMinecraftKeyClass())
.build()));
MethodAccessor getHolder = null;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
try {
getHolder = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.banModifier(Modifier.STATIC)
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.PUBLIC)
.build()));
} catch (IllegalArgumentException ignored) {
}
}
GET_HOLDER = getHolder;
}
private final Object handle;
@@ -125,6 +152,10 @@ public class WrappedRegistry {
return (int) GET_ID.invoke(this.handle, entry);
}
public Object getHolder(Object generic) {
return GET_HOLDER.invoke(handle, generic);
}
public static WrappedRegistry getAttributeRegistry() {
return getRegistry(MinecraftReflection.getAttributeBase());
}
@@ -133,6 +164,10 @@ public class WrappedRegistry {
return getRegistry(MinecraftReflection.getDimensionManager());
}
public static WrappedRegistry getSoundRegistry() {
return getRegistry(MinecraftReflection.getSoundEffectClass());
}
public static WrappedRegistry getRegistry(Class<?> type) {
return REGISTRY.get(type);
}
@@ -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,16 +1,13 @@
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.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.utility.Util;
import com.comphenix.protocol.wrappers.ping.LegacyServerPing;
import com.comphenix.protocol.wrappers.ping.ServerPingImpl;
import com.comphenix.protocol.wrappers.ping.ServerPingRecord;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -18,8 +15,6 @@ import com.google.common.io.ByteStreams;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import javax.imageio.ImageIO;
@@ -37,52 +32,14 @@ import java.util.List;
* Represents a server ping packet data.
* @author Kristian
*/
public class WrappedServerPing extends AbstractWrapper implements ClonableWrapper {
private static Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static Class<?> GAME_PROFILE_ARRAY = MinecraftReflection.getArrayClass(GAME_PROFILE);
// Server ping fields
private static Class<?> SERVER_PING = MinecraftReflection.getServerPingClass();
private static ConstructorAccessor SERVER_PING_CONSTRUCTOR = Accessors.getConstructorAccessor(SERVER_PING);
private static FieldAccessor DESCRIPTION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), true);
private static FieldAccessor PLAYERS = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingPlayerSampleClass(), true);
private static FieldAccessor VERSION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingServerDataClass(), true);
private static FieldAccessor FAVICON = Accessors.getFieldAccessor(SERVER_PING, String.class, true);
private static FieldAccessor[] BOOLEAN_ACCESSORS = Accessors.getFieldAccessorArray(SERVER_PING, boolean.class, true);
// For converting to the underlying array
private static EquivalentConverter<Iterable<? extends WrappedGameProfile>> PROFILE_CONVERT =
BukkitConverters.getArrayConverter(GAME_PROFILE, BukkitConverters.getWrappedGameProfileConverter());
// Server ping player sample fields
private static Class<?> PLAYERS_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
private static ConstructorAccessor PLAYERS_CONSTRUCTOR = Accessors.getConstructorAccessor(PLAYERS_CLASS, int.class, int.class);
private static FieldAccessor[] PLAYERS_INTS = Accessors.getFieldAccessorArray(PLAYERS_CLASS, int.class, true);
private static FieldAccessor PLAYERS_PROFILES = Accessors.getFieldAccessor(PLAYERS_CLASS, GAME_PROFILE_ARRAY, true);
private static FieldAccessor PLAYERS_MAXIMUM = PLAYERS_INTS[0];
private static FieldAccessor PLAYERS_ONLINE = PLAYERS_INTS[1];
// Server ping serialization
private static Class<?> GSON_CLASS = MinecraftReflection.getMinecraftGsonClass();
private static MethodAccessor GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class);
private static MethodAccessor GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class);
private static FieldAccessor PING_GSON = Accessors.getMemorizing(Accessors.getFieldAccessor(
PacketType.Status.Server.SERVER_INFO.getPacketClass(), GSON_CLASS, true
));
// Server data fields
private static Class<?> VERSION_CLASS = MinecraftReflection.getServerPingServerDataClass();
private static ConstructorAccessor VERSION_CONSTRUCTOR = Accessors.getConstructorAccessor(VERSION_CLASS, String.class, int.class);
private static FieldAccessor VERSION_NAME = Accessors.getFieldAccessor(VERSION_CLASS, String.class, true);
private static FieldAccessor VERSION_PROTOCOL = Accessors.getFieldAccessor(VERSION_CLASS, int.class, true);
public class WrappedServerPing implements ClonableWrapper {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
// Get profile from player
private static FieldAccessor ENTITY_HUMAN_PROFILE = Accessors.getFieldAccessor(
MinecraftReflection.getEntityPlayerClass().getSuperclass(), GAME_PROFILE, true);
private static final FieldAccessor ENTITY_HUMAN_PROFILE = Accessors.getFieldAccessor(
MinecraftReflection.getEntityPlayerClass().getSuperclass(), GAME_PROFILE, true);
// Inner class
private Object players; // may be NULL
private Object version;
private final ServerPingImpl impl;
/**
* Construct a new server ping initialized with a zero player count, and zero maximum.
@@ -90,34 +47,41 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* Note that the version string is set to 1.9.4.
*/
public WrappedServerPing() {
super(MinecraftReflection.getServerPingClass());
setHandle(SERVER_PING_CONSTRUCTOR.invoke());
resetPlayers();
resetVersion();
this.impl = newImpl();
}
private WrappedServerPing(Object handle) {
super(MinecraftReflection.getServerPingClass());
setHandle(handle);
this.players = PLAYERS.get(handle);
this.version = VERSION.get(handle);
this.impl = newImpl(handle);
}
private static ServerPingImpl newImpl() {
if (MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove()) {
return new ServerPingRecord();
}
return new LegacyServerPing();
}
private static ServerPingImpl newImpl(Object handle) {
if (MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove()) {
return new ServerPingRecord(handle);
}
return new LegacyServerPing(handle);
}
/**
* Set the player count and player maximum to the default values.
*/
protected void resetPlayers() {
players = PLAYERS_CONSTRUCTOR.invoke(0, 0);
PLAYERS.set(handle, players);
private void resetPlayers() {
impl.resetPlayers();
}
/**
* Reset the version string to the default state.
*/
protected void resetVersion() {
MinecraftVersion minecraftVersion = MinecraftVersion.getCurrentVersion();
version = VERSION_CONSTRUCTOR.invoke(minecraftVersion.toString(), MinecraftProtocolVersion.getCurrentVersion());
VERSION.set(handle, version);
private void resetVersion() {
impl.resetVersion();
}
/**
@@ -135,15 +99,18 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return The wrapped server ping.
*/
public static WrappedServerPing fromJson(String json) {
return fromHandle(GSON_FROM_JSON.invoke(PING_GSON.get(null), json, SERVER_PING));
if(MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove()) {
return new WrappedServerPing(ServerPingRecord.fromJson(json).getHandle());
}
return new WrappedServerPing(LegacyServerPing.fromJson(json));
}
/**
* Retrieve the message of the day.
* @return The messge of the day.
* @return The message of the day.
*/
public WrappedChatComponent getMotD() {
return WrappedChatComponent.fromHandle(DESCRIPTION.get(handle));
return impl.getMotD();
}
/**
@@ -151,7 +118,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param description - message of the day.
*/
public void setMotD(WrappedChatComponent description) {
DESCRIPTION.set(handle, description.getHandle());
impl.setMotD(description);
}
/**
@@ -167,7 +134,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return The favicon, or NULL if no favicon will be displayed.
*/
public CompressedImage getFavicon() {
String favicon = (String) FAVICON.get(handle);
String favicon = impl.getFavicon();
return (favicon != null) ? CompressedImage.fromEncodedText(favicon) : null;
}
@@ -176,25 +143,29 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param image - the new compressed image or NULL if no favicon should be displayed.
*/
public void setFavicon(CompressedImage image) {
FAVICON.set(handle, (image != null) ? image.toEncodedText() : null);
impl.setFavicon((image != null) ? image.toEncodedText() : null);
}
/**
* Retrieve whether chat preview is enabled on the server.
* @return whether chat preview is enabled on the server.
* @since 1.19
* @deprecated Removed in 1.19.3
*/
@Deprecated
public boolean isChatPreviewEnabled() {
return (Boolean) BOOLEAN_ACCESSORS[0].get(handle);
return impl.isChatPreviewEnabled();
}
/**
* Sets whether chat preview is enabled on the server.
* @param chatPreviewEnabled true if enabled, false otherwise.
* @since 1.19
* @deprecated Removed in 1.19.3
*/
@Deprecated
public void setChatPreviewEnabled(boolean chatPreviewEnabled) {
BOOLEAN_ACCESSORS[0].set(handle, chatPreviewEnabled);
impl.setChatPreviewEnabled(chatPreviewEnabled);
}
/**
@@ -203,7 +174,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @since 1.19.1
*/
public boolean isEnforceSecureChat() {
return (Boolean) BOOLEAN_ACCESSORS[1].get(handle);
return impl.isEnforceSecureChat();
}
/**
@@ -212,7 +183,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @since 1.19.1
*/
public void setEnforceSecureChat(boolean enforceSecureChat) {
BOOLEAN_ACCESSORS[1].set(handle, enforceSecureChat);
impl.setEnforceSecureChat(enforceSecureChat);
}
/**
@@ -222,9 +193,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @see #setPlayersOnline(int)
*/
public int getPlayersOnline() {
if (players == null)
throw new IllegalStateException("The player count has been hidden.");
return (Integer) PLAYERS_ONLINE.get(players);
return impl.getPlayersOnline();
}
/**
@@ -235,9 +204,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param online - online players.
*/
public void setPlayersOnline(int online) {
if (players == null)
resetPlayers();
PLAYERS_ONLINE.set(players, online);
impl.setPlayersOnline(online);
}
/**
@@ -247,9 +214,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @see #setPlayersMaximum(int)
*/
public int getPlayersMaximum() {
if (players == null)
throw new IllegalStateException("The player maximum has been hidden.");
return (Integer) PLAYERS_MAXIMUM.get(players);
return impl.getPlayersMaximum();
}
/**
@@ -260,9 +225,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param maximum - maximum player count.
*/
public void setPlayersMaximum(int maximum) {
if (players == null)
resetPlayers();
PLAYERS_MAXIMUM.set(players, maximum);
impl.setPlayersMaximum(maximum);
}
/**
@@ -273,14 +236,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
*/
public void setPlayersVisible(boolean visible) {
if (isPlayersVisible() != visible) {
if (visible) {
// Recreate the count and maximum
Server server = Bukkit.getServer();
setPlayersMaximum(server.getMaxPlayers());
setPlayersOnline(Bukkit.getOnlinePlayers().size());
} else {
PLAYERS.set(handle, players = null);
}
impl.setPlayersVisible(visible);
}
}
@@ -291,7 +247,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return TRUE if the player statistics is visible, FALSE otherwise.
*/
public boolean isPlayersVisible() {
return players != null;
return impl.arePlayersVisible();
}
/**
@@ -299,12 +255,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return Logged in players or an empty list if no player names will be displayed.
*/
public ImmutableList<WrappedGameProfile> getPlayers() {
if (players == null)
return ImmutableList.of();
Object playerProfiles = PLAYERS_PROFILES.get(players);
if (playerProfiles == null)
return ImmutableList.of();
return ImmutableList.copyOf(PROFILE_CONVERT.getSpecific(playerProfiles));
return impl.getPlayers();
}
/**
@@ -312,9 +263,9 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param profile - every logged in player.
*/
public void setPlayers(Iterable<? extends WrappedGameProfile> profile) {
if (players == null)
if (!isPlayersVisible())
resetPlayers();
PLAYERS_PROFILES.set(players, (profile != null) ? PROFILE_CONVERT.getGeneric(profile) : null);
impl.setPlayers(profile);
}
/**
@@ -337,7 +288,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return The version name.
*/
public String getVersionName() {
return (String) VERSION_NAME.get(version);
return impl.getVersionName();
}
/**
@@ -345,7 +296,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param name - the new version name.
*/
public void setVersionName(String name) {
VERSION_NAME.set(version, name);
impl.setVersionName(name);
}
/**
@@ -353,7 +304,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return The protocol.
*/
public int getVersionProtocol() {
return (Integer) VERSION_PROTOCOL.get(version);
return impl.getVersionProtocol();
}
/**
@@ -361,7 +312,12 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @param protocol - the protocol number.
*/
public void setVersionProtocol(int protocol) {
VERSION_PROTOCOL.set(version, protocol);
impl.setVersionProtocol(protocol);
}
@Override
public Object getHandle() {
return impl.getHandle();
}
/**
@@ -392,7 +348,7 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
* @return The JSON representation.
*/
public String toJson() {
return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), handle);
return impl.getJson();
}
@Override
@@ -587,4 +543,17 @@ public class WrappedServerPing extends AbstractWrapper implements ClonableWrappe
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);
}
}
@@ -136,10 +136,10 @@ public abstract class ConvertedMap<Key, VInner, VOuter> extends AbstractConverte
/**
* Returns a string representation of this map. The string representation
* consists of a list of key-value mappings in the order returned by the
* map's <tt>entrySet</tt> view's iterator, enclosed in braces
* (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
* <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<tt>"="</tt>) followed by the
* map's <code>entrySet</code> view's iterator, enclosed in braces
* (<code>"{}"</code>). Adjacent mappings are separated by the characters
* <code>", "</code> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<code>"="</code>) followed by the
* associated value. Keys and values are converted to strings as by
* {@link String#valueOf(Object)}.
*
@@ -204,10 +204,10 @@ public abstract class ConvertedMultimap<Key, VInner, VOuter> extends AbstractCon
/**
* Returns a string representation of this map. The string representation
* consists of a list of key-value mappings in the order returned by the
* map's <tt>entrySet</tt> view's iterator, enclosed in braces
* (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
* <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<tt>"="</tt>) followed by the
* map's <code>entrySet</code> view's iterator, enclosed in braces
* (<code>"{}"</code>). Adjacent mappings are separated by the characters
* <code>", "</code> (comma and space). Each key-value mapping is rendered as
* the key followed by an equals sign (<code>"="</code>) followed by the
* associated value. Keys and values are converted to strings as by
* {@link String#valueOf(Object)}.
*
@@ -0,0 +1,402 @@
package com.comphenix.protocol.wrappers.ping;
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.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.AbstractWrapper;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.google.common.collect.ImmutableList;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a server ping packet data.
* @author Kristian
*/
public final class LegacyServerPing extends AbstractWrapper implements ServerPingImpl {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
// For converting to the underlying array
private static final EquivalentConverter<Iterable<? extends WrappedGameProfile>> PROFILE_CONVERT =
BukkitConverters.getArrayConverter(GAME_PROFILE, BukkitConverters.getWrappedGameProfileConverter());
// Get profile from player
private static final FieldAccessor ENTITY_HUMAN_PROFILE = Accessors.getFieldAccessor(
MinecraftReflection.getEntityPlayerClass().getSuperclass(), GAME_PROFILE, true);
private static final Class<?> GAME_PROFILE_ARRAY = MinecraftReflection.getArrayClass(GAME_PROFILE);
// Server ping fields
private static final Class<?> SERVER_PING = MinecraftReflection.getServerPingClass();
private static final ConstructorAccessor SERVER_PING_CONSTRUCTOR = Accessors.getConstructorAccessor(SERVER_PING);
private static final FieldAccessor DESCRIPTION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), true);
private static final FieldAccessor PLAYERS = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingPlayerSampleClass(), true);
private static final FieldAccessor VERSION = Accessors.getFieldAccessor(SERVER_PING, MinecraftReflection.getServerPingServerDataClass(), true);
private static final FieldAccessor FAVICON = Accessors.getFieldAccessor(SERVER_PING, String.class, true);
private static final FieldAccessor[] BOOLEAN_ACCESSORS = Accessors.getFieldAccessorArray(SERVER_PING, boolean.class, true);
// Server ping player sample fields
private static final Class<?> PLAYERS_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
private static final ConstructorAccessor PLAYERS_CONSTRUCTOR = Accessors.getConstructorAccessor(PLAYERS_CLASS, int.class, int.class);
private static final FieldAccessor[] PLAYERS_INTS = Accessors.getFieldAccessorArray(PLAYERS_CLASS, int.class, true);
private static final FieldAccessor PLAYERS_PROFILES = Accessors.getFieldAccessor(PLAYERS_CLASS, GAME_PROFILE_ARRAY, true);
private static final FieldAccessor PLAYERS_MAXIMUM = PLAYERS_INTS[0];
private static final FieldAccessor PLAYERS_ONLINE = PLAYERS_INTS[1];
// Server ping serialization
private static final Class<?> GSON_CLASS = MinecraftReflection.getMinecraftGsonClass();
private static final MethodAccessor GSON_TO_JSON = Accessors.getMethodAccessor(GSON_CLASS, "toJson", Object.class);
private static final MethodAccessor GSON_FROM_JSON = Accessors.getMethodAccessor(GSON_CLASS, "fromJson", String.class, Class.class);
private static final FieldAccessor PING_GSON = Accessors.getMemorizing(Accessors.getFieldAccessor(
PacketType.Status.Server.SERVER_INFO.getPacketClass(), GSON_CLASS, true
));
// Server data fields
private static final Class<?> VERSION_CLASS = MinecraftReflection.getServerPingServerDataClass();
private static final ConstructorAccessor VERSION_CONSTRUCTOR = Accessors.getConstructorAccessor(VERSION_CLASS, String.class, int.class);
private static final FieldAccessor VERSION_NAME = Accessors.getFieldAccessor(VERSION_CLASS, String.class, true);
private static final FieldAccessor VERSION_PROTOCOL = Accessors.getFieldAccessor(VERSION_CLASS, int.class, true);
// Inner class
private Object players; // may be NULL
private Object version;
/**
* Construct a new server ping initialized with a zero player count, and zero maximum.
* <p>
* Note that the version string is set to 1.9.4.
*/
public LegacyServerPing() {
super(MinecraftReflection.getServerPingClass());
setHandle(SERVER_PING_CONSTRUCTOR.invoke());
resetPlayers();
resetVersion();
}
public LegacyServerPing(Object handle) {
super(MinecraftReflection.getServerPingClass());
setHandle(handle);
this.players = PLAYERS.get(handle);
this.version = VERSION.get(handle);
}
/**
* Set the player count and player maximum to the default values.
*/
public void resetPlayers() {
players = PLAYERS_CONSTRUCTOR.invoke(0, 0);
PLAYERS.set(handle, players);
}
/**
* Reset the version string to the default state.
*/
public void resetVersion() {
MinecraftVersion minecraftVersion = MinecraftVersion.getCurrentVersion();
version = VERSION_CONSTRUCTOR.invoke(minecraftVersion.toString(), MinecraftProtocolVersion.getCurrentVersion());
VERSION.set(handle, version);
}
/**
* Construct a wrapped server ping from a native NMS object.
* @param handle - the native object.
* @return The wrapped server ping object.
*/
public static LegacyServerPing fromHandle(Object handle) {
return new LegacyServerPing(handle);
}
/**
* Construct a wrapper server ping from an encoded JSON string.
* @param json - the JSON string.
* @return The wrapped server ping.
*/
public static LegacyServerPing fromJson(String json) {
return fromHandle(GSON_FROM_JSON.invoke(PING_GSON.get(null), json, SERVER_PING));
}
/**
* Retrieve the message of the day.
* @return The message of the day.
*/
@Override
public WrappedChatComponent getMotD() {
return WrappedChatComponent.fromHandle(DESCRIPTION.get(handle));
}
/**
* Set the message of the day.
* @param description - message of the day.
*/
@Override
public void setMotD(WrappedChatComponent description) {
DESCRIPTION.set(handle, description != null ? description.getHandle() : null);
}
/**
* Retrieve the compressed PNG file that is being displayed as a favicon.
* @return The favicon, or NULL if no favicon will be displayed.
*/
@Override
public String getFavicon() {
return (String) FAVICON.get(handle);
}
/**
* Set the compressed PNG file that is being displayed.
* @param image - the new compressed image or NULL if no favicon should be displayed.
*/
@Override
public void setFavicon(String image) {
FAVICON.set(handle, image);
}
/**
* Retrieve whether chat preview is enabled on the server.
* @return whether chat preview is enabled on the server.
* @since 1.19
* @deprecated Removed in 1.19.3
*/
@Deprecated
public boolean isChatPreviewEnabled() {
return (Boolean) BOOLEAN_ACCESSORS[0].get(handle);
}
/**
* Sets whether chat preview is enabled on the server.
* @param chatPreviewEnabled true if enabled, false otherwise.
* @since 1.19
* @deprecated Removed in 1.19.3
*/
@Deprecated
public void setChatPreviewEnabled(boolean chatPreviewEnabled) {
BOOLEAN_ACCESSORS[0].set(handle, chatPreviewEnabled);
}
/**
* Sets whether the server enforces secure chat.
* @return whether the server enforces secure chat.
* @since 1.19.1
*/
@Override
public boolean isEnforceSecureChat() {
int index = MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove() ? 0 : 1;
return (Boolean) BOOLEAN_ACCESSORS[index].get(handle);
}
/**
* Sets whether the server enforces secure chat.
* @param enforceSecureChat true if enabled, false otherwise.
* @since 1.19.1
*/
@Override
public void setEnforceSecureChat(boolean enforceSecureChat) {
int index = MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove() ? 0 : 1;
BOOLEAN_ACCESSORS[index].set(handle, enforceSecureChat);
}
/**
* Retrieve the displayed number of online players.
* @return The displayed number.
* @throws IllegalStateException If the player count has been hidden via {@link #setPlayersVisible(boolean)}.
* @see #setPlayersOnline(int)
*/
@Override
public int getPlayersOnline() {
if (players == null)
throw new IllegalStateException("The player count has been hidden.");
return (Integer) PLAYERS_ONLINE.get(players);
}
/**
* Set the displayed number of online players.
* <p>
* As of 1.7.2, this is completely unrestricted, and can be both positive and
* negative, as well as higher than the player maximum.
* @param online - online players.
*/
@Override
public void setPlayersOnline(int online) {
if (players == null)
resetPlayers();
PLAYERS_ONLINE.set(players, online);
}
/**
* Retrieve the displayed maximum number of players.
* @return The maximum number.
* @throws IllegalStateException If the player maximum has been hidden via {@link #setPlayersVisible(boolean)}.
* @see #setPlayersMaximum(int)
*/
@Override
public int getPlayersMaximum() {
if (players == null)
throw new IllegalStateException("The player maximum has been hidden.");
return (Integer) PLAYERS_MAXIMUM.get(players);
}
/**
* Set the displayed maximum number of players.
* <p>
* The 1.7.2 accepts any value as a player maximum, positive or negative. It even permits a player maximum that
* is less than the player count.
* @param maximum - maximum player count.
*/
@Override
public void setPlayersMaximum(int maximum) {
if (players == null)
resetPlayers();
PLAYERS_MAXIMUM.set(players, maximum);
}
/**
* Set whether or not the player count and player maximum is visible.
* <p>
* Note that this may set the current player count and maximum to their respective real values.
* @param visible - TRUE if it should be visible, FALSE otherwise.
*/
@Override
public void setPlayersVisible(boolean visible) {
if (arePlayersVisible() != visible) {
if (visible) {
// Recreate the count and maximum
Server server = Bukkit.getServer();
setPlayersMaximum(server.getMaxPlayers());
setPlayersOnline(Bukkit.getOnlinePlayers().size());
} else {
PLAYERS.set(handle, players = null);
}
}
}
@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>
* If not, the client will display ??? in the same location.
* @return TRUE if the player statistics is visible, FALSE otherwise.
*/
@Override
public boolean arePlayersVisible() {
return players != null;
}
/**
* Retrieve a copy of all the logged in players.
* @return Logged in players or an empty list if no player names will be displayed.
*/
@Override
public ImmutableList<WrappedGameProfile> getPlayers() {
if (players == null)
return ImmutableList.of();
Object playerProfiles = PLAYERS_PROFILES.get(players);
if (playerProfiles == null)
return ImmutableList.of();
return ImmutableList.copyOf(PROFILE_CONVERT.getSpecific(playerProfiles));
}
/**
* Set the displayed list of logged in players.
* @param playerSample - every logged in player.
*/
@Override
public void setPlayers(Iterable<? extends WrappedGameProfile> playerSample) {
if (players == null)
resetPlayers();
PLAYERS_PROFILES.set(players, PROFILE_CONVERT.getGeneric(playerSample));
}
/**
* Retrieve the version name of the current server.
* @return The version name.
*/
@Override
public String getVersionName() {
return (String) VERSION_NAME.get(version);
}
/**
* Set the version name of the current server.
* @param name - the new version name.
*/
@Override
public void setVersionName(String name) {
VERSION_NAME.set(version, name);
}
/**
* Retrieve the protocol number.
* @return The protocol.
*/
@Override
public int getVersionProtocol() {
return (Integer) VERSION_PROTOCOL.get(version);
}
/**
* Set the version protocol
* @param protocol - the protocol number.
*/
@Override
public void setVersionProtocol(int protocol) {
VERSION_PROTOCOL.set(version, protocol);
}
/**
* Retrieve a deep copy of the current wrapper object.
* @return The current object.
*/
public LegacyServerPing deepClone() {
LegacyServerPing copy = new LegacyServerPing();
WrappedChatComponent motd = getMotD();
copy.setPlayers(getPlayers());
copy.setFavicon(getFavicon());
copy.setMotD(motd != null ? motd : null);
copy.setVersionName(getVersionName());
copy.setVersionProtocol(getVersionProtocol());
if (arePlayersVisible()) {
copy.setPlayersMaximum(getPlayersMaximum());
copy.setPlayersOnline(getPlayersOnline());
} else {
copy.setPlayersVisible(false);
}
return copy;
}
/**
* Retrieve the underlying JSON representation of this server ping.
* @return The JSON representation.
*/
public String toJson() {
return (String) GSON_TO_JSON.invoke(PING_GSON.get(null), handle);
}
@Override
public String toString() {
return "WrappedServerPing< " + toJson() + ">";
}
}
@@ -0,0 +1,41 @@
package com.comphenix.protocol.wrappers.ping;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.collect.ImmutableList;
public interface ServerPingImpl extends Cloneable {
WrappedChatComponent getMotD();
void setMotD(WrappedChatComponent description);
int getPlayersMaximum();
void setPlayersMaximum(int maxPlayers);
int getPlayersOnline();
void setPlayersOnline(int onlineCount);
ImmutableList<WrappedGameProfile> getPlayers();
void setPlayers(Iterable<? extends WrappedGameProfile> playerSample);
String getVersionName();
void setVersionName(String versionName);
int getVersionProtocol();
void setVersionProtocol(int protocolVersion);
String getFavicon();
void setFavicon(String favicon);
boolean isEnforceSecureChat();
void setEnforceSecureChat(boolean safeChat);
void resetPlayers();
void resetVersion();
default boolean isChatPreviewEnabled() {
return false;
}
default void setChatPreviewEnabled(boolean enabled) {
}
boolean arePlayersVisible();
void setPlayersVisible(boolean visible);
String getJson();
Object getHandle();
}
@@ -0,0 +1,346 @@
package com.comphenix.protocol.wrappers.ping;
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;
private static boolean initialized = false;
private static final Object lock = new Object();
private static void initialize() {
if (initialized) {
return;
}
synchronized (lock) {
// may have been initialized while waiting for the lock
if (initialized) {
return;
}
try {
SERVER_PING = MinecraftReflection.getServerPingClass();
PLAYER_SAMPLE_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
SERVER_DATA_CLASS = MinecraftReflection.getServerPingServerDataClass();
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING.getConstructors()[0]);
DATA_WRAPPER = AutoWrapper.wrap(ServerData.class, SERVER_DATA_CLASS);
SAMPLE_WRAPPER = AutoWrapper.wrap(PlayerSample.class, PLAYER_SAMPLE_CLASS);
FAVICON_WRAPPER = AutoWrapper.wrap(Favicon.class, MinecraftReflection.getMinecraftClass("network.protocol.status.ServerPing$a"));
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 {
initialized = true;
}
}
}
public static final class PlayerSample {
public int max;
public int online;
public Object sample;
public PlayerSample(int max, int online, Object sample) {
this.max = max;
this.online = online;
this.sample = sample;
}
public PlayerSample() {
this(0, 0, null);
}
}
public static final class ServerData {
public String name;
public int protocol;
public ServerData(String name, int protocol) {
this.name = name;
this.protocol = protocol;
}
public ServerData() {
this("", 0);
}
}
static final byte[] EMPTY_FAVICON = new byte[0];
public static final class Favicon {
public byte[] iconBytes;
public Favicon(byte[] iconBytes) {
this.iconBytes = iconBytes;
}
public Favicon() {
this(EMPTY_FAVICON);
}
}
private static AutoWrapper<PlayerSample> SAMPLE_WRAPPER;
private static AutoWrapper<ServerData> DATA_WRAPPER;
private static AutoWrapper<Favicon> FAVICON_WRAPPER;
private WrappedChatComponent description;
private PlayerSample playerSample;
private ServerData serverData;
private Favicon favicon;
private boolean enforceSafeChat;
private boolean playersVisible = true;
private static ServerData defaultData() {
String name = MinecraftVersion.getCurrentVersion().toString();
int protocol = MinecraftProtocolVersion.getCurrentVersion();
return new ServerData(name, protocol);
}
private static PlayerSample defaultSample() {
int max = Bukkit.getMaxPlayers();
int online = Bukkit.getOnlinePlayers().size();
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);
this.description = structure.getChatComponents().readSafely(0);
StructureModifier<Optional<Object>> optionals = structure.getOptionals(Converters.passthrough(Object.class));
Optional<Object> sampleHandle = optionals.readSafely(0);
this.playerSample = sampleHandle.isPresent() ? SAMPLE_WRAPPER.wrap(sampleHandle.get()) : defaultSample();
Optional<Object> dataHandle = optionals.readSafely(1);
this.serverData = dataHandle.isPresent() ? DATA_WRAPPER.wrap(dataHandle.get()) : defaultData();
Optional<Object> faviconHandle = optionals.readSafely(2);
this.favicon = faviconHandle.isPresent() ? FAVICON_WRAPPER.wrap(faviconHandle.get()) : defaultFavicon();
this.enforceSafeChat = structure.getBooleans().readSafely(0);
}
public ServerPingRecord() {
initialize();
this.description = DEFAULT_DESCRIPTION;
this.playerSample = defaultSample();
this.serverData = defaultData();
this.favicon = defaultFavicon();
}
@Override
public WrappedChatComponent getMotD() {
return description;
}
@Override
public void setMotD(WrappedChatComponent description) {
this.description = description;
}
@Override
public int getPlayersMaximum() {
return playerSample.max;
}
@Override
public void setPlayersMaximum(int maxPlayers) {
playerSample.max = maxPlayers;
}
@Override
public int getPlayersOnline() {
return playerSample.online;
}
@Override
public void setPlayersOnline(int onlineCount) {
playerSample.online = onlineCount;
}
@Override
public ImmutableList<WrappedGameProfile> getPlayers() {
if (playerSample.sample == null) {
return ImmutableList.of();
}
List<WrappedGameProfile> list = PROFILE_LIST_CONVERTER.getSpecific(playerSample.sample);
if (list == null) {
return ImmutableList.of();
}
return ImmutableList.copyOf(list);
}
@Override
public void setPlayers(Iterable<? extends WrappedGameProfile> playerSample) {
if (playerSample == null) {
this.playerSample.sample = null;
return;
}
List<WrappedGameProfile> list = Converters.toList(playerSample);
this.playerSample.sample = PROFILE_LIST_CONVERTER.getGeneric(list);
}
@Override
public String getVersionName() {
return serverData.name;
}
@Override
public void setVersionName(String versionName) {
serverData.name = versionName;
}
@Override
public int getVersionProtocol() {
return serverData.protocol;
}
@Override
public void setVersionProtocol(int protocolVersion) {
serverData.protocol = protocolVersion;
}
@Override
public String getFavicon() {
return new String(favicon.iconBytes, StandardCharsets.UTF_8);
}
@Override
public void setFavicon(String favicon) {
this.favicon.iconBytes = favicon.getBytes(StandardCharsets.UTF_8);
}
@Override
public boolean isEnforceSecureChat() {
return enforceSafeChat;
}
@Override
public void setEnforceSecureChat(boolean safeChat) {
this.enforceSafeChat = safeChat;
}
@Override
public void resetPlayers() {
this.playerSample = defaultSample();
}
@Override
public void resetVersion() {
this.serverData = defaultData();
}
@Override
public boolean arePlayersVisible() {
return playersVisible;
}
@Override
public void setPlayersVisible(boolean visible) {
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(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
+1 -1
View File
@@ -1,5 +1,5 @@
name: ProtocolLib
version: ${project.fullVersion}
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2, comphenix]
@@ -1,13 +1,11 @@
package com.comphenix.protocol;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflectionTestUtil;
import java.util.Collections;
import java.util.List;
import net.minecraft.SharedConstants;
import net.minecraft.core.IRegistry;
import net.minecraft.server.DispenserRegistry;
@@ -16,12 +14,15 @@ import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_19_R1.util.Versioning;
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_19_R3.util.Versioning;
import org.spigotmc.SpigotWorldConfig;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Used to ensure that ProtocolLib and Bukkit is prepared to be tested.
*
@@ -40,17 +41,24 @@ public class BukkitInitialization {
/**
* Statically initializes the mock server for unit testing
*/
public static synchronized void initializeAll() {
public static void initializeAll() {
instance.initialize();
}
private static final Object initLock = new Object();
/**
* Initialize Bukkit and ProtocolLib such that we can perfrom unit testing
* Initialize Bukkit and ProtocolLib such that we can perform unit testing
*/
private void initialize() {
if (!this.initialized) {
// Denote that we're done
this.initialized = true;
if (initialized) {
return;
}
synchronized (initLock) {
if (initialized) {
return;
}
try {
LogManager.getLogger();
@@ -70,7 +78,7 @@ public class BukkitInitialization {
ex.printStackTrace();
}
String releaseTarget = SharedConstants.b().getReleaseTarget();
String releaseTarget = MinecraftReflectionTestUtil.RELEASE_TARGET;
String serverVersion = CraftServer.class.getPackage().getImplementationVersion();
// Mock the server object
@@ -103,6 +111,8 @@ public class BukkitInitialization {
// Inject this fake server
Bukkit.setServer(mockedServer);
initialized = true;
}
}
@@ -123,4 +133,4 @@ public class BukkitInitialization {
MinecraftReflectionTestUtil.init();
}
}
}
}
@@ -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;
@@ -49,9 +53,9 @@ public class PacketTypeTest {
BukkitInitialization.initializeAll();
// I'm well aware this is jank, but it does in fact work correctly and give the desired result
PacketType.onDynamicCreate = className -> {
/*PacketType.onDynamicCreate = className -> {
throw new RuntimeException("Dynamically generated packet " + className);
};
};*/
}
@AfterAll
@@ -61,7 +65,9 @@ public class PacketTypeTest {
}
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
// @Test
// public static void main(String[] args) throws Exception {
public void generateNewPackets() throws Exception {
MinecraftReflectionTestUtil.init();
Set<Class<?>> allTypes = new HashSet<>();
@@ -71,7 +77,7 @@ public class PacketTypeTest {
for (EnumProtocol protocol : protocols) {
System.out.println(WordUtils.capitalize(protocol.name().toLowerCase()));
Field field = EnumProtocol.class.getDeclaredField("j");
Field field = EnumProtocol.class.getDeclaredField("k");
field.setAccessible(true);
Map<EnumProtocolDirection, Object> map = (Map<EnumProtocolDirection, Object>) field.get(protocol);
@@ -173,18 +179,35 @@ public class PacketTypeTest {
className = classNames.get(classNames.size() - 1);
// Format it like SET_PROTOCOL
StringBuilder fieldName = new StringBuilder();
char[] chars = className.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (i != 0 && Character.isUpperCase(c)) {
fieldName.append("_");
PacketType existing = null;
try {
existing = PacketType.fromClass(clazz);
if (existing.isDynamic()) {
existing = null;
}
fieldName.append(Character.toUpperCase(c));
} catch (Exception ignored) {
// doesn't exist
}
builder.append(fieldName.toString().replace("N_B_T", "NBT"));
String fieldName;
if (existing == null) {
// Format it like SET_PROTOCOL
StringBuilder fieldNameBuilder = new StringBuilder();
char[] chars = className.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (i != 0 && Character.isUpperCase(c)) {
fieldNameBuilder.append("_");
}
fieldNameBuilder.append(Character.toUpperCase(c));
}
fieldName = fieldNameBuilder.toString().replace("N_B_T", "NBT");
} else {
fieldName = existing.name();
}
builder.append(fieldName);
builder.append(" = ");
// Add spacing
@@ -248,11 +271,6 @@ public class PacketTypeTest {
return builder.toString();
}
@BeforeAll
public static void initializeReflection() {
BukkitInitialization.initializeAll();
}
@Test
public void testFindCurrent() {
assertEquals(PacketType.Play.Client.STEER_VEHICLE,
@@ -278,42 +296,54 @@ public class PacketTypeTest {
@Test
@SuppressWarnings("unchecked")
public void ensureTypesAreCorrect() throws Exception {
boolean fail = false;
PacketType.onDynamicCreate = className -> {
throw new RuntimeException("Dynamically generated packet " + className);
};
EnumProtocol[] protocols = EnumProtocol.values();
for (EnumProtocol protocol : protocols) {
Field field = EnumProtocol.class.getDeclaredField("j");
field.setAccessible(true);
try {
boolean fail = false;
Map<EnumProtocolDirection, Object> map = (Map<EnumProtocolDirection, Object>) field.get(protocol);
for (Entry<EnumProtocolDirection, Object> entry : map.entrySet()) {
Field mapField = entry.getValue().getClass().getDeclaredField("b");
mapField.setAccessible(true);
EnumProtocol[] protocols = EnumProtocol.values();
for (EnumProtocol protocol : protocols) {
Field field = EnumProtocol.class.getDeclaredField("k");
field.setAccessible(true);
Map<Class<?>, Integer> reverseMap = (Map<Class<?>, Integer>) mapField.get(entry.getValue());
Map<EnumProtocolDirection, Object> map = (Map<EnumProtocolDirection, Object>) field.get(protocol);
for (Entry<EnumProtocolDirection, Object> entry : map.entrySet()) {
Field mapField = entry.getValue().getClass().getDeclaredField("b");
mapField.setAccessible(true);
Map<Integer, Class<?>> treeMap = new TreeMap<>();
for (Entry<Class<?>, Integer> entry1 : reverseMap.entrySet()) {
treeMap.put(entry1.getValue(), entry1.getKey());
}
Map<Class<?>, Integer> reverseMap = (Map<Class<?>, Integer>) mapField.get(entry.getValue());
for (Entry<Integer, Class<?>> entry1 : treeMap.entrySet()) {
try {
PacketType type = PacketType.fromClass(entry1.getValue());
if (type.getCurrentId() != entry1.getKey()) {
throw new IllegalStateException(
"Packet ID for " + type + " is incorrect. Expected " + entry1.getKey() + ", but got "
+ type.getCurrentId());
Map<Integer, Class<?>> treeMap = new TreeMap<>();
for (Entry<Class<?>, Integer> entry1 : reverseMap.entrySet()) {
treeMap.put(entry1.getValue(), entry1.getKey());
}
for (Entry<Integer, Class<?>> entry1 : treeMap.entrySet()) {
try {
PacketType type = PacketType.fromClass(entry1.getValue());
if (type.getCurrentId() != entry1.getKey()) {
throw new IllegalStateException(
"Packet ID for " + type + " is incorrect. Expected " + entry1.getKey() + ", but got "
+ type.getCurrentId());
}
} catch (Throwable ex) {
if (ex.getMessage().contains("BundleDelimiterPacket")) {
continue;
}
ex.printStackTrace();
fail = true;
}
} catch (Throwable ex) {
ex.printStackTrace();
fail = true;
}
}
}
}
assertFalse(fail, "Packet type(s) were incorrect!");
assertFalse(fail, "Packet type(s) were incorrect!");
} finally {
PacketType.onDynamicCreate = __ -> { };
}
}
@Test
@@ -331,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);
}
}
@@ -19,6 +19,9 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -37,27 +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.Either;
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.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Lists;
@@ -67,7 +56,7 @@ import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.minecraft.core.IRegistry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
@@ -90,6 +79,7 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.assertItemCollectionsEqual;
@@ -100,7 +90,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -342,26 +331,19 @@ public class PacketContainerTest {
}
@Test
public void testGetWatchableCollectionModifier() {
public void testGetDataValueCollectionModifier() {
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
entityMetadata.getWatchableCollectionModifier();
StructureModifier<List<WrappedDataValue>> watchableAccessor = entityMetadata.getDataValueCollectionModifier();
assertNull(watchableAccessor.read(0));
assertEquals(0, watchableAccessor.read(0).size());
WrappedDataWatcher watcher = new WrappedDataWatcher();
watcher.setObject(0, Registry.get(String.class), "Test");
watcher.setObject(1, Registry.get(Byte.class), (byte) 21);
List<WrappedWatchableObject> list = watcher.getWatchableObjects();
List<WrappedDataValue> values = Lists.newArrayList(
new WrappedDataValue(0, Registry.get(Byte.class), (byte) 21),
new WrappedDataValue(1, Registry.get(String.class), "World"));
// Insert and read back
watchableAccessor.write(0, list);
assertEquals(list, watchableAccessor.read(0));
// Put it into a new data watcher
WrappedDataWatcher newWatcher = new WrappedDataWatcher(watchableAccessor.read(0));
assertEquals(newWatcher.getWatchableObjects(), list);
watchableAccessor.write(0, values);
assertEquals(values, watchableAccessor.read(0));
}
@Test
@@ -461,7 +443,7 @@ public class PacketContainerTest {
// are inner classes (which is ultimately pointless because AttributeSnapshots don't access any
// members of the packet itself)
PacketPlayOutUpdateAttributes packet = (PacketPlayOutUpdateAttributes) attribute.getHandle();
AttributeBase base = IRegistry.ak.a(MinecraftKey.a("generic.max_health"));
AttributeBase base = BuiltInRegistries.u.a(MinecraftKey.a("generic.max_health"));
AttributeSnapshot snapshot = new AttributeSnapshot(base, 20.0D, modifiers);
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
@@ -575,6 +557,8 @@ public class PacketContainerTest {
@Test
public void testSoundEffects() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.NAMED_SOUND_EFFECT);
container.getSoundEffects().optionRead(0);
container.getSoundEffects().write(0, Sound.ENTITY_CAT_HISS);
assertEquals(container.getSoundEffects().read(0), Sound.ENTITY_CAT_HISS);
@@ -753,47 +737,54 @@ public class PacketContainerTest {
assertArrayEquals(components, back);
}
@Test
public void testLoginSignatureNonce() {
PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3});
@Test
public void testPlayerInfoActions() {
PacketContainer updatePlayerInfoActions = new PacketContainer(PacketType.Play.Server.PLAYER_INFO);
byte[] nonce = {4, 5, 6};
encryptionStart.getLoginSignatures().write(0, Either.left(nonce));
EnumSet<EnumWrappers.PlayerInfoAction> actions = EnumSet.of(
EnumWrappers.PlayerInfoAction.ADD_PLAYER,
EnumWrappers.PlayerInfoAction.UPDATE_LATENCY,
EnumWrappers.PlayerInfoAction.UPDATE_LISTED);
updatePlayerInfoActions.getPlayerInfoActions().write(0, actions);
byte[] read = encryptionStart.getLoginSignatures().read(0).left().get();
assertArrayEquals(nonce, read);
}
UUID id = UUID.randomUUID();
PlayerInfoData data = new PlayerInfoData(
id,
20,
false,
NativeGameMode.CREATIVE,
new WrappedGameProfile(new UUID(0, 0), "system"),
null,
(WrappedRemoteChatSessionData) null);
updatePlayerInfoActions.getPlayerInfoDataLists().write(1, Collections.singletonList(data));
@Test
public void testLoginSignatureSigned() {
PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3});
Set<EnumWrappers.PlayerInfoAction> readActions = updatePlayerInfoActions.getPlayerInfoActions().read(0);
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.ADD_PLAYER));
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.UPDATE_LATENCY));
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.UPDATE_LISTED));
byte[] signature = new byte[512];
long salt = 124L;
encryptionStart.getLoginSignatures().write(0, Either.right(new WrappedSaltedSignature(salt, signature)));
Collection<PlayerInfoData> readData = updatePlayerInfoActions.getPlayerInfoDataLists().read(1);
Assertions.assertEquals(1, readData.size());
WrappedSaltedSignature read = encryptionStart.getLoginSignatures().read(0).right().get();
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}
PlayerInfoData firstData = readData.iterator().next();
Assertions.assertFalse(data.isListed());
Assertions.assertEquals(id, data.getProfileId());
Assertions.assertEquals("system", firstData.getProfile().getName());
Assertions.assertEquals(20, firstData.getLatency());
Assertions.assertEquals(NativeGameMode.CREATIVE, firstData.getGameMode());
}
// TODO: fix this this at some point
/*
@Test
public void testSignedChatMessage() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Client.CHAT);
byte[] signature = new byte[512];
long salt = 124L;
WrappedSaltedSignature wrappedSignature = new WrappedSaltedSignature(salt, signature);
chatPacket.getSignatures().write(0, wrappedSignature);
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();
@@ -821,7 +812,8 @@ public class PacketContainerTest {
// Try constructing all the packets
for (PacketType type : PacketType.values()) {
// TODO: try to support chat - for now chat contains to many sub classes to properly clone it
if (type.isDeprecated() || !type.isSupported() || type.name().contains("CUSTOM_PAYLOAD") || type.name().contains("CHAT")) {
if (type.isDeprecated() || !type.isSupported() || type.name().contains("CUSTOM_PAYLOAD") || type.name().contains("CHAT")
|| type.name().contains("BUNDLE")) {
continue;
}
@@ -833,34 +825,21 @@ public class PacketContainerTest {
// Make sure watchable collections can be cloned
if (type == PacketType.Play.Server.ENTITY_METADATA) {
constructed.getWatchableCollectionModifier().write(0, Lists.newArrayList(
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Byte.class)),
(byte) 1),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Integer.class)),
1),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Float.class)),
1F),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(String.class)),
"String"),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Boolean.class)),
true),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)),
constructed.getDataValueCollectionModifier().write(0, Lists.newArrayList(
new WrappedDataValue(0, Registry.get(Byte.class), (byte) 1),
new WrappedDataValue(0, Registry.get(Float.class), 5F),
new WrappedDataValue(0, Registry.get(String.class), "String"),
new WrappedDataValue(0, Registry.get(Boolean.class), true),
new WrappedDataValue(
0,
Registry.getChatComponentSerializer(true),
Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.getItemStackSerializer(false)),
new WrappedDataValue(
0,
Registry.getItemStackSerializer(false),
BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.WOODEN_AXE))),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(CatVariant.class)),
CatVariant.a),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(FrogVariant.class)),
FrogVariant.c)
new WrappedDataValue(0, Registry.get(CatVariant.class), BuiltInRegistries.ai.e(CatVariant.e)),
new WrappedDataValue(0, Registry.get(FrogVariant.class), FrogVariant.a)
));
} else if (type == PacketType.Play.Server.CHAT) {
constructed.getChatComponents().write(0, ComponentConverter.fromBaseComponent(TEST_COMPONENT));
@@ -875,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();
@@ -890,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);
}
}
}
@@ -1,25 +1,26 @@
package com.comphenix.protocol.injector;
import static com.comphenix.protocol.utility.TestUtils.setFinalField;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.lang.reflect.Field;
import net.minecraft.server.level.ChunkProviderServer;
import net.minecraft.server.level.PlayerChunkMap;
import net.minecraft.server.level.PlayerChunkMap.EntityTracker;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.entity.Entity;
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.setFinalField;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class EntityUtilitiesTest {
@BeforeAll
@@ -0,0 +1,37 @@
package com.comphenix.protocol.utility;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class CachedPackageTest {
private CachedPackage pack;
@BeforeEach
public void prepare() {
ClassSource source = ClassSource.fromClassLoader();
this.pack = new CachedPackage("java.lang", source);
}
@Test
public void testGetPackageClass() {
Optional<Class<?>> result = pack.getPackageClass("Object");
assertTrue(result.isPresent());
assertEquals(result.get(), Object.class);
}
@Test
public void testUsingAliases() {
Optional<Class<?>> result = pack.getPackageClass("NOT_A_CLASS", "Object");
assertTrue(result.isPresent());
assertEquals(result.get(), Object.class);
result = pack.getPackageClass("NOT_A_CLASS", "STILL_NOT_A_CLASS", "Object");
assertTrue(result.isPresent());
assertEquals(result.get(), Object.class);
}
}
@@ -1,14 +1,5 @@
package com.comphenix.protocol.utility;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.comphenix.protocol.BukkitInitialization;
import com.mojang.authlib.GameProfile;
import net.minecraft.nbt.NBTCompressedStreamTools;
@@ -22,13 +13,22 @@ import net.minecraft.world.level.ChunkCoordIntPair;
import net.minecraft.world.level.block.state.IBlockData;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class MinecraftReflectionTest {
@BeforeAll
@@ -120,10 +120,10 @@ public class MinecraftReflectionTest {
assertEquals(DataWatcher.Item.class, MinecraftReflection.getDataWatcherItemClass());
}
@Test
public void testLoginSignature() {
assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass());
}
@Test
public void testLoginSignature() {
assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass());
}
@Test
public void testItemStacks() {
@@ -132,7 +132,8 @@ public class MinecraftReflectionTest {
assertItemsEqual(stack, MinecraftReflection.getBukkitItemStack(nmsStack));
// The NMS handle for CraftItemStack is null with Material.AIR, make sure it is handled correctly
assertNotNull(MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR))));
assertNotNull(
MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR))));
}
@Test
@@ -2,7 +2,8 @@ package com.comphenix.protocol.utility;
public class MinecraftReflectionTestUtil {
public static final String PACKAGE_VERSION = "v1_19_R1";
public static final String RELEASE_TARGET = "1.19.3";
public static final String PACKAGE_VERSION = "v1_19_R3";
public static final String NMS = "net.minecraft";
public static final String OBC = "org.bukkit.craftbukkit." + PACKAGE_VERSION;
@@ -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));
}
}
@@ -8,6 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.utility.MinecraftReflection;
import net.minecraft.advancements.AdvancementDisplay;
import net.minecraft.advancements.AdvancementFrameType;
import net.minecraft.network.chat.IChatBaseComponent;
@@ -20,100 +22,100 @@ import org.junit.jupiter.api.Test;
public class AutoWrapperTest {
@BeforeAll
public static void initializeBukkit() {
BukkitInitialization.initializeAll();
}
@BeforeAll
public static void initializeBukkit() {
BukkitInitialization.initializeAll();
}
@Test
public void testToNms() {
WrappedAdvancementDisplay display = new WrappedAdvancementDisplay();
display.title = WrappedChatComponent.fromText("Test123");
display.description = WrappedChatComponent.fromText("Test567");
display.item = new ItemStack(Material.DARK_OAK_SAPLING);
display.background = new MinecraftKey("test");
display.frameType = WrappedFrameType.CHALLENGE;
display.announceChat = false;
display.showToast = true;
display.hidden = true;
display.x = 5f;
display.y = 67f;
@Test
public void testToNms() {
WrappedAdvancementDisplay display = new WrappedAdvancementDisplay();
display.title = WrappedChatComponent.fromText("Test123");
display.description = WrappedChatComponent.fromText("Test567");
display.item = new ItemStack(Material.GOLD_INGOT);
display.background = new MinecraftKey("test");
display.frameType = WrappedFrameType.CHALLENGE;
display.announceChat = false;
display.showToast = true;
display.hidden = true;
display.x = 5f;
display.y = 67f;
AdvancementDisplay nms = (AdvancementDisplay) displayWrapper().unwrap(display);
AdvancementDisplay nms = (AdvancementDisplay) displayWrapper().unwrap(display);
assertTrue(nms.h());
assertTrue(nms.j());
assertFalse(nms.i());
assertEquals("test", nms.d().a());
validateRawText(nms.a(), "Test123");
validateRawText(nms.b(), "Test567");
assertSame(AdvancementFrameType.b, nms.e());
assertSame(Items.L, nms.c().c());
assertEquals(5f, nms.f(), 0f);
assertEquals(67f, nms.g(), 0f);
}
assertTrue(nms.h());
assertTrue(nms.j());
assertFalse(nms.i());
assertEquals("test", nms.d().a());
validateRawText(nms.a(), "Test123");
validateRawText(nms.b(), "Test567");
assertSame(AdvancementFrameType.b, nms.e());
assertSame(MinecraftReflection.getBukkitItemStack(nms.c()).getType(), Material.GOLD_INGOT);
assertEquals(5f, nms.f(), 0f);
assertEquals(67f, nms.g(), 0f);
}
@Test
public void testFromNms() {
AdvancementDisplay display = new AdvancementDisplay(
new net.minecraft.world.item.ItemStack(Items.qA),
IChatBaseComponent.b("Test123"),
IChatBaseComponent.b("Test567"),
new net.minecraft.resources.MinecraftKey("minecraft", "test"),
AdvancementFrameType.b,
true,
false,
true
);
display.a(5f, 67f);
@Test
public void testFromNms() {
AdvancementDisplay display = new AdvancementDisplay(
(net.minecraft.world.item.ItemStack)MinecraftReflection.getMinecraftItemStack(new ItemStack(Material.ENDER_EYE)),
IChatBaseComponent.b("Test123"),
IChatBaseComponent.b("Test567"),
new net.minecraft.resources.MinecraftKey("minecraft", "test"),
AdvancementFrameType.b,
true,
false,
true
);
display.a(5f, 67f);
WrappedAdvancementDisplay wrapped = displayWrapper().wrap(display);
WrappedAdvancementDisplay wrapped = displayWrapper().wrap(display);
assertTrue(wrapped.showToast);
assertTrue(wrapped.hidden);
assertFalse(wrapped.announceChat);
assertEquals("test", wrapped.background.getKey());
assertEquals("{\"text\":\"Test123\"}", wrapped.title.getJson());
assertEquals("{\"text\":\"Test567\"}", wrapped.description.getJson());
assertSame(WrappedFrameType.CHALLENGE, wrapped.frameType);
assertSame(Material.ENDER_EYE, wrapped.item.getType());
assertEquals(5f, wrapped.x, 0f);
assertEquals(67f, wrapped.y, 0f);
}
assertTrue(wrapped.showToast);
assertTrue(wrapped.hidden);
assertFalse(wrapped.announceChat);
assertEquals("test", wrapped.background.getKey());
assertEquals("{\"text\":\"Test123\"}", wrapped.title.getJson());
assertEquals("{\"text\":\"Test567\"}", wrapped.description.getJson());
assertSame(WrappedFrameType.CHALLENGE, wrapped.frameType);
assertSame(Material.ENDER_EYE, wrapped.item.getType());
assertEquals(5f, wrapped.x, 0f);
assertEquals(67f, wrapped.y, 0f);
}
private AutoWrapper<WrappedAdvancementDisplay> displayWrapper() {
return AutoWrapper
.wrap(WrappedAdvancementDisplay.class, "advancements.AdvancementDisplay", "advancements.DisplayInfo")
.field(0, BukkitConverters.getWrappedChatComponentConverter())
.field(1, BukkitConverters.getWrappedChatComponentConverter())
.field(2, BukkitConverters.getItemStackConverter())
.field(3, MinecraftKey.getConverter())
.field(4, EnumWrappers.getGenericConverter(getMinecraftClass("advancements.AdvancementFrameType", "advancements.FrameType"),
WrappedFrameType.class));
}
private AutoWrapper<WrappedAdvancementDisplay> displayWrapper() {
return AutoWrapper
.wrap(WrappedAdvancementDisplay.class, "advancements.AdvancementDisplay", "advancements.DisplayInfo")
.field(0, BukkitConverters.getWrappedChatComponentConverter())
.field(1, BukkitConverters.getWrappedChatComponentConverter())
.field(2, BukkitConverters.getItemStackConverter())
.field(3, MinecraftKey.getConverter())
.field(4, EnumWrappers.getGenericConverter(getMinecraftClass("advancements.AdvancementFrameType", "advancements.FrameType"),
WrappedFrameType.class));
}
private void validateRawText(IChatBaseComponent component, String expected) {
LiteralContents content = assertInstanceOf(LiteralContents.class, component.b());
assertEquals(expected, content.a());
}
private void validateRawText(IChatBaseComponent component, String expected) {
LiteralContents content = assertInstanceOf(LiteralContents.class, component.b());
assertEquals(expected, content.a());
}
public enum WrappedFrameType {
TASK,
CHALLENGE,
GOAL
}
public enum WrappedFrameType {
TASK,
CHALLENGE,
GOAL
}
public static final class WrappedAdvancementDisplay {
public static final class WrappedAdvancementDisplay {
public WrappedChatComponent title;
public WrappedChatComponent description;
public ItemStack item;
public MinecraftKey background;
public WrappedFrameType frameType;
public boolean showToast;
public boolean announceChat;
public boolean hidden;
public float x;
public float y;
}
public WrappedChatComponent title;
public WrappedChatComponent description;
public ItemStack item;
public MinecraftKey background;
public WrappedFrameType frameType;
public boolean showToast;
public boolean announceChat;
public boolean hidden;
public float x;
public float y;
}
}
@@ -1,12 +1,12 @@
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.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;
import org.bukkit.Material;
@@ -16,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);
@@ -55,4 +59,36 @@ public class BukkitConvertersTest {
assertEquals(wrapped.left(), nmsEither.left());
assertEquals(wrapped.right(), nmsEither.right());
}
@Test
public void testPacketContainerConverter() {
for (PacketType type : PacketType.values()) {
if(!type.isSupported()) {
continue;
}
PacketContainer container = new PacketContainer(type);
Object generic = BukkitConverters.getPacketContainerConverter().getGeneric(container);
Object specific = BukkitConverters.getPacketContainerConverter().getSpecific(generic);
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);
}
}
@@ -0,0 +1,27 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Lukas Alt
* @since 26.03.2023
*/
public class ConverterTest {
@Test
public void testIterableConverter() {
EquivalentConverter<Iterable<String>> converter = Converters.iterable(Converters.passthrough(String.class), ArrayList::new, ArrayList::new);
List<String> l = Arrays.asList("a", "b", "c");
Object generic = converter.getGeneric(l);
Object specific = converter.getSpecific(generic);
assertEquals(l, specific);
}
}
@@ -1,22 +1,23 @@
package com.comphenix.protocol.wrappers;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.google.common.collect.Sets;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class EnumWrappersTest {
private static final Set<String> KNOWN_INVALID = Sets.newHashSet(
"Particle", "WorldBorderAction", "CombatEventType", "TitleAction", "ChatType"
"Particle", "WorldBorderAction", "CombatEventType", "TitleAction", "ChatType", "TitleAction"
);
@BeforeAll
@@ -54,4 +55,4 @@ public class EnumWrappersTest {
public void testValidity() {
assertEquals(EnumWrappers.INVALID, KNOWN_INVALID);
}
}
}
@@ -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);
}
}
@@ -1,18 +1,14 @@
package com.comphenix.protocol.wrappers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.WrappedAttributeModifier.Operation;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.core.IRegistry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.world.entity.ai.attributes.AttributeBase;
@@ -21,6 +17,10 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class WrappedAttributeTest {
private WrappedAttributeModifier doubleModifier;
@@ -93,7 +93,7 @@ public class WrappedAttributeTest {
modifiers.add((AttributeModifier) wrapper.getHandle());
}
AttributeBase base = IRegistry.ak.a(MinecraftKey.a(attribute.getAttributeKey()));
AttributeBase base = BuiltInRegistries.u.a(MinecraftKey.a(attribute.getAttributeKey()));
return new AttributeSnapshot(base, attribute.getBaseValue(), modifiers);
}
@@ -14,19 +14,19 @@
*/
package com.comphenix.protocol.wrappers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.comphenix.protocol.BukkitInitialization;
import net.minecraft.world.level.block.state.IBlockData;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.GlassPane;
import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_19_R1.block.impl.CraftStainedGlassPane;
import org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;
import org.bukkit.craftbukkit.v1_19_R3.block.impl.CraftStainedGlassPane;
import org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author dmulloy2
*/
@@ -56,7 +56,7 @@ public class WrappedBlockDataTest {
@Test
public void testDataCreation() {
IBlockData nmsData = CraftMagicNumbers.getBlock(Material.CYAN_STAINED_GLASS_PANE).m();
IBlockData nmsData = CraftMagicNumbers.getBlock(Material.CYAN_STAINED_GLASS_PANE).o();
GlassPane data = (GlassPane) CraftBlockData.fromData(nmsData);
data.setFace(BlockFace.EAST, true);
@@ -14,23 +14,24 @@
*/
package com.comphenix.protocol.wrappers;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.UUID;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject;
import java.util.UUID;
import net.minecraft.world.entity.projectile.EntityEgg;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEgg;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEgg;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author dmulloy2
*/
@@ -1,5 +1,9 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.util.BitSet;
import java.util.List;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FuzzyReflection;
@@ -8,8 +12,7 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import net.minecraft.core.BlockPosition;
import net.minecraft.core.IRegistry;
import net.minecraft.core.IRegistryCustom;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.server.level.WorldServer;
@@ -22,15 +25,12 @@ import net.minecraft.world.level.chunk.ILightAccess;
import net.minecraft.world.level.lighting.LightEngine;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.internal.matchers.apachecommons.ReflectionEquals;
import java.lang.reflect.Field;
import java.util.BitSet;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
/**
* @author Etrayed
*/
@Disabled // todo: figure this registry update out
public class WrappedLevelChunkDataTest {
@BeforeAll
@@ -47,12 +48,12 @@ public class WrappedLevelChunkDataTest {
ILightAccess access = mock(ILightAccess.class);
when(access.c(0, 0)).thenReturn(BlockAccessAir.a);
when(access.p()).thenReturn(BlockAccessAir.a);
when(access.q()).thenReturn(BlockAccessAir.a);
LightEngine engine = new LightEngine(access, true, true);
WorldServer nmsWorld = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
when(nmsWorld.s()).thenReturn(IRegistryCustom.d.get());
//when(nmsWorld.s()).thenReturn();
// TODO: somehow find a way to always call the real code for all LevelHeightAccessor implementations
when(nmsWorld.v_()).thenReturn(256);
when(nmsWorld.ai()).thenReturn(16); // LevelHeightAccessor is mocked and therefore always returns 0, there are further methods like this which might cause errors in the future
@@ -68,7 +69,7 @@ public class WrappedLevelChunkDataTest {
this.nmsWorld = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle();
this.chunk = new Chunk(nmsWorld, new ChunkCoordIntPair(5, 5));
IBlockData bellData = IRegistry.V.a(new MinecraftKey("bell")).m();
IBlockData bellData = BuiltInRegistries.f.a(new MinecraftKey("bell")).o();
chunk.b(0).a(0, 0, 0, bellData);
chunk.a(new TileEntityBell(BlockPosition.b, bellData));
@@ -76,8 +77,7 @@ public class WrappedLevelChunkDataTest {
@Test
public void testChunkData() {
ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk,
nmsWorld.l_(), null, null, false);
ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk, nmsWorld.l_(), null, null, false);
PacketContainer container = PacketContainer.fromPacket(packet);
Object rawInstance = container.getSpecificModifier(MinecraftReflection.getLevelChunkPacketDataClass()).read(0);
Object virtualInstance = BukkitConverters.getWrappedChunkDataConverter().getGeneric(container.getLevelChunkData().read(0));
@@ -117,8 +117,7 @@ public class WrappedLevelChunkDataTest {
@Test
public void testLightData() {
ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk,
nmsWorld.l_(), null, null, false);
ClientboundLevelChunkWithLightPacket packet = new ClientboundLevelChunkWithLightPacket(chunk, nmsWorld.l_(), null, null, false);
PacketContainer container = PacketContainer.fromPacket(packet);
randomizeBitSets(container.getSpecificModifier(MinecraftReflection.getLightUpdatePacketDataClass()).read(0));
@@ -1,17 +1,21 @@
package com.comphenix.protocol.wrappers;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.util.Optional;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
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;
import static org.junit.jupiter.api.Assertions.*;
public class WrappedServerPingTest {
@BeforeAll
@@ -20,34 +24,80 @@ public class WrappedServerPingTest {
}
@Test
public void test() {
try {
CompressedImage tux = CompressedImage.fromPng(Resources.getResource("tux.png").openStream());
byte[] original = tux.getDataCopy();
public void fullTest() throws IOException {
PacketContainer packet = new PacketContainer(PacketType.Status.Server.SERVER_INFO);
Optional<WrappedServerPing> optionalPing = packet.getServerPings().optionRead(0);
assertTrue(optionalPing.isPresent());
WrappedServerPing serverPing = new WrappedServerPing();
serverPing.setMotD("Hello, this is a test.");
serverPing.setPlayersOnline(5);
serverPing.setPlayersMaximum(10);
serverPing.setVersionName("Minecraft 123");
serverPing.setVersionProtocol(4);
serverPing.setFavicon(tux);
serverPing.setChatPreviewEnabled(true);
serverPing.setEnforceSecureChat(true);
WrappedServerPing serverPing = optionalPing.get();
assertNotNull(serverPing.getMotD());
assertNotNull(serverPing.getFavicon());
assertNotNull(serverPing.getPlayers());
assertNotNull(serverPing.getVersionName());
assertEquals(5, serverPing.getPlayersOnline());
assertEquals(10, serverPing.getPlayersMaximum());
assertEquals("Minecraft 123", serverPing.getVersionName());
assertEquals(4, serverPing.getVersionProtocol());
assertTrue(serverPing.isChatPreviewEnabled());
assertTrue(serverPing.isEnforceSecureChat());
CompressedImage tux = CompressedImage.fromPng(Resources.getResource("tux.png").openStream());
byte[] original = tux.getDataCopy();
assertArrayEquals(original, serverPing.getFavicon().getData());
serverPing.setMotD("Hello, this is a test.");
serverPing.setPlayersOnline(5);
serverPing.setPlayersMaximum(10);
serverPing.setVersionName("Minecraft 123");
serverPing.setVersionProtocol(4);
serverPing.setFavicon(tux);
serverPing.setEnforceSecureChat(true);
CompressedImage copy = CompressedImage.fromBase64Png(Base64Coder.encodeLines(tux.getData()));
assertArrayEquals(copy.getData(), serverPing.getFavicon().getData());
} catch (Throwable ex) {
fail("Encountered an exception testing ServerPing", ex);
}
packet.getServerPings().write(0, serverPing);
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());
assertEquals(4, roundTrip.getVersionProtocol());
assertTrue(roundTrip.isEnforceSecureChat());
assertArrayEquals(original, roundTrip.getFavicon().getData());
CompressedImage copy = CompressedImage.fromBase64Png(Base64Coder.encodeLines(tux.getData()));
assertArrayEquals(copy.getData(), roundTrip.getFavicon().getData());
}
@Test
public void testDefaultData() {
PacketContainer packet = new PacketContainer(PacketType.Status.Server.SERVER_INFO);
packet.getServerPings().write(0, new WrappedServerPing());
WrappedServerPing serverPing = packet.getServerPings().read(0);
assertEquals(serverPing.getMotD(), WrappedChatComponent.fromLegacyText("A Minecraft Server"));
assertEquals(serverPing.getVersionProtocol(), MinecraftProtocolVersion.getCurrentVersion());
}
@Test
public void testSetPartialData() {
PacketContainer packet = new PacketContainer(PacketType.Status.Server.SERVER_INFO);
WrappedServerPing serverPing = new WrappedServerPing();
serverPing.setPlayersOnline(69);
serverPing.setPlayersMaximum(420);
packet.getServerPings().write(0, serverPing);
WrappedServerPing roundTrip = packet.getServerPings().read(0);
assertEquals(roundTrip.getMotD(), WrappedChatComponent.fromLegacyText("A Minecraft Server"));
assertEquals(roundTrip.getVersionProtocol(), MinecraftProtocolVersion.getCurrentVersion());
assertEquals(roundTrip.getPlayersOnline(), 69);
assertEquals(roundTrip.getPlayersMaximum(), 420);
}
}