Compare commits

...
52 Commits
Author SHA1 Message Date
NotAlexNoyle a783b380a2 Exclude CODEOWNERS from language statistics 2026-10-27 08:25:13 -04:00
NotAlexNoyle 9065ec1848 Add CODEOWNERS (one staff approval) 2026-10-27 08:25:13 -04:00
NotAlexNoyle caeaab7fba Disable debugging 2026-06-04 11:54:57 -04:00
NotAlexNoyle 3e9af0bf7e Fix building with TrueOG Bootstrap 2026-04-08 12:44:43 -04:00
NotAlexNoyle adc02cfce4 Fix config file interpretation 2026-04-01 15:18:10 -04:00
NotAlexNoyle 47661a5746 Include version in jarfile 2026-04-01 13:22:41 -04:00
NotAlexNoyle 8c8a39f423 Fix build 2026-04-01 13:20:28 -04:00
NotAlexNoyle 0396dbe917 Update config file 2026-04-01 13:16:04 -04:00
NotAlexNoyle ff46d7dbe5 Remove jitpack.yml 2025-11-14 11:47:47 -05:00
NotAlexNoyle ab0d75004e Update shadow to 8.3.8 2025-09-24 10:52:21 -04:00
NotAlexNoyle d818379207 Update gradle to 8.14.3 2025-09-01 12:34:30 -04:00
NotAlexNoyle 9b37cbc72c Update gradle to 8.14.1, use kotlin instead of groovy to build 2025-06-13 11:52:52 -04:00
NotAlexNoyle 077c9691ac Update gradle 2025-06-01 15:57:00 -04:00
NotAlexNoyle c3019d5f01 Properly support TrueOG Bootstrap 2025-01-01 17:48:59 -05:00
NotAlexNoyle 315370b985 Fix build with TrueOG Bootstrap 2025-01-01 17:19:18 -05:00
Dan Mulloy 16f4870714 Update version to 5.0.0 for release 2023-05-12 10:10:49 -04:00
Jinyu Yu e77ed96957 Add 1.17 support to TinyProtocol (#194) 2023-05-06 17:26:49 -05:00
Hasan Demirtaş 46f6e76f91 Add BlockEntityType support. (#2111) 2023-05-06 17:18:09 -05:00
Lukas Alt 38bbd764cc Fixed creation of packet bundles (#2383) 2023-05-06 17:12:47 -05:00
Lukas Alt 2931af58db Added wrappers for MessageSignature (#2362) 2023-04-30 22:28:19 -05:00
Lukas Alt 08ea2da642 Improved Wrapping of PlayerInfoData and support chat session data (#2361)
* Improved Wrapping of PlayerInfoData and support chat session data
* added constructor for unambiguous creation of playerinfodata without signature
2023-04-29 19:49:51 +00:00
Lukas Alt 448e9369de JSON Parsing for WrappedServerPing and fixed modifying favicon (#2265)
* Fix WrappedServerPing access and ensure legacy compatability for JSON parsing
* added wrappers for mojang codecs and allow serializing server pings
2023-04-29 14:45:47 -05:00
Nassim Jahnke ac6f911f15 Fix bundle packet processing errors (#2339) 2023-04-15 18:27:52 -05:00
Dan Mulloy c69bcc36f5 Migrate to Gradle (#2319) 2023-04-15 16:09:15 -05:00
Lukas Alt 0ee93acd65 Fixed null packet handles in Bundle (#2328) 2023-04-11 08:13:03 -05:00
LOOHP d83dd9ab8a Get around expensive validate calls (#2318) 2023-04-09 16:40:57 -05:00
Pasqual Koschmieder ab32f938d7 resolve packet instance construction concurrency issue (#2312) 2023-04-07 20:57:49 -05:00
Dan Mulloy c7753a9d5b Better server ping concurrency
Addresses #2289
2023-04-03 22:55:32 -04:00
libraryaddict b51812655a Correct the version from 1.19.4 to 1.19.3 (#2299) 2023-04-03 18:27:38 -05:00
libraryaddict a910edc16b Fix WrappedRegistry for versions older than 1.19.4 (#2298) 2023-04-03 11:27:12 -05:00
Dan Mulloy fb2075b774 Fix exception for null sounds
Fixes #2276
2023-03-30 17:07:34 -05:00
Dan Mulloy 4b78bf6a34 Update dependencies to support Java 20
Fixes #2274
Fixes #2270
Fixes #2251
2023-03-28 12:40:32 -05:00
libraryaddict edce5b6d0f Fix /packet arguments, fixes #1847 (#2271)
Pretty simple, it's deleting the argument twice. Which means you have to use `/packet add add <remaining>`
2023-03-27 22:03:27 -05:00
Dan Mulloy 7217b11ba7 Fix reading class aliases
Fixes #2263
2023-03-26 14:09:45 -05:00
Dan Mulloy 18c2b389a4 Improve unit tests for server ping 2023-03-26 13:58:46 -05:00
Dan Mulloy 1912a9c871 Fix setting ping player sample in 1.19.4 2023-03-26 13:31:39 -05:00
Dan Mulloy 0c6fa46871 Optimize class lookups 2023-03-25 23:16:04 -05:00
Dan Mulloy df3b68df4c Some cleanup
Fixes super critical issue #2255
2023-03-25 21:45:29 -05:00
Lukas Alt aebefded86 Packet filtering for bundled packets in 1.19.4 (#2258)
Since Minecraft 1.19.4, the protocol supports bundling consecutive packets to ensure the client processes them in one tick. However, Packet Events are not called for the individual packets in such a bundle in the current dev build of ProtocolLib. For example, no packet events are currently sent for the ENTITY_METADATA packet when an entity is first spawned as the packet is bundled with the ENTITY_SPAWN packet. However, if the entity metadata is changed later on, the event will be called.
This PR proposes to fix this by unpacking the bundled packets and invoking the packet filtering for each packet.

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

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

---------

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

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

Fixes #2048
2023-01-10 03:43:39 +00:00
Elioby 9d183e85d7 Force login disconnect packet to be run async (#2021) 2022-12-18 15:32:45 +00:00
Dan MulloyandPasqual Koschmieder eebb99fa37 1.19.3 update
Co-authored-by: Pasqual Koschmieder <pasqual.koschmieder@gmail.com>
2022-12-13 20:38:37 -05:00
Dan Mulloy 6aaf0ec26b Normalize line endings to LF 2022-12-07 13:52:09 -05:00
108 changed files with 9887 additions and 8438 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
* text=auto eol=lf
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
+5 -5
View File
@@ -15,14 +15,14 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
java-version: '19'
cache: 'gradle'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Run gradle build lifecycle
run: ./gradlew build shadowJar --no-daemon
- name: Upload plugin file
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: target/ProtocolLib.jar
path: build/libs/ProtocolLib.jar
+4 -4
View File
@@ -26,16 +26,16 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
java-version: '19'
cache: 'gradle'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'java'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Run gradle build lifecycle
run: ./gradlew build -x test --no-daemon
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+167 -167
View File
@@ -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.
-137
View File
@@ -1,137 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.tinyprotocol</groupId>
<artifactId>TinyProtocol</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>TinyProtocol</name>
<description>Intercept packets without ProtocolLib.</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib-Parent</artifactId>
<version>v4-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
</archive>
<finalName>TinyProtocol</finalName>
<outputDirectory>../../target/</outputDirectory>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<phase>package</phase>
<id>attach-javadocs</id>
</execution>
</executions>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -1,117 +0,0 @@
package com.comphenix.tinyprotocol;
import io.netty.channel.Channel;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.tinyprotocol.Reflection.ConstructorInvoker;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
/**
* Represents an example plugin utilizing TinyProtocol
*/
public class ExamplePlugin extends JavaPlugin {
// Chat packets
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField("{nms}.PacketPlayInChat", String.class, 0);
// Explosion packet
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
private FieldAccessor<Float> particleX = Reflection.getField(particleClass, float.class, 0);
private FieldAccessor<Float> particleY = Reflection.getField(particleClass, float.class, 1);
private FieldAccessor<Float> particleZ = Reflection.getField(particleClass, float.class, 2);
private FieldAccessor<Integer> particleCount = Reflection.getField(particleClass, int.class, 0);
// Server info packet
private Class<?> serverInfoClass = Reflection.getClass("{nms}.PacketStatusOutServerInfo");
private Class<Object> serverPingClass = Reflection.getUntypedClass("{nms}.ServerPing");
private Class<Object> playerSampleClass = Reflection.getUntypedClass("{nms}.ServerPingPlayerSample");
private FieldAccessor<Object> serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0);
private FieldAccessor<Object> playerSample = Reflection.getField(serverPingClass, playerSampleClass, 0);
private ConstructorInvoker playerSampleInvoker = Reflection.getConstructor(playerSampleClass, int.class, int.class);
private TinyProtocol protocol;
@Override
public void onEnable() {
protocol = new TinyProtocol(this) {
@Override
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
// Cancel chat packets
if (CHAT_MESSAGE.hasField(packet)) {
if (CHAT_MESSAGE.get(packet).contains("dirty")) {
sendExplosion(sender);
return null;
}
}
if (particleName.hasField(packet)) {
System.out.println("Sending particle field:" + packet);
}
return super.onPacketInAsync(sender, channel, packet);
}
@Override
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
if (serverInfoClass.isInstance(packet)) {
Object ping = serverPing.get(packet);
playerSample.set(ping, playerSampleInvoker.invoke(1000, 0));
// Which is equivalent to:
// serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
return packet;
}
return super.onPacketOutAsync(reciever, channel, packet);
}
};
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
// Toggle injection
if (protocol.hasInjected(player)) {
protocol.uninjectPlayer(player);
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
} else {
protocol.injectPlayer(player);
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
}
return true;
} else {
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
}
return false;
}
private void sendExplosion(Player player) {
try {
// Only visible for the client
Object explosionPacket = particleClass.newInstance();
Location loc = player.getLocation();
particleName.set(explosionPacket, "hugeexplosion");
particleX.set(explosionPacket, (float) loc.getX());
particleY.set(explosionPacket, (float) loc.getY());
particleZ.set(explosionPacket, (float) loc.getZ());
particleCount.set(explosionPacket, 1);
// Send the packet to the player
protocol.sendPacket(player, explosionPacket);
} catch (Exception e) {
throw new RuntimeException("Cannot send packet.", e);
}
}
}
@@ -1,403 +0,0 @@
package com.comphenix.tinyprotocol;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
/**
* An utility class that simplifies reflection in Bukkit plugins.
*
* @author Kristian
*/
public final class Reflection {
/**
* An interface for invoking a specific constructor.
*/
public interface ConstructorInvoker {
/**
* Invoke a constructor for a specific class.
*
* @param arguments - the arguments to pass to the constructor.
* @return The constructed object.
*/
public Object invoke(Object... arguments);
}
/**
* An interface for invoking a specific method.
*/
public interface MethodInvoker {
/**
* Invoke a method on a specific target object.
*
* @param target - the target object, or NULL for a static method.
* @param arguments - the arguments to pass to the method.
* @return The return value, or NULL if is void.
*/
public Object invoke(Object target, Object... arguments);
}
/**
* An interface for retrieving the field content.
*
* @param <T> - field type.
*/
public interface FieldAccessor<T> {
/**
* Retrieve the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @return The value of the field.
*/
public T get(Object target);
/**
* Set the content of a field.
*
* @param target - the target object, or NULL for a static field.
* @param value - the new value of the field.
*/
public void set(Object target, Object value);
/**
* Determine if the given object has this field.
*
* @param target - the object to test.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasField(Object target);
}
// Deduce the net.minecraft.server.v* package
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server");
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "");
// Variable replacement
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
private Reflection() {
// Seal class
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param name - the name of the field, or NULL to ignore.
* @param fieldType - a compatible field type.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
return getField(getClass(className), name, fieldType, 0);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param target - the target type.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
/**
* Retrieve a field accessor for a specific field type and name.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param fieldType - a compatible field type.
* @param index - the number of compatible fields to skip.
* @return The field accessor.
*/
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
// Common method
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) {
for (final Field field : target.getDeclaredFields()) {
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) {
field.setAccessible(true);
// A function for retrieving a specific field value
return new FieldAccessor<T>() {
@Override
@SuppressWarnings("unchecked")
public T get(Object target) {
try {
return (T) field.get(target);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public void set(Object target, Object value) {
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
}
}
@Override
public boolean hasField(Object target) {
// target instanceof DeclaringClass
return field.getDeclaringClass().isAssignableFrom(target.getClass());
}
};
}
}
// Search in parent classes
if (target.getSuperclass() != null)
return getField(target.getSuperclass(), name, fieldType, index);
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) {
return getTypedMethod(getClass(className), methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) {
return getTypedMethod(clazz, methodName, null, params);
}
/**
* Search for the first publicly and privately defined method of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param methodName - the method name, or NULL to skip.
* @param returnType - the expected return type, or NULL to ignore.
* @param params - the expected parameters.
* @return An object that invokes this specific method.
* @throws IllegalStateException If we cannot find this method.
*/
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
for (final Method method : clazz.getDeclaredMethods()) {
if ((methodName == null || method.getName().equals(methodName))
&& (returnType == null || method.getReturnType().equals(returnType))
&& Arrays.equals(method.getParameterTypes(), params)) {
method.setAccessible(true);
return new MethodInvoker() {
@Override
public Object invoke(Object target, Object... arguments) {
try {
return method.invoke(target, arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke method " + method, e);
}
}
};
}
}
// Search in every superclass
if (clazz.getSuperclass() != null)
return getMethod(clazz.getSuperclass(), methodName, params);
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param className - lookup name of the class, see {@link #getClass(String)}.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(String className, Class<?>... params) {
return getConstructor(getClass(className), params);
}
/**
* Search for the first publically and privately defined constructor of the given name and parameter count.
*
* @param clazz - a class to start with.
* @param params - the expected parameters.
* @return An object that invokes this constructor.
* @throws IllegalStateException If we cannot find this method.
*/
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) {
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Arrays.equals(constructor.getParameterTypes(), params)) {
constructor.setAccessible(true);
return new ConstructorInvoker() {
@Override
public Object invoke(Object... arguments) {
try {
return constructor.newInstance(arguments);
} catch (Exception e) {
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
}
}
};
}
}
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
}
/**
* Retrieve a class from its full name, without knowing its type on compile time.
* <p>
* This is useful when looking up fields by a NMS or OBC type.
* <p>
*
* @see {@link #getClass()} for more information.
* @param lookupName - the class name with variables.
* @return The class.
*/
public static Class<Object> getUntypedClass(String lookupName) {
@SuppressWarnings({ "rawtypes", "unchecked" })
Class<Object> clazz = (Class) getClass(lookupName);
return clazz;
}
/**
* Retrieve a class from its full name.
* <p>
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
* <p>
* <table border="1">
* <tr>
* <th>Variable</th>
* <th>Content</th>
* </tr>
* <tr>
* <td>{nms}</td>
* <td>Actual package name of net.minecraft.server.VERSION</td>
* </tr>
* <tr>
* <td>{obc}</td>
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td>
* </tr>
* <tr>
* <td>{version}</td>
* <td>The current Minecraft package VERSION, if any.</td>
* </tr>
* </table>
*
* @param lookupName - the class name with variables.
* @return The looked up class.
* @throws IllegalArgumentException If a variable or class could not be found.
*/
public static Class<?> getClass(String lookupName) {
return getCanonicalClass(expandVariables(lookupName));
}
/**
* Retrieve a class in the net.minecraft.server.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getMinecraftClass(String name) {
return getCanonicalClass(NMS_PREFIX + "." + name);
}
/**
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package.
*
* @param name - the name of the class, excluding the package.
* @throws IllegalArgumentException If the class doesn't exist.
*/
public static Class<?> getCraftBukkitClass(String name) {
return getCanonicalClass(OBC_PREFIX + "." + name);
}
/**
* Retrieve a class by its canonical name.
*
* @param canonicalName - the canonical name.
* @return The class.
*/
private static Class<?> getCanonicalClass(String canonicalName) {
try {
return Class.forName(canonicalName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
}
}
/**
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages.
*
* @param name - the full name of the class.
* @return The expanded string.
*/
private static String expandVariables(String name) {
StringBuffer output = new StringBuffer();
Matcher matcher = MATCH_VARIABLE.matcher(name);
while (matcher.find()) {
String variable = matcher.group(1);
String replacement = "";
// Expand all detected variables
if ("nms".equalsIgnoreCase(variable))
replacement = NMS_PREFIX;
else if ("obc".equalsIgnoreCase(variable))
replacement = OBC_PREFIX;
else if ("version".equalsIgnoreCase(variable))
replacement = VERSION;
else
throw new IllegalArgumentException("Unknown variable: " + variable);
// Assume the expanded variables are all packages, and append a dot
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.')
replacement += ".";
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(output);
return output.toString();
}
}
@@ -1,522 +0,0 @@
package com.comphenix.tinyprotocol;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
import com.comphenix.tinyprotocol.Reflection.MethodInvoker;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.mojang.authlib.GameProfile;
/**
* Represents a very tiny alternative to ProtocolLib.
* <p>
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
*
* @author Kristian
*/
public abstract class TinyProtocol {
private static final AtomicInteger ID = new AtomicInteger(0);
// Used in order to lookup a channel
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
private static final FieldAccessor<Object> getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
private static final FieldAccessor<Channel> getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0);
// Looking up ServerConnection
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
private static final FieldAccessor<Object> getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass);
// Packets we have to intercept
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart");
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
// Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
private Listener listener;
// Channels that have already been removed
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap());
// List of network markers
private List<Object> networkManagers;
// Injected channel handlers
private List<Channel> serverChannels = new ArrayList<>();
private ChannelInboundHandlerAdapter serverChannelHandler;
private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol;
// Current handler name
private String handlerName;
protected volatile boolean closed;
protected Plugin plugin;
/**
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
* <p>
* You can construct multiple instances per plugin.
*
* @param plugin - the plugin.
*/
public TinyProtocol(final Plugin plugin) {
this.plugin = plugin;
// Compute handler name
this.handlerName = getHandlerName();
// Prepare existing players
registerBukkitEvents();
try {
registerChannelHandler();
registerPlayers(plugin);
} catch (IllegalArgumentException ex) {
// Damn you, late bind
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind.");
new BukkitRunnable() {
@Override
public void run() {
registerChannelHandler();
registerPlayers(plugin);
plugin.getLogger().info("[TinyProtocol] Late bind injection successful.");
}
}.runTask(plugin);
}
}
private void createServerChannelHandler() {
// Handle connected channels
endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
try {
// This can take a while, so we need to stop the main thread from interfering
synchronized (networkManagers) {
// Stop injecting channels
if (!closed) {
channel.eventLoop().submit(() -> injectChannelInternal(channel));
}
}
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
}
}
};
// This is executed before Minecraft's channel handler
beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(endInitProtocol);
}
};
serverChannelHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
}
/**
* Register bukkit events.
*/
private void registerBukkitEvents() {
listener = new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public final void onPlayerLogin(PlayerLoginEvent e) {
if (closed)
return;
Channel channel = getChannel(e.getPlayer());
// Don't inject players that have been explicitly uninjected
if (!uninjectedChannels.contains(channel)) {
injectPlayer(e.getPlayer());
}
}
@EventHandler
public final void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) {
close();
}
}
};
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
@SuppressWarnings("unchecked")
private void registerChannelHandler() {
Object mcServer = getMinecraftServer.get(Bukkit.getServer());
Object serverConnection = getServerConnection.get(mcServer);
boolean looking = true;
// We need to synchronize against this list
networkManagers = (List<Object>) getNetworkMarkers.invoke(null, serverConnection);
createServerChannelHandler();
// Find the correct list, or implicitly throw an exception
for (int i = 0; looking; i++) {
List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
for (Object item : list) {
if (!ChannelFuture.class.isInstance(item))
break;
// Channel future that contains the server connection
Channel serverChannel = ((ChannelFuture) item).channel();
serverChannels.add(serverChannel);
serverChannel.pipeline().addFirst(serverChannelHandler);
looking = false;
}
}
}
private void unregisterChannelHandler() {
if (serverChannelHandler == null)
return;
for (Channel serverChannel : serverChannels) {
final ChannelPipeline pipeline = serverChannel.pipeline();
// Remove channel handler
serverChannel.eventLoop().execute(new Runnable() {
@Override
public void run() {
try {
pipeline.remove(serverChannelHandler);
} catch (NoSuchElementException e) {
// That's fine
}
}
});
}
}
private void registerPlayers(Plugin plugin) {
for (Player player : plugin.getServer().getOnlinePlayers()) {
injectPlayer(player);
}
}
/**
* Invoked when the server is starting to send a packet to a player.
* <p>
* Note that this is not executed on the main thread.
*
* @param receiver - the receiving player, NULL for early login/status packets.
* @param channel - the channel that received the packet. Never NULL.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
return packet;
}
/**
* Invoked when the server has received a packet from a given player.
* <p>
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
*
* @param sender - the player that sent the packet, NULL for early login/status packets.
* @param channel - channel that received the packet. Never NULL.
* @param packet - the packet being received.
* @return The packet to recieve instead, or NULL to cancel.
*/
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
return packet;
}
/**
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
*/
public void sendPacket(Player player, Object packet) {
sendPacket(getChannel(player), packet);
}
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet to send.
*/
public void sendPacket(Channel channel, Object packet) {
channel.pipeline().writeAndFlush(packet);
}
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the player that sent the packet.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Player player, Object packet) {
receivePacket(getChannel(player), packet);
}
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Channel channel, Object packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
/**
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
* <p>
* Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
*
* @return A unique channel handler name.
*/
protected String getHandlerName() {
return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
}
/**
* Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
* <p>
* This will automatically be called when a player has logged in.
*
* @param player - the player to inject.
*/
public void injectPlayer(Player player) {
injectChannelInternal(getChannel(player)).player = player;
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/
public void injectChannel(Channel channel) {
injectChannelInternal(channel);
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) {
try {
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
// Inject our packet interceptor
if (interceptor == null) {
interceptor = new PacketInterceptor();
channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
uninjectedChannels.remove(channel);
}
return interceptor;
} catch (IllegalArgumentException e) {
// Try again
return (PacketInterceptor) channel.pipeline().get(handlerName);
}
}
/**
* Retrieve the Netty channel associated with a player. This is cached.
*
* @param player - the player.
* @return The Netty channel.
*/
public Channel getChannel(Player player) {
Channel channel = channelLookup.get(player.getName());
// Lookup channel again
if (channel == null) {
Object connection = getConnection.get(getPlayerHandle.invoke(player));
Object manager = getManager.get(connection);
channelLookup.put(player.getName(), channel = getChannel.get(manager));
}
return channel;
}
/**
* Uninject a specific player.
*
* @param player - the injected player.
*/
public void uninjectPlayer(Player player) {
uninjectChannel(getChannel(player));
}
/**
* Uninject a specific channel.
* <p>
* This will also disable the automatic channel injection that occurs when a player has properly logged in.
*
* @param channel - the injected channel.
*/
public void uninjectChannel(final Channel channel) {
// No need to guard against this if we're closing
if (!closed) {
uninjectedChannels.add(channel);
}
// See ChannelInjector in ProtocolLib, line 590
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.pipeline().remove(handlerName);
}
});
}
/**
* Determine if the given player has been injected by TinyProtocol.
*
* @param player - the player.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Player player) {
return hasInjected(getChannel(player));
}
/**
* Determine if the given channel has been injected by TinyProtocol.
*
* @param channel - the channel.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Channel channel) {
return channel.pipeline().get(handlerName) != null;
}
/**
* Cease listening for packets. This is called automatically when your plugin is disabled.
*/
public final void close() {
if (!closed) {
closed = true;
// Remove our handlers
for (Player player : plugin.getServer().getOnlinePlayers()) {
uninjectPlayer(player);
}
// Clean up Bukkit
HandlerList.unregisterAll(listener);
unregisterChannelHandler();
}
}
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
* @author Kristian
*/
private final class PacketInterceptor extends ChannelDuplexHandler {
// Updated by the login event
public volatile Player player;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// Intercept channel
final Channel channel = ctx.channel();
handleLoginStart(channel, msg);
try {
msg = onPacketInAsync(player, channel, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
}
if (msg != null) {
super.channelRead(ctx, msg);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = onPacketOutAsync(player, ctx.channel(), msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
GameProfile profile = getGameProfile.get(packet);
channelLookup.put(profile.getName(), channel);
}
}
}
}
@@ -1,14 +0,0 @@
name: TinyProtocol
version: 0.1.0
description: Intercept packets without ProtocolLib
author: Comphenix
load: startup
main: com.comphenix.tinyprotocol.ExamplePlugin
database: false
commands:
toggleinjection:
description: Toggle the TinyProtocol injection of the current player.
usage: /<command>
+185
View File
@@ -0,0 +1,185 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources
plugins {
id("java-library")
id("eclipse")
id("com.gradleup.shadow") version "8.3.9"
}
group = "com.comphenix.protocol"
version = "5.0.0-dev"
description = "Provides access to the Minecraft protocol"
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
repositories {
val bootstrapRepo = System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let { // TrueOG Bootstrap mavenLocal().
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
dir
} else {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
null
}
} ?: run {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
null
}
// Prefer the bootstrap-local Spigot artifacts when present. If ~/.m2 contains
// partial module metadata, resolving there first can block Gradle from falling
// through to the bootstrap repo for the actual jar.
maven {
url = uri("file://${(bootstrapRepo ?: file("${System.getProperty("user.home")}/.m2/repository")).absolutePath}")
content {
includeGroup("org.spigotmc")
}
}
if (bootstrapRepo != null) {
maven {
url = uri("file://${System.getProperty("user.home")}/.m2/repository")
content {
includeGroup("org.spigotmc")
}
}
}
mavenCentral()
maven {
name = "Rosewood Public"
url = uri("https://repo.rosewooddev.io/repository/public/")
content {
includeGroup("org.spigotmc")
}
}
maven {
name = "ElMakers Mirror"
url = uri("https://maven.elmakers.com/repository/")
content {
includeGroup("org.spigotmc")
}
}
maven {
name = "dmulloy2"
url = uri("https://repo.dmulloy2.net/repository/public/")
content {
excludeGroup("org.spigotmc")
}
}
maven {
name = "Spigot Snapshots"
url = uri("https://hub.spigotmc.org/nexus/content/groups/public/")
content {
includeGroup("org.spigotmc")
}
}
maven {
url = uri("https://libraries.minecraft.net/")
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation("net.bytebuddy:byte-buddy:1.14.3")
compileOnly("org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT") {
isTransitive = false
}
compileOnly("com.google.guava:guava:31.1-jre")
compileOnly("org.apache.commons:commons-lang3:3.17.0")
compileOnly("commons-lang:commons-lang:2.6")
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("com.mojang:datafixerupper:6.0.6")
compileOnly("net.md-5:bungeecord-chat:1.16-R0.4")
compileOnly("org.yaml:snakeyaml:1.33")
compileOnly("org.joml:joml:1.10.5")
compileOnly("io.netty:netty-all:4.0.23.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.13.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1") {
isTransitive = false
}
testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2")
testImplementation("org.mockito:mockito-core:4.11.0")
testImplementation("org.mockito:mockito-inline:4.11.0")
testImplementation("io.netty:netty-common:4.1.77.Final")
testImplementation("io.netty:netty-transport:4.1.77.Final")
testImplementation("org.spigotmc:spigot-api:1.19.4-R0.1-SNAPSHOT")
testImplementation("org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT")
testImplementation("com.google.guava:guava:31.1-jre")
testImplementation("org.apache.commons:commons-lang3:3.17.0")
testImplementation("commons-lang:commons-lang:2.6")
testImplementation("com.google.code.findbugs:jsr305:3.0.2")
testImplementation("com.mojang:authlib:3.17.30")
testImplementation("com.mojang:brigadier:1.0.18")
testImplementation("com.mojang:datafixerupper:6.0.6")
testImplementation("com.mojang:logging:1.1.1")
testImplementation("net.md-5:bungeecord-chat:1.16-R0.4")
testImplementation("org.apache.logging.log4j:log4j-api:2.17.1")
testRuntimeOnly("org.slf4j:slf4j-api:2.0.0")
testImplementation("org.yaml:snakeyaml:1.33")
testImplementation("org.joml:joml:1.10.5")
testImplementation("net.kyori:adventure-text-serializer-gson:4.13.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.13.1")
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.named<ShadowJar>("shadowJar") {
dependencies {
include(dependency("net.bytebuddy:byte-buddy:.*"))
}
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
archiveFileName.set("ProtocolLib-${project.version}.jar")
}
tasks.named<Jar>("jar") {
enabled = false
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
tasks.named<ProcessResources>("processResources") {
val includeBuild = isSnapshot && System.getenv("BUILD_NUMBER") != null
val fullVersion = if (includeBuild) "$version-${System.getenv("BUILD_NUMBER")}" else version
filesMatching("**/*") {
expand("version" to fullVersion)
}
}
tasks.named("build") {
dependsOn("shadowJar")
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
tasks.withType<Javadoc> {
options.encoding = "UTF-8"
}
testing {
suites {
val test by getting(JvmTestSuite::class) {
useJUnitJupiter()
}
}
}
+2
View File
@@ -0,0 +1,2 @@
nexusUsername=""
nexusPassword=""
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
-5
View File
@@ -1,5 +0,0 @@
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 17.0.4-tem
- sdk use java 17.0.4-tem
-379
View File
@@ -1,379 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>5.0.0-SNAPSHOT</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>https://github.com/dmulloy2/ProtocolLib</url>
<packaging>jar</packaging>
<inceptionYear>2012</inceptionYear>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
<junit.version>5.9.0</junit.version>
<mockito.version>4.6.1</mockito.version>
<netty.version>4.1.77.Final</netty.version>
<spigot.version>1.19.2-R0.1-SNAPSHOT</spigot.version>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<excludes>
<exclude>org.spigotmc:spigot</exclude>
<exclude>org.spigotmc:spigot-api</exclude>
<exclude>junit:junit</exclude>
<exclude>io.netty:*</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifestEntries>
<Main-Class>com.comphenix.protocol.Application</Main-Class>
<Implementation-Title>ProtocolLib</Implementation-Title>
<Implementation-Version>${project.fullVersion}</Implementation-Version>
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
</manifestEntries>
</archive>
<finalName>${project.name}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
<configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
<!-- We need this as we use reflection in tests to ensure equality -->
<argLine>--add-opens java.base/java.util=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<failOnError>false</failOnError>
<encoding>ISO-8859-1</encoding>
<doctitle>ProtocolLib JavaDocs</doctitle>
<windowtitle>ProtocolLib JavaDocs</windowtitle>
<bottom>Copyright &#169; {inceptionYear}&#x2013;{currentYear}
Comphenix and dmulloy2. Licensed under the GNU GPL v2.
</bottom>
<additionalOptions>
<additionalOption>-Xdoclint:none</additionalOption>
</additionalOptions>
</configuration>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>jenkins</id>
<activation>
<property>
<name>env.BUILD_NUMBER</name>
</property>
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
<!--<project.fullVersion>${project.version}</project.fullVersion>-->
</properties>
</profile>
<profile>
<id>release-sign-artifacts</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<properties>
<project.fullVersion>${project.version}</project.fullVersion>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- TODO: Look into signing releases
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
-->
</plugins>
</build>
</profile>
</profiles>
<scm>
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
<url>https://github.com/dmulloy2/ProtocolLib</url>
</scm>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
<url>https://www.gnu.org/licenses/gpl-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dmulloy2</id>
<name>Dan Mulloy</name>
<url>https://dmulloy2.net/</url>
<roles>
<role>developer</role>
<role>maintainer</role>
</roles>
</developer>
<developer>
<id>aadnk</id>
<name>Kristian S. Stangeland</name>
<email>kr_stang@hotmail.com</email>
<url>https://comphenix.net/</url>
<roles>
<role>former author</role>
</roles>
<timezone>1</timezone>
</developer>
</developers>
<distributionManagement>
<repository>
<id>dmulloy2-releases</id>
<url>https://repo.dmulloy2.net/repository/releases/</url>
</repository>
<snapshotRepository>
<id>dmulloy2-snapshots</id>
<url>https://repo.dmulloy2.net/repository/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
</repositories>
<dependencies>
<!-- core minecraft dependencies -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Compile with the old version of Netty for backwards compat -->
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.23.Final</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-gson -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.11.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.12.12</version>
</dependency>
<!-- Testing dependencies -->
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-transport -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
+5
View File
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
rootProject.name = "ProtocolLib"
@@ -446,7 +446,7 @@ class CommandPacket extends CommandBase {
{
final String text = arguments.remove().toLowerCase();
switch (arguments.remove().toLowerCase()) {
switch (text) {
case "add":
return SubCommand.ADD;
case "remove":
@@ -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&lt;T&gt; in 1.19.3.
* @param genericType NMS type of T
* @param converter Converter from genericType to T
* @return A modifier for Holder fields
* @param <T> Bukkit type
*/
public <T> StructureModifier<T> getHolders(Class<?> genericType,
EquivalentConverter<T> converter) {
return structureModifier.withParamType(
MinecraftReflection.getHolderClass(),
Converters.holder(converter, WrappedRegistry.getRegistry(genericType)),
genericType
);
}
/**
* Retrieve a read/write structure for the SoundEffect enum in 1.9.
* @return A modifier for SoundEffect enum fields.
*/
public StructureModifier<Sound> getSoundEffects() {
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return getHolders(MinecraftReflection.getSoundEffectClass(), BukkitConverters.getSoundConverter());
}
// Convert to and from Bukkit
return structureModifier.withType(
MinecraftReflection.getSoundEffectClass(),
@@ -901,6 +972,12 @@ public abstract class AbstractStructure {
);
}
public StructureModifier<List<UUID>> getUUIDLists() {
return structureModifier.withType(
List.class,
BukkitConverters.getListConverter(Converters.passthrough(UUID.class)));
}
/**
* Retrieve a read/write structure for Instants in (mostly for use in 1.19+)
* @return The Structure Modifier
@@ -910,7 +987,7 @@ public abstract class AbstractStructure {
}
/**
* Retrieve a read/write structure for profile public keys in 1.9
* Retrieve a read/write structure for profile public keys in 1.19
* @return The Structure Modifier
*/
public StructureModifier<WrappedProfilePublicKey> getProfilePublicKeys() {
@@ -920,13 +997,24 @@ public abstract class AbstractStructure {
}
/**
* Retrieve a read/write structure for profile public key data in 1.9
* Retrieve a read/write structure for profile public key data in 1.19
* @return The Structure Modifier
*/
public StructureModifier<WrappedProfileKeyData> getProfilePublicKeyData() {
return structureModifier.withType(
MinecraftReflection.getProfilePublicKeyDataClass(),
BukkitConverters.getWrappedPublicKeyDataConverter());
MinecraftReflection.getProfilePublicKeyDataClass(),
BukkitConverters.getWrappedPublicKeyDataConverter());
}
/**
* Retrieves read/write structure for remote chat session data in 1.19.3
* @return The Structure Modifier
*/
public StructureModifier<WrappedRemoteChatSessionData> getRemoteChatSessionData() {
return structureModifier.withType(
MinecraftReflection.getRemoteChatSessionDataClass(),
BukkitConverters.getWrappedRemoteChatSessionDataConverter()
);
}
/**
@@ -955,7 +1043,7 @@ public abstract class AbstractStructure {
}
/**
* @return read/writer structure direct access to signature data like chat messages
* @return read/writer structure direct access to salted signature data like chat messages
*/
public StructureModifier<WrappedSaltedSignature> getSignatures() {
return structureModifier.withType(
@@ -964,6 +1052,16 @@ public abstract class AbstractStructure {
);
}
/**
* @return read/writer structure direct access to unsalted signature data for example in chat message (since 1.19.3)
*/
public StructureModifier<WrappedMessageSignature> getMessageSignatures() {
return structureModifier.withType(
MinecraftReflection.getMessageSignatureClass(),
BukkitConverters.getWrappedMessageSignatureConverter()
);
}
/**
* @param leftConverter converter for left values
* @param rightConverter converter for right values
@@ -1070,6 +1168,12 @@ public abstract class AbstractStructure {
return structureModifier.withType(Optional.class, Converters.optional(converter));
}
public StructureModifier<Iterable<PacketContainer>> getPacketBundles() {
return structureModifier.withType(Iterable.class, Converters.iterable(
BukkitConverters.getPacketContainerConverter(), ArrayList::new, ArrayList::new
));
}
/**
* Represents an equivalent converter for ItemStack arrays.
* @author Kristian
@@ -8,7 +8,7 @@ import com.comphenix.protocol.wrappers.Converters;
public class InternalStructure extends AbstractStructure {
protected InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
public InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
super(handle, structureModifier);
}
@@ -40,6 +40,8 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import javax.annotation.Nullable;
/**
* Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be
* sent or received. It is also possible to cancel an event.
@@ -70,6 +72,9 @@ public class PacketEvent extends EventObject implements Cancellable {
private boolean readOnly;
private boolean filtered;
@Nullable
private PacketEvent bundle;
/**
* Use the static constructors to create instances of this event.
*
@@ -81,27 +86,31 @@ public class PacketEvent extends EventObject implements Cancellable {
}
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
this(source, packet, null, player, serverPacket, true);
this(source, packet, null, player, serverPacket, true, null);
}
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
boolean filtered) {
boolean filtered, @Nullable PacketEvent bundleEvent) {
super(source);
if(packet == null) {
throw new IllegalArgumentException("packet cannot be null");
}
this.packet = packet;
this.playerReference = new WeakReference<>(player);
this.networkMarker = marker;
this.serverPacket = serverPacket;
this.filtered = filtered;
this.bundle = bundleEvent;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
this.playerReference = origial.getPlayerReference();
this.cancel = origial.cancel;
this.serverPacket = origial.serverPacket;
this.filtered = origial.filtered;
this.networkMarker = origial.networkMarker;
private PacketEvent(PacketEvent original, AsyncMarker asyncMarker) {
super(original.source);
this.packet = original.packet;
this.playerReference = original.getPlayerReference();
this.cancel = original.cancel;
this.serverPacket = original.serverPacket;
this.filtered = original.filtered;
this.networkMarker = original.networkMarker;
this.asyncMarker = asyncMarker;
this.asynchronous = true;
}
@@ -128,7 +137,7 @@ public class PacketEvent extends EventObject implements Cancellable {
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
return new PacketEvent(source, packet, marker, client, false, true);
return new PacketEvent(source, packet, marker, client, false, true, null);
}
/**
@@ -145,7 +154,7 @@ public class PacketEvent extends EventObject implements Cancellable {
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
boolean filtered) {
return new PacketEvent(source, packet, marker, client, false, filtered);
return new PacketEvent(source, packet, marker, client, false, filtered, null);
}
/**
@@ -170,7 +179,7 @@ public class PacketEvent extends EventObject implements Cancellable {
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
return new PacketEvent(source, packet, marker, recipient, true, true);
return new PacketEvent(source, packet, marker, recipient, true, true, null);
}
/**
@@ -187,7 +196,25 @@ public class PacketEvent extends EventObject implements Cancellable {
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
boolean filtered) {
return new PacketEvent(source, packet, marker, recipient, true, filtered);
return fromServer(source, packet, marker, recipient, filtered, null);
}
/**
* Creates an event representing a server packet transmission.
* <p>
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @param filtered - whether this packet event is processed by every packet listener.
* @param bundle - The corresponding packet event of the bundle if this packet is part of a bundle. Otherwise, null.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
boolean filtered, @Nullable PacketEvent bundle) {
return new PacketEvent(source, packet, marker, recipient, true, filtered, bundle);
}
/**
@@ -517,6 +544,15 @@ public class PacketEvent extends EventObject implements Cancellable {
}
}
/**
* Returns the packet event corresponding to the bundle if this packet is sent as a part of a bundle, t. Otherwise, null.
* @return Corresponding packet event or null.
*/
@Nullable
public PacketEvent getBundle() {
return bundle;
}
@Override
public String toString() {
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
@@ -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;
}
}
}
@@ -27,12 +27,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.ByteBuddyGenerated;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.*;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
@@ -101,7 +96,7 @@ public class NettyChannelInjector implements Injector {
// packet marking
private final Map<Object, NetworkMarker> savedMarkers = new WeakHashMap<>(16, 0.9f);
private final Set<Object> skippedPackets = Collections.synchronizedSet(new HashSet<>());
private final Set<Object> skippedPackets = ConcurrentHashMap.newKeySet();
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
// status of this injector
@@ -553,7 +548,7 @@ public class NettyChannelInjector implements Injector {
}
// no listener and no marker - no magic :)
if (!this.channelListener.hasListener(packet.getClass()) && marker == null) {
if (!this.channelListener.hasListener(packet.getClass()) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
return action;
}
@@ -28,6 +28,7 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.Util;
import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.ChannelFuture;
import org.bukkit.Server;
@@ -90,7 +91,7 @@ public class NetworkManagerInjector implements ChannelListener {
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
// check if we need to intercept the packet
Class<?> packetClass = packet.getClass();
if (this.outboundListeners.contains(packetClass) || marker != null) {
if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || outboundListeners.contains(packetClass)) {
// wrap packet and construct the event
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
PacketEvent packetEvent = PacketEvent.fromServer(this, container, marker, injector.getPlayer());
@@ -108,7 +109,7 @@ public class NetworkManagerInjector implements ChannelListener {
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
// check if we need to intercept the packet
Class<?> packetClass = packet.getClass();
if (this.inboundListeners.contains(packetClass) || marker != null) {
if (marker != null || inboundListeners.contains(packetClass)) {
// wrap the packet and construct the event
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), packet);
PacketEvent packetEvent = PacketEvent.fromClient(this, container, marker, injector.getPlayer());
@@ -45,12 +45,12 @@ public class PacketRegistry {
*/
protected static class Register {
// The main lookup table
private final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
private final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
private volatile Set<PacketType> serverPackets = new HashSet<>();
private volatile Set<PacketType> clientPackets = new HashSet<>();
private final List<MapContainer> containers = new ArrayList<>();
volatile Set<PacketType> serverPackets = new HashSet<>();
volatile Set<PacketType> clientPackets = new HashSet<>();
final List<MapContainer> containers = new ArrayList<>();
public Register() {}
@@ -251,10 +251,13 @@ public class PacketRegistry {
protected static void associatePackets(Register register, Map<Integer, Class<?>> lookup, PacketType.Protocol protocol, Sender sender) {
for (Map.Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
int packetId = entry.getKey();
Class<?> packetClass = entry.getValue();
PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass);
try {
register.registerPacket(type, entry.getValue(), sender);
register.registerPacket(type, packetClass, sender);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
@@ -270,18 +273,28 @@ public class PacketRegistry {
}
}
private static final Object registryLock = new Object();
/**
* Initializes the packet registry.
*/
private static void initialize() {
if (!INITIALIZED) {
INITIALIZED = true;
if (INITIALIZED) {
return;
}
synchronized (registryLock) {
if (INITIALIZED) {
return;
}
if (MinecraftVersion.BEE_UPDATE.atOrAbove()) {
REGISTER = createNewRegister();
} else {
REGISTER = createOldRegister();
}
INITIALIZED = true;
}
}
@@ -350,6 +363,9 @@ public class PacketRegistry {
// Try the lookup first (may be null, so check contains)
Optional<Class<?>> res = REGISTER.typeToClass.get(type);
if (res != null) {
if(res.isPresent() && MinecraftReflection.isBundleDelimiter(res.get())) {
return MinecraftReflection.getPackedBundlePacketClass();
}
return res;
}
@@ -362,6 +378,9 @@ public class PacketRegistry {
// cache it for next time
associate(type, clazz);
if(clazz != null && MinecraftReflection.isBundleDelimiter(clazz)) {
clazz = MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet bundle class not found."));
}
return Optional.ofNullable(clazz);
}
@@ -383,6 +402,11 @@ public class PacketRegistry {
*/
public static PacketType getPacketType(Class<?> packet) {
initialize();
if (MinecraftReflection.isBundlePacket(packet)) {
return PacketType.Play.Server.BUNDLE;
}
return REGISTER.classToType.get(packet);
}
@@ -116,7 +116,9 @@ public class FuzzyReflection {
public static <T> Set<T> combineArrays(T[]... arrays) {
Set<T> result = new LinkedHashSet<>();
for (T[] elements : arrays) {
Collections.addAll(result, elements);
if (elements != null) {
Collections.addAll(result, elements);
}
}
return result;
@@ -23,6 +23,11 @@ import com.comphenix.protocol.reflect.instances.BannedGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.InstanceProvider;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -151,12 +156,34 @@ public class StructureModifier<T> {
return requireDefaults;
}
// Used to filter out irrelevant fields
private static final ThreadLocal<Table<Class<?>, Class<?>, Reference<List<FieldAccessor>>>>
fieldCacheLocal = ThreadLocal.withInitial(HashBasedTable::create);
private static final Class<?> NULL_CACHE_CLASS_REPLACEMENT = Void.class;
// Used to filter out irrelevant fields
private static List<FieldAccessor> getFields(Class<?> type, Class<?> superclassExclude) {
return FuzzyReflection.fromClass(type, true).getDeclaredFields(superclassExclude).stream()
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(Accessors::getFieldAccessor)
.collect(Collectors.toList());
if (type == null) {
throw new IllegalArgumentException("Type cannot be NULL.");
}
Table<Class<?>, Class<?>, Reference<List<FieldAccessor>>> fieldCache = fieldCacheLocal.get();
Class<?> superclassKey = superclassExclude == null ? NULL_CACHE_CLASS_REPLACEMENT : superclassExclude;
Reference<List<FieldAccessor>> cacheEntryReference = fieldCache.get(type, superclassKey);
if (cacheEntryReference != null) {
List<FieldAccessor> cacheEntry = cacheEntryReference.get();
if (cacheEntry != null) {
return cacheEntry;
}
}
List<FieldAccessor> accessors = FuzzyReflection.fromClass(type, true)
.getDeclaredFields(superclassExclude)
.stream()
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(Accessors::getFieldAccessor)
.collect(Collectors.toList());
fieldCache.put(type, superclassKey, new SoftReference<>(accessors));
fieldCache.cellSet().removeIf(entry -> entry.getValue().get() == null);
return accessors;
}
/**
@@ -4,6 +4,8 @@ import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import com.google.common.base.Preconditions;
final class DefaultFieldAccessor implements FieldAccessor {
private final Field field;
@@ -13,9 +15,9 @@ final class DefaultFieldAccessor implements FieldAccessor {
private final MethodHandle getter;
public DefaultFieldAccessor(Field field, MethodHandle setter, MethodHandle getter, boolean staticField) {
this.field = field;
this.setter = setter;
this.getter = getter;
this.field = Preconditions.checkNotNull(field, "field");
this.setter = Preconditions.checkNotNull(setter, "setter");
this.getter = Preconditions.checkNotNull(getter, "getter");
this.staticField = staticField;
}
@@ -11,6 +11,8 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
import com.google.common.base.Preconditions;
final class MethodHandleHelper {
private static final Lookup LOOKUP;
@@ -51,29 +53,35 @@ final class MethodHandleHelper {
}
public static MethodAccessor getMethodAccessor(Method method) {
Preconditions.checkNotNull(method, "method");
try {
MethodHandle unreflected = LOOKUP.unreflect(method);
boolean staticMethod = Modifier.isStatic(method.getModifiers());
MethodHandle generified = convertToGeneric(unreflected, staticMethod, false);
return new DefaultMethodAccessor(method, generified, staticMethod);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to access method " + method);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Unable to access method " + method, ex);
}
}
public static ConstructorAccessor getConstructorAccessor(Constructor<?> constructor) {
Preconditions.checkNotNull(constructor, "constructor");
try {
MethodHandle unreflected = LOOKUP.unreflectConstructor(constructor);
MethodHandle generified = convertToGeneric(unreflected, false, true);
return new DefaultConstrutorAccessor(constructor, generified);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Unable to access constructor " + constructor);
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Unable to access constructor " + constructor, ex);
}
}
public static FieldAccessor getFieldAccessor(Field field) {
Preconditions.checkNotNull(field, "field");
try {
boolean staticField = Modifier.isStatic(field.getModifiers());
@@ -97,10 +105,18 @@ final class MethodHandleHelper {
setter = setter.asType(VIRTUAL_FIELD_SETTER);
}
if (getter == null) {
throw new IllegalStateException("Unable to access field " + field + ". Could not find getter");
}
if (setter == null) {
throw new IllegalStateException("Unable to access field " + field + ". Could not find setter");
}
return new DefaultFieldAccessor(field, setter, getter, staticField);
} catch (IllegalAccessException | NoSuchFieldException exception) {
} catch (IllegalAccessException | NoSuchFieldException ex) {
// NoSuchFieldException can never happen, the field always exists
throw new IllegalStateException("Unable to access field " + field);
throw new IllegalStateException("Unable to access field " + field, ex);
}
}
@@ -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)));
}
}
+12 -12
View File
@@ -1,30 +1,30 @@
global:
# Settings for the automatic version updater
auto updater:
notify: true
notify: false
download: false
# Number of seconds to wait until a new update is downloaded
delay: 43200 # 12 hours
metrics: true
metrics: false
# Prints certain warnings to players with the protocol.info permission
chat warnings: true
# Automatically compile structure modifiers
# Automatically compile structure modifiers
background compiler: true
# Disable version checking for the given Minecraft version. Backup your world first!
ignore version check:
ignore version check: false
# Whether or not to enable the filter command
debug: false
# Whether or not to print a stack trace for every warning
detailed error: false
# The engine used by the filter command
script engine: JavaScript
suppressed reports:
suppressed reports: all
+44 -44
View File
@@ -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