Compare commits
52
Commits
line-endings
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a783b380a2
|
||
|
|
9065ec1848
|
||
|
|
caeaab7fba | ||
|
|
3e9af0bf7e | ||
|
|
adc02cfce4 | ||
|
|
47661a5746 | ||
|
|
8c8a39f423 | ||
|
|
0396dbe917 | ||
|
|
ff46d7dbe5 | ||
|
|
ab0d75004e | ||
|
|
d818379207 | ||
|
|
9b37cbc72c | ||
|
|
077c9691ac | ||
|
|
c3019d5f01 | ||
|
|
315370b985 | ||
|
|
16f4870714 | ||
|
|
e77ed96957 | ||
|
|
46f6e76f91 | ||
|
|
38bbd764cc | ||
|
|
2931af58db | ||
|
|
08ea2da642 | ||
|
|
448e9369de | ||
|
|
ac6f911f15 | ||
|
|
c69bcc36f5 | ||
|
|
0ee93acd65 | ||
|
|
d83dd9ab8a | ||
|
|
ab32f938d7 | ||
|
|
c7753a9d5b | ||
|
|
b51812655a | ||
|
|
a910edc16b | ||
|
|
fb2075b774 | ||
|
|
4b78bf6a34 | ||
|
|
edce5b6d0f | ||
|
|
7217b11ba7 | ||
|
|
18c2b389a4 | ||
|
|
1912a9c871 | ||
|
|
0c6fa46871 | ||
|
|
df3b68df4c | ||
|
|
aebefded86 | ||
|
|
64e1e7de24 | ||
|
|
05fa147b48 | ||
|
|
bba534d694 | ||
|
|
4f0fe72add | ||
|
|
365bb66d2a | ||
|
|
0a41cf9353 | ||
|
|
d0b274249c | ||
|
|
bdbbb7cb9c | ||
|
|
531f28cbaf | ||
|
|
30b69d3ecf | ||
|
|
9d183e85d7 | ||
|
|
eebb99fa37 | ||
|
|
6aaf0ec26b |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
* text=auto eol=lf
|
||||
.gitea/CODEOWNERS linguist-detectable=false
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,167 +1,167 @@
|
||||
# ProtocolLib
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modifying Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
Critically, different plugins that use this approach may _hook_ into the same classes,
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
lead to more subtle bugs.
|
||||
|
||||
Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/).
|
||||
|
||||
### Resources
|
||||
|
||||
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
|
||||
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
|
||||
|
||||
### Compilation
|
||||
|
||||
ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run
|
||||
`mvn package` in the root project folder.
|
||||
|
||||
### A new API
|
||||
|
||||
__ProtocolLib__ attempts to solve this problem by providing an event API, much like Bukkit,
|
||||
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
|
||||
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
|
||||
You no longer have to reference CraftBukkit!
|
||||
|
||||
### Using ProtocolLib
|
||||
|
||||
To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
|
||||
as a dependency or soft dependency to your plugin.yml file like any other plugin:
|
||||
|
||||
````yml
|
||||
depend: [ ProtocolLib ]
|
||||
````
|
||||
|
||||
You can also add ProtocolLib as a Maven dependency:
|
||||
|
||||
````xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.7.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
````
|
||||
|
||||
Or use the maven dependency with gradle:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.7.0";
|
||||
}
|
||||
```
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
|
||||
|
||||
````java
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
````
|
||||
|
||||
To listen for packets sent by the server to a client, add a server-side listener:
|
||||
|
||||
````java
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(new PacketAdapter(
|
||||
this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT
|
||||
) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
It's also possible to read and modify the content of these packets. For instance, you can create a global
|
||||
censor by listening for Packet3Chat events:
|
||||
|
||||
````java
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(
|
||||
this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Client.CHAT
|
||||
) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getStrings().read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
### Sending packets
|
||||
|
||||
Normally, you might have to do something ugly like the following:
|
||||
|
||||
````java
|
||||
PacketPlayOutExplosion fakeExplosion = new PacketPlayOutExplosion(
|
||||
player.getLocation().getX(),
|
||||
player.getLocation().getY(),
|
||||
player.getLocation().getZ(),
|
||||
3.0F,
|
||||
new ArrayList<>(),
|
||||
new Vec3D(
|
||||
player.getVelocity().getX() + 1,
|
||||
player.getVelocity().getY() + 1,
|
||||
player.getVelocity().getZ() + 1
|
||||
)
|
||||
);
|
||||
|
||||
((CraftPlayer) player).getHandle().b.a(fakeExplosion);
|
||||
````
|
||||
|
||||
But with ProtocolLib, you can turn that into something more manageable:
|
||||
|
||||
````java
|
||||
PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
fakeExplosion.getDoubles()
|
||||
.write(0, player.getLocation().getX())
|
||||
.write(1, player.getLocation().getY())
|
||||
.write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getFloat().write(0, 3.0F);
|
||||
fakeExplosion.getBlockPositionCollectionModifier().write(0, new ArrayList<>());
|
||||
fakeExplosion.getVectors().write(0, player.getVelocity().add(new Vector(1, 1, 1)));
|
||||
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
````
|
||||
|
||||
### Compatibility
|
||||
|
||||
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
|
||||
result is quite flexible. It's likely that I won't have to update ProtocolLib for anything but bug fixes
|
||||
and new features.
|
||||
|
||||
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
|
||||
every field, method and class is deduced by looking at field types, package names or parameter
|
||||
types. It's remarkably consistent across different versions.
|
||||
# ProtocolLib
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modifying Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
Critically, different plugins that use this approach may _hook_ into the same classes,
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
lead to more subtle bugs.
|
||||
|
||||
### Resources
|
||||
|
||||
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
|
||||
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
|
||||
|
||||
### Compilation
|
||||
|
||||
ProtocolLib is built with [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
|
||||
|
||||
__ProtocolLib__ attempts to solve this problem by providing an event API, much like Bukkit,
|
||||
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
|
||||
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
|
||||
You no longer have to reference CraftBukkit!
|
||||
|
||||
### Using ProtocolLib
|
||||
|
||||
To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
|
||||
as a dependency or soft dependency to your plugin.yml file like any other plugin:
|
||||
|
||||
````yml
|
||||
depend: [ ProtocolLib ]
|
||||
````
|
||||
|
||||
You can also add ProtocolLib as a Maven dependency:
|
||||
|
||||
````xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
````
|
||||
|
||||
Or use the maven dependency with gradle:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.comphenix.protocol:ProtocolLib:5.0.0'
|
||||
}
|
||||
```
|
||||
|
||||
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
|
||||
|
||||
````java
|
||||
private ProtocolManager protocolManager;
|
||||
|
||||
public void onLoad() {
|
||||
protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
````
|
||||
|
||||
To listen for packets sent by the server to a client, add a server-side listener:
|
||||
|
||||
````java
|
||||
// Disable all sound effects
|
||||
protocolManager.addPacketListener(new PacketAdapter(
|
||||
this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Server.NAMED_SOUND_EFFECT
|
||||
) {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
It's also possible to read and modify the content of these packets. For instance, you can create a global
|
||||
censor by listening for Packet3Chat events:
|
||||
|
||||
````java
|
||||
// Censor
|
||||
protocolManager.addPacketListener(new PacketAdapter(
|
||||
this,
|
||||
ListenerPriority.NORMAL,
|
||||
PacketType.Play.Client.CHAT
|
||||
) {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
String message = packet.getStrings().read(0);
|
||||
|
||||
if (message.contains("shit") || message.contains("damn")) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage("Bad manners!");
|
||||
}
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
### Sending packets
|
||||
|
||||
Normally, you might have to do something ugly like the following:
|
||||
|
||||
````java
|
||||
PacketPlayOutExplosion fakeExplosion = new PacketPlayOutExplosion(
|
||||
player.getLocation().getX(),
|
||||
player.getLocation().getY(),
|
||||
player.getLocation().getZ(),
|
||||
3.0F,
|
||||
new ArrayList<>(),
|
||||
new Vec3D(
|
||||
player.getVelocity().getX() + 1,
|
||||
player.getVelocity().getY() + 1,
|
||||
player.getVelocity().getZ() + 1
|
||||
)
|
||||
);
|
||||
|
||||
((CraftPlayer) player).getHandle().b.a(fakeExplosion);
|
||||
````
|
||||
|
||||
But with ProtocolLib, you can turn that into something more manageable:
|
||||
|
||||
````java
|
||||
PacketContainer fakeExplosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
|
||||
fakeExplosion.getDoubles()
|
||||
.write(0, player.getLocation().getX())
|
||||
.write(1, player.getLocation().getY())
|
||||
.write(2, player.getLocation().getZ());
|
||||
fakeExplosion.getFloat().write(0, 3.0F);
|
||||
fakeExplosion.getBlockPositionCollectionModifier().write(0, new ArrayList<>());
|
||||
fakeExplosion.getVectors().write(0, player.getVelocity().add(new Vector(1, 1, 1)));
|
||||
|
||||
protocolManager.sendServerPacket(player, fakeExplosion);
|
||||
````
|
||||
|
||||
### Compatibility
|
||||
|
||||
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
|
||||
result is quite flexible. It's likely that I won't have to update ProtocolLib for anything but bug fixes
|
||||
and new features.
|
||||
|
||||
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
|
||||
every field, method and class is deduced by looking at field types, package names or parameter
|
||||
types. It's remarkably consistent across different versions.
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
nexusUsername=""
|
||||
nexusPassword=""
|
||||
Vendored
BIN
Binary file not shown.
+7
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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 © {inceptionYear}–{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>
|
||||
@@ -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":
|
||||
|
||||
@@ -1,284 +1,284 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Logs packets to a given stream
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketLogging implements CommandExecutor, PacketListener {
|
||||
public static final String NAME = "packetlog";
|
||||
|
||||
private static MethodAccessor HEX_DUMP;
|
||||
|
||||
private List<PacketType> sendingTypes = new ArrayList<>();
|
||||
private List<PacketType> receivingTypes = new ArrayList<>();
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
|
||||
private Logger fileLogger;
|
||||
private LogLocation location = LogLocation.FILE;
|
||||
|
||||
private final ProtocolManager manager;
|
||||
private final Plugin plugin;
|
||||
|
||||
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
PacketType type = null;
|
||||
|
||||
try {
|
||||
if (args.length > 2) {
|
||||
Protocol protocol;
|
||||
|
||||
try {
|
||||
protocol = Protocol.valueOf(args[0].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Sender pSender;
|
||||
|
||||
try {
|
||||
pSender = Sender.valueOf(args[1].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
try { // Try IDs first
|
||||
int id = Integer.parseInt(args[2]);
|
||||
type = PacketType.findCurrent(protocol, pSender, id);
|
||||
} catch (NumberFormatException ex) { // Check packet names
|
||||
String name = args[2];
|
||||
for (PacketType packet : PacketType.values()) {
|
||||
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
||||
if (packet.name().equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
for (String className : packet.getClassNames()) {
|
||||
if (className.equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) { // RIP
|
||||
type = null;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 3) {
|
||||
if (args[3].equalsIgnoreCase("console")) {
|
||||
this.location = LogLocation.CONSOLE;
|
||||
} else {
|
||||
this.location = LogLocation.FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pSender == Sender.CLIENT) {
|
||||
if (receivingTypes.contains(type)) {
|
||||
receivingTypes.remove(type);
|
||||
} else {
|
||||
receivingTypes.add(type);
|
||||
}
|
||||
} else {
|
||||
if (sendingTypes.contains(type)) {
|
||||
sendingTypes.remove(type);
|
||||
} else {
|
||||
sendingTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
startLogging();
|
||||
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void startLogging() {
|
||||
manager.removePacketListener(this);
|
||||
|
||||
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
||||
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
||||
|
||||
// Setup the file logger if it hasn't been already
|
||||
if (location == LogLocation.FILE && fileLogger == null) {
|
||||
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
||||
|
||||
for (Handler handler : fileLogger.getHandlers())
|
||||
fileLogger.removeHandler(handler);
|
||||
fileLogger.setUseParentHandlers(false);
|
||||
|
||||
try {
|
||||
File logFile = new File(plugin.getDataFolder(), "log.log");
|
||||
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
fileLogger.addHandler(handler);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
manager.addPacketListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
// Here's where the magic happens
|
||||
|
||||
private static String hexDump(byte[] bytes) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
if (HEX_DUMP == null) {
|
||||
Class<?> hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump");
|
||||
HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass)
|
||||
.getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class));
|
||||
}
|
||||
|
||||
HEX_DUMP.invoke(null, bytes, 0, output, 0);
|
||||
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(PacketEvent event) {
|
||||
try {
|
||||
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
||||
String hexDump = hexDump(bytes);
|
||||
|
||||
if (location == LogLocation.FILE) {
|
||||
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
||||
fileLogger.log(Level.INFO, hexDump);
|
||||
fileLogger.log(Level.INFO, "");
|
||||
} else {
|
||||
System.out.println(event.getPacketType() + ":");
|
||||
System.out.println(hexDump);
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
||||
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
||||
|
||||
sendingTypes.clear();
|
||||
receivingTypes.clear();
|
||||
startLogging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private enum LogLocation {
|
||||
CONSOLE, FILE
|
||||
}
|
||||
|
||||
private static class LogFormatter extends Formatter {
|
||||
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final String FORMAT = "[{0}] {1}";
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String string = formatMessage(record);
|
||||
if (string.isEmpty()) {
|
||||
return LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
|
||||
message.append(LINE_SEPARATOR);
|
||||
return message.toString();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Logs packets to a given stream
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketLogging implements CommandExecutor, PacketListener {
|
||||
public static final String NAME = "packetlog";
|
||||
|
||||
private static MethodAccessor HEX_DUMP;
|
||||
|
||||
private List<PacketType> sendingTypes = new ArrayList<>();
|
||||
private List<PacketType> receivingTypes = new ArrayList<>();
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
|
||||
private Logger fileLogger;
|
||||
private LogLocation location = LogLocation.FILE;
|
||||
|
||||
private final ProtocolManager manager;
|
||||
private final Plugin plugin;
|
||||
|
||||
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
PacketType type = null;
|
||||
|
||||
try {
|
||||
if (args.length > 2) {
|
||||
Protocol protocol;
|
||||
|
||||
try {
|
||||
protocol = Protocol.valueOf(args[0].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Sender pSender;
|
||||
|
||||
try {
|
||||
pSender = Sender.valueOf(args[1].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
try { // Try IDs first
|
||||
int id = Integer.parseInt(args[2]);
|
||||
type = PacketType.findCurrent(protocol, pSender, id);
|
||||
} catch (NumberFormatException ex) { // Check packet names
|
||||
String name = args[2];
|
||||
for (PacketType packet : PacketType.values()) {
|
||||
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
||||
if (packet.name().equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
for (String className : packet.getClassNames()) {
|
||||
if (className.equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) { // RIP
|
||||
type = null;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 3) {
|
||||
if (args[3].equalsIgnoreCase("console")) {
|
||||
this.location = LogLocation.CONSOLE;
|
||||
} else {
|
||||
this.location = LogLocation.FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pSender == Sender.CLIENT) {
|
||||
if (receivingTypes.contains(type)) {
|
||||
receivingTypes.remove(type);
|
||||
} else {
|
||||
receivingTypes.add(type);
|
||||
}
|
||||
} else {
|
||||
if (sendingTypes.contains(type)) {
|
||||
sendingTypes.remove(type);
|
||||
} else {
|
||||
sendingTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
startLogging();
|
||||
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void startLogging() {
|
||||
manager.removePacketListener(this);
|
||||
|
||||
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
||||
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
||||
|
||||
// Setup the file logger if it hasn't been already
|
||||
if (location == LogLocation.FILE && fileLogger == null) {
|
||||
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
||||
|
||||
for (Handler handler : fileLogger.getHandlers())
|
||||
fileLogger.removeHandler(handler);
|
||||
fileLogger.setUseParentHandlers(false);
|
||||
|
||||
try {
|
||||
File logFile = new File(plugin.getDataFolder(), "log.log");
|
||||
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
fileLogger.addHandler(handler);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
manager.addPacketListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
// Here's where the magic happens
|
||||
|
||||
private static String hexDump(byte[] bytes) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
if (HEX_DUMP == null) {
|
||||
Class<?> hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump");
|
||||
HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass)
|
||||
.getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class));
|
||||
}
|
||||
|
||||
HEX_DUMP.invoke(null, bytes, 0, output, 0);
|
||||
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(PacketEvent event) {
|
||||
try {
|
||||
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
||||
String hexDump = hexDump(bytes);
|
||||
|
||||
if (location == LogLocation.FILE) {
|
||||
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
||||
fileLogger.log(Level.INFO, hexDump);
|
||||
fileLogger.log(Level.INFO, "");
|
||||
} else {
|
||||
System.out.println(event.getPacketType() + ":");
|
||||
System.out.println(hexDump);
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
||||
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
||||
|
||||
sendingTypes.clear();
|
||||
receivingTypes.clear();
|
||||
startLogging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private enum LogLocation {
|
||||
CONSOLE, FILE
|
||||
}
|
||||
|
||||
private static class LogFormatter extends Formatter {
|
||||
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final String FORMAT = "[{0}] {1}";
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String string = formatMessage(record);
|
||||
if (string.isEmpty()) {
|
||||
return LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
|
||||
message.append(LINE_SEPARATOR);
|
||||
return message.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> 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 + "]";
|
||||
|
||||
@@ -1,143 +1,143 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
||||
/**
|
||||
* Stores and retrieves metadata for applicable packet objects.
|
||||
* @author dmulloy2
|
||||
*/
|
||||
class PacketMetadata {
|
||||
|
||||
private static class MetaObject<T> {
|
||||
private final String key;
|
||||
private final T value;
|
||||
|
||||
private MetaObject(String key, T value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
|
||||
if (o instanceof MetaObject) {
|
||||
MetaObject that = (MetaObject) o;
|
||||
return that.key.equals(this.key) &&
|
||||
that.value.equals(this.value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaObject[" + key + "=" + value + "]";
|
||||
}
|
||||
}
|
||||
|
||||
// Packet meta cache
|
||||
private static Cache<Object, List<MetaObject>> META_CACHE;
|
||||
|
||||
public static <T> Optional<T> get(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
|
||||
if (meta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
for (MetaObject object : meta) {
|
||||
if (object.key.equals(key)) {
|
||||
return Optional.of((T) object.value);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static void createCache() {
|
||||
META_CACHE = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> void set(Object packet, String key, T value) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
createCache();
|
||||
}
|
||||
|
||||
List<MetaObject> packetMeta;
|
||||
|
||||
try {
|
||||
packetMeta = META_CACHE.get(packet, ArrayList::new);
|
||||
} catch (ExecutionException ex) {
|
||||
// Not possible, but let's humor the array list constructor having an issue
|
||||
packetMeta = new ArrayList<>();
|
||||
}
|
||||
|
||||
packetMeta.removeIf(meta -> meta.key.equals(key));
|
||||
packetMeta.add(new MetaObject<>(key, value));
|
||||
META_CACHE.put(packet, packetMeta);
|
||||
}
|
||||
|
||||
public static <T> Optional<T> remove(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
|
||||
if (packetMeta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<T> value = Optional.empty();
|
||||
Iterator<MetaObject> iter = packetMeta.iterator();
|
||||
while (iter.hasNext()) {
|
||||
MetaObject meta = iter.next();
|
||||
if (meta.key.equals(key)) {
|
||||
value = Optional.of((T) meta.value);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
|
||||
/**
|
||||
* Stores and retrieves metadata for applicable packet objects.
|
||||
* @author dmulloy2
|
||||
*/
|
||||
class PacketMetadata {
|
||||
|
||||
private static class MetaObject<T> {
|
||||
private final String key;
|
||||
private final T value;
|
||||
|
||||
private MetaObject(String key, T value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
|
||||
if (o instanceof MetaObject) {
|
||||
MetaObject that = (MetaObject) o;
|
||||
return that.key.equals(this.key) &&
|
||||
that.value.equals(this.value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaObject[" + key + "=" + value + "]";
|
||||
}
|
||||
}
|
||||
|
||||
// Packet meta cache
|
||||
private static Cache<Object, List<MetaObject>> META_CACHE;
|
||||
|
||||
public static <T> Optional<T> get(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
|
||||
if (meta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
for (MetaObject object : meta) {
|
||||
if (object.key.equals(key)) {
|
||||
return Optional.of((T) object.value);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static void createCache() {
|
||||
META_CACHE = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> void set(Object packet, String key, T value) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
createCache();
|
||||
}
|
||||
|
||||
List<MetaObject> packetMeta;
|
||||
|
||||
try {
|
||||
packetMeta = META_CACHE.get(packet, ArrayList::new);
|
||||
} catch (ExecutionException ex) {
|
||||
// Not possible, but let's humor the array list constructor having an issue
|
||||
packetMeta = new ArrayList<>();
|
||||
}
|
||||
|
||||
packetMeta.removeIf(meta -> meta.key.equals(key));
|
||||
packetMeta.add(new MetaObject<>(key, value));
|
||||
META_CACHE.put(packet, packetMeta);
|
||||
}
|
||||
|
||||
public static <T> Optional<T> remove(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
|
||||
if (packetMeta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<T> value = Optional.empty();
|
||||
Iterator<MetaObject> iter = packetMeta.iterator();
|
||||
while (iter.hasNext()) {
|
||||
MetaObject meta = iter.next();
|
||||
if (meta.key.equals(key)) {
|
||||
value = Optional.of((T) meta.value);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-8
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
/**
|
||||
* (c) 2018 dmulloy2
|
||||
*/
|
||||
package com.comphenix.protocol.reflect.cloning;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/**
|
||||
* A cloner that can clone Java Optional objects
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class JavaOptionalCloner implements Cloner {
|
||||
protected Cloner wrapped;
|
||||
|
||||
public JavaOptionalCloner(Cloner wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
return source instanceof Optional || source instanceof OptionalInt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source instanceof Optional) {
|
||||
Optional<?> optional = (Optional<?>) source;
|
||||
return optional.map(o -> wrapped.clone(o));
|
||||
} else if (source instanceof OptionalInt) {
|
||||
// why Java felt the need to make each optional class distinct is beyond me
|
||||
// like why couldn't they have given us at least a common interface or something
|
||||
OptionalInt optional = (OptionalInt) source;
|
||||
return optional.isPresent() ? OptionalInt.of(optional.getAsInt()) : OptionalInt.empty();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cloner getWrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* (c) 2018 dmulloy2
|
||||
*/
|
||||
package com.comphenix.protocol.reflect.cloning;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
/**
|
||||
* A cloner that can clone Java Optional objects
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class JavaOptionalCloner implements Cloner {
|
||||
protected Cloner wrapped;
|
||||
|
||||
public JavaOptionalCloner(Cloner wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClone(Object source) {
|
||||
return source instanceof Optional || source instanceof OptionalInt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone(Object source) {
|
||||
if (source instanceof Optional) {
|
||||
Optional<?> optional = (Optional<?>) source;
|
||||
return optional.map(o -> wrapped.clone(o));
|
||||
} else if (source instanceof OptionalInt) {
|
||||
// why Java felt the need to make each optional class distinct is beyond me
|
||||
// like why couldn't they have given us at least a common interface or something
|
||||
OptionalInt optional = (OptionalInt) source;
|
||||
return optional.isPresent() ? OptionalInt.of(optional.getAsInt()) : OptionalInt.empty();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cloner getWrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,421 +1,435 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Ordering;
|
||||
import java.io.Serializable;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
|
||||
/**
|
||||
* Determine the current Minecraft version.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
|
||||
|
||||
/**
|
||||
* Version 1.19 - the wild update
|
||||
*/
|
||||
public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19");
|
||||
/**
|
||||
* Version 1.18 - caves and cliffs part 2
|
||||
*/
|
||||
public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18");
|
||||
/**
|
||||
* Version 1.17 - caves and cliffs part 1
|
||||
*/
|
||||
public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17");
|
||||
/**
|
||||
* Version 1.16.2 - breaking change to the nether update
|
||||
*/
|
||||
public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2");
|
||||
/**
|
||||
* Version 1.16.0 - the nether update
|
||||
*/
|
||||
public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16");
|
||||
/**
|
||||
* Version 1.15 - the bee update
|
||||
*/
|
||||
public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15");
|
||||
/**
|
||||
* Version 1.14 - village and pillage update.
|
||||
*/
|
||||
public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14");
|
||||
/**
|
||||
* Version 1.13 - update aquatic.
|
||||
*/
|
||||
public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13");
|
||||
/**
|
||||
* Version 1.12 - the world of color update.
|
||||
*/
|
||||
public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12");
|
||||
/**
|
||||
* Version 1.11 - the exploration update.
|
||||
*/
|
||||
public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11");
|
||||
/**
|
||||
* Version 1.10 - the frostburn update.
|
||||
*/
|
||||
public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10");
|
||||
/**
|
||||
* Version 1.9 - the combat update.
|
||||
*/
|
||||
public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9");
|
||||
/**
|
||||
* Version 1.8 - the "bountiful" update.
|
||||
*/
|
||||
public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8");
|
||||
/**
|
||||
* Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise)
|
||||
*/
|
||||
public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8");
|
||||
/**
|
||||
* Version 1.7.2 - the update that changed the world.
|
||||
*/
|
||||
public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2");
|
||||
/**
|
||||
* Version 1.6.1 - the horse update.
|
||||
*/
|
||||
public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1");
|
||||
/**
|
||||
* Version 1.5.0 - the redstone update.
|
||||
*/
|
||||
public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0");
|
||||
/**
|
||||
* Version 1.4.2 - the scary update (Wither Boss).
|
||||
*/
|
||||
public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2");
|
||||
|
||||
/**
|
||||
* The latest release version of minecraft.
|
||||
*/
|
||||
public static final MinecraftVersion LATEST = WILD_UPDATE;
|
||||
|
||||
// used when serializing
|
||||
private static final long serialVersionUID = -8695133558996459770L;
|
||||
|
||||
/**
|
||||
* Regular expression used to parse version strings.
|
||||
*/
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*");
|
||||
|
||||
/**
|
||||
* The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion()
|
||||
*/
|
||||
private static MinecraftVersion currentVersion;
|
||||
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int build;
|
||||
// The development stage
|
||||
private final String development;
|
||||
|
||||
// Snapshot?
|
||||
private final SnapshotVersion snapshot;
|
||||
private volatile Boolean atCurrentOrAbove;
|
||||
|
||||
/**
|
||||
* Determine the current Minecraft version.
|
||||
*
|
||||
* @param server - the Bukkit server that will be used to examine the MC version.
|
||||
*/
|
||||
public MinecraftVersion(Server server) {
|
||||
this(extractVersion(server.getVersion()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object from the format major.minor.build, or the snapshot format.
|
||||
*
|
||||
* @param versionOnly - the version in text form.
|
||||
*/
|
||||
public MinecraftVersion(String versionOnly) {
|
||||
this(versionOnly, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version format from the standard release version or the snapshot verison.
|
||||
*
|
||||
* @param versionOnly - the version.
|
||||
* @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise.
|
||||
*/
|
||||
private MinecraftVersion(String versionOnly, boolean parseSnapshot) {
|
||||
String[] section = versionOnly.split("-");
|
||||
SnapshotVersion snapshot = null;
|
||||
int[] numbers = new int[3];
|
||||
|
||||
try {
|
||||
numbers = this.parseVersion(section[0]);
|
||||
} catch (NumberFormatException cause) {
|
||||
// Skip snapshot parsing
|
||||
if (!parseSnapshot) {
|
||||
throw cause;
|
||||
}
|
||||
|
||||
try {
|
||||
// Determine if the snapshot is newer than the current release version
|
||||
snapshot = new SnapshotVersion(section[0]);
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
|
||||
MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false);
|
||||
boolean newer = snapshot.getSnapshotDate().compareTo(
|
||||
format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0;
|
||||
|
||||
numbers[0] = latest.getMajor();
|
||||
numbers[1] = latest.getMinor() + (newer ? 1 : -1);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot parse " + section[0], e);
|
||||
}
|
||||
}
|
||||
|
||||
this.major = numbers[0];
|
||||
this.minor = numbers[1];
|
||||
this.build = numbers[2];
|
||||
this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null);
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object directly.
|
||||
*
|
||||
* @param major - major version number.
|
||||
* @param minor - minor version number.
|
||||
* @param build - build version number.
|
||||
*/
|
||||
public MinecraftVersion(int major, int minor, int build) {
|
||||
this(major, minor, build, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object directly.
|
||||
*
|
||||
* @param major - major version number.
|
||||
* @param minor - minor version number.
|
||||
* @param build - build version number.
|
||||
* @param development - development stage.
|
||||
*/
|
||||
public MinecraftVersion(int major, int minor, int build, String development) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.build = build;
|
||||
this.development = development;
|
||||
this.snapshot = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Minecraft version from CraftBukkit itself.
|
||||
*
|
||||
* @param text - the server version in text form.
|
||||
* @return The underlying MC version.
|
||||
* @throws IllegalStateException If we could not parse the version string.
|
||||
*/
|
||||
public static String extractVersion(String text) {
|
||||
Matcher version = VERSION_PATTERN.matcher(text);
|
||||
|
||||
if (version.matches() && version.group(1) != null) {
|
||||
return version.group(1);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot parse version String '" + text + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given server version into a Minecraft version.
|
||||
*
|
||||
* @param serverVersion - the server version.
|
||||
* @return The resulting Minecraft version.
|
||||
*/
|
||||
public static MinecraftVersion fromServerVersion(String serverVersion) {
|
||||
return new MinecraftVersion(extractVersion(serverVersion));
|
||||
}
|
||||
|
||||
public static MinecraftVersion getCurrentVersion() {
|
||||
if (currentVersion == null) {
|
||||
currentVersion = fromServerVersion(Bukkit.getVersion());
|
||||
}
|
||||
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
public static void setCurrentVersion(MinecraftVersion version) {
|
||||
currentVersion = version;
|
||||
}
|
||||
|
||||
public static boolean atOrAbove(MinecraftVersion version) {
|
||||
return getCurrentVersion().isAtLeast(version);
|
||||
}
|
||||
|
||||
private int[] parseVersion(String version) {
|
||||
String[] elements = version.split("\\.");
|
||||
int[] numbers = new int[3];
|
||||
|
||||
// Make sure it's even a valid version
|
||||
if (elements.length < 1) {
|
||||
throw new IllegalStateException("Corrupt MC version: " + version);
|
||||
}
|
||||
|
||||
// The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively.
|
||||
for (int i = 0; i < Math.min(numbers.length, elements.length); i++) {
|
||||
numbers[i] = Integer.parseInt(elements[i].trim());
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Major version number
|
||||
*
|
||||
* @return Current major version number.
|
||||
*/
|
||||
public int getMajor() {
|
||||
return this.major;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minor version number
|
||||
*
|
||||
* @return Current minor version number.
|
||||
*/
|
||||
public int getMinor() {
|
||||
return this.minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build version number
|
||||
*
|
||||
* @return Current build version number.
|
||||
*/
|
||||
public int getBuild() {
|
||||
return this.build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the development stage.
|
||||
*
|
||||
* @return Development stage, or NULL if this is a release.
|
||||
*/
|
||||
public String getDevelopmentStage() {
|
||||
return this.development;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the snapshot version, or NULL if this is a release.
|
||||
*
|
||||
* @return The snapshot version.
|
||||
*/
|
||||
public SnapshotVersion getSnapshot() {
|
||||
return this.snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this version is a snapshot.
|
||||
*
|
||||
* @return The snapshot version.
|
||||
*/
|
||||
public boolean isSnapshot() {
|
||||
return this.snapshot != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this version is at or above the current version the server is running.
|
||||
*
|
||||
* @return true if this version is equal or newer than the server version, false otherwise.
|
||||
*/
|
||||
public boolean atOrAbove() {
|
||||
if (this.atCurrentOrAbove == null) {
|
||||
this.atCurrentOrAbove = MinecraftVersion.atOrAbove(this);
|
||||
}
|
||||
|
||||
return this.atCurrentOrAbove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the version String (major.minor.build) only.
|
||||
*
|
||||
* @return A normal version string.
|
||||
*/
|
||||
public String getVersion() {
|
||||
if (this.getDevelopmentStage() == null) {
|
||||
return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild());
|
||||
} else {
|
||||
return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(),
|
||||
this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MinecraftVersion o) {
|
||||
if (o == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ComparisonChain.start()
|
||||
.compare(this.getMajor(), o.getMajor())
|
||||
.compare(this.getMinor(), o.getMinor())
|
||||
.compare(this.getBuild(), o.getBuild())
|
||||
.compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast())
|
||||
.compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst())
|
||||
.result();
|
||||
}
|
||||
|
||||
public boolean isAtLeast(MinecraftVersion other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.compareTo(other) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof MinecraftVersion) {
|
||||
MinecraftVersion other = (MinecraftVersion) obj;
|
||||
|
||||
return this.getMajor() == other.getMajor() &&
|
||||
this.getMinor() == other.getMinor() &&
|
||||
this.getBuild() == other.getBuild() &&
|
||||
Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// Convert to a String that we can parse back again
|
||||
return String.format("(MC: %s)", this.getVersion());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import 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;
|
||||
|
||||
/**
|
||||
* Determine the current Minecraft version.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public static final MinecraftVersion WILD_UPDATE = new MinecraftVersion("1.19");
|
||||
/**
|
||||
* Version 1.18 - caves and cliffs part 2
|
||||
*/
|
||||
public static final MinecraftVersion CAVES_CLIFFS_2 = new MinecraftVersion("1.18");
|
||||
/**
|
||||
* Version 1.17 - caves and cliffs part 1
|
||||
*/
|
||||
public static final MinecraftVersion CAVES_CLIFFS_1 = new MinecraftVersion("1.17");
|
||||
/**
|
||||
* Version 1.16.4
|
||||
*/
|
||||
public static final MinecraftVersion NETHER_UPDATE_4 = new MinecraftVersion("1.16.4");
|
||||
/**
|
||||
* Version 1.16.2 - breaking change to the nether update
|
||||
*/
|
||||
public static final MinecraftVersion NETHER_UPDATE_2 = new MinecraftVersion("1.16.2");
|
||||
/**
|
||||
* Version 1.16.0 - the nether update
|
||||
*/
|
||||
public static final MinecraftVersion NETHER_UPDATE = new MinecraftVersion("1.16");
|
||||
/**
|
||||
* Version 1.15 - the bee update
|
||||
*/
|
||||
public static final MinecraftVersion BEE_UPDATE = new MinecraftVersion("1.15");
|
||||
/**
|
||||
* Version 1.14 - village and pillage update.
|
||||
*/
|
||||
public static final MinecraftVersion VILLAGE_UPDATE = new MinecraftVersion("1.14");
|
||||
/**
|
||||
* Version 1.13 - update aquatic.
|
||||
*/
|
||||
public static final MinecraftVersion AQUATIC_UPDATE = new MinecraftVersion("1.13");
|
||||
/**
|
||||
* Version 1.12 - the world of color update.
|
||||
*/
|
||||
public static final MinecraftVersion COLOR_UPDATE = new MinecraftVersion("1.12");
|
||||
/**
|
||||
* Version 1.11 - the exploration update.
|
||||
*/
|
||||
public static final MinecraftVersion EXPLORATION_UPDATE = new MinecraftVersion("1.11");
|
||||
/**
|
||||
* Version 1.10 - the frostburn update.
|
||||
*/
|
||||
public static final MinecraftVersion FROSTBURN_UPDATE = new MinecraftVersion("1.10");
|
||||
/**
|
||||
* Version 1.9 - the combat update.
|
||||
*/
|
||||
public static final MinecraftVersion COMBAT_UPDATE = new MinecraftVersion("1.9");
|
||||
/**
|
||||
* Version 1.8 - the "bountiful" update.
|
||||
*/
|
||||
public static final MinecraftVersion BOUNTIFUL_UPDATE = new MinecraftVersion("1.8");
|
||||
/**
|
||||
* Version 1.7.8 - the update that changed the skin format (and distribution - R.I.P. player disguise)
|
||||
*/
|
||||
public static final MinecraftVersion SKIN_UPDATE = new MinecraftVersion("1.7.8");
|
||||
/**
|
||||
* Version 1.7.2 - the update that changed the world.
|
||||
*/
|
||||
public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2");
|
||||
/**
|
||||
* Version 1.6.1 - the horse update.
|
||||
*/
|
||||
public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1");
|
||||
/**
|
||||
* Version 1.5.0 - the redstone update.
|
||||
*/
|
||||
public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0");
|
||||
/**
|
||||
* Version 1.4.2 - the scary update (Wither Boss).
|
||||
*/
|
||||
public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2");
|
||||
|
||||
/**
|
||||
* The latest release version of minecraft.
|
||||
*/
|
||||
public static final MinecraftVersion LATEST = FEATURE_PREVIEW_2;
|
||||
|
||||
// used when serializing
|
||||
private static final long serialVersionUID = -8695133558996459770L;
|
||||
|
||||
/**
|
||||
* Regular expression used to parse version strings.
|
||||
*/
|
||||
private static final Pattern VERSION_PATTERN = Pattern.compile(".*\\(.*MC.\\s*([a-zA-z0-9\\-.]+).*");
|
||||
|
||||
/**
|
||||
* The current version of minecraft, lazy initialized by MinecraftVersion.currentVersion()
|
||||
*/
|
||||
private static MinecraftVersion currentVersion;
|
||||
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int build;
|
||||
// The development stage
|
||||
private final String development;
|
||||
|
||||
// Snapshot?
|
||||
private final SnapshotVersion snapshot;
|
||||
private volatile Boolean atCurrentOrAbove;
|
||||
|
||||
/**
|
||||
* Determine the current Minecraft version.
|
||||
*
|
||||
* @param server - the Bukkit server that will be used to examine the MC version.
|
||||
*/
|
||||
public MinecraftVersion(Server server) {
|
||||
this(extractVersion(server.getVersion()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object from the format major.minor.build, or the snapshot format.
|
||||
*
|
||||
* @param versionOnly - the version in text form.
|
||||
*/
|
||||
public MinecraftVersion(String versionOnly) {
|
||||
this(versionOnly, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version format from the standard release version or the snapshot verison.
|
||||
*
|
||||
* @param versionOnly - the version.
|
||||
* @param parseSnapshot - TRUE to parse the snapshot, FALSE otherwise.
|
||||
*/
|
||||
private MinecraftVersion(String versionOnly, boolean parseSnapshot) {
|
||||
String[] section = versionOnly.split("-");
|
||||
SnapshotVersion snapshot = null;
|
||||
int[] numbers = new int[3];
|
||||
|
||||
try {
|
||||
numbers = this.parseVersion(section[0]);
|
||||
} catch (NumberFormatException cause) {
|
||||
// Skip snapshot parsing
|
||||
if (!parseSnapshot) {
|
||||
throw cause;
|
||||
}
|
||||
|
||||
try {
|
||||
// Determine if the snapshot is newer than the current release version
|
||||
snapshot = new SnapshotVersion(section[0]);
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
|
||||
MinecraftVersion latest = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION, false);
|
||||
boolean newer = snapshot.getSnapshotDate().compareTo(
|
||||
format.parse(ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE)) > 0;
|
||||
|
||||
numbers[0] = latest.getMajor();
|
||||
numbers[1] = latest.getMinor() + (newer ? 1 : -1);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Cannot parse " + section[0], e);
|
||||
}
|
||||
}
|
||||
|
||||
this.major = numbers[0];
|
||||
this.minor = numbers[1];
|
||||
this.build = numbers[2];
|
||||
this.development = section.length > 1 ? section[1] : (snapshot != null ? "snapshot" : null);
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object directly.
|
||||
*
|
||||
* @param major - major version number.
|
||||
* @param minor - minor version number.
|
||||
* @param build - build version number.
|
||||
*/
|
||||
public MinecraftVersion(int major, int minor, int build) {
|
||||
this(major, minor, build, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a version object directly.
|
||||
*
|
||||
* @param major - major version number.
|
||||
* @param minor - minor version number.
|
||||
* @param build - build version number.
|
||||
* @param development - development stage.
|
||||
*/
|
||||
public MinecraftVersion(int major, int minor, int build, String development) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.build = build;
|
||||
this.development = development;
|
||||
this.snapshot = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Minecraft version from CraftBukkit itself.
|
||||
*
|
||||
* @param text - the server version in text form.
|
||||
* @return The underlying MC version.
|
||||
* @throws IllegalStateException If we could not parse the version string.
|
||||
*/
|
||||
public static String extractVersion(String text) {
|
||||
Matcher version = VERSION_PATTERN.matcher(text);
|
||||
|
||||
if (version.matches() && version.group(1) != null) {
|
||||
return version.group(1);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot parse version String '" + text + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given server version into a Minecraft version.
|
||||
*
|
||||
* @param serverVersion - the server version.
|
||||
* @return The resulting Minecraft version.
|
||||
*/
|
||||
public static MinecraftVersion fromServerVersion(String serverVersion) {
|
||||
return new MinecraftVersion(extractVersion(serverVersion));
|
||||
}
|
||||
|
||||
public static MinecraftVersion getCurrentVersion() {
|
||||
if (currentVersion == null) {
|
||||
currentVersion = fromServerVersion(Bukkit.getVersion());
|
||||
}
|
||||
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
public static void setCurrentVersion(MinecraftVersion version) {
|
||||
currentVersion = version;
|
||||
}
|
||||
|
||||
private static boolean atOrAbove(MinecraftVersion version) {
|
||||
return getCurrentVersion().isAtLeast(version);
|
||||
}
|
||||
|
||||
private int[] parseVersion(String version) {
|
||||
String[] elements = version.split("\\.");
|
||||
int[] numbers = new int[3];
|
||||
|
||||
// Make sure it's even a valid version
|
||||
if (elements.length < 1) {
|
||||
throw new IllegalStateException("Corrupt MC version: " + version);
|
||||
}
|
||||
|
||||
// The String 1 or 1.2 is interpreted as 1.0.0 and 1.2.0 respectively.
|
||||
for (int i = 0; i < Math.min(numbers.length, elements.length); i++) {
|
||||
numbers[i] = Integer.parseInt(elements[i].trim());
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Major version number
|
||||
*
|
||||
* @return Current major version number.
|
||||
*/
|
||||
public int getMajor() {
|
||||
return this.major;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minor version number
|
||||
*
|
||||
* @return Current minor version number.
|
||||
*/
|
||||
public int getMinor() {
|
||||
return this.minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build version number
|
||||
*
|
||||
* @return Current build version number.
|
||||
*/
|
||||
public int getBuild() {
|
||||
return this.build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the development stage.
|
||||
*
|
||||
* @return Development stage, or NULL if this is a release.
|
||||
*/
|
||||
public String getDevelopmentStage() {
|
||||
return this.development;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the snapshot version, or NULL if this is a release.
|
||||
*
|
||||
* @return The snapshot version.
|
||||
*/
|
||||
public SnapshotVersion getSnapshot() {
|
||||
return this.snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this version is a snapshot.
|
||||
*
|
||||
* @return The snapshot version.
|
||||
*/
|
||||
public boolean isSnapshot() {
|
||||
return this.snapshot != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this version is at or above the current version the server is running.
|
||||
*
|
||||
* @return true if this version is equal or newer than the server version, false otherwise.
|
||||
*/
|
||||
public boolean atOrAbove() {
|
||||
if (this.atCurrentOrAbove == null) {
|
||||
this.atCurrentOrAbove = atOrAbove(this);
|
||||
}
|
||||
|
||||
return this.atCurrentOrAbove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the version String (major.minor.build) only.
|
||||
*
|
||||
* @return A normal version string.
|
||||
*/
|
||||
public String getVersion() {
|
||||
if (this.getDevelopmentStage() == null) {
|
||||
return String.format("%s.%s.%s", this.getMajor(), this.getMinor(), this.getBuild());
|
||||
} else {
|
||||
return String.format("%s.%s.%s-%s%s", this.getMajor(), this.getMinor(), this.getBuild(),
|
||||
this.getDevelopmentStage(), this.isSnapshot() ? this.snapshot : "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MinecraftVersion o) {
|
||||
if (o == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ComparisonChain.start()
|
||||
.compare(this.getMajor(), o.getMajor())
|
||||
.compare(this.getMinor(), o.getMinor())
|
||||
.compare(this.getBuild(), o.getBuild())
|
||||
.compare(this.getDevelopmentStage(), o.getDevelopmentStage(), Ordering.natural().nullsLast())
|
||||
.compare(this.getSnapshot(), o.getSnapshot(), Ordering.natural().nullsFirst())
|
||||
.result();
|
||||
}
|
||||
|
||||
public boolean isAtLeast(MinecraftVersion other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.compareTo(other) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof MinecraftVersion) {
|
||||
MinecraftVersion other = (MinecraftVersion) obj;
|
||||
|
||||
return this.getMajor() == other.getMajor() &&
|
||||
this.getMinor() == other.getMinor() &&
|
||||
this.getBuild() == other.getBuild() &&
|
||||
Objects.equals(this.getDevelopmentStage(), other.getDevelopmentStage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.getMajor(), this.getMinor(), this.getBuild());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// Convert to a String that we can parse back again
|
||||
return String.format("(MC: %s)", this.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,194 +1,199 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.google.common.base.Defaults;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Automatically wraps an internal NMS class to a non-versioned, deofbuscated class.
|
||||
* Requirements:
|
||||
* <ul>
|
||||
* <li>The wrapper must be public</li>
|
||||
* <li>If the wrapper is an internal class, it must be static</li>
|
||||
* <li>The wrapper must have one public constructor with no arguments (the default constructor is acceptable)</li>
|
||||
* <li>The wrapper must have the the same number of fields as the NMS class</li>
|
||||
* <li>Each field should correspond, in order, to its NMS counterpart</li>
|
||||
* <li>Non-generic fields must have a converter</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class AutoWrapper<T> implements EquivalentConverter<T> {
|
||||
private static final Object[] NO_ARGS = new Object[0];
|
||||
|
||||
private Map<Integer, Function<Object, Object>> wrappers = new HashMap<>();
|
||||
private Map<Integer, Function<Object, Object>> unwrappers = new HashMap<>();
|
||||
|
||||
// lazy
|
||||
private FieldAccessor[] nmsAccessors;
|
||||
private FieldAccessor[] wrapperAccessors;
|
||||
|
||||
private Object[] nmsDefaultArgs;
|
||||
private ConstructorAccessor nmsInstanceCreator;
|
||||
|
||||
private Class<T> wrapperClass;
|
||||
private Class<?> nmsClass;
|
||||
|
||||
private AutoWrapper(Class<T> wrapperClass, Class<?> nmsClass) {
|
||||
this.wrapperClass = wrapperClass;
|
||||
this.nmsClass = nmsClass;
|
||||
}
|
||||
|
||||
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, Class<?> nmsClass) {
|
||||
return new AutoWrapper<>(wrapperClass, nmsClass);
|
||||
}
|
||||
|
||||
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, String nmsClassName) {
|
||||
return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName));
|
||||
}
|
||||
|
||||
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, String nmsClassName, String... aliases) {
|
||||
return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName, aliases));
|
||||
}
|
||||
|
||||
public AutoWrapper<T> field(int index, Function<Object, Object> wrapper, Function<Object, Object> unwrapper) {
|
||||
wrappers.put(index, wrapper);
|
||||
unwrappers.put(index, unwrapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutoWrapper<T> field(int index, EquivalentConverter converter) {
|
||||
return field(index, converter::getSpecific, specific -> converter.getGeneric(specific));
|
||||
}
|
||||
|
||||
public T wrap(Object nmsObject) {
|
||||
T instance;
|
||||
|
||||
try {
|
||||
instance = wrapperClass.newInstance();
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex);
|
||||
}
|
||||
|
||||
// ensures that all accessors are present
|
||||
computeFieldAccessors();
|
||||
|
||||
for (int i = 0; i < wrapperAccessors.length; i++) {
|
||||
FieldAccessor source = nmsAccessors[i];
|
||||
FieldAccessor target = wrapperAccessors[i];
|
||||
|
||||
Object value = source.get(nmsObject);
|
||||
if (wrappers.containsKey(i))
|
||||
value = wrappers.get(i).apply(value);
|
||||
|
||||
target.set(instance, value);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Object unwrap(Object wrapper) {
|
||||
// ensures that all accessors are present
|
||||
computeFieldAccessors();
|
||||
computeNmsConstructorAccess();
|
||||
|
||||
Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs);
|
||||
|
||||
for (int i = 0; i < wrapperAccessors.length; i++) {
|
||||
FieldAccessor source = wrapperAccessors[i];
|
||||
FieldAccessor target = nmsAccessors[i];
|
||||
|
||||
Object value = source.get(wrapper);
|
||||
if (unwrappers.containsKey(i))
|
||||
value = unwrappers.get(i).apply(value);
|
||||
|
||||
target.set(instance, value);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void computeFieldAccessors() {
|
||||
if (nmsAccessors == null) {
|
||||
nmsAccessors = Arrays
|
||||
.stream(nmsClass.getDeclaredFields())
|
||||
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
||||
.map(field -> Accessors.getFieldAccessor(field))
|
||||
.toArray(FieldAccessor[]::new);
|
||||
}
|
||||
|
||||
if (wrapperAccessors == null) {
|
||||
wrapperAccessors = Arrays
|
||||
.stream(wrapperClass.getDeclaredFields())
|
||||
.map(field -> Accessors.getFieldAccessor(field))
|
||||
.toArray(FieldAccessor[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeNmsConstructorAccess() {
|
||||
if (nmsInstanceCreator == null) {
|
||||
ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass);
|
||||
if (noArgs != null) {
|
||||
// no args constructor is available - use it
|
||||
nmsInstanceCreator = noArgs;
|
||||
nmsDefaultArgs = NO_ARGS;
|
||||
} else {
|
||||
// use the first constructor of the class
|
||||
nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]);
|
||||
nmsDefaultArgs = Arrays
|
||||
.stream(nmsInstanceCreator.getConstructor().getParameterTypes())
|
||||
.map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null)
|
||||
.toArray(Object[]::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Equivalent conversion
|
||||
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return wrap(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Object specific) {
|
||||
return unwrap(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return wrapperClass;
|
||||
}
|
||||
|
||||
public static class InvalidWrapperException extends RuntimeException {
|
||||
private InvalidWrapperException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.google.common.base.Defaults;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Automatically wraps an internal NMS class to a non-versioned, deofbuscated class.
|
||||
* Requirements:
|
||||
* <ul>
|
||||
* <li>The wrapper must be public</li>
|
||||
* <li>If the wrapper is an internal class, it must be static</li>
|
||||
* <li>The wrapper must have one public constructor with no arguments (the default constructor is acceptable)</li>
|
||||
* <li>The wrapper must have the the same number of fields as the NMS class</li>
|
||||
* <li>Each field should correspond, in order, to its NMS counterpart</li>
|
||||
* <li>Non-generic fields must have a converter</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class AutoWrapper<T> implements EquivalentConverter<T> {
|
||||
private static final Object[] NO_ARGS = new Object[0];
|
||||
|
||||
private Map<Integer, Function<Object, Object>> wrappers = new HashMap<>();
|
||||
private Map<Integer, Function<Object, Object>> unwrappers = new HashMap<>();
|
||||
|
||||
// lazy
|
||||
private FieldAccessor[] nmsAccessors;
|
||||
private FieldAccessor[] wrapperAccessors;
|
||||
|
||||
private Object[] nmsDefaultArgs;
|
||||
private ConstructorAccessor nmsInstanceCreator;
|
||||
|
||||
private Class<T> wrapperClass;
|
||||
private Class<?> nmsClass;
|
||||
|
||||
private AutoWrapper(Class<T> wrapperClass, Class<?> nmsClass) {
|
||||
this.wrapperClass = wrapperClass;
|
||||
this.nmsClass = nmsClass;
|
||||
}
|
||||
|
||||
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, Class<?> nmsClass) {
|
||||
return new AutoWrapper<>(wrapperClass, nmsClass);
|
||||
}
|
||||
|
||||
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, String nmsClassName) {
|
||||
return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName));
|
||||
}
|
||||
|
||||
public static <T> AutoWrapper<T> wrap(Class<T> wrapperClass, String nmsClassName, String... aliases) {
|
||||
return wrap(wrapperClass, MinecraftReflection.getMinecraftClass(nmsClassName, aliases));
|
||||
}
|
||||
|
||||
public AutoWrapper<T> field(int index, Function<Object, Object> wrapper, Function<Object, Object> unwrapper) {
|
||||
wrappers.put(index, wrapper);
|
||||
unwrappers.put(index, unwrapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AutoWrapper<T> field(int index, EquivalentConverter converter) {
|
||||
return field(index, converter::getSpecific, specific -> converter.getGeneric(specific));
|
||||
}
|
||||
|
||||
public T wrap(Object nmsObject) {
|
||||
Preconditions.checkNotNull(nmsObject);
|
||||
|
||||
T instance;
|
||||
|
||||
try {
|
||||
instance = wrapperClass.newInstance();
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new InvalidWrapperException(wrapperClass.getSimpleName() + " is not accessible!", ex);
|
||||
}
|
||||
|
||||
// ensures that all accessors are present
|
||||
computeFieldAccessors();
|
||||
|
||||
for (int i = 0; i < wrapperAccessors.length; i++) {
|
||||
FieldAccessor source = nmsAccessors[i];
|
||||
FieldAccessor target = wrapperAccessors[i];
|
||||
|
||||
Object value = source.get(nmsObject);
|
||||
if (wrappers.containsKey(i))
|
||||
value = wrappers.get(i).apply(value);
|
||||
|
||||
target.set(instance, value);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Object unwrap(Object wrapper) {
|
||||
Preconditions.checkNotNull(wrapper);
|
||||
|
||||
// ensures that all accessors are present
|
||||
computeFieldAccessors();
|
||||
computeNmsConstructorAccess();
|
||||
|
||||
Object instance = nmsInstanceCreator.invoke(nmsDefaultArgs);
|
||||
|
||||
for (int i = 0; i < wrapperAccessors.length; i++) {
|
||||
FieldAccessor source = wrapperAccessors[i];
|
||||
FieldAccessor target = nmsAccessors[i];
|
||||
|
||||
Object value = source.get(wrapper);
|
||||
if (unwrappers.containsKey(i))
|
||||
value = unwrappers.get(i).apply(value);
|
||||
|
||||
target.set(instance, value);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private void computeFieldAccessors() {
|
||||
if (nmsAccessors == null) {
|
||||
nmsAccessors = Arrays
|
||||
.stream(nmsClass.getDeclaredFields())
|
||||
.filter(field -> !Modifier.isStatic(field.getModifiers()))
|
||||
.map(Accessors::getFieldAccessor)
|
||||
.toArray(FieldAccessor[]::new);
|
||||
}
|
||||
|
||||
if (wrapperAccessors == null) {
|
||||
wrapperAccessors = Arrays
|
||||
.stream(wrapperClass.getDeclaredFields())
|
||||
.map(Accessors::getFieldAccessor)
|
||||
.toArray(FieldAccessor[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeNmsConstructorAccess() {
|
||||
if (nmsInstanceCreator == null) {
|
||||
ConstructorAccessor noArgs = Accessors.getConstructorAccessorOrNull(nmsClass);
|
||||
if (noArgs != null) {
|
||||
// no args constructor is available - use it
|
||||
nmsInstanceCreator = noArgs;
|
||||
nmsDefaultArgs = NO_ARGS;
|
||||
} else {
|
||||
// use the first constructor of the class
|
||||
nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]);
|
||||
nmsDefaultArgs = Arrays
|
||||
.stream(nmsInstanceCreator.getConstructor().getParameterTypes())
|
||||
.map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null)
|
||||
.toArray(Object[]::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Equivalent conversion
|
||||
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return wrap(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Object specific) {
|
||||
return unwrap(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return wrapperClass;
|
||||
}
|
||||
|
||||
public static class InvalidWrapperException extends RuntimeException {
|
||||
private InvalidWrapperException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,7 +1,7 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
public interface ClonableWrapper {
|
||||
Object getHandle();
|
||||
ClonableWrapper deepClone();
|
||||
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
public interface ClonableWrapper {
|
||||
Object getHandle();
|
||||
ClonableWrapper deepClone();
|
||||
|
||||
}
|
||||
@@ -1,75 +1,75 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2016 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* Handles component parsing in 1.8
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class ComponentParser {
|
||||
|
||||
private static Constructor readerConstructor;
|
||||
private static Method setLenient;
|
||||
private static Method getAdapter;
|
||||
private static Method read;
|
||||
|
||||
private ComponentParser() {
|
||||
}
|
||||
|
||||
public static Object deserialize(Object gson, Class<?> component, StringReader str) {
|
||||
try {
|
||||
com.google.gson.stream.JsonReader reader = new com.google.gson.stream.JsonReader(str);
|
||||
reader.setLenient(true);
|
||||
return ((com.google.gson.Gson) gson).getAdapter(component).read(reader);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Failed to read JSON", ex);
|
||||
} catch (LinkageError er) {
|
||||
return deserializeLegacy(gson, component, str);
|
||||
}
|
||||
}
|
||||
|
||||
// Should only be needed on 1.8.
|
||||
private static Object deserializeLegacy(Object gson, Class<?> component, StringReader str) {
|
||||
try {
|
||||
if (readerConstructor == null) {
|
||||
|
||||
Class<?> readerClass = Class.forName("org.bukkit.craftbukkit.libs.com.google.gson.stream.JsonReader");
|
||||
readerConstructor = readerClass.getDeclaredConstructor(Reader.class);
|
||||
readerConstructor.setAccessible(true);
|
||||
setLenient = readerClass.getDeclaredMethod("setLenient", boolean.class);
|
||||
setLenient.setAccessible(true);
|
||||
getAdapter = gson.getClass().getDeclaredMethod("getAdapter", Class.class);
|
||||
getAdapter.setAccessible(true);
|
||||
Object adapter = getAdapter.invoke(gson, component);
|
||||
read = adapter.getClass().getDeclaredMethod("read", readerClass);
|
||||
read.setAccessible(true);
|
||||
}
|
||||
Object reader = readerConstructor.newInstance(str);
|
||||
setLenient.invoke(reader, true);
|
||||
Object adapter = getAdapter.invoke(gson, component);
|
||||
return read.invoke(adapter, reader);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException("Failed to read JSON", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2016 dmulloy2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* Handles component parsing in 1.8
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class ComponentParser {
|
||||
|
||||
private static Constructor readerConstructor;
|
||||
private static Method setLenient;
|
||||
private static Method getAdapter;
|
||||
private static Method read;
|
||||
|
||||
private ComponentParser() {
|
||||
}
|
||||
|
||||
public static Object deserialize(Object gson, Class<?> component, StringReader str) {
|
||||
try {
|
||||
com.google.gson.stream.JsonReader reader = new com.google.gson.stream.JsonReader(str);
|
||||
reader.setLenient(true);
|
||||
return ((com.google.gson.Gson) gson).getAdapter(component).read(reader);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Failed to read JSON", ex);
|
||||
} catch (LinkageError er) {
|
||||
return deserializeLegacy(gson, component, str);
|
||||
}
|
||||
}
|
||||
|
||||
// Should only be needed on 1.8.
|
||||
private static Object deserializeLegacy(Object gson, Class<?> component, StringReader str) {
|
||||
try {
|
||||
if (readerConstructor == null) {
|
||||
|
||||
Class<?> readerClass = Class.forName("org.bukkit.craftbukkit.libs.com.google.gson.stream.JsonReader");
|
||||
readerConstructor = readerClass.getDeclaredConstructor(Reader.class);
|
||||
readerConstructor.setAccessible(true);
|
||||
setLenient = readerClass.getDeclaredMethod("setLenient", boolean.class);
|
||||
setLenient.setAccessible(true);
|
||||
getAdapter = gson.getClass().getDeclaredMethod("getAdapter", Class.class);
|
||||
getAdapter.setAccessible(true);
|
||||
Object adapter = getAdapter.invoke(gson, component);
|
||||
read = adapter.getClass().getDeclaredMethod("read", readerClass);
|
||||
read.setAccessible(true);
|
||||
}
|
||||
Object reader = readerConstructor.newInstance(str);
|
||||
setLenient.invoke(reader, true);
|
||||
Object adapter = getAdapter.invoke(gson, component);
|
||||
return read.invoke(adapter, reader);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException("Failed to read JSON", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,174 +1,328 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2017 Dan Mulloy
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Utility class for converters
|
||||
* @author dmulloy2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Converters {
|
||||
|
||||
/**
|
||||
* Returns a converter that ignores null elements, so that the underlying converter doesn't have to worry about them.
|
||||
* @param converter Underlying converter
|
||||
* @param <T> Element type
|
||||
* @return An ignore null converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T> ignoreNull(final EquivalentConverter<T> converter) {
|
||||
return new EquivalentConverter<T>() {
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return generic != null ? converter.getSpecific(generic) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T specific) {
|
||||
return specific != null ? converter.getGeneric(specific) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return converter.getSpecificType();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a converter that passes generic and specific values through without converting.
|
||||
* @param clazz Element class
|
||||
* @param <T> Element type
|
||||
* @return A passthrough converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T> passthrough(final Class<T> clazz) {
|
||||
return ignoreNull(new EquivalentConverter<T>() {
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return (T) generic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T specific) {
|
||||
return specific;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return clazz;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple converter for wrappers with {@code getHandle()} and {@code fromHandle(...)} methods. With Java 8,
|
||||
* converters can be reduced to a single line (see {@link BukkitConverters#getWrappedGameProfileConverter()}).
|
||||
* @param toHandle Function from wrapper to handle (i.e. {@code getHandle()})
|
||||
* @param fromHandle Function from handle to wrapper (i.e. {@code fromHandle(Object)})
|
||||
* @param <T> Wrapper type
|
||||
* @return A handle converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T> handle(final Function<T, Object> toHandle,
|
||||
final Function<Object, T> fromHandle, final Class<T> specificType) {
|
||||
return new EquivalentConverter<T>() {
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return fromHandle.apply(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T specific) {
|
||||
return toHandle.apply(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return specificType;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic array converter. Converts a NMS object array to and from a wrapper array by converting
|
||||
* each element individually.
|
||||
*
|
||||
* @param nmsClass NMS class
|
||||
* @param converter Underlying converter
|
||||
* @param <T> Generic type
|
||||
* @return An array converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T[]> array(final Class<?> nmsClass, final EquivalentConverter<T> converter) {
|
||||
return new EquivalentConverter<T[]>() {
|
||||
@Override
|
||||
public T[] getSpecific(Object generic) {
|
||||
Object[] array = (Object[]) generic;
|
||||
Class<T[]> clazz = getSpecificType();
|
||||
T[] result = clazz.cast(Array.newInstance(clazz.getComponentType(), array.length));
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = converter.getSpecific(array[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T[] specific) {
|
||||
Object[] result = (Object[]) Array.newInstance(nmsClass, specific.length);
|
||||
|
||||
// Wrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = converter.getGeneric(specific[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T[]> getSpecificType() {
|
||||
return (Class<T[]>) MinecraftReflection.getArrayClass(converter.getSpecificType());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> EquivalentConverter<Optional<T>> optional(final EquivalentConverter<T> converter) {
|
||||
return new EquivalentConverter<Optional<T>>() {
|
||||
@Override
|
||||
public Object getGeneric(Optional<T> specific) {
|
||||
return specific.map(converter::getGeneric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<T> getSpecific(Object generic) {
|
||||
Optional<Object> optional = (Optional<Object>) generic;
|
||||
return optional.map(converter::getSpecific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Optional<T>> getSpecificType() {
|
||||
return (Class<Optional<T>>) Optional.empty().getClass();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Utility class for converters
|
||||
* @author dmulloy2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Converters {
|
||||
|
||||
/**
|
||||
* Returns a converter that ignores null elements, so that the underlying converter doesn't have to worry about them.
|
||||
* @param converter Underlying converter
|
||||
* @param <T> Element type
|
||||
* @return An ignore null converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T> ignoreNull(final EquivalentConverter<T> converter) {
|
||||
return new EquivalentConverter<T>() {
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return generic != null ? converter.getSpecific(generic) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T specific) {
|
||||
return specific != null ? converter.getGeneric(specific) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return converter.getSpecificType();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a converter that passes generic and specific values through without converting.
|
||||
* @param clazz Element class
|
||||
* @param <T> Element type
|
||||
* @return A passthrough converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T> passthrough(final Class<T> clazz) {
|
||||
return ignoreNull(new EquivalentConverter<T>() {
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return (T) generic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T specific) {
|
||||
return specific;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return clazz;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple converter for wrappers with {@code getHandle()} and {@code fromHandle(...)} methods. With Java 8,
|
||||
* converters can be reduced to a single line (see {@link BukkitConverters#getWrappedGameProfileConverter()}).
|
||||
* @param toHandle Function from wrapper to handle (i.e. {@code getHandle()})
|
||||
* @param fromHandle Function from handle to wrapper (i.e. {@code fromHandle(Object)})
|
||||
* @param <T> Wrapper type
|
||||
* @return A handle converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T> handle(final Function<T, Object> toHandle,
|
||||
final Function<Object, T> fromHandle, final Class<T> specificType) {
|
||||
return new EquivalentConverter<T>() {
|
||||
@Override
|
||||
public T getSpecific(Object generic) {
|
||||
return fromHandle.apply(generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T specific) {
|
||||
return toHandle.apply(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getSpecificType() {
|
||||
return specificType;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic array converter. Converts a NMS object array to and from a wrapper array by converting
|
||||
* each element individually.
|
||||
*
|
||||
* @param nmsClass NMS class
|
||||
* @param converter Underlying converter
|
||||
* @param <T> Generic type
|
||||
* @return An array converter
|
||||
*/
|
||||
public static <T> EquivalentConverter<T[]> array(final Class<?> nmsClass, final EquivalentConverter<T> converter) {
|
||||
return new EquivalentConverter<T[]>() {
|
||||
@Override
|
||||
public T[] getSpecific(Object generic) {
|
||||
Object[] array = (Object[]) generic;
|
||||
Class<T[]> clazz = getSpecificType();
|
||||
T[] result = clazz.cast(Array.newInstance(clazz.getComponentType(), array.length));
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = converter.getSpecific(array[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(T[] specific) {
|
||||
Object[] result = (Object[]) Array.newInstance(nmsClass, specific.length);
|
||||
|
||||
// Wrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = converter.getGeneric(specific[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T[]> getSpecificType() {
|
||||
return (Class<T[]>) MinecraftReflection.getArrayClass(converter.getSpecificType());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> EquivalentConverter<Optional<T>> optional(final EquivalentConverter<T> converter) {
|
||||
return new EquivalentConverter<Optional<T>>() {
|
||||
@Override
|
||||
public Object getGeneric(Optional<T> specific) {
|
||||
return specific.map(converter::getGeneric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<T> getSpecific(Object generic) {
|
||||
Optional<Object> optional = (Optional<Object>) generic;
|
||||
return optional.map(converter::getSpecific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Optional<T>> getSpecificType() {
|
||||
return (Class<Optional<T>>) Optional.empty().getClass();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class MovingObjectPositionBlock implements Cloneable {
|
||||
private static final Class<?> NMS_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.phys.MovingObjectPositionBlock", "world.phys.BlockHitResult", "MovingObjectPositionBlock");
|
||||
|
||||
private BlockPosition position;
|
||||
private Vector posVector;
|
||||
private Direction direction;
|
||||
private boolean insideBlock;
|
||||
|
||||
public MovingObjectPositionBlock() { }
|
||||
|
||||
public MovingObjectPositionBlock(BlockPosition position, Vector posVector, Direction direction, boolean insideBlock) {
|
||||
this.position = position;
|
||||
this.posVector = posVector;
|
||||
this.direction = direction;
|
||||
this.insideBlock = insideBlock;
|
||||
}
|
||||
|
||||
public static Class<?> getNmsClass() {
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
public BlockPosition getBlockPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setBlockPosition(BlockPosition position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Vector getPosVector() {
|
||||
return posVector;
|
||||
}
|
||||
|
||||
public void setPosVector(Vector vector) {
|
||||
this.posVector = vector;
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public void setDirection(Direction direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public boolean isInsideBlock() {
|
||||
return insideBlock;
|
||||
}
|
||||
|
||||
public void setInsideBlock(boolean insideBlock) {
|
||||
this.insideBlock = insideBlock;
|
||||
}
|
||||
|
||||
private static ConstructorAccessor constructor;
|
||||
|
||||
public static EquivalentConverter<MovingObjectPositionBlock> getConverter() {
|
||||
return Converters.ignoreNull(new EquivalentConverter<MovingObjectPositionBlock>() {
|
||||
@Override
|
||||
public Object getGeneric(MovingObjectPositionBlock specific) {
|
||||
if (constructor == null) {
|
||||
constructor = Accessors.getConstructorAccessor(NMS_CLASS,
|
||||
MinecraftReflection.getVec3DClass(),
|
||||
EnumWrappers.getDirectionClass(),
|
||||
MinecraftReflection.getBlockPositionClass(),
|
||||
boolean.class);
|
||||
}
|
||||
|
||||
Object nmsVector = BukkitConverters.getVectorConverter().getGeneric(specific.posVector);
|
||||
Object nmsDirection = EnumWrappers.getDirectionConverter().getGeneric(specific.direction);
|
||||
Object nmsBlockPos = BlockPosition.getConverter().getGeneric(specific.position);
|
||||
|
||||
return constructor.invoke(nmsVector, nmsDirection, nmsBlockPos, specific.insideBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovingObjectPositionBlock getSpecific(Object generic) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(generic.getClass()).withTarget(generic);
|
||||
Direction direction = modifier.withType(EnumWrappers.getDirectionClass(), EnumWrappers.getDirectionConverter()).read(0);
|
||||
BlockPosition blockPos = modifier.withType(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()).read(0);
|
||||
Vector posVector = modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
|
||||
boolean insideBlock = (boolean) modifier.withType(boolean.class).read(1);
|
||||
return new MovingObjectPositionBlock(blockPos, posVector, direction, insideBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<MovingObjectPositionBlock> getSpecificType() {
|
||||
return MovingObjectPositionBlock.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
public class MovingObjectPositionBlock implements Cloneable {
|
||||
private static final Class<?> NMS_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.phys.MovingObjectPositionBlock", "world.phys.BlockHitResult", "MovingObjectPositionBlock");
|
||||
|
||||
private BlockPosition position;
|
||||
private Vector posVector;
|
||||
private Direction direction;
|
||||
private boolean insideBlock;
|
||||
|
||||
public MovingObjectPositionBlock() { }
|
||||
|
||||
public MovingObjectPositionBlock(BlockPosition position, Vector posVector, Direction direction, boolean insideBlock) {
|
||||
this.position = position;
|
||||
this.posVector = posVector;
|
||||
this.direction = direction;
|
||||
this.insideBlock = insideBlock;
|
||||
}
|
||||
|
||||
public static Class<?> getNmsClass() {
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
public BlockPosition getBlockPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setBlockPosition(BlockPosition position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Vector getPosVector() {
|
||||
return posVector;
|
||||
}
|
||||
|
||||
public void setPosVector(Vector vector) {
|
||||
this.posVector = vector;
|
||||
}
|
||||
|
||||
public Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public void setDirection(Direction direction) {
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public boolean isInsideBlock() {
|
||||
return insideBlock;
|
||||
}
|
||||
|
||||
public void setInsideBlock(boolean insideBlock) {
|
||||
this.insideBlock = insideBlock;
|
||||
}
|
||||
|
||||
private static ConstructorAccessor constructor;
|
||||
|
||||
public static EquivalentConverter<MovingObjectPositionBlock> getConverter() {
|
||||
return Converters.ignoreNull(new EquivalentConverter<MovingObjectPositionBlock>() {
|
||||
@Override
|
||||
public Object getGeneric(MovingObjectPositionBlock specific) {
|
||||
if (constructor == null) {
|
||||
constructor = Accessors.getConstructorAccessor(NMS_CLASS,
|
||||
MinecraftReflection.getVec3DClass(),
|
||||
EnumWrappers.getDirectionClass(),
|
||||
MinecraftReflection.getBlockPositionClass(),
|
||||
boolean.class);
|
||||
}
|
||||
|
||||
Object nmsVector = BukkitConverters.getVectorConverter().getGeneric(specific.posVector);
|
||||
Object nmsDirection = EnumWrappers.getDirectionConverter().getGeneric(specific.direction);
|
||||
Object nmsBlockPos = BlockPosition.getConverter().getGeneric(specific.position);
|
||||
|
||||
return constructor.invoke(nmsVector, nmsDirection, nmsBlockPos, specific.insideBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MovingObjectPositionBlock getSpecific(Object generic) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(generic.getClass()).withTarget(generic);
|
||||
Direction direction = modifier.withType(EnumWrappers.getDirectionClass(), EnumWrappers.getDirectionConverter()).read(0);
|
||||
BlockPosition blockPos = modifier.withType(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()).read(0);
|
||||
Vector posVector = modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
|
||||
boolean insideBlock = (boolean) modifier.withType(boolean.class).read(1);
|
||||
return new MovingObjectPositionBlock(blockPos, posVector, direction, insideBlock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<MovingObjectPositionBlock> getSpecificType() {
|
||||
return MovingObjectPositionBlock.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,43 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Pair<A, B> {
|
||||
private A first;
|
||||
private B second;
|
||||
|
||||
public Pair(A first, B second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public A getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public B getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void setFirst(A first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public void setSecond(B second) {
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Pair<?, ?> pair = (Pair<?, ?>) o;
|
||||
return Objects.equals(first, pair.first) &&
|
||||
Objects.equals(second, pair.second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(first, second);
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Pair<A, B> {
|
||||
private A first;
|
||||
private B second;
|
||||
|
||||
public Pair(A first, B second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public A getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public B getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void setFirst(A first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public void setSecond(B second) {
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Pair<?, ?> pair = (Pair<?, ?>) o;
|
||||
return Objects.equals(first, pair.first) &&
|
||||
Objects.equals(second, pair.second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(first, second);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -1,127 +1,127 @@
|
||||
/**
|
||||
* (c) 2016 dmulloy2
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class Vector3F {
|
||||
protected float x;
|
||||
protected float y;
|
||||
protected float z;
|
||||
|
||||
public Vector3F() {
|
||||
this(0, 0, 0);
|
||||
}
|
||||
|
||||
public Vector3F(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public Vector3F setX(float x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public Vector3F setY(float y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public Vector3F setZ(float z) {
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Float.floatToIntBits(x);
|
||||
result = prime * result + Float.floatToIntBits(y);
|
||||
result = prime * result + Float.floatToIntBits(z);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
|
||||
if (obj instanceof Vector3F) {
|
||||
Vector3F that = (Vector3F) obj;
|
||||
if (Float.floatToIntBits(x) != Float.floatToIntBits(that.x))
|
||||
return false;
|
||||
if (Float.floatToIntBits(y) != Float.floatToIntBits(that.y))
|
||||
return false;
|
||||
if (Float.floatToIntBits(z) != Float.floatToIntBits(that.z))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Constructor<?> constructor = null;
|
||||
private static final Class<?> NMS_CLASS = MinecraftReflection.getNullableNMS("core.Vector3f", "core.Rotations", "Vector3f");
|
||||
|
||||
public static Class<?> getMinecraftClass() {
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
public static EquivalentConverter<Vector3F> getConverter() {
|
||||
return Converters.ignoreNull(new EquivalentConverter<Vector3F>() {
|
||||
@Override
|
||||
public Class<Vector3F> getSpecificType() {
|
||||
return Vector3F.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Vector3F specific) {
|
||||
if (constructor == null) {
|
||||
try {
|
||||
constructor = NMS_CLASS.getConstructor(float.class, float.class, float.class);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException("Failed to find constructor for Vector3f", ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return constructor.newInstance(specific.x, specific.y, specific.z);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException("Failed to create new instance of Vector3f", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3F getSpecific(Object generic) {
|
||||
StructureModifier<Float> modifier = new StructureModifier<Float>(generic.getClass())
|
||||
.withTarget(generic).withType(float.class);
|
||||
float x = modifier.read(0);
|
||||
float y = modifier.read(1);
|
||||
float z = modifier.read(2);
|
||||
return new Vector3F(x, y, z);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* (c) 2016 dmulloy2
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class Vector3F {
|
||||
protected float x;
|
||||
protected float y;
|
||||
protected float z;
|
||||
|
||||
public Vector3F() {
|
||||
this(0, 0, 0);
|
||||
}
|
||||
|
||||
public Vector3F(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public Vector3F setX(float x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public Vector3F setY(float y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public Vector3F setZ(float z) {
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Float.floatToIntBits(x);
|
||||
result = prime * result + Float.floatToIntBits(y);
|
||||
result = prime * result + Float.floatToIntBits(z);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
|
||||
if (obj instanceof Vector3F) {
|
||||
Vector3F that = (Vector3F) obj;
|
||||
if (Float.floatToIntBits(x) != Float.floatToIntBits(that.x))
|
||||
return false;
|
||||
if (Float.floatToIntBits(y) != Float.floatToIntBits(that.y))
|
||||
return false;
|
||||
if (Float.floatToIntBits(z) != Float.floatToIntBits(that.z))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Constructor<?> constructor = null;
|
||||
private static final Class<?> NMS_CLASS = MinecraftReflection.getNullableNMS("core.Vector3f", "core.Rotations", "Vector3f");
|
||||
|
||||
public static Class<?> getMinecraftClass() {
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
public static EquivalentConverter<Vector3F> getConverter() {
|
||||
return Converters.ignoreNull(new EquivalentConverter<Vector3F>() {
|
||||
@Override
|
||||
public Class<Vector3F> getSpecificType() {
|
||||
return Vector3F.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Vector3F specific) {
|
||||
if (constructor == null) {
|
||||
try {
|
||||
constructor = NMS_CLASS.getConstructor(float.class, float.class, float.class);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException("Failed to find constructor for Vector3f", ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return constructor.newInstance(specific.x, specific.y, specific.z);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException("Failed to create new instance of Vector3f", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3F getSpecific(Object generic) {
|
||||
StructureModifier<Float> modifier = new StructureModifier<Float>(generic.getClass())
|
||||
.withTarget(generic).withType(float.class);
|
||||
float x = modifier.read(0);
|
||||
float y = modifier.read(1);
|
||||
float z = modifier.read(2);
|
||||
return new Vector3F(x, y, z);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,169 +1,183 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.mojang.math.Vector3fa;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
/**
|
||||
* Represents an immutable wrapped ParticleParam in 1.13
|
||||
*/
|
||||
public class WrappedParticle<T> {
|
||||
private static MethodAccessor toBukkit;
|
||||
private static MethodAccessor toNMS;
|
||||
private static MethodAccessor toCraftData;
|
||||
|
||||
private static void ensureMethods() {
|
||||
if (toBukkit != null && toNMS != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("CraftParticle"));
|
||||
FuzzyMethodContract contract = FuzzyMethodContract
|
||||
.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(Particle.class)
|
||||
.parameterExactType(MinecraftReflection.getParticleParam())
|
||||
.build();
|
||||
toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
|
||||
contract = FuzzyMethodContract
|
||||
.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(MinecraftReflection.getParticleParam())
|
||||
.parameterCount(2)
|
||||
.build();
|
||||
toNMS = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
|
||||
Class<?> cbData = MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData");
|
||||
fuzzy = FuzzyReflection.fromClass(cbData);
|
||||
contract = FuzzyMethodContract
|
||||
.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(cbData)
|
||||
.parameterExactArray(MinecraftReflection.getIBlockDataClass())
|
||||
.build();
|
||||
toCraftData = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
}
|
||||
|
||||
private final Particle particle;
|
||||
private final T data;
|
||||
private final Object handle;
|
||||
|
||||
private WrappedParticle(Object handle, Particle particle, T data) {
|
||||
this.handle = handle;
|
||||
this.particle = particle;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return This particle's Bukkit type
|
||||
*/
|
||||
public Particle getParticle() {
|
||||
return particle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this Particle's Bukkit/ProtocolLib data. The type of this data depends on the
|
||||
* {@link #getParticle() Particle type}. For Block particles it will be {@link WrappedBlockData},
|
||||
* for Item crack particles, it will be an {@link ItemStack}, and for redstone particles it will
|
||||
* be {@link Particle.DustOptions}
|
||||
*
|
||||
* @return The particle data
|
||||
*/
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NMS handle
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
public static WrappedParticle fromHandle(Object handle) {
|
||||
ensureMethods();
|
||||
|
||||
Particle bukkit = (Particle) toBukkit.invoke(null, handle);
|
||||
Object data = null;
|
||||
|
||||
switch (bukkit) {
|
||||
case BLOCK_CRACK:
|
||||
case BLOCK_DUST:
|
||||
case FALLING_DUST:
|
||||
data = getBlockData(handle);
|
||||
break;
|
||||
case ITEM_CRACK:
|
||||
data = getItem(handle);
|
||||
break;
|
||||
case REDSTONE:
|
||||
data = getRedstone(handle);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return new WrappedParticle<>(handle, bukkit, data);
|
||||
}
|
||||
|
||||
private static WrappedBlockData getBlockData(Object handle) {
|
||||
return new StructureModifier<>(handle.getClass())
|
||||
.withTarget(handle)
|
||||
.withType(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter())
|
||||
.read(0);
|
||||
}
|
||||
|
||||
private static Object getItem(Object handle) {
|
||||
return new StructureModifier<>(handle.getClass())
|
||||
.withTarget(handle)
|
||||
.withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter())
|
||||
.read(0);
|
||||
}
|
||||
|
||||
private static Object getRedstone(Object handle) {
|
||||
int r, g, b;
|
||||
float alpha;
|
||||
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
Vector3fa rgb = (Vector3fa) modifier.withType(Vector3fa.class).read(0);
|
||||
|
||||
r = (int) (rgb.a() * 255);
|
||||
g = (int) (rgb.b() * 255);
|
||||
b = (int) (rgb.c() * 255);
|
||||
alpha = (float) modifier.withType(float.class).read(0);
|
||||
} else {
|
||||
StructureModifier<Float> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class);
|
||||
r = (int) (modifier.read(0) * 255);
|
||||
g = (int) (modifier.read(1) * 255);
|
||||
b = (int) (modifier.read(2) * 255);
|
||||
alpha = modifier.read(3);
|
||||
}
|
||||
|
||||
return new Particle.DustOptions(Color.fromRGB(r, g, b), alpha);
|
||||
}
|
||||
|
||||
public static <T> WrappedParticle<T> create(Particle particle, T data) {
|
||||
ensureMethods();
|
||||
|
||||
Object bukkitData = data;
|
||||
if (data instanceof WrappedBlockData) {
|
||||
WrappedBlockData blockData = (WrappedBlockData) data;
|
||||
bukkitData = toCraftData.invoke(null, blockData.getHandle());
|
||||
}
|
||||
|
||||
Object handle = toNMS.invoke(null, particle, bukkitData);
|
||||
return new WrappedParticle<>(handle, particle, data);
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Particle;
|
||||
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;
|
||||
|
||||
private static void ensureMethods() {
|
||||
if (toBukkit != null && toNMS != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("CraftParticle"));
|
||||
FuzzyMethodContract contract = FuzzyMethodContract
|
||||
.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(Particle.class)
|
||||
.parameterExactType(MinecraftReflection.getParticleParam())
|
||||
.build();
|
||||
toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
|
||||
contract = FuzzyMethodContract
|
||||
.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(MinecraftReflection.getParticleParam())
|
||||
.parameterCount(2)
|
||||
.build();
|
||||
toNMS = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
|
||||
Class<?> cbData = MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData");
|
||||
fuzzy = FuzzyReflection.fromClass(cbData);
|
||||
contract = FuzzyMethodContract
|
||||
.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(cbData)
|
||||
.parameterExactArray(MinecraftReflection.getIBlockDataClass())
|
||||
.build();
|
||||
toCraftData = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
}
|
||||
|
||||
private final Particle particle;
|
||||
private final T data;
|
||||
private final Object handle;
|
||||
|
||||
private WrappedParticle(Object handle, Particle particle, T data) {
|
||||
this.handle = handle;
|
||||
this.particle = particle;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return This particle's Bukkit type
|
||||
*/
|
||||
public Particle getParticle() {
|
||||
return particle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this Particle's Bukkit/ProtocolLib data. The type of this data depends on the
|
||||
* {@link #getParticle() Particle type}. For Block particles it will be {@link WrappedBlockData},
|
||||
* for Item crack particles, it will be an {@link ItemStack}, and for redstone particles it will
|
||||
* be {@link Particle.DustOptions}
|
||||
*
|
||||
* @return The particle data
|
||||
*/
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NMS handle
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
public static WrappedParticle fromHandle(Object handle) {
|
||||
ensureMethods();
|
||||
|
||||
Particle bukkit = (Particle) toBukkit.invoke(null, handle);
|
||||
Object data = null;
|
||||
|
||||
switch (bukkit) {
|
||||
case BLOCK_CRACK:
|
||||
case BLOCK_DUST:
|
||||
case FALLING_DUST:
|
||||
data = getBlockData(handle);
|
||||
break;
|
||||
case ITEM_CRACK:
|
||||
data = getItem(handle);
|
||||
break;
|
||||
case REDSTONE:
|
||||
data = getRedstone(handle);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return new WrappedParticle<>(handle, bukkit, data);
|
||||
}
|
||||
|
||||
private static WrappedBlockData getBlockData(Object handle) {
|
||||
return new StructureModifier<>(handle.getClass())
|
||||
.withTarget(handle)
|
||||
.withType(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter())
|
||||
.read(0);
|
||||
}
|
||||
|
||||
private static Object getItem(Object handle) {
|
||||
return new StructureModifier<>(handle.getClass())
|
||||
.withTarget(handle)
|
||||
.withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter())
|
||||
.read(0);
|
||||
}
|
||||
|
||||
private static Object getRedstone(Object handle) {
|
||||
int r, g, b;
|
||||
float alpha;
|
||||
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
org.joml.Vector3f rgb = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(0);
|
||||
|
||||
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);
|
||||
r = (int) (modifier.read(0) * 255);
|
||||
g = (int) (modifier.read(1) * 255);
|
||||
b = (int) (modifier.read(2) * 255);
|
||||
alpha = modifier.read(3);
|
||||
}
|
||||
|
||||
return new Particle.DustOptions(Color.fromRGB(r, g, b), alpha);
|
||||
}
|
||||
|
||||
public static <T> WrappedParticle<T> create(Particle particle, T data) {
|
||||
ensureMethods();
|
||||
|
||||
Object bukkitData = data;
|
||||
if (data instanceof WrappedBlockData) {
|
||||
WrappedBlockData blockData = (WrappedBlockData) data;
|
||||
bukkitData = toCraftData.invoke(null, blockData.getHandle());
|
||||
}
|
||||
|
||||
Object handle = toNMS.invoke(null, particle, bukkitData);
|
||||
return new WrappedParticle<>(handle, particle, data);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,84 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
public class WrappedVillagerData extends AbstractWrapper implements ClonableWrapper {
|
||||
private static final Class<?> NMS_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.entity.npc.VillagerData","VillagerData");
|
||||
private static final Class<?> TYPE_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.entity.npc.VillagerType", "VillagerType");
|
||||
private static final Class<?> PROF_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.entity.npc.VillagerProfession", "VillagerProfession");
|
||||
|
||||
private static EquivalentConverter<Type> TYPE_CONVERTER;
|
||||
private static EquivalentConverter<Profession> PROF_CONVERTER;
|
||||
|
||||
static {
|
||||
if (NMS_CLASS != null) {
|
||||
TYPE_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Type.class, TYPE_CLASS);
|
||||
PROF_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Profession.class, PROF_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
DESERT, JUNGLE, PLAINS, SAVANNA, SNOW, SWAMP, TAIGA
|
||||
}
|
||||
|
||||
public enum Profession {
|
||||
NONE, ARMORER, BUTCHER, CARTOGRAPHER, CLERIC, FARMER, FISHERMAN,
|
||||
FLETCHER, LEATHERWORKER, LIBRARIAN, MASON, NITWIT, SHEPHERD,
|
||||
TOOLSMITH, WEAPONSMITH
|
||||
}
|
||||
|
||||
private StructureModifier<Object> modifier;
|
||||
|
||||
private WrappedVillagerData(Object handle) {
|
||||
super(NMS_CLASS);
|
||||
setHandle(handle);
|
||||
|
||||
modifier = new StructureModifier<>(NMS_CLASS).withTarget(handle);
|
||||
}
|
||||
|
||||
public static WrappedVillagerData fromHandle(Object handle) {
|
||||
return new WrappedVillagerData(handle);
|
||||
}
|
||||
|
||||
private static ConstructorAccessor CONSTRUCTOR;
|
||||
|
||||
public static WrappedVillagerData fromValues(Type type, Profession profession, int level) {
|
||||
Object genericType = TYPE_CONVERTER.getGeneric(type);
|
||||
Object genericProf = PROF_CONVERTER.getGeneric(profession);
|
||||
|
||||
if (CONSTRUCTOR == null) {
|
||||
CONSTRUCTOR = Accessors.getConstructorAccessor(NMS_CLASS, TYPE_CLASS, PROF_CLASS, int.class);
|
||||
}
|
||||
|
||||
Object handle = CONSTRUCTOR.invoke(genericType, genericProf, level);
|
||||
return fromHandle(handle);
|
||||
}
|
||||
|
||||
public static Class<?> getNmsClass() {
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return modifier.<Integer>withType(int.class).read(0);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return modifier.withType(TYPE_CLASS, TYPE_CONVERTER).read(0);
|
||||
}
|
||||
|
||||
public Profession getProfession() {
|
||||
return modifier.withType(PROF_CLASS, PROF_CONVERTER).read(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedVillagerData deepClone() {
|
||||
return WrappedVillagerData.fromValues(getType(), getProfession(), getLevel());
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
public class WrappedVillagerData extends AbstractWrapper implements ClonableWrapper {
|
||||
private static final Class<?> NMS_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.entity.npc.VillagerData","VillagerData");
|
||||
private static final Class<?> TYPE_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.entity.npc.VillagerType", "VillagerType");
|
||||
private static final Class<?> PROF_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.entity.npc.VillagerProfession", "VillagerProfession");
|
||||
|
||||
private static EquivalentConverter<Type> TYPE_CONVERTER;
|
||||
private static EquivalentConverter<Profession> PROF_CONVERTER;
|
||||
|
||||
static {
|
||||
if (NMS_CLASS != null) {
|
||||
TYPE_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Type.class, TYPE_CLASS);
|
||||
PROF_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Profession.class, PROF_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
DESERT, JUNGLE, PLAINS, SAVANNA, SNOW, SWAMP, TAIGA
|
||||
}
|
||||
|
||||
public enum Profession {
|
||||
NONE, ARMORER, BUTCHER, CARTOGRAPHER, CLERIC, FARMER, FISHERMAN,
|
||||
FLETCHER, LEATHERWORKER, LIBRARIAN, MASON, NITWIT, SHEPHERD,
|
||||
TOOLSMITH, WEAPONSMITH
|
||||
}
|
||||
|
||||
private StructureModifier<Object> modifier;
|
||||
|
||||
private WrappedVillagerData(Object handle) {
|
||||
super(NMS_CLASS);
|
||||
setHandle(handle);
|
||||
|
||||
modifier = new StructureModifier<>(NMS_CLASS).withTarget(handle);
|
||||
}
|
||||
|
||||
public static WrappedVillagerData fromHandle(Object handle) {
|
||||
return new WrappedVillagerData(handle);
|
||||
}
|
||||
|
||||
private static ConstructorAccessor CONSTRUCTOR;
|
||||
|
||||
public static WrappedVillagerData fromValues(Type type, Profession profession, int level) {
|
||||
Object genericType = TYPE_CONVERTER.getGeneric(type);
|
||||
Object genericProf = PROF_CONVERTER.getGeneric(profession);
|
||||
|
||||
if (CONSTRUCTOR == null) {
|
||||
CONSTRUCTOR = Accessors.getConstructorAccessor(NMS_CLASS, TYPE_CLASS, PROF_CLASS, int.class);
|
||||
}
|
||||
|
||||
Object handle = CONSTRUCTOR.invoke(genericType, genericProf, level);
|
||||
return fromHandle(handle);
|
||||
}
|
||||
|
||||
public static Class<?> getNmsClass() {
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return modifier.<Integer>withType(int.class).read(0);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return modifier.withType(TYPE_CLASS, TYPE_CONVERTER).read(0);
|
||||
}
|
||||
|
||||
public Profession getProfession() {
|
||||
return modifier.withType(PROF_CLASS, PROF_CONVERTER).read(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedVillagerData deepClone() {
|
||||
return WrappedVillagerData.fromValues(getType(), getProfession(), getLevel());
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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,45 +1,45 @@
|
||||
name: ProtocolLib
|
||||
version: ${project.fullVersion}
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
authors: [dmulloy2, comphenix]
|
||||
|
||||
main: com.comphenix.protocol.ProtocolLib
|
||||
load: STARTUP
|
||||
database: false
|
||||
api-version: "1.13"
|
||||
|
||||
commands:
|
||||
protocol:
|
||||
description: Performs administrative tasks regarding ProtocolLib.
|
||||
usage: /<command> config|check|update|timings|listeners|version|dump
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
packet:
|
||||
description: Add or remove a simple packet listener.
|
||||
usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed]
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
filter:
|
||||
description: Add or remove programmable filters to the packet listeners.
|
||||
usage: /<command> add|remove name [ID start]-[ID stop]
|
||||
aliases: [packet_filter]
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
packetlog:
|
||||
description: Logs hex representations of packets to a file or console
|
||||
usage: /<command> <protocol> <sender> <packet> [location]
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
|
||||
permissions:
|
||||
protocol.*:
|
||||
description: Gives access to everything.
|
||||
children:
|
||||
protocol.admin: true
|
||||
protocol.info: true
|
||||
protocol.admin:
|
||||
description: Able to initiate the update process, and can configure debug mode.
|
||||
default: op
|
||||
protocol.info:
|
||||
description: Can read update notifications and error reports.
|
||||
name: ProtocolLib
|
||||
version: ${version}
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
authors: [dmulloy2, comphenix]
|
||||
|
||||
main: com.comphenix.protocol.ProtocolLib
|
||||
load: STARTUP
|
||||
database: false
|
||||
api-version: "1.13"
|
||||
|
||||
commands:
|
||||
protocol:
|
||||
description: Performs administrative tasks regarding ProtocolLib.
|
||||
usage: /<command> config|check|update|timings|listeners|version|dump
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
packet:
|
||||
description: Add or remove a simple packet listener.
|
||||
usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed]
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
filter:
|
||||
description: Add or remove programmable filters to the packet listeners.
|
||||
usage: /<command> add|remove name [ID start]-[ID stop]
|
||||
aliases: [packet_filter]
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
packetlog:
|
||||
description: Logs hex representations of packets to a file or console
|
||||
usage: /<command> <protocol> <sender> <packet> [location]
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
|
||||
permissions:
|
||||
protocol.*:
|
||||
description: Gives access to everything.
|
||||
children:
|
||||
protocol.admin: true
|
||||
protocol.info: true
|
||||
protocol.admin:
|
||||
description: Able to initiate the update process, and can configure debug mode.
|
||||
default: op
|
||||
protocol.info:
|
||||
description: Can read update notifications and error reports.
|
||||
default: op
|
||||
@@ -1,126 +1,136 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflectionTestUtil;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.IRegistry;
|
||||
import net.minecraft.server.DispenserRegistry;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemFactory;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.util.Versioning;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
|
||||
/**
|
||||
* Used to ensure that ProtocolLib and Bukkit is prepared to be tested.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitInitialization {
|
||||
|
||||
private static final BukkitInitialization instance = new BukkitInitialization();
|
||||
private boolean initialized;
|
||||
private boolean packaged;
|
||||
|
||||
private BukkitInitialization() {
|
||||
System.out.println("Created new BukkitInitialization on " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Statically initializes the mock server for unit testing
|
||||
*/
|
||||
public static synchronized void initializeAll() {
|
||||
instance.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Bukkit and ProtocolLib such that we can perfrom unit testing
|
||||
*/
|
||||
private void initialize() {
|
||||
if (!this.initialized) {
|
||||
// Denote that we're done
|
||||
this.initialized = true;
|
||||
|
||||
try {
|
||||
LogManager.getLogger();
|
||||
} catch (Throwable ex) {
|
||||
// Happens only on my Jenkins, but if it errors here it works when it matters
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
instance.setPackage();
|
||||
|
||||
SharedConstants.a();
|
||||
DispenserRegistry.a();
|
||||
|
||||
try {
|
||||
IRegistry.class.getName();
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
String releaseTarget = SharedConstants.b().getReleaseTarget();
|
||||
String serverVersion = CraftServer.class.getPackage().getImplementationVersion();
|
||||
|
||||
// Mock the server object
|
||||
Server mockedServer = mock(Server.class);
|
||||
|
||||
when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft"));
|
||||
when(mockedServer.getName()).thenReturn("Mock Server");
|
||||
when(mockedServer.getVersion()).thenReturn(serverVersion + " (MC: " + releaseTarget + ")");
|
||||
when(mockedServer.getBukkitVersion()).thenReturn(Versioning.getBukkitVersion());
|
||||
|
||||
when(mockedServer.getItemFactory()).thenReturn(CraftItemFactory.instance());
|
||||
when(mockedServer.isPrimaryThread()).thenReturn(true);
|
||||
|
||||
WorldServer nmsWorld = mock(WorldServer.class);
|
||||
|
||||
SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class);
|
||||
|
||||
try {
|
||||
FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig"));
|
||||
spigotConfig.set(nmsWorld, mockWorldConfig);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
CraftWorld world = mock(CraftWorld.class);
|
||||
when(world.getHandle()).thenReturn(nmsWorld);
|
||||
|
||||
List<World> worlds = Collections.singletonList(world);
|
||||
when(mockedServer.getWorlds()).thenReturn(worlds);
|
||||
|
||||
// Inject this fake server
|
||||
Bukkit.setServer(mockedServer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that package names are correctly set up.
|
||||
*/
|
||||
private void setPackage() {
|
||||
if (!this.packaged) {
|
||||
this.packaged = true;
|
||||
|
||||
try {
|
||||
LogManager.getLogger();
|
||||
} catch (Throwable ex) {
|
||||
// Happens only on my Jenkins, but if it errors here it works when it matters
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
MinecraftReflectionTestUtil.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import 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 net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.IRegistry;
|
||||
import net.minecraft.server.DispenserRegistry;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_19_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.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitInitialization {
|
||||
|
||||
private static final BukkitInitialization instance = new BukkitInitialization();
|
||||
private boolean initialized;
|
||||
private boolean packaged;
|
||||
|
||||
private BukkitInitialization() {
|
||||
System.out.println("Created new BukkitInitialization on " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Statically initializes the mock server for unit testing
|
||||
*/
|
||||
public static void initializeAll() {
|
||||
instance.initialize();
|
||||
}
|
||||
|
||||
private static final Object initLock = new Object();
|
||||
|
||||
/**
|
||||
* Initialize Bukkit and ProtocolLib such that we can perform unit testing
|
||||
*/
|
||||
private void initialize() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (initLock) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LogManager.getLogger();
|
||||
} catch (Throwable ex) {
|
||||
// Happens only on my Jenkins, but if it errors here it works when it matters
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
instance.setPackage();
|
||||
|
||||
SharedConstants.a();
|
||||
DispenserRegistry.a();
|
||||
|
||||
try {
|
||||
IRegistry.class.getName();
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
String releaseTarget = MinecraftReflectionTestUtil.RELEASE_TARGET;
|
||||
String serverVersion = CraftServer.class.getPackage().getImplementationVersion();
|
||||
|
||||
// Mock the server object
|
||||
Server mockedServer = mock(Server.class);
|
||||
|
||||
when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft"));
|
||||
when(mockedServer.getName()).thenReturn("Mock Server");
|
||||
when(mockedServer.getVersion()).thenReturn(serverVersion + " (MC: " + releaseTarget + ")");
|
||||
when(mockedServer.getBukkitVersion()).thenReturn(Versioning.getBukkitVersion());
|
||||
|
||||
when(mockedServer.getItemFactory()).thenReturn(CraftItemFactory.instance());
|
||||
when(mockedServer.isPrimaryThread()).thenReturn(true);
|
||||
|
||||
WorldServer nmsWorld = mock(WorldServer.class);
|
||||
|
||||
SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class);
|
||||
|
||||
try {
|
||||
FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig"));
|
||||
spigotConfig.set(nmsWorld, mockWorldConfig);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
CraftWorld world = mock(CraftWorld.class);
|
||||
when(world.getHandle()).thenReturn(nmsWorld);
|
||||
|
||||
List<World> worlds = Collections.singletonList(world);
|
||||
when(mockedServer.getWorlds()).thenReturn(worlds);
|
||||
|
||||
// Inject this fake server
|
||||
Bukkit.setServer(mockedServer);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that package names are correctly set up.
|
||||
*/
|
||||
private void setPackage() {
|
||||
if (!this.packaged) {
|
||||
this.packaged = true;
|
||||
|
||||
try {
|
||||
LogManager.getLogger();
|
||||
} catch (Throwable ex) {
|
||||
// Happens only on my Jenkins, but if it errors here it works when it matters
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
MinecraftReflectionTestUtil.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,334 +1,377 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2016 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;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.utility.MinecraftReflectionTestUtil;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import net.minecraft.network.EnumProtocol;
|
||||
import net.minecraft.network.protocol.EnumProtocolDirection;
|
||||
import net.minecraft.network.protocol.login.PacketLoginInStart;
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketTypeTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
|
||||
// I'm well aware this is jank, but it does in fact work correctly and give the desired result
|
||||
PacketType.onDynamicCreate = className -> {
|
||||
throw new RuntimeException("Dynamically generated packet " + className);
|
||||
};
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass() {
|
||||
PacketType.onDynamicCreate = __ -> {
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void main(String[] args) throws Exception {
|
||||
MinecraftReflectionTestUtil.init();
|
||||
|
||||
Set<Class<?>> allTypes = new HashSet<>();
|
||||
List<Class<?>> newTypes = new ArrayList<>();
|
||||
|
||||
EnumProtocol[] protocols = EnumProtocol.values();
|
||||
for (EnumProtocol protocol : protocols) {
|
||||
System.out.println(WordUtils.capitalize(protocol.name().toLowerCase()));
|
||||
|
||||
Field field = EnumProtocol.class.getDeclaredField("j");
|
||||
field.setAccessible(true);
|
||||
|
||||
Map<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<Class<?>, Integer> reverseMap = (Map<Class<?>, Integer>) mapField.get(entry.getValue());
|
||||
|
||||
Map<Integer, Class<?>> treeMap = new TreeMap<>();
|
||||
for (Entry<Class<?>, Integer> entry1 : reverseMap.entrySet()) {
|
||||
treeMap.put(entry1.getValue(), entry1.getKey());
|
||||
}
|
||||
|
||||
System.out.println(" " + entry.getKey());
|
||||
for (Entry<Integer, Class<?>> entry1 : treeMap.entrySet()) {
|
||||
System.out.println(generateNewType(entry1.getKey(), entry1.getValue()));
|
||||
allTypes.add(entry1.getValue());
|
||||
|
||||
try {
|
||||
PacketType.fromClass(entry1.getValue());
|
||||
} catch (Exception ex) {
|
||||
newTypes.add(entry1.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("New types: " + newTypes);
|
||||
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (type.isDeprecated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!allTypes.contains(type.getPacketClass())) {
|
||||
System.out.println(type + " was removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatHex(int dec) {
|
||||
if (dec < 0) {
|
||||
return "0xFF";
|
||||
}
|
||||
|
||||
String hex = Integer.toHexString(dec).toUpperCase();
|
||||
return "0x" + (hex.length() < 2 ? "0" : "") + hex;
|
||||
}
|
||||
|
||||
private static List<String> splitOnCaps(String string) {
|
||||
List<String> list = new ArrayList<>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char[] chars = string.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char c = chars[i];
|
||||
if (i != 0 && Character.isUpperCase(c)) {
|
||||
list.add(builder.toString());
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
|
||||
builder.append(c);
|
||||
}
|
||||
|
||||
list.add(builder.toString());
|
||||
return list;
|
||||
}
|
||||
|
||||
private static String generateNewType(int packetId, Class<?> clazz) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("\t\t\t");
|
||||
builder.append("public static final PacketType ");
|
||||
|
||||
String fullName = clazz.getName();
|
||||
fullName = fullName.substring(fullName.lastIndexOf(".") + 1);
|
||||
|
||||
String className;
|
||||
List<String> classNames = new ArrayList<>();
|
||||
|
||||
if (fullName.endsWith("Packet")) {
|
||||
for (String name : fullName.split("\\$")) {
|
||||
List<String> split = splitOnCaps(name);
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
for (int i = 1; i < split.size() - 1; i++) {
|
||||
nameBuilder.append(split.get(i));
|
||||
}
|
||||
classNames.add(nameBuilder.toString());
|
||||
}
|
||||
} else {
|
||||
for (String name : fullName.split("\\$")) {
|
||||
List<String> split = splitOnCaps(name);
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
for (int i = 3; i < split.size(); i++) {
|
||||
nameBuilder.append(split.get(i));
|
||||
}
|
||||
classNames.add(nameBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
className = classNames.get(classNames.size() - 1);
|
||||
|
||||
// Format it like SET_PROTOCOL
|
||||
StringBuilder fieldName = new StringBuilder();
|
||||
char[] chars = className.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char c = chars[i];
|
||||
if (i != 0 && Character.isUpperCase(c)) {
|
||||
fieldName.append("_");
|
||||
}
|
||||
fieldName.append(Character.toUpperCase(c));
|
||||
}
|
||||
|
||||
builder.append(fieldName.toString().replace("N_B_T", "NBT"));
|
||||
builder.append(" = ");
|
||||
|
||||
// Add spacing
|
||||
if (builder.length() > 65) {
|
||||
builder.append("\n");
|
||||
} else {
|
||||
while (builder.length() < 65) {
|
||||
builder.append(" ");
|
||||
}
|
||||
}
|
||||
builder.append("new ");
|
||||
builder.append("PacketType(PROTOCOL, SENDER, ");
|
||||
|
||||
builder.append(formatHex(packetId));
|
||||
builder.append(", ");
|
||||
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
for (int i = 0; i < classNames.size(); i++) {
|
||||
if (i != 0) {
|
||||
nameBuilder.append("$");
|
||||
}
|
||||
nameBuilder.append(classNames.get(i));
|
||||
}
|
||||
|
||||
String name = nameBuilder.toString();
|
||||
String namesArg = listToString(getAllNames(clazz, name));
|
||||
|
||||
builder.append(namesArg);
|
||||
builder.append(");");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static List<String> getAllNames(Class<?> packetClass, String newName) {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add(newName);
|
||||
|
||||
try {
|
||||
PacketType type = PacketType.fromClass(packetClass);
|
||||
for (String alias : type.names) {
|
||||
alias = alias.substring(alias.lastIndexOf('.') + 1);
|
||||
if (!names.contains(alias)) {
|
||||
names.add(alias);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private static String listToString(List<String> list) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i != 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append("\"").append(list.get(i)).append("\"");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeReflection() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindCurrent() {
|
||||
assertEquals(PacketType.Play.Client.STEER_VEHICLE,
|
||||
PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, "SteerVehicle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginStart() {
|
||||
// This packet is critical for handleLoin
|
||||
assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeprecation() {
|
||||
assertTrue(PacketType.Status.Server.OUT_SERVER_INFO.isDeprecated(), "Packet isn't properly deprecated");
|
||||
assertTrue(PacketRegistry.getServerPacketTypes().contains(PacketType.Status.Server.OUT_SERVER_INFO),
|
||||
"Deprecated packet isn't properly included");
|
||||
assertFalse(PacketType.Play.Server.CHAT.isDeprecated(), "Packet isn't properly deprecated");
|
||||
assertEquals(PacketType.Status.Server.OUT_SERVER_INFO, PacketType.Status.Server.SERVER_INFO,
|
||||
"Deprecated packets aren't equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void ensureTypesAreCorrect() throws Exception {
|
||||
boolean fail = false;
|
||||
|
||||
EnumProtocol[] protocols = EnumProtocol.values();
|
||||
for (EnumProtocol protocol : protocols) {
|
||||
Field field = EnumProtocol.class.getDeclaredField("j");
|
||||
field.setAccessible(true);
|
||||
|
||||
Map<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<Class<?>, Integer> reverseMap = (Map<Class<?>, Integer>) mapField.get(entry.getValue());
|
||||
|
||||
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) {
|
||||
ex.printStackTrace();
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(fail, "Packet type(s) were incorrect!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPacketCreation() {
|
||||
boolean fail = false;
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (type.isSupported()) {
|
||||
try {
|
||||
new PacketContainer(type);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertFalse(fail, "Packet type(s) failed to instantiate");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2016 dmulloy2
|
||||
* <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;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflectionTestUtil;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import net.minecraft.network.EnumProtocol;
|
||||
import net.minecraft.network.protocol.EnumProtocolDirection;
|
||||
import net.minecraft.network.protocol.login.PacketLoginInStart;
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketTypeTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
|
||||
// I'm well aware this is jank, but it does in fact work correctly and give the desired result
|
||||
/*PacketType.onDynamicCreate = className -> {
|
||||
throw new RuntimeException("Dynamically generated packet " + className);
|
||||
};*/
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass() {
|
||||
PacketType.onDynamicCreate = __ -> {
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// @Test
|
||||
// public static void main(String[] args) throws Exception {
|
||||
public void generateNewPackets() throws Exception {
|
||||
MinecraftReflectionTestUtil.init();
|
||||
|
||||
Set<Class<?>> allTypes = new HashSet<>();
|
||||
List<Class<?>> newTypes = new ArrayList<>();
|
||||
|
||||
EnumProtocol[] protocols = EnumProtocol.values();
|
||||
for (EnumProtocol protocol : protocols) {
|
||||
System.out.println(WordUtils.capitalize(protocol.name().toLowerCase()));
|
||||
|
||||
Field field = EnumProtocol.class.getDeclaredField("k");
|
||||
field.setAccessible(true);
|
||||
|
||||
Map<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<Class<?>, Integer> reverseMap = (Map<Class<?>, Integer>) mapField.get(entry.getValue());
|
||||
|
||||
Map<Integer, Class<?>> treeMap = new TreeMap<>();
|
||||
for (Entry<Class<?>, Integer> entry1 : reverseMap.entrySet()) {
|
||||
treeMap.put(entry1.getValue(), entry1.getKey());
|
||||
}
|
||||
|
||||
System.out.println(" " + entry.getKey());
|
||||
for (Entry<Integer, Class<?>> entry1 : treeMap.entrySet()) {
|
||||
System.out.println(generateNewType(entry1.getKey(), entry1.getValue()));
|
||||
allTypes.add(entry1.getValue());
|
||||
|
||||
try {
|
||||
PacketType.fromClass(entry1.getValue());
|
||||
} catch (Exception ex) {
|
||||
newTypes.add(entry1.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("New types: " + newTypes);
|
||||
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (type.isDeprecated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!allTypes.contains(type.getPacketClass())) {
|
||||
System.out.println(type + " was removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatHex(int dec) {
|
||||
if (dec < 0) {
|
||||
return "0xFF";
|
||||
}
|
||||
|
||||
String hex = Integer.toHexString(dec).toUpperCase();
|
||||
return "0x" + (hex.length() < 2 ? "0" : "") + hex;
|
||||
}
|
||||
|
||||
private static List<String> splitOnCaps(String string) {
|
||||
List<String> list = new ArrayList<>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char[] chars = string.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char c = chars[i];
|
||||
if (i != 0 && Character.isUpperCase(c)) {
|
||||
list.add(builder.toString());
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
|
||||
builder.append(c);
|
||||
}
|
||||
|
||||
list.add(builder.toString());
|
||||
return list;
|
||||
}
|
||||
|
||||
private static String generateNewType(int packetId, Class<?> clazz) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("\t\t\t");
|
||||
builder.append("public static final PacketType ");
|
||||
|
||||
String fullName = clazz.getName();
|
||||
fullName = fullName.substring(fullName.lastIndexOf(".") + 1);
|
||||
|
||||
String className;
|
||||
List<String> classNames = new ArrayList<>();
|
||||
|
||||
if (fullName.endsWith("Packet")) {
|
||||
for (String name : fullName.split("\\$")) {
|
||||
List<String> split = splitOnCaps(name);
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
for (int i = 1; i < split.size() - 1; i++) {
|
||||
nameBuilder.append(split.get(i));
|
||||
}
|
||||
classNames.add(nameBuilder.toString());
|
||||
}
|
||||
} else {
|
||||
for (String name : fullName.split("\\$")) {
|
||||
List<String> split = splitOnCaps(name);
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
for (int i = 3; i < split.size(); i++) {
|
||||
nameBuilder.append(split.get(i));
|
||||
}
|
||||
classNames.add(nameBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
className = classNames.get(classNames.size() - 1);
|
||||
|
||||
PacketType existing = null;
|
||||
try {
|
||||
existing = PacketType.fromClass(clazz);
|
||||
if (existing.isDynamic()) {
|
||||
existing = null;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// doesn't exist
|
||||
}
|
||||
|
||||
String fieldName;
|
||||
if (existing == null) {
|
||||
// Format it like SET_PROTOCOL
|
||||
StringBuilder fieldNameBuilder = new StringBuilder();
|
||||
char[] chars = className.toCharArray();
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
char c = chars[i];
|
||||
if (i != 0 && Character.isUpperCase(c)) {
|
||||
fieldNameBuilder.append("_");
|
||||
}
|
||||
fieldNameBuilder.append(Character.toUpperCase(c));
|
||||
}
|
||||
|
||||
fieldName = fieldNameBuilder.toString().replace("N_B_T", "NBT");
|
||||
} else {
|
||||
fieldName = existing.name();
|
||||
}
|
||||
|
||||
builder.append(fieldName);
|
||||
builder.append(" = ");
|
||||
|
||||
// Add spacing
|
||||
if (builder.length() > 65) {
|
||||
builder.append("\n");
|
||||
} else {
|
||||
while (builder.length() < 65) {
|
||||
builder.append(" ");
|
||||
}
|
||||
}
|
||||
builder.append("new ");
|
||||
builder.append("PacketType(PROTOCOL, SENDER, ");
|
||||
|
||||
builder.append(formatHex(packetId));
|
||||
builder.append(", ");
|
||||
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
for (int i = 0; i < classNames.size(); i++) {
|
||||
if (i != 0) {
|
||||
nameBuilder.append("$");
|
||||
}
|
||||
nameBuilder.append(classNames.get(i));
|
||||
}
|
||||
|
||||
String name = nameBuilder.toString();
|
||||
String namesArg = listToString(getAllNames(clazz, name));
|
||||
|
||||
builder.append(namesArg);
|
||||
builder.append(");");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static List<String> getAllNames(Class<?> packetClass, String newName) {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add(newName);
|
||||
|
||||
try {
|
||||
PacketType type = PacketType.fromClass(packetClass);
|
||||
for (String alias : type.names) {
|
||||
alias = alias.substring(alias.lastIndexOf('.') + 1);
|
||||
if (!names.contains(alias)) {
|
||||
names.add(alias);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private static String listToString(List<String> list) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i != 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append("\"").append(list.get(i)).append("\"");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindCurrent() {
|
||||
assertEquals(PacketType.Play.Client.STEER_VEHICLE,
|
||||
PacketType.findCurrent(Protocol.PLAY, Sender.CLIENT, "SteerVehicle"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginStart() {
|
||||
// This packet is critical for handleLoin
|
||||
assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeprecation() {
|
||||
assertTrue(PacketType.Status.Server.OUT_SERVER_INFO.isDeprecated(), "Packet isn't properly deprecated");
|
||||
assertTrue(PacketRegistry.getServerPacketTypes().contains(PacketType.Status.Server.OUT_SERVER_INFO),
|
||||
"Deprecated packet isn't properly included");
|
||||
assertFalse(PacketType.Play.Server.CHAT.isDeprecated(), "Packet isn't properly deprecated");
|
||||
assertEquals(PacketType.Status.Server.OUT_SERVER_INFO, PacketType.Status.Server.SERVER_INFO,
|
||||
"Deprecated packets aren't equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void ensureTypesAreCorrect() throws Exception {
|
||||
PacketType.onDynamicCreate = className -> {
|
||||
throw new RuntimeException("Dynamically generated packet " + className);
|
||||
};
|
||||
|
||||
try {
|
||||
boolean fail = false;
|
||||
|
||||
EnumProtocol[] protocols = EnumProtocol.values();
|
||||
for (EnumProtocol protocol : protocols) {
|
||||
Field field = EnumProtocol.class.getDeclaredField("k");
|
||||
field.setAccessible(true);
|
||||
|
||||
Map<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<Class<?>, Integer> reverseMap = (Map<Class<?>, Integer>) mapField.get(entry.getValue());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertFalse(fail, "Packet type(s) were incorrect!");
|
||||
} finally {
|
||||
PacketType.onDynamicCreate = __ -> { };
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPacketCreation() {
|
||||
boolean fail = false;
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (type.isSupported()) {
|
||||
try {
|
||||
new PacketContainer(type);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertFalse(fail, "Packet type(s) failed to instantiate");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPacketBundleWriting() {
|
||||
PacketContainer bundlePacket = new PacketContainer(PacketType.Play.Server.BUNDLE);
|
||||
assertEquals(MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet Bundle class is not present")), bundlePacket.getHandle().getClass());
|
||||
List<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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,59 +1,60 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import static com.comphenix.protocol.utility.TestUtils.setFinalField;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import java.lang.reflect.Field;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.PlayerChunkMap.EntityTracker;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class EntityUtilitiesTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReflection() {
|
||||
CraftWorld bukkit = mock(CraftWorld.class);
|
||||
WorldServer world = mock(WorldServer.class);
|
||||
when(bukkit.getHandle()).thenReturn(world);
|
||||
|
||||
ChunkProviderServer provider = mock(ChunkProviderServer.class);
|
||||
when(world.k()).thenReturn(provider);
|
||||
|
||||
PlayerChunkMap chunkMap = mock(PlayerChunkMap.class);
|
||||
Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build());
|
||||
setFinalField(provider, chunkMapField, chunkMap);
|
||||
|
||||
CraftEntity bukkitEntity = mock(CraftEntity.class);
|
||||
Entity fakeEntity = mock(Entity.class);
|
||||
when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity);
|
||||
|
||||
EntityTracker tracker = mock(EntityTracker.class);
|
||||
Field trackerField = FuzzyReflection.fromClass(EntityTracker.class, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(Entity.class).build());
|
||||
setFinalField(tracker, trackerField, fakeEntity);
|
||||
|
||||
Int2ObjectMap<EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>();
|
||||
trackerMap.put(1, tracker);
|
||||
Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build());
|
||||
setFinalField(chunkMap, trackedEntitiesField, trackerMap);
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import 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 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_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
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReflection() {
|
||||
CraftWorld bukkit = mock(CraftWorld.class);
|
||||
WorldServer world = mock(WorldServer.class);
|
||||
when(bukkit.getHandle()).thenReturn(world);
|
||||
|
||||
ChunkProviderServer provider = mock(ChunkProviderServer.class);
|
||||
when(world.k()).thenReturn(provider);
|
||||
|
||||
PlayerChunkMap chunkMap = mock(PlayerChunkMap.class);
|
||||
Field chunkMapField = FuzzyReflection.fromClass(ChunkProviderServer.class, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(PlayerChunkMap.class).build());
|
||||
setFinalField(provider, chunkMapField, chunkMap);
|
||||
|
||||
CraftEntity bukkitEntity = mock(CraftEntity.class);
|
||||
Entity fakeEntity = mock(Entity.class);
|
||||
when(fakeEntity.getBukkitEntity()).thenReturn(bukkitEntity);
|
||||
|
||||
EntityTracker tracker = mock(EntityTracker.class);
|
||||
Field trackerField = FuzzyReflection.fromClass(EntityTracker.class, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(Entity.class).build());
|
||||
setFinalField(tracker, trackerField, fakeEntity);
|
||||
|
||||
Int2ObjectMap<EntityTracker> trackerMap = new Int2ObjectOpenHashMap<>();
|
||||
trackerMap.put(1, tracker);
|
||||
Field trackedEntitiesField = FuzzyReflection.fromClass(PlayerChunkMap.class, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build());
|
||||
setFinalField(chunkMap, trackedEntitiesField, trackerMap);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,69 @@
|
||||
/**
|
||||
* (c) 2016 dmulloy2
|
||||
*/
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class WirePacketTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testPackets() {
|
||||
List<String> failures = new ArrayList<>();
|
||||
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (type.isDeprecated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
PacketContainer packet = new PacketContainer(type);
|
||||
WirePacket wire = WirePacket.fromPacket(packet);
|
||||
WirePacket handle = WirePacket.fromPacket(packet.getHandle());
|
||||
assertEquals(wire, handle);
|
||||
} catch (Exception ex) {
|
||||
failures.add(type + " :: " + ex.getMessage());
|
||||
System.out.println(type);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(failures, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() {
|
||||
int id = 42;
|
||||
byte[] array = {1, 3, 7, 21, 88, 67, 8};
|
||||
|
||||
WirePacket packet = new WirePacket(id, array);
|
||||
|
||||
ByteBuf buf = packet.serialize();
|
||||
|
||||
int backId = WirePacket.readVarInt(buf);
|
||||
byte[] backArray = new byte[buf.readableBytes()];
|
||||
buf.readBytes(backArray);
|
||||
|
||||
assertEquals(id, backId);
|
||||
assertArrayEquals(array, backArray);
|
||||
}
|
||||
/**
|
||||
* (c) 2016 dmulloy2
|
||||
*/
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class WirePacketTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
// @Test
|
||||
public void testPackets() {
|
||||
List<String> failures = new ArrayList<>();
|
||||
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (type.isDeprecated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
PacketContainer packet = new PacketContainer(type);
|
||||
WirePacket wire = WirePacket.fromPacket(packet);
|
||||
WirePacket handle = WirePacket.fromPacket(packet.getHandle());
|
||||
assertEquals(wire, handle);
|
||||
} catch (Exception ex) {
|
||||
failures.add(type + " :: " + ex.getMessage());
|
||||
System.out.println(type);
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(failures, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() {
|
||||
int id = 42;
|
||||
byte[] array = {1, 3, 7, 21, 88, 67, 8};
|
||||
|
||||
WirePacket packet = new WirePacket(id, array);
|
||||
|
||||
ByteBuf buf = packet.serialize();
|
||||
|
||||
int backId = WirePacket.readVarInt(buf);
|
||||
byte[] backArray = new byte[buf.readableBytes()];
|
||||
buf.readBytes(backArray);
|
||||
|
||||
assertEquals(id, backId);
|
||||
assertArrayEquals(array, backArray);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,45 @@
|
||||
package com.comphenix.protocol.reflect.cloning;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class AggregateClonerTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrays() {
|
||||
List<Integer> input = Arrays.asList(1, 2, 3);
|
||||
assertEquals(input, AggregateCloner.DEFAULT.clone(input));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// Usages of NonNullList were removed in 1.17.1
|
||||
public void testNonNullList() {
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS);
|
||||
|
||||
NonNullList<ItemStack> list = NonNullList.a(16, ItemStack.b);
|
||||
packet.getModifier().write(1, list);
|
||||
|
||||
PacketContainer cloned = packet.deepClone();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
NonNullList<ItemStack> list1 = (NonNullList<ItemStack>) cloned.getModifier().read(1);
|
||||
|
||||
assertEquals(list.size(), list1.size());
|
||||
Assertions.assertArrayEquals(list.toArray(), list1.toArray());
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.reflect.cloning;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class AggregateClonerTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrays() {
|
||||
List<Integer> input = Arrays.asList(1, 2, 3);
|
||||
assertEquals(input, AggregateCloner.DEFAULT.clone(input));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// Usages of NonNullList were removed in 1.17.1
|
||||
public void testNonNullList() {
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS);
|
||||
|
||||
NonNullList<ItemStack> list = NonNullList.a(16, ItemStack.b);
|
||||
packet.getModifier().write(1, list);
|
||||
|
||||
PacketContainer cloned = packet.deepClone();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
NonNullList<ItemStack> list1 = (NonNullList<ItemStack>) cloned.getModifier().read(1);
|
||||
|
||||
assertEquals(list.size(), list1.size());
|
||||
Assertions.assertArrayEquals(list.toArray(), list1.toArray());
|
||||
}
|
||||
}
|
||||
@@ -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,159 +1,160 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.nbt.NBTCompressedStreamTools;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
|
||||
import net.minecraft.network.protocol.status.ServerPing;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.util.MinecraftEncryption;
|
||||
import net.minecraft.world.level.ChunkCoordIntPair;
|
||||
import net.minecraft.world.level.block.state.IBlockData;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class MinecraftReflectionTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void undoMocking() {
|
||||
// NOP
|
||||
MinecraftReflection.minecraftPackage = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBukkitMethod() {
|
||||
FakeEntity entity = mock(FakeEntity.class);
|
||||
FakeBlock block = mock(FakeBlock.class);
|
||||
|
||||
MinecraftReflection.getBukkitEntity(entity);
|
||||
MinecraftReflection.getBukkitEntity(block);
|
||||
|
||||
verify(entity, times(1)).getBukkitEntity();
|
||||
verify(block, times(1)).getBukkitEntity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalClass() {
|
||||
assertThrows(IllegalArgumentException.class, () -> MinecraftReflection.getBukkitEntity("Hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullable() {
|
||||
assertNull(MinecraftReflection.getNullableNMS("ProtocolLib"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttributeSnapshot() {
|
||||
assertEquals(PacketPlayOutUpdateAttributes.AttributeSnapshot.class,
|
||||
MinecraftReflection.getAttributeSnapshotClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatComponent() {
|
||||
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializer() {
|
||||
assertEquals(IChatBaseComponent.ChatSerializer.class, MinecraftReflection.getChatSerializerClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunkCoordIntPair() {
|
||||
assertEquals(ChunkCoordIntPair.class, MinecraftReflection.getChunkCoordIntPair());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIBlockData() {
|
||||
assertEquals(IBlockData.class, MinecraftReflection.getIBlockDataClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayerConnection() {
|
||||
assertEquals(PlayerConnection.class, MinecraftReflection.getPlayerConnectionClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPing() {
|
||||
assertEquals(ServerPing.class, MinecraftReflection.getServerPingClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingPlayerSample() {
|
||||
assertEquals(ServerPing.ServerPingPlayerSample.class, MinecraftReflection.getServerPingPlayerSampleClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingServerData() {
|
||||
assertEquals(ServerPing.ServerData.class, MinecraftReflection.getServerPingServerDataClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNbtStreamTools() {
|
||||
assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataWatcherItem() {
|
||||
assertEquals(DataWatcher.Item.class, MinecraftReflection.getDataWatcherItemClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginSignature() {
|
||||
assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemStacks() {
|
||||
ItemStack stack = new ItemStack(Material.GOLDEN_SWORD);
|
||||
Object nmsStack = MinecraftReflection.getMinecraftItemStack(stack);
|
||||
assertItemsEqual(stack, MinecraftReflection.getBukkitItemStack(nmsStack));
|
||||
|
||||
// The NMS handle for CraftItemStack is null with Material.AIR, make sure it is handled correctly
|
||||
assertNotNull(MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameProfile() {
|
||||
assertEquals(GameProfile.class, MinecraftReflection.getGameProfileClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumEntityUseAction() {
|
||||
// this class is package-private in PacketPlayInUseEntity, so we can only check if no exception is thrown during retrieval
|
||||
MinecraftReflection.getEnumEntityUseActionClass();
|
||||
}
|
||||
|
||||
// Mocking objects
|
||||
private interface FakeEntity {
|
||||
|
||||
Entity getBukkitEntity();
|
||||
}
|
||||
|
||||
private interface FakeBlock {
|
||||
|
||||
Block getBukkitEntity();
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.nbt.NBTCompressedStreamTools;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
|
||||
import net.minecraft.network.protocol.status.ServerPing;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.util.MinecraftEncryption;
|
||||
import net.minecraft.world.level.ChunkCoordIntPair;
|
||||
import net.minecraft.world.level.block.state.IBlockData;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.craftbukkit.v1_19_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
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void undoMocking() {
|
||||
// NOP
|
||||
MinecraftReflection.minecraftPackage = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBukkitMethod() {
|
||||
FakeEntity entity = mock(FakeEntity.class);
|
||||
FakeBlock block = mock(FakeBlock.class);
|
||||
|
||||
MinecraftReflection.getBukkitEntity(entity);
|
||||
MinecraftReflection.getBukkitEntity(block);
|
||||
|
||||
verify(entity, times(1)).getBukkitEntity();
|
||||
verify(block, times(1)).getBukkitEntity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIllegalClass() {
|
||||
assertThrows(IllegalArgumentException.class, () -> MinecraftReflection.getBukkitEntity("Hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullable() {
|
||||
assertNull(MinecraftReflection.getNullableNMS("ProtocolLib"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttributeSnapshot() {
|
||||
assertEquals(PacketPlayOutUpdateAttributes.AttributeSnapshot.class,
|
||||
MinecraftReflection.getAttributeSnapshotClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatComponent() {
|
||||
assertEquals(IChatBaseComponent.class, MinecraftReflection.getIChatBaseComponentClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChatSerializer() {
|
||||
assertEquals(IChatBaseComponent.ChatSerializer.class, MinecraftReflection.getChatSerializerClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChunkCoordIntPair() {
|
||||
assertEquals(ChunkCoordIntPair.class, MinecraftReflection.getChunkCoordIntPair());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIBlockData() {
|
||||
assertEquals(IBlockData.class, MinecraftReflection.getIBlockDataClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayerConnection() {
|
||||
assertEquals(PlayerConnection.class, MinecraftReflection.getPlayerConnectionClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPing() {
|
||||
assertEquals(ServerPing.class, MinecraftReflection.getServerPingClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingPlayerSample() {
|
||||
assertEquals(ServerPing.ServerPingPlayerSample.class, MinecraftReflection.getServerPingPlayerSampleClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPingServerData() {
|
||||
assertEquals(ServerPing.ServerData.class, MinecraftReflection.getServerPingServerDataClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNbtStreamTools() {
|
||||
assertEquals(NBTCompressedStreamTools.class, MinecraftReflection.getNbtCompressedStreamToolsClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataWatcherItem() {
|
||||
assertEquals(DataWatcher.Item.class, MinecraftReflection.getDataWatcherItemClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginSignature() {
|
||||
assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemStacks() {
|
||||
ItemStack stack = new ItemStack(Material.GOLDEN_SWORD);
|
||||
Object nmsStack = MinecraftReflection.getMinecraftItemStack(stack);
|
||||
assertItemsEqual(stack, MinecraftReflection.getBukkitItemStack(nmsStack));
|
||||
|
||||
// The NMS handle for CraftItemStack is null with Material.AIR, make sure it is handled correctly
|
||||
assertNotNull(
|
||||
MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGameProfile() {
|
||||
assertEquals(GameProfile.class, MinecraftReflection.getGameProfileClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumEntityUseAction() {
|
||||
// this class is package-private in PacketPlayInUseEntity, so we can only check if no exception is thrown during retrieval
|
||||
MinecraftReflection.getEnumEntityUseActionClass();
|
||||
}
|
||||
|
||||
// Mocking objects
|
||||
private interface FakeEntity {
|
||||
|
||||
Entity getBukkitEntity();
|
||||
}
|
||||
|
||||
private interface FakeBlock {
|
||||
|
||||
Block getBukkitEntity();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,52 +1,73 @@
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
public static void assertItemCollectionsEqual(List<ItemStack> first, List<ItemStack> second) {
|
||||
assertEquals(first.size(), second.size());
|
||||
for (int i = 0; i < first.size(); i++) {
|
||||
assertItemsEqual(first.get(i), second.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertItemsEqual(ItemStack first, ItemStack second) {
|
||||
if (first == null) {
|
||||
assertNull(second);
|
||||
} else {
|
||||
assertNotNull(first);
|
||||
|
||||
// The legacy check in ItemStack#isSimilar causes a null pointer
|
||||
assertEquals(first.getType(), second.getType());
|
||||
assertEquals(first.getDurability(), second.getDurability());
|
||||
assertEquals(first.hasItemMeta(), second.hasItemMeta());
|
||||
if (first.hasItemMeta()) {
|
||||
assertTrue(Bukkit.getItemFactory().equals(first.getItemMeta(), second.getItemMeta()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equivalentItem(ItemStack first, ItemStack second) {
|
||||
if (first == null) {
|
||||
return second == null;
|
||||
} else if (second == null) {
|
||||
return false;
|
||||
} else {
|
||||
return first.getType().equals(second.getType());
|
||||
}
|
||||
}
|
||||
|
||||
public static void setFinalField(Object obj, Field field, Object newValue) {
|
||||
Accessors.getFieldAccessor(field).set(obj, newValue);
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.utility;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import java.lang.reflect.Field;
|
||||
import java.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;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
public static void assertItemCollectionsEqual(List<ItemStack> first, List<ItemStack> second) {
|
||||
assertEquals(first.size(), second.size());
|
||||
for (int i = 0; i < first.size(); i++) {
|
||||
assertItemsEqual(first.get(i), second.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertItemsEqual(ItemStack first, ItemStack second) {
|
||||
if (first == null) {
|
||||
assertNull(second);
|
||||
} else {
|
||||
assertNotNull(first);
|
||||
|
||||
// The legacy check in ItemStack#isSimilar causes a null pointer
|
||||
assertEquals(first.getType(), second.getType());
|
||||
assertEquals(first.getDurability(), second.getDurability());
|
||||
assertEquals(first.hasItemMeta(), second.hasItemMeta());
|
||||
if (first.hasItemMeta()) {
|
||||
assertTrue(Bukkit.getItemFactory().equals(first.getItemMeta(), second.getItemMeta()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equivalentItem(ItemStack first, ItemStack second) {
|
||||
if (first == null) {
|
||||
return second == null;
|
||||
} else if (second == null) {
|
||||
return false;
|
||||
} else {
|
||||
return first.getType().equals(second.getType());
|
||||
}
|
||||
}
|
||||
|
||||
public static void setFinalField(Object obj, Field field, Object newValue) {
|
||||
Accessors.getFieldAccessor(field).set(obj, newValue);
|
||||
}
|
||||
|
||||
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,58 +1,94 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.wrappers.Either.Left;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class BukkitConvertersTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemStacks() {
|
||||
ItemStack item = new ItemStack(Material.DIAMOND_SWORD, 16);
|
||||
item.addEnchantment(Enchantment.DAMAGE_ALL, 4);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(ChatColor.GREEN + "Diamond Sword");
|
||||
item.setItemMeta(meta);
|
||||
|
||||
EquivalentConverter<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()));
|
||||
}
|
||||
|
||||
@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)
|
||||
);
|
||||
|
||||
com.mojang.datafixers.util.Either<String, String> nmsEither = (com.mojang.datafixers.util.Either<String, String>) converter.getGeneric(test);
|
||||
Either<String, String> wrapped = converter.getSpecific(nmsEither);
|
||||
|
||||
assertEquals(wrapped.left(), nmsEither.left());
|
||||
assertEquals(wrapped.right(), nmsEither.right());
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
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;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class BukkitConvertersTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemStacks() {
|
||||
ItemStack item = new ItemStack(Material.DIAMOND_SWORD, 16);
|
||||
item.addEnchantment(Enchantment.DAMAGE_ALL, 4);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(ChatColor.GREEN + "Diamond Sword");
|
||||
item.setItemMeta(meta);
|
||||
|
||||
EquivalentConverter<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()));
|
||||
}
|
||||
|
||||
@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)
|
||||
);
|
||||
|
||||
com.mojang.datafixers.util.Either<String, String> nmsEither = (com.mojang.datafixers.util.Either<String, String>) converter.getGeneric(test);
|
||||
Either<String, String> wrapped = converter.getSpecific(nmsEither);
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,31 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ChunkCoordIntPairTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
net.minecraft.world.level.ChunkCoordIntPair pair = new net.minecraft.world.level.ChunkCoordIntPair(1, 2);
|
||||
ChunkCoordIntPair specific = ChunkCoordIntPair.getConverter().getSpecific(pair);
|
||||
|
||||
assertEquals(1, specific.getChunkX());
|
||||
assertEquals(2, specific.getChunkZ());
|
||||
|
||||
net.minecraft.world.level.ChunkCoordIntPair roundtrip =
|
||||
(net.minecraft.world.level.ChunkCoordIntPair) ChunkCoordIntPair.getConverter().
|
||||
getGeneric(specific);
|
||||
|
||||
assertEquals(1, roundtrip.e);
|
||||
assertEquals(2, roundtrip.f);
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ChunkCoordIntPairTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
net.minecraft.world.level.ChunkCoordIntPair pair = new net.minecraft.world.level.ChunkCoordIntPair(1, 2);
|
||||
ChunkCoordIntPair specific = ChunkCoordIntPair.getConverter().getSpecific(pair);
|
||||
|
||||
assertEquals(1, specific.getChunkX());
|
||||
assertEquals(2, specific.getChunkZ());
|
||||
|
||||
net.minecraft.world.level.ChunkCoordIntPair roundtrip =
|
||||
(net.minecraft.world.level.ChunkCoordIntPair) ChunkCoordIntPair.getConverter().
|
||||
getGeneric(specific);
|
||||
|
||||
assertEquals(1, roundtrip.e);
|
||||
assertEquals(2, roundtrip.f);
|
||||
}
|
||||
}
|
||||
@@ -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,57 +1,58 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import com.comphenix.protocol.BukkitInitialization;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class EnumWrappersTest {
|
||||
|
||||
private static final Set<String> KNOWN_INVALID = Sets.newHashSet(
|
||||
"Particle", "WorldBorderAction", "CombatEventType", "TitleAction", "ChatType"
|
||||
);
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
EnumWrappers.getPlayerInfoActionClass(); // just to initialize the classes and converters
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void validateAllEnumFieldsAreWrapped() {
|
||||
Map<Class<?>, EquivalentConverter<?>> nativeEnums = EnumWrappers.getFromNativeMap();
|
||||
for (Entry<Class<?>, EquivalentConverter<?>> entry : nativeEnums.entrySet()) {
|
||||
for (Object nativeConstant : entry.getKey().getEnumConstants()) {
|
||||
try {
|
||||
// yay, generics
|
||||
EquivalentConverter<Object> converter = (EquivalentConverter<Object>) entry.getValue();
|
||||
|
||||
// try to convert the native constant to a wrapper and back
|
||||
Object wrappedValue = converter.getSpecific(nativeConstant);
|
||||
assertNotNull(wrappedValue);
|
||||
|
||||
Object unwrappedValue = converter.getGeneric(wrappedValue);
|
||||
assertNotNull(unwrappedValue);
|
||||
|
||||
assertEquals(nativeConstant, unwrappedValue);
|
||||
} catch (Exception exception) {
|
||||
fail(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidity() {
|
||||
assertEquals(EnumWrappers.INVALID, KNOWN_INVALID);
|
||||
}
|
||||
}
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import 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;
|
||||
|
||||
public class EnumWrappersTest {
|
||||
|
||||
private static final Set<String> KNOWN_INVALID = Sets.newHashSet(
|
||||
"Particle", "WorldBorderAction", "CombatEventType", "TitleAction", "ChatType", "TitleAction"
|
||||
);
|
||||
|
||||
@BeforeAll
|
||||
public static void initializeBukkit() {
|
||||
BukkitInitialization.initializeAll();
|
||||
EnumWrappers.getPlayerInfoActionClass(); // just to initialize the classes and converters
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void validateAllEnumFieldsAreWrapped() {
|
||||
Map<Class<?>, EquivalentConverter<?>> nativeEnums = EnumWrappers.getFromNativeMap();
|
||||
for (Entry<Class<?>, EquivalentConverter<?>> entry : nativeEnums.entrySet()) {
|
||||
for (Object nativeConstant : entry.getKey().getEnumConstants()) {
|
||||
try {
|
||||
// yay, generics
|
||||
EquivalentConverter<Object> converter = (EquivalentConverter<Object>) entry.getValue();
|
||||
|
||||
// try to convert the native constant to a wrapper and back
|
||||
Object wrappedValue = converter.getSpecific(nativeConstant);
|
||||
assertNotNull(wrappedValue);
|
||||
|
||||
Object unwrappedValue = converter.getGeneric(wrappedValue);
|
||||
assertNotNull(unwrappedValue);
|
||||
|
||||
assertEquals(nativeConstant, unwrappedValue);
|
||||
} catch (Exception exception) {
|
||||
fail(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidity() {
|
||||
assertEquals(EnumWrappers.INVALID, KNOWN_INVALID);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user