Compare commits

...
72 Commits
Author SHA1 Message Date
Dan Mulloy d9c623a254 backwards compat 2022-12-13 20:32:53 -05:00
Dan Mulloy fe0abbf9b3 revert some changes to MinecraftReflection 2022-12-13 20:30:51 -05:00
Dan Mulloy 92b604506f line endings 2022-12-13 20:25:07 -05:00
derklaro 574c34ad5c remove unneeded packet class names 2022-12-08 09:33:29 +01:00
derklaro 115cc83040 resolve conflicts 2022-12-08 09:14:04 +01:00
derklaro a18c418f42 1.19.3 2022-12-08 09:03:45 +01:00
Dan Mulloy 6aaf0ec26b Normalize line endings to LF 2022-12-07 13:52:09 -05:00
lennoxlotl 86e586da26 Fix not being able to read effects from RemoveEntityEffect packet (#1908)
fix being unable to read from RemoveEntityEffect due to wrong alias order
2022-11-26 22:54:33 +00:00
Nassim Jahnke 069783a353 Fix getEntityClass full qualifier (#1967) 2022-11-26 17:49:35 -05:00
opl- cc95e19ba4 Fix Magma support (#1953)
Tested with Magma v1.16.5 and Paper v1.18.2
2022-11-25 15:13:33 -05:00
derklaro 5ef24a7471 22w43a 2022-10-27 16:04:17 +02:00
derklaro 475f89ab89 deprecate WrappedServerPing chat preview stuff 2022-10-20 10:10:41 +02:00
derklaro e3d4339659 player info action changes 2022-10-19 20:04:33 +02:00
derklaro e7d54cdabd update to 1.19.3 2022-10-19 16:24:10 +02:00
Pasqual Koschmieder c31133c20f Improve performance by removing hash computations of packets (#1933) 2022-10-16 20:31:42 +00:00
caoli5288 69ae3656b5 Fix StreamSerializer backwards compatibility (#1868)
Fixes #1813
2022-10-16 18:37:08 +00:00
Pasqual Koschmieder 43145bd478 cleanup MonitorAdapter (#1831) 2022-08-24 00:53:55 +00:00
Nassim Jahnke b7c1e096c4 Support Mojang mapped servers (#1869) 2022-08-23 20:44:48 -04:00
Pasqual Koschmieder 6707c4811e only run inbound listeners on the main thread if requested (#1851) 2022-08-16 23:51:54 -04:00
Rodney 2092b8f48e Make getRandomKey() properly random (#1834) 2022-08-13 15:08:01 +00:00
Pasqual Koschmieder 20e73369fa mark some critical packets as forced-async (#1840) 2022-08-13 11:04:17 -04:00
Pasqual Koschmieder abc0db8281 remove off-event-loop logic from inbound packet listeners (#1836) 2022-08-12 17:29:07 -04:00
Pasqual Koschmieder 1beb95115f Improve cloning tests (#1822) 2022-08-12 02:07:38 +00:00
Pasqual Koschmieder 7fcfcdc365 fix fix of fix of getEntityFromId (#1827) 2022-08-11 22:04:27 -04:00
Pasqual Koschmieder 2be216899a use correct packet for chat extensions on 1.19+ (#1824) 2022-08-10 20:59:03 +00:00
Pasqual Koschmieder 8876ce323b add support for sync & async receiving listeners (#1815) 2022-08-10 20:50:33 +00:00
Pasqual Koschmieder 575174580e Small update for 1.19.2 (#1814) 2022-08-10 16:49:01 -04:00
Miklas 7fd4ec3172 Support for 1.18+ ClientboundLevelChunkWithLightPacket (#1592) 2022-08-06 18:52:42 -04:00
Pasqual Koschmieder a75d383001 fix injection exception when plugin is disabled (#1798) 2022-08-02 20:59:33 -04:00
Pasqual Koschmieder 7ddfd4f347 Fix & improve PacketContainer serialization & cloning (#1794) 2022-07-31 11:54:26 -04:00
Pasqual Koschmieder 7e137cbfc5 dependency updates (#1790) 2022-07-30 20:01:11 -04:00
Pasqual Koschmieder d40762e69d Update to 1.19.1 (#1699)
* add support for enforceSecureChat in ServerPing
* remove security exception check from test
2022-07-27 17:09:25 -04:00
Snowiiii ae19478007 Updated Dependencies (#1769) 2022-07-26 17:31:58 +00:00
games647 11a8184c3e Add StructureModifier for extracting the signature data in chat and login packets (#1742) 2022-07-26 13:29:34 -04:00
Pasqual Koschmieder c3dc00de05 fix two smaller issues (#1774) 2022-07-26 13:20:07 -04:00
Dan Mulloy 84cb541866 Merge branch 'pr-1659' 2022-07-24 11:07:39 -04:00
Photon-Github 624f6aaca6 Anonymous Class Replacement 2022-07-24 11:02:56 -04:00
Pasqual Koschmieder c5f0550953 Use MethodHandles for reflection (#1561)
* don't enforce async calls for thread-safe listeners (closes #1551)
* cleanups, remove structure compiling
* improve cloning a bit
* fix small issue in no-op structure modifier
* remove last usages of FieldUtils
* improve and fix equality check in container test
2022-07-24 10:16:05 -04:00
Pasqual Koschmieder 7f0bc7fd24 don't suppress deserialization issues (#1759) 2022-07-24 10:08:35 -04:00
Pasqual Koschmieder 96155b1065 Improve getEntityFromID (#1740) 2022-07-16 11:09:07 -04:00
Pasqual Koschmieder 23dac3287b detect server reloads and print a big warning message (#1733) 2022-07-16 11:00:01 -04:00
Pasqual Koschmieder 0bbbd961aa synchronize collections with possible concurrent accesses (#1723) 2022-07-07 07:51:06 +00:00
Pasqual Koschmieder dd19e1040a [ci skip] cleanup & improve readme a bit (#1724)
* [ci skip] cleanup & improve readme a bit

* [ci skip] improve code indention
2022-07-07 08:49:41 +01:00
Dan Mulloy e45c16d490 Missed a hyphen 2022-06-29 19:23:21 -04:00
Dan Mulloy a19959cea4 Update issue templates 2022-06-29 19:22:25 -04:00
Pasqual Koschmieder 4f18d37832 fix exceptions when reloading the server (#1689) 2022-06-29 19:08:14 -04:00
Photon-GitHubandDan Mulloy a2bf242097 Cleanup (#1603)
* Replace guava Charsets with StandardCharsets.

* Use try-with-resources

* Faster Util asList, inline getOnlinePlayers.

* Use direct ArrayList allocation.

* Use new instead of Lists#...

* Use new instead of Lists#...

* Faster looping.

* Use switch.

* Remove diamond operators.

* Use ArrayDeque instead of LinkedList.

* Actually conform to the documentation and always use an ArrayList as backing list.

* Potentially breaking change: Use switch instead of startsWith as this e.g. causes "n" to be interpreted as "names" and any future new commands starting with "n" will be ignored.

* Use addAll().

* Remove IntegerSet. Unused and also covered by fastutils IntSet.

* Much faster boolean parsing. Might have breaking changes as parameterName is now checked after the false block.

* Make most fields final, fix JavaDoc and remove diamond operators.

* Make fields final.

* Much cleaner getAllInterfaces() method.

Co-authored-by: Dan Mulloy <dev@dmulloy2.net>
2022-06-25 11:32:42 -04:00
Pasqual Koschmieder 4e105c59ed fix exceptions on older server versions (#1645) 2022-06-14 12:54:45 -04:00
Pasqual Koschmieder 59ca841ed5 correctly mark changed packet as processed (#1639) 2022-06-13 10:01:27 -04:00
Dan Mulloy 5e8f044a18 Run codeql analysis (#1630) 2022-06-11 13:03:03 -04:00
Pasqual Koschmieder 4db1e39ac7 fix packet listener calling when processed in event loop (#1621) 2022-06-11 12:36:46 -04:00
Pasqual Koschmieder e202503c09 allow setting if chat preview is enabled in WrappedServerPing (#1623) 2022-06-11 12:35:12 -04:00
Pasqual Koschmieder aed98abac6 fix registration of parameterized registry types (#1628) 2022-06-11 12:32:59 -04:00
Pasqual Koschmieder 250f94e9cd fix invalid packet id setting (#1629) 2022-06-11 12:30:38 -04:00
Pasqual Koschmieder d7bf43001f expand and document WrappedProfilePublicKey (#1618) 2022-06-11 11:58:08 -04:00
Pasqual Koschmieder 868b357527 add missing enum constants added in 1.19 (#1617) 2022-06-11 11:57:50 -04:00
Pasqual Koschmieder 9a609c2053 fix compile issues introduced by latest spigot api changes (#1622) 2022-06-11 11:57:25 -04:00
Pasqual Koschmieder f3acce99d8 improve and fix equality check in container test (#1614) 2022-06-08 16:33:22 -04:00
Pasqual Koschmieder 374e6cd5ee allow easier access to registries (#1613) 2022-06-08 16:32:52 -04:00
Pasqual Koschmieder 84a0b5ffdd allow easier access to converters (#1610) 2022-06-08 13:34:01 -04:00
Pasqual Koschmieder 4cc3957723 bring back raw text component creation (#1612) 2022-06-08 13:33:07 -04:00
Pasqual Koschmieder a0a5469988 Update to Minecraft 1.19 (#1601) 2022-06-07 21:24:31 -04:00
Photon-GitHub 240920d642 Diamond inference cleanup (#1593) 2022-05-25 23:30:08 -04:00
Pasqual Koschmieder 764195bd55 switch to a queue for queued scheduled packets (#1598) 2022-05-25 23:24:59 -04:00
Pasqual Koschmieder 7bfee67a29 fix packet listener invocation when packet is sent async (#1587) 2022-05-04 00:22:00 -04:00
Pasqual Koschmieder c87604cf0c don't enforce async calls for thread-safe listeners (#1555)
closes #1551
2022-03-26 13:00:22 -04:00
Pasqual Koschmieder 4096952c16 remove setCancelled / isCancelled from PacketInjector (#1553)
closes #1552
2022-03-26 13:00:12 -04:00
Dan Mulloy b4eff32213 Allow chat error messages to be disabled
Fixes #1549
2022-03-26 10:32:03 -04:00
Pasqual Koschmieder d361526371 Fix missing temp player in pre-join channel injectors (#1535)
Closes #1534
2022-03-12 14:33:24 -05:00
Pasqual Koschmieder 0d4e4c818f fix duplicate element creation when wrapping the bootstrap list (#1532)
closes #1530
2022-03-09 23:17:03 -05:00
Pasqual Koschmieder 073bfa2b86 Out/In bound protocol injection improvements (#1524)
* Clear up some stuff, fix location of wire packet encoder
* Ensure that the player injection cache is always up-to-date
* Make uninjection from a channel more reliable
* Don't schedule an empty runnable if there is no need to do that
* Remove unnecessary throw declarations from some methods
* Adjust uninjection to remove the injector reference as well
* improve channel future injection in network manager
2022-03-07 22:09:04 -05:00
Dan Mulloy f0059f39f6 Update version to 5.0.0-SNAPSHOT 2022-03-07 22:07:54 -05:00
276 changed files with 19370 additions and 26623 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+1
View File
@@ -2,6 +2,7 @@ _Follow this template except for feature requests. Use pastebin when providing /
Make sure you've done the following:
- [ ] You're using the latest build for your server version
- [ ] This isn't fixed in a recent development build
- [ ] This isn't an issue caused by another plugin
- [ ] You've checked for duplicate issues
- [ ] You didn't use `/reload`
+2
View File
@@ -7,6 +7,8 @@ assignees: ''
---
- [ ] This issue is not solved in a development build
**Describe the bug**
A clear and concise description of what the bug is.
@@ -7,6 +7,8 @@ assignees: ''
---
- [ ] This feature is not currently present in a development build
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+7 -4
View File
@@ -8,12 +8,15 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
@@ -21,5 +24,5 @@ jobs:
- name: Upload plugin file
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: target/ProtocolLib.jar
name: ProtocolLib
path: target/ProtocolLib.jar
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '28 9 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- 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: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+167 -169
View File
@@ -1,169 +1,167 @@
# ProtocolLib
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modify 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)
### Compilation
ProtocolLib is built with Maven and requires Spigot and SpigotAPI, which can be found [here](https://www.spigotmc.org/wiki/buildtools/).
### A new API
__ProtocolLib__ attempts to solve this problem by providing a 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>
</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) {
// Item packets (id: 0x29)
if (event.getPacketType() ==
PacketType.Play.Server.NAMED_SOUND_EFFECT) {
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) {
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
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
Packet60Explosion fakeExplosion = new Packet60Explosion();
fakeExplosion.a = player.getLocation().getX();
fakeExplosion.b = player.getLocation().getY();
fakeExplosion.c = player.getLocation().getZ();
fakeExplosion.d = 3.0F;
fakeExplosion.e = new ArrayList<Object>();
((CraftPlayer) player).getHandle().netServerHandler.sendPacket(fakeExplosion);
````
But with ProtocolLib, you can turn that into something more manageable. Notice that
you don't have to create an ArrayList with this version:
````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);
try {
protocolManager.sendServerPacket(player, fakeExplosion);
} catch (InvocationTargetException e) {
throw new RuntimeException(
"Cannot send packet " + fakeExplosion, e);
}
````
### Compatibility
One of the main goals of this project was to achieve maximum compatibility with CraftBukkit. And the end
result is quite flexible. Aside from netty package changes, it should be resilient against future changes.
It's likely that I won't have to update ProtocolLib for anything but bug fixes and new features.
How is this possible? It all comes down to reflection in the end. Essentially, no name is hard coded -
every field, method and class is deduced by looking at field types, package names or parameter
types. It's remarkably consistent across different versions.
# ProtocolLib
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modifying Minecraft directly. A common technique is to modify incoming
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
stream. This is quite cumbersome to do, however, and most implementations will break
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
Critically, different plugins that use this approach may _hook_ into the same classes,
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
lead to more subtle bugs.
Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/).
### Resources
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
### Compilation
ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run
`mvn package` in the root project folder.
### A new API
__ProtocolLib__ attempts to solve this problem by providing an event API, much like Bukkit,
that allows plugins to monitor, modify, or cancel packets sent and received. But, more importantly,
the API also hides all the gritty, obfuscated classes with a simple index based read/write system.
You no longer have to reference CraftBukkit!
### Using ProtocolLib
To use this library, first add ProtocolLib.jar to your Java build path. Then, add ProtocolLib
as a dependency or soft dependency to your plugin.yml file like any other plugin:
````yml
depend: [ ProtocolLib ]
````
You can also add ProtocolLib as a Maven dependency:
````xml
<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.
+1 -1
View File
@@ -25,7 +25,7 @@
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
@@ -72,7 +72,7 @@ public abstract class TinyProtocol {
private List<Object> networkManagers;
// Injected channel handlers
private List<Channel> serverChannels = Lists.newArrayList();
private List<Channel> serverChannels = new ArrayList<>();
private ChannelInboundHandlerAdapter serverChannelHandler;
private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol;
+2 -2
View File
@@ -1,5 +1,5 @@
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 17.0.1-tem
- sdk use java 17.0.1-tem
- sdk install java 17.0.4-tem
- sdk use java 17.0.4-tem
+16 -12
View File
@@ -4,7 +4,7 @@
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>4.8.0</version>
<version>5.0.0-SNAPSHOT</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>https://github.com/dmulloy2/ProtocolLib</url>
@@ -16,10 +16,10 @@
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
<junit.version>5.8.2</junit.version>
<mockito.version>4.3.1</mockito.version>
<netty.version>4.1.74.Final</netty.version>
<spigot.version>1.18.2-R0.1-SNAPSHOT</spigot.version>
<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.3-R0.1-SNAPSHOT</spigot.version>
</properties>
<build>
@@ -40,7 +40,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -73,7 +73,7 @@
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.0</version>
<version>3.10.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
@@ -100,7 +100,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M7</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
@@ -125,7 +125,7 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
<version>3.4.0</version>
<configuration>
<failOnError>false</failOnError>
<encoding>ISO-8859-1</encoding>
@@ -195,7 +195,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -310,6 +310,10 @@
<groupId>junit</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
@@ -325,14 +329,14 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.9.3</version>
<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.8</version>
<version>1.12.12</version>
</dependency>
<!-- Testing dependencies -->
@@ -41,9 +41,9 @@ abstract class CommandBase implements CommandExecutor {
public static final String PERMISSION_ADMIN = "protocol.admin";
private String permission;
private String name;
private int minimumArgumentCount;
private final String permission;
private final String name;
private final int minimumArgumentCount;
protected ErrorReporter reporter;
@@ -97,18 +97,23 @@ abstract class CommandBase implements CommandExecutor {
Boolean result = null;
if (!arguments.isEmpty()) {
String arg = arguments.peek();
if (arg.equalsIgnoreCase("true") || arg.equalsIgnoreCase("on"))
result = true;
else if (arg.equalsIgnoreCase(parameterName))
result = true;
else if (arg.equalsIgnoreCase("false") || arg.equalsIgnoreCase("off"))
result = false;
final String arg = arguments.peek().toLowerCase();
switch (arg) {
case "true":
case "on":
result = true;
break;
case "false":
case "off":
result = false;
break;
default:
if (arg.equalsIgnoreCase(parameterName)) result = true;
break;
}
}
if (result != null)
arguments.poll();
if (result != null) arguments.poll();
return result;
}
@@ -119,7 +124,7 @@ abstract class CommandBase implements CommandExecutor {
* @return A queue that contains every element in the array, starting at the given index.
*/
protected Deque<String> toQueue(String[] args, int start) {
return new ArrayDeque<String>(Arrays.asList(args).subList(start, args.length));
return new ArrayDeque<>(Arrays.asList(args).subList(start, args.length));
}
/**
@@ -2,6 +2,7 @@ package com.comphenix.protocol;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -26,7 +27,6 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* A command to apply JavaScript filtering to the packet command.
@@ -79,7 +79,7 @@ public class CommandFilter extends CommandBase {
public Filter(String name, String predicate, Set<PacketType> packets) {
this.name = name;
this.predicate = predicate;
this.packets = Sets.newHashSet(packets);
this.packets = new HashSet<>(packets);
}
/**
@@ -103,7 +103,7 @@ public class CommandFilter extends CommandBase {
* @return Set of packets this filter applies to.
*/
public Set<PacketType> getRanges() {
return Sets.newHashSet(packets);
return new HashSet<>(packets);
}
/**
@@ -22,6 +22,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -34,25 +35,17 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.utility.ByteBuddyGenerated;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.reflect.PrettyPrinter.ObjectPrinter;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.HexDumper;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
/**
* Handles the "packet" debug command.
@@ -121,13 +114,7 @@ class CommandPacket extends CommandBase {
* @return TRUE if the message was sent successfully, FALSE otherwise.
*/
public void sendMessageSilently(CommandSender receiver, String message) {
try {
chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(receiver, message)
);
}
chatter.sendMessageSilently(receiver, message);
}
/**
@@ -136,13 +123,7 @@ class CommandPacket extends CommandBase {
* @param permission - permission required to receieve the message. NULL to target everyone.
*/
public void broadcastMessageSilently(String message, String permission) {
try {
chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_SEND_MESSAGE).error(e).callerParam(message, permission)
);
}
chatter.broadcastMessageSilently(message, permission);
}
private void printPage(CommandSender sender, int pageIndex) {
@@ -306,7 +287,7 @@ class CommandPacket extends CommandBase {
}
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
Set<PacketType> result = Sets.newHashSet();
final Set<PacketType> result = new HashSet<>();
for (PacketType type : types) {
if (type.getSender() == sender) {
@@ -461,19 +442,21 @@ class CommandPacket extends CommandBase {
return listener;
}
private SubCommand parseCommand(Deque<String> arguments) {
String text = arguments.poll().toLowerCase();
// Parse this too
if ("add".startsWith(text))
return SubCommand.ADD;
else if ("remove".startsWith(text))
return SubCommand.REMOVE;
else if ("names".startsWith(text))
return SubCommand.NAMES;
else if ("page".startsWith(text))
return SubCommand.PAGE;
else
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
private SubCommand parseCommand(Deque<String> arguments)
{
final String text = arguments.remove().toLowerCase();
switch (arguments.remove().toLowerCase()) {
case "add":
return SubCommand.ADD;
case "remove":
return SubCommand.REMOVE;
case "names":
return SubCommand.NAMES;
case "page":
return SubCommand.PAGE;
default:
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
}
}
}
@@ -218,14 +218,13 @@ class CommandProtocol extends CommandBase {
private static SimpleDateFormat TIMESTAMP_FORMAT;
private void dump(CommandSender sender) {
Closer closer = Closer.create();
if (FILE_FORMAT == null)
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
if (TIMESTAMP_FORMAT == null)
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
try {
try (Closer closer = Closer.create()) {
Date date = new Date();
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
if (file.exists()) {
@@ -233,7 +232,7 @@ class CommandProtocol extends CommandBase {
}
file.createNewFile();
FileWriter fw = closer.register(new FileWriter(file));
PrintWriter pw = closer.register(new PrintWriter(fw));
@@ -285,8 +284,6 @@ class CommandProtocol extends CommandBase {
} catch (IOException ex) {
ProtocolLogger.log(Level.SEVERE, "Failed to create dump:", ex);
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
} finally {
closer.close();
}
}
}
@@ -1,280 +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 java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.*;
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 com.google.common.base.Charsets;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
/**
* 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(), Charsets.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();
}
}
}
@@ -17,98 +17,87 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.WirePacket;
import org.bukkit.entity.Player;
/**
* Represents a object capable of sending or receiving packets.
*
*
* @author Kristian
*/
public interface PacketStream {
/**
* Send a packet to the given player.
* @param receiver - the reciever.
* @param packet - packet to send.
* @throws InvocationTargetException - if an error occured when sending the packet.
*/
public void sendServerPacket(Player receiver, PacketContainer packet)
throws InvocationTargetException;
/**
* Send a packet to the given player.
*
* @param receiver - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
* @param packet - packet to send.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet);
/**
* Send a packet to the given player.
* @param receiver - the receiver.
* @param packet - packet to send.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occured when sending the packet.
*
* @param receiver - the reciever.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
*/
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
/**
* Send a packet to the given player.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
/**
* Send a wire packet to the given player.
*
* @param receiver - the receiver.
* @param id - packet id.
* @param bytes - packet bytes.
* @throws InvocationTargetException if an error occured when sending the packet.
* @param id - packet id.
* @param bytes - packet bytes.
*/
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException;
void sendWirePacket(Player receiver, int id, byte[] bytes);
/**
* Send a wire packet to the given player.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @throws InvocationTargetException if an error occured when sending the packet.
* @param packet - packet to send.
*/
public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException;
void sendWirePacket(Player receiver, WirePacket packet);
/**
* Simulate recieving a certain packet from a given player.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet);
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
/**
* Simulate recieving a certain packet from a given player.
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param marker - the network marker to use.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param marker - the network marker to use.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters);
}
@@ -11,13 +11,11 @@ import java.util.function.Consumer;
import com.comphenix.protocol.PacketTypeLookup.ClassLookup;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.Constants;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
@@ -52,6 +50,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Client extends PacketTypeEnum {
private final static 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();
@@ -105,59 +104,59 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
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 SPAWN_ENTITY_LIVING = new PacketType(PROTOCOL, SENDER, 0x02, "SpawnEntityLiving", "SPacketSpawnMob");
public static final PacketType SPAWN_ENTITY_PAINTING = new PacketType(PROTOCOL, SENDER, 0x03, "SpawnEntityPainting", "SPacketSpawnPainting");
public static final PacketType NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 0x04, "NamedEntitySpawn", "SPacketSpawnPlayer");
public static final PacketType ADD_VIBRATION_SIGNAL = new PacketType(PROTOCOL, SENDER, 0x05, "AddVibrationSignal");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x07, "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_BREAK = new PacketType(PROTOCOL, SENDER, 0x08, "BlockBreak");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x09, "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x0A, "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x0B, "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x0C, "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0D, "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0E, "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x0F, "Chat", "SPacketChat");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x10, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x11, "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x12, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x13, "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x14, "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x15, "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x16, "SetSlot", "SPacketSetSlot");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x17, "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x18, "CustomPayload", "SPacketCustomPayload");
public static final PacketType CUSTOM_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x19, "CustomSoundEffect", "SPacketCustomSound");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1A, "KickDisconnect", "SPacketDisconnect");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1B, "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1C, "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1D, "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x1E, "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x1F, "OpenWindowHorse");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x20, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x21, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x22, "MapChunk", "SPacketChunkData", "LevelChunkWithLight");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x23, "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x24, "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x25, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x26, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x27, "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x28, "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x29, "Entity$PacketPlayOutRelEntityMove", "Entity$RelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2A, "Entity$PacketPlayOutRelEntityMoveLook", "Entity$RelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x2B, "Entity$PacketPlayOutEntityLook", "Entity$EntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2C, "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x2D, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x2E, "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x2F, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x30, "Ping");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x31, "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x32, "Abilities", "SPacketPlayerAbilities");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x33, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x34, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x35, "PlayerCombatKill");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x36, "PlayerInfo", "SPacketPlayerListItem");
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 CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0C, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0D, "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x0E, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0F, "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x10, "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x11, "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x12, "SetSlot", "SPacketSetSlot");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x13, "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x14, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x15, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x16, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x17, "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x18, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x19, "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1A, "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1B, "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x1C, "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x1D, "OpenWindowHorse");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x1E, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1F, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x20, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x21, "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x22, "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x23, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x24, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x25, "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x26, "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x27, "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x28, "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x29, "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2A, "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x2B, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x2C, "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x2D, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x2E, "Ping");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x2F, "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x30, "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x31, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x32, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x33, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x34, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x35, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x36, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x37, "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x38, "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x39, "Recipes", "SPacketRecipeBook");
@@ -168,45 +167,48 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x3E, "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x3F, "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x40, "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x41, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x42, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x43, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x44, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x45, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x46, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x47, "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x48, "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x49, "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4A, "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x4B, "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x4C, "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x4D, "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x4E, "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x4F, "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x50, "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x51, "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x52, "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x53, "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x54, "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x55, "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x56, "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x57, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x58, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x59, "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5A, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x5B, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x5C, "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x5D, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x5E, "StopSound");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x5F, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x60, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x61, "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x62, "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x63, "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x64, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x65, "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x66, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x67, "Tags");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x41, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x42, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x43, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x44, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x45, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x46, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x47, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x48, "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x49, "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x4A, "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4B, "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x4C, "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x4D, "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x4E, "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x4F, "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x50, "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x51, "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x52, "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x53, "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x54, "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x55, "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x56, "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x57, "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x58, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x59, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x5A, "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5B, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x5C, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x5D, "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x5E, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x5F, "StopSound");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x60, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x61, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x62, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x63, "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x64, "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x65, "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x66, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x67, "UpdateEnabledFeatures");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x68, "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x69, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x6A, "Tags");
// ---- Removed in 1.9
@@ -313,6 +315,54 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType ENTITY = new PacketType(PROTOCOL, SENDER, 245, "Entity", "SPacketEntity");
/**
* @deprecated Removed in 1.19
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_LIVING = new PacketType(PROTOCOL, SENDER, 244, "SpawnEntityLiving", "SPacketSpawnMob");
/**
* @deprecated Removed in 1.19
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_PAINTING = new PacketType(PROTOCOL, SENDER, 243, "SpawnEntityPainting", "SPacketSpawnPainting");
/**
* @deprecated Removed in 1.19
*/
@Deprecated
public static final PacketType ADD_VIBRATION_SIGNAL = new PacketType(PROTOCOL, SENDER, 242, "AddVibrationSignal");
/**
* @deprecated Removed in 1.19
*/
@Deprecated
public static final PacketType BLOCK_BREAK = new PacketType(PROTOCOL, SENDER, 241, "BlockBreak");
/**
* @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 final static Server INSTANCE = new Server();
// Prevent accidental construction
@@ -336,51 +386,54 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
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");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x02, "DifficultyChange");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x03, "Chat", "CPacketChatMessage");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, "ClientCommand", "CPacketClientStatus");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x05, "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x06, "TabComplete", "CPacketTabComplete");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x07, "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x08, "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x09, "CloseWindow", "CPacketCloseWindow");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x0A, "CustomPayload", "CPacketCustomPayload");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x0B, "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x0C, "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x0D, "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x0E, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x0F, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x10, "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x11, "Flying$PacketPlayInPosition", "Flying$Position", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x12, "Flying$PacketPlayInPositionLook", "Flying$PositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x13, "Flying$PacketPlayInLook", "Flying$Look", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x14, "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x15, "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x16, "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x17, "PickItem");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x18, "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x19, "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x1A, "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x1B, "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x1C, "SteerVehicle", "CPacketInput");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x1D, "Pong", "ServerboundPongPacket");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x1E, "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x1F, "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x20, "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x21, "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x22, "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x23, "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x24, "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x25, "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x26, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x27, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x28, "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x29, "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x2A, "Struct");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x2B, "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2C, "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x2D, "Spectate", "CPacketSpectate");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x2E, "UseItem", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x2F, "BlockPlace", "CPacketPlayerTryUseItem");
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 CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x06, "ClientCommand", "CPacketClientStatus");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x07, "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x08, "TabComplete", "CPacketTabComplete");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x09, "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0A, "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0B, "CloseWindow", "CPacketCloseWindow");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x0C, "CustomPayload", "CPacketCustomPayload");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x0D, "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x0E, "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x0F, "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x10, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x11, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x12, "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x13, "Flying$PacketPlayInPosition", "Flying$Position", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x14, "Flying$PacketPlayInPositionLook", "Flying$PositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x15, "Flying$PacketPlayInLook", "Flying$Look", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x16, "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x17, "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x18, "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x19, "PickItem");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x1A, "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x1B, "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x1C, "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x1D, "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x1E, "SteerVehicle", "CPacketInput");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x1F, "Pong");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x20, "ChatSessionUpdate");
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");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x24, "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x25, "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x26, "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x27, "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x28, "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x29, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x2A, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x2B, "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x2C, "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x2D, "Struct");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x2E, "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2F, "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x30, "Spectate", "CPacketSpectate");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x31, "UseItem", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x32, "BlockPlace", "CPacketPlayerTryUseItem");
/**
* @deprecated Removed in 1.17
@@ -394,6 +447,12 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType FLYING = new PacketType(PROTOCOL, SENDER, 254, "Flying", "CPacketPlayer");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x06, "ChatPreview");
private final static Client INSTANCE = new Client();
// Prevent accidental construction
@@ -428,6 +487,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@ForceAsync
public static final PacketType SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, "ServerInfo", "SPacketServerInfo");
@ForceAsync
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x01, "Pong", "SPacketPong");
/**
@@ -458,6 +518,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private final static 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();
@@ -655,7 +716,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
/**
* Protocol version of all the current IDs.
*/
private static final MinecraftVersion PROTOCOL_VERSION = Constants.CURRENT_VERSION;
private static final MinecraftVersion PROTOCOL_VERSION = MinecraftVersion.LATEST;
private final Protocol protocol;
private final Sender sender;
@@ -695,7 +756,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @return Every packet type.
*/
public static Iterable<PacketType> values() {
List<Iterable<? extends PacketType>> sources = Lists.newArrayList();
final List<Iterable<? extends PacketType>> sources = new ArrayList<>();
sources.add(Handshake.Client.getInstance());
sources.add(Handshake.Server.getInstance());
sources.add(Play.Client.getInstance());
@@ -23,11 +23,6 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Sets;
/**
* Represents a more modern object-based enum.
* <p>
@@ -37,7 +32,7 @@ import com.google.common.collect.Sets;
*/
public class PacketTypeEnum implements Iterable<PacketType> {
// Used to convert between IDs and names
protected Set<PacketType> members = Sets.newHashSet();
protected Set<PacketType> members = new HashSet<>();
/**
* Registers every declared PacketType field.
@@ -19,14 +19,14 @@ import com.google.common.collect.Multimap;
class PacketTypeLookup {
public static class ProtocolSenderLookup {
// Unroll lookup for performance reasons
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = new IntegerMap<>();
public final IntegerMap<PacketType> GAME_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> GAME_SERVER = new IntegerMap<>();
public final IntegerMap<PacketType> STATUS_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> STATUS_SERVER = new IntegerMap<>();
public final IntegerMap<PacketType> LOGIN_CLIENT = new IntegerMap<>();
public final IntegerMap<PacketType> LOGIN_SERVER = new IntegerMap<>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
@@ -52,14 +52,14 @@ class PacketTypeLookup {
public static class ClassLookup {
// Unroll lookup for performance reasons
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<String, PacketType>();
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
@@ -84,9 +84,9 @@ class PacketTypeLookup {
}
// Packet IDs from 1.6.4 and below
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<PacketType>();
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<>();
private final IntegerMap<PacketType> serverLookup = new IntegerMap<>();
private final IntegerMap<PacketType> clientLookup = new IntegerMap<>();
// Packets for 1.7.2
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
@@ -1,29 +1,29 @@
package com.comphenix.protocol;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ConnectionSide;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
class PacketTypeParser {
public final static Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
public static final Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
private Sender side = null;
private Protocol protocol = null;
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
Set<PacketType> result = Sets.newHashSet();
final Set<PacketType> result = new HashSet<>();
side = null;
protocol = null;
@@ -68,8 +68,7 @@ class PacketTypeParser {
// Supply a default integer range
if (ranges.isEmpty() && result.isEmpty()) {
ranges = Lists.newArrayList();
ranges.add(defaultRange);
ranges = Collections.singletonList(defaultRange);
}
for (Range<Integer> range : ranges) {
@@ -130,19 +129,20 @@ class PacketTypeParser {
* @return The protocol, or NULL if not found.
*/
public Protocol parseProtocol(String text) {
if (text == null)
return null;
String candidate = text.toLowerCase();
if ("handshake".equals(candidate) || "handshaking".equals(candidate))
return Protocol.HANDSHAKING;
else if ("login".equals(candidate))
return Protocol.LOGIN;
else if ("play".equals(candidate) || "game".equals(candidate))
return Protocol.PLAY;
else if ("status".equals(candidate))
return Protocol.STATUS;
else
return null;
if (text == null) return null;
switch (text.toLowerCase()) {
case "handshake":
case "handshaking":
return Protocol.HANDSHAKING;
case "login":
return Protocol.LOGIN;
case "play":
case "game":
return Protocol.PLAY;
case "status":
return Protocol.STATUS;
default: return null;
}
}
}
@@ -1,43 +1,38 @@
/**
* 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
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S.
* Stangeland
* <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 java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
/**
* Represents the configuration of ProtocolLib.
*
*
* @author Kristian
*/
public class ProtocolConfig {
private static final String LAST_UPDATE_FILE = "lastupdate";
private static final String SECTION_GLOBAL = "global";
@@ -46,11 +41,10 @@ public class ProtocolConfig {
private static final String METRICS_ENABLED = "metrics";
private static final String IGNORE_VERSION_CHECK = "ignore version check";
private static final String BACKGROUND_COMPILER_ENABLED = "background compiler";
private static final String DEBUG_MODE_ENABLED = "debug";
private static final String DETAILED_ERROR = "detailed error";
private static final String INJECTION_METHOD = "injection method";
private static final String CHAT_WARNINGS = "chat warnings";
private static final String SCRIPT_ENGINE_NAME = "script engine";
private static final String SUPPRESSED_REPORTS = "suppressed reports";
@@ -98,7 +92,7 @@ public class ProtocolConfig {
/**
* Load the last update time stamp from the file system.
*
*
* @return Last update time stamp.
*/
private long loadLastUpdate() {
@@ -106,7 +100,7 @@ public class ProtocolConfig {
if (dataFile.exists()) {
try {
return Long.parseLong(Files.toString(dataFile, Charsets.UTF_8));
return Long.parseLong(Files.toString(dataFile, StandardCharsets.UTF_8));
} catch (NumberFormatException e) {
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
} catch (IOException e) {
@@ -119,7 +113,7 @@ public class ProtocolConfig {
/**
* Store the given time stamp.
*
*
* @param value - time stamp to store.
*/
private void saveLastUpdate(long value) {
@@ -128,11 +122,12 @@ public class ProtocolConfig {
// The data folder must exist
dataFile.getParentFile().mkdirs();
if (dataFile.exists())
if (dataFile.exists()) {
dataFile.delete();
}
try {
Files.write(Long.toString(value), dataFile, Charsets.UTF_8);
Files.write(Long.toString(value), dataFile, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Cannot write " + dataFile, e);
}
@@ -140,7 +135,7 @@ public class ProtocolConfig {
/**
* Retrieve the file that is used to store the update time stamp.
*
*
* @return File storing the update time stamp.
*/
private File getLastUpdateFile() {
@@ -149,7 +144,7 @@ public class ProtocolConfig {
/**
* Load data sections.
*
*
* @param copyDefaults - whether or not to copy configuration defaults.
*/
private void loadSections(boolean copyDefaults) {
@@ -167,8 +162,9 @@ public class ProtocolConfig {
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
loadingSections = true;
if (config != null)
if (config != null) {
config.options().copyDefaults(true);
}
plugin.saveDefaultConfig();
plugin.reloadConfig();
loadingSections = false;
@@ -180,7 +176,7 @@ public class ProtocolConfig {
/**
* Set a particular configuration key value pair.
*
*
* @param section - the configuration root.
* @param path - the path to the key.
* @param value - the value to set.
@@ -210,7 +206,7 @@ public class ProtocolConfig {
/**
* Retrieve a reference to the configuration file.
*
*
* @return Configuration file on disk.
*/
public File getFile() {
@@ -219,7 +215,7 @@ public class ProtocolConfig {
/**
* Determine if detailed error reporting is enabled. Default FALSE.
*
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isDetailedErrorReporting() {
@@ -227,57 +223,36 @@ public class ProtocolConfig {
}
/**
* Set whether or not detailed error reporting is enabled.
*
* @param value - TRUE if it is enabled, FALSE otherwise.
* Print warnings to players with protocol.info
* @return true if enabled, false if not
*/
public void setDetailedErrorReporting(boolean value) {
global.set(DETAILED_ERROR, value);
public boolean isChatWarnings() {
return getGlobalValue(CHAT_WARNINGS, true);
}
/**
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
*
*
* @return TRUE if it should do this automatically, FALSE otherwise.
*/
public boolean isAutoNotify() {
return getUpdaterValue(UPDATER_NOTIFY, true);
}
/**
* Set whether or not ProtocolLib should determine if a new version has been released.
*
* @param value - TRUE to do this automatically, FALSE otherwise.
*/
public void setAutoNotify(boolean value) {
setConfig(updater, UPDATER_NOTIFY, value);
modCount++;
}
/**
* Retrieve whether or not ProtocolLib should automatically download the new version.
*
*
* @return TRUE if it should, FALSE otherwise.
*/
public boolean isAutoDownload() {
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, false);
}
/**
* Set whether or not ProtocolLib should automatically download the new version.
*
* @param value - TRUE if it should. FALSE otherwise.
*/
public void setAutoDownload(boolean value) {
setConfig(updater, UPDATER_DOWNLAD, value);
modCount++;
}
/**
* Determine whether or not debug mode is enabled.
* <p>
* This grants access to the filter command.
*
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug() {
@@ -286,7 +261,7 @@ public class ProtocolConfig {
/**
* Set whether or not debug mode is enabled.
*
*
* @param value - TRUE if it is enabled, FALSE otherwise.
*/
public void setDebug(boolean value) {
@@ -296,26 +271,16 @@ public class ProtocolConfig {
/**
* Retrieve an immutable list of every suppressed report type.
*
*
* @return Every suppressed report type.
*/
public ImmutableList<String> getSuppressedReports() {
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
}
/**
* Set the list of suppressed report types,
*
* @param reports - suppressed report types.
*/
public void setSuppressedReports(List<String> reports) {
global.set(SUPPRESSED_REPORTS, Lists.newArrayList(reports));
modCount++;
}
/**
* Retrieve the amount of time to wait until checking for a new update.
*
*
* @return The amount of time to wait.
*/
public long getAutoDelay() {
@@ -323,87 +288,27 @@ public class ProtocolConfig {
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
}
/**
* Set the amount of time to wait until checking for a new update.
* <p>
* This time must be greater than 59 seconds.
*
* @param delaySeconds - the amount of time to wait.
*/
public void setAutoDelay(long delaySeconds) {
// Silently fix the delay
if (delaySeconds < DEFAULT_UPDATER_DELAY)
delaySeconds = DEFAULT_UPDATER_DELAY;
setConfig(updater, UPDATER_DELAY, delaySeconds);
modCount++;
}
/**
* The version of Minecraft to ignore the built-in safety feature.
*
*
* @return The version to ignore ProtocolLib's satefy.
*/
public String getIgnoreVersionCheck() {
return getGlobalValue(IGNORE_VERSION_CHECK, "");
}
/**
* Sets under which version of Minecraft the version safety feature will be ignored.
* <p>
* This is useful if a server operator has tested and verified that a version of ProtocolLib works, but doesn't want or can't upgrade to a newer version.
*
* @param ignoreVersion - the version of Minecraft where the satefy will be disabled.
*/
public void setIgnoreVersionCheck(String ignoreVersion) {
setConfig(global, IGNORE_VERSION_CHECK, ignoreVersion);
modCount++;
}
/**
* Retrieve whether or not metrics is enabled.
*
*
* @return TRUE if metrics is enabled, FALSE otherwise.
*/
public boolean isMetricsEnabled() {
return getGlobalValue(METRICS_ENABLED, true);
}
/**
* Set whether or not metrics is enabled.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param enabled - whether or not metrics is enabled.
*/
public void setMetricsEnabled(boolean enabled) {
setConfig(global, METRICS_ENABLED, enabled);
modCount++;
}
/**
* Retrieve whether or not the background compiler for structure modifiers is enabled or not.
*
* @return TRUE if it is enabled, FALSE otherwise.
*/
public boolean isBackgroundCompilerEnabled() {
return getGlobalValue(BACKGROUND_COMPILER_ENABLED, true);
}
/**
* Set whether or not the background compiler for structure modifiers is enabled or not.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
* @param enabled - TRUE if is enabled/running, FALSE otherwise.
*/
public void setBackgroundCompilerEnabled(boolean enabled) {
setConfig(global, BACKGROUND_COMPILER_ENABLED, enabled);
modCount++;
}
/**
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
*
*
* @return Last update time.
*/
public long getAutoLastTime() {
@@ -414,7 +319,7 @@ public class ProtocolConfig {
* Set the last time we updated, in seconds since 1970.01.01 00:00.
* <p>
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
*
*
* @param lastTimeSeconds - new last update time.
*/
public void setAutoLastTime(long lastTimeSeconds) {
@@ -424,7 +329,7 @@ public class ProtocolConfig {
/**
* Retrieve the unique name of the script engine to use for filtering.
*
*
* @return Script engine to use.
*/
public String getScriptEngineName() {
@@ -435,7 +340,7 @@ public class ProtocolConfig {
* Set the unique name of the script engine to use for filtering.
* <p>
* This setting will take effect next time ProtocolLib is started.
*
*
* @param name - name of the script engine to use.
*/
public void setScriptEngineName(String name) {
@@ -443,45 +348,9 @@ public class ProtocolConfig {
modCount++;
}
/**
* Retrieve the default injection method.
*
* @return Default method.
*/
public PlayerInjectHooks getDefaultMethod() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
/**
* Retrieve the injection method that has been set in the configuration, or use a default value.
*
* @return Injection method to use.
* @throws IllegalArgumentException If the configuration option is malformed.
*/
public PlayerInjectHooks getInjectionMethod() throws IllegalArgumentException {
String text = global.getString(INJECTION_METHOD);
// Default hook if nothing has been set
PlayerInjectHooks hook = getDefaultMethod();
if (text != null)
hook = PlayerInjectHooks.valueOf(text.toUpperCase(Locale.ENGLISH).replace(" ", "_"));
return hook;
}
/**
* Set the starting injection method to use.
*
* @return Injection method.
*/
public void setInjectionMethod(PlayerInjectHooks hook) {
setConfig(global, INJECTION_METHOD, hook.name());
modCount++;
}
/**
* Retrieve the number of modifications made to this configuration.
*
*
* @return The number of modifications.
*/
public int getModificationCount() {
@@ -492,10 +361,12 @@ public class ProtocolConfig {
* Save the current configuration file.
*/
public void saveAll() {
if (valuesChanged)
if (valuesChanged) {
saveLastUpdate(lastUpdateTime);
if (configChanged)
}
if (configChanged) {
plugin.saveConfig();
}
// And we're done
valuesChanged = false;
@@ -1,49 +1,49 @@
/**
* 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
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S.
* Stangeland
* <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 com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.*;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.NettyVersion;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
@@ -57,111 +57,88 @@ import org.bukkit.plugin.java.JavaPlugin;
* @author Kristian
*/
public class ProtocolLib extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType("Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_CANNOT_PARSE_INJECTION_METHOD = new ReportType("Cannot parse injection method. Using default.");
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType(
"Cannot delete old ProtocolLib configuration.");
public static final ReportType REPORT_PLUGIN_LOAD_ERROR = new ReportType("Cannot load ProtocolLib.");
public static final ReportType REPORT_CANNOT_LOAD_CONFIG = new ReportType("Cannot load configuration");
public static final ReportType REPORT_PLUGIN_ENABLE_ERROR = new ReportType("Cannot enable ProtocolLib.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType("Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_IO_ERROR = new ReportType(
"Unable to enable metrics due to network problems.");
public static final ReportType REPORT_METRICS_GENERIC_ERROR = new ReportType(
"Unable to enable metrics due to network problems.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType("Unable to retrieve current Minecraft version. Assuming %s");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType("Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType(
"Unable to retrieve current Minecraft version. Assuming %s");
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType(
"Unable to detect conflicting plugin versions.");
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType("Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType(
"Unable to create packet timeout task.");
public static final ReportType REPORT_CANNOT_UPDATE_PLUGIN = new ReportType("Cannot perform automatic updates.");
// Update information
static final String BUKKIT_DEV_SLUG = "protocollib";
static final int BUKKIT_DEV_ID = 45564;
// Different commands
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL,
LOGGING;
}
/**
* The number of milliseconds per second.
*/
static final long MILLI_PER_SECOND = 1000;
static final long MILLI_PER_SECOND = TimeUnit.SECONDS.toMillis(1);
private static final int ASYNC_MANAGER_DELAY = 1;
private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static
private static InternalManager protocolManager;
// Error reporter
private static ErrorReporter reporter = new BasicErrorReporter();
// Strongly typed configuration
// these fields are only existing once, we can make them static
private static Logger logger;
private static ProtocolConfig config;
// Metrics and statistics
private static InternalManager protocolManager;
private static ErrorReporter reporter = new BasicErrorReporter();
private Statistics statistics;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
// Used to clean up server packets that have expired, but mostly required to simulate
// recieving client packets.
private int packetTask = -1;
private int tickCounter = 0;
private static final int ASYNC_MANAGER_DELAY = 1;
// Used to unhook players after a delay
private DelayedSingleTask unhookTask;
// Settings/options
private int configExpectedMod = -1;
// Updater
// updater
private Updater updater;
public static boolean UPDATES_DISABLED;
// Logger
private static Logger logger;
private Handler redirectHandler;
// Commands
// commands
private CommandProtocol commandProtocol;
private CommandPacket commandPacket;
private CommandFilter commandFilter;
private PacketLogging packetLogging;
// Whether or not disable is not needed
// Whether disabling field resetting is needed
private boolean skipDisable;
@Override
public void onLoad() {
// Logging
logger = getLogger();
logger = this.getLogger();
ProtocolLogger.init(this);
// Initialize enhancer factory
ByteBuddyFactory.getInstance().setClassLoader(getClassLoader());
ByteBuddyFactory.getInstance().setClassLoader(this.getClassLoader());
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
reporter = getFilteredReporter(detailedReporter);
reporter = this.getFilteredReporter(detailedReporter);
// Configuration
saveDefaultConfig();
reloadConfig();
this.saveDefaultConfig();
this.reloadConfig();
try {
config = new ProtocolConfig(this);
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(e));
} catch (Exception exception) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_CONFIG).error(exception));
// Load it again
if (deleteConfig()) {
if (this.deleteConfig()) {
config = new ProtocolConfig(this);
} else {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DELETE_CONFIG));
@@ -171,11 +148,8 @@ public class ProtocolLib extends JavaPlugin {
// Print the state of the debug mode
if (config.isDebug()) {
logger.warning("Debug mode is enabled!");
logger.info("Detected netty version: " + NettyVersion.getVersion());
} else {
NettyVersion.getVersion(); // this will cache the version
}
// And the state of the error reporter
if (config.isDetailedErrorReporting()) {
detailedReporter.setDetailedReporting(true);
@@ -184,52 +158,33 @@ public class ProtocolLib extends JavaPlugin {
try {
// Check for other versions
checkConflictingVersions();
this.checkConflictingVersions();
// Handle unexpected Minecraft versions
MinecraftVersion version = verifyMinecraftVersion();
MinecraftVersion version = this.verifyMinecraftVersion();
// Set updater - this will not perform any update automatically
updater = Updater.create(this, BUKKIT_DEV_ID, getFile(), UpdateType.NO_DOWNLOAD, true);
this.updater = Updater.create(this, 0, this.getFile(), UpdateType.NO_DOWNLOAD, true);
unhookTask = new DelayedSingleTask(this);
// api init
protocolManager = PacketFilterManager.newBuilder()
.classLoader(getClassLoader())
.server(getServer())
.server(this.getServer())
.library(this)
.minecraftVersion(version)
.unhookTask(unhookTask)
.reporter(reporter)
.build();
// Initialize the API
ProtocolLibrary.init(this, config, protocolManager, reporter);
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
// Update injection hook
try {
PlayerInjectHooks hook = config.getInjectionMethod();
// Only update the hook if it's different
if (!protocolManager.getPlayerHook().equals(hook)) {
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
protocolManager.setPlayerHook(hook);
}
} catch (IllegalArgumentException e) {
reporter.reportWarning(config, Report.newBuilder(REPORT_CANNOT_PARSE_INJECTION_METHOD).error(e));
}
// Send logging information to player listeners too
initializeCommands();
setupBroadcastUsers(PERMISSION_INFO);
this.initializeCommands();
this.setupBroadcastUsers(PERMISSION_INFO);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_LOAD_ERROR).error(e).callerParam(protocolManager));
disablePlugin();
this.disablePlugin();
}
}
@@ -241,18 +196,18 @@ public class ProtocolLib extends JavaPlugin {
for (ProtocolCommand command : ProtocolCommand.values()) {
try {
switch (command) {
case PROTOCOL:
commandProtocol = new CommandProtocol(reporter, this, updater, config);
break;
case FILTER:
commandFilter = new CommandFilter(reporter, this, config);
break;
case PACKET:
commandPacket = new CommandPacket(reporter, this, logger, commandFilter, protocolManager);
break;
case LOGGING:
packetLogging = new PacketLogging(this, protocolManager);
break;
case PROTOCOL:
this.commandProtocol = new CommandProtocol(reporter, this, this.updater, config);
break;
case FILTER:
this.commandFilter = new CommandFilter(reporter, this, config);
break;
case PACKET:
this.commandPacket = new CommandPacket(reporter, this, logger, this.commandFilter, protocolManager);
break;
case LOGGING:
this.packetLogging = new PacketLogging(this, protocolManager);
break;
}
} catch (OutOfMemoryError e) {
throw e;
@@ -267,12 +222,13 @@ public class ProtocolLib extends JavaPlugin {
/**
* Retrieve a error reporter that may be filtered by the configuration.
*
* @return The new default error reporter.
*/
private ErrorReporter getFilteredReporter(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
private int lastModCount = -1;
private Set<String> reports = Sets.newHashSet();
private Set<String> reports = new HashSet<>();
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
@@ -280,19 +236,20 @@ public class ProtocolLib extends JavaPlugin {
String canonicalName = ReportType.getReportName(sender, report.getType());
String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase();
if (config != null && config.getModificationCount() != lastModCount) {
if (config != null && config.getModificationCount() != this.lastModCount) {
// Update our cached set again
reports = Sets.newHashSet(config.getSuppressedReports());
lastModCount = config.getModificationCount();
this.reports = new HashSet<>(config.getSuppressedReports());
this.lastModCount = config.getModificationCount();
}
// Cancel reports either on the full canonical name, or just the report name
if (reports.contains(canonicalName) || reports.contains(reportName))
if (this.reports.contains(canonicalName) || this.reports.contains(reportName)) {
return null;
}
} catch (Exception e) {
// Only report this with a minor message
logger.warning("Error filtering reports: " + e.toString());
logger.warning("Error filtering reports: " + e);
}
// Don't filter anything
return report;
@@ -316,16 +273,17 @@ public class ProtocolLib extends JavaPlugin {
private void setupBroadcastUsers(final String permission) {
// Guard against multiple calls
if (redirectHandler != null)
if (this.redirectHandler != null) {
return;
}
// Broadcast information to every user too
redirectHandler = new Handler() {
this.redirectHandler = new Handler() {
@Override
public void publish(LogRecord record) {
// Only display warnings and above
if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
ProtocolLib.this.commandPacket.broadcastMessageSilently(record.getMessage(), permission);
}
}
@@ -340,23 +298,19 @@ public class ProtocolLib extends JavaPlugin {
}
};
logger.addHandler(redirectHandler);
logger.addHandler(this.redirectHandler);
}
@Override
public void onEnable() {
try {
Server server = getServer();
Server server = this.getServer();
PluginManager manager = server.getPluginManager();
// Don't do anything else!
if (manager == null)
return;
// Silly plugin reloaders!
if (protocolManager == null) {
Logger directLogging = Logger.getLogger("Minecraft");
String[] message = new String[] {
String[] message = new String[]{
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
};
@@ -365,59 +319,48 @@ public class ProtocolLib extends JavaPlugin {
directLogging.severe(line);
}
disablePlugin();
this.disablePlugin();
return;
}
// Check for incompatible plugins
checkForIncompatibility(manager);
// Initialize background compiler
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
BackgroundCompiler.setInstance(backgroundCompiler);
logger.info("Started structure compiler thread.");
} else {
logger.info("Structure compiler thread has been disabled.");
}
this.checkForIncompatibility(manager);
// Set up command handlers
registerCommand(CommandProtocol.NAME, commandProtocol);
registerCommand(CommandPacket.NAME, commandPacket);
registerCommand(CommandFilter.NAME, commandFilter);
registerCommand(PacketLogging.NAME, packetLogging);
this.registerCommand(CommandProtocol.NAME, this.commandProtocol);
this.registerCommand(CommandPacket.NAME, this.commandPacket);
this.registerCommand(CommandFilter.NAME, this.commandFilter);
this.registerCommand(PacketLogging.NAME, this.packetLogging);
// Player login and logout events
protocolManager.registerEvents(manager, this);
// Worker that ensures that async packets are eventually sent
// It also performs the update check.
createPacketTask(server);
this.createPacketTask(server);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_PLUGIN_ENABLE_ERROR).error(e));
disablePlugin();
this.disablePlugin();
return;
}
// Try to enable statistics
try {
if (config.isMetricsEnabled()) {
statistics = new Statistics(this);
this.statistics = new Statistics(this);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(statistics));
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_IO_ERROR).error(e).callerParam(this.statistics));
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(statistics));
reporter.reportDetailed(this, Report.newBuilder(REPORT_METRICS_GENERIC_ERROR).error(e).callerParam(
this.statistics));
}
}
// Plugin authors: Notify me to remove these
private void checkForIncompatibility(PluginManager manager) {
for (String plugin : ProtocolLibrary.INCOMPATIBLE) {
if (manager.getPlugin(plugin) != null) {
@@ -434,26 +377,31 @@ public class ProtocolLib extends JavaPlugin {
}
}
// Plugin authors: Notify me to remove these
// Used to check Minecraft version
private MinecraftVersion verifyMinecraftVersion() {
MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION);
MinecraftVersion maximum = new MinecraftVersion(ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION);
try {
MinecraftVersion current = new MinecraftVersion(getServer());
MinecraftVersion current = new MinecraftVersion(this.getServer());
// Skip certain versions
if (!config.getIgnoreVersionCheck().equals(current.getVersion())) {
// We'll just warn the user for now
if (current.compareTo(minimum) < 0)
if (current.compareTo(minimum) < 0) {
logger.warning("Version " + current + " is lower than the minimum " + minimum);
if (current.compareTo(maximum) > 0)
}
if (current.compareTo(maximum) > 0) {
logger.warning("Version " + current + " has not yet been tested! Proceed with caution.");
}
}
return current;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_PARSE_MINECRAFT_VERSION).error(e).messageParam(maximum));
// Unknown version - just assume it is the latest
return maximum;
@@ -462,16 +410,16 @@ public class ProtocolLib extends JavaPlugin {
private void checkConflictingVersions() {
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
MinecraftVersion currentVersion = new MinecraftVersion(getDescription().getVersion());
MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion());
MinecraftVersion newestVersion = null;
// Skip the file that contains this current instance however
File loadedFile = getFile();
File loadedFile = this.getFile();
try {
// Scan the plugin folder for newer versions of ProtocolLib
// The plugin folder isn't always plugins/
File pluginFolder = getDataFolder().getParentFile();
File pluginFolder = this.getDataFolder().getParentFile();
File[] candidates = pluginFolder.listFiles();
if (candidates != null) {
@@ -499,7 +447,7 @@ public class ProtocolLib extends JavaPlugin {
// See if the newest version is actually higher
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
skipDisable = true;
this.skipDisable = true;
throw new IllegalStateException(String.format(
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
@@ -510,10 +458,11 @@ public class ProtocolLib extends JavaPlugin {
private void registerCommand(String name, CommandExecutor executor) {
try {
// Ignore these - they must have printed an error already
if (executor == null)
if (executor == null) {
return;
}
PluginCommand command = getCommand(name);
PluginCommand command = this.getCommand(name);
// Try to load the command
if (command != null) {
@@ -522,7 +471,8 @@ public class ProtocolLib extends JavaPlugin {
throw new RuntimeException("plugin.yml might be corrupt.");
}
} catch (RuntimeException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
}
}
@@ -530,44 +480,42 @@ public class ProtocolLib extends JavaPlugin {
* Disable the current plugin.
*/
private void disablePlugin() {
getServer().getPluginManager().disablePlugin(this);
this.getServer().getPluginManager().disablePlugin(this);
}
private void createPacketTask(Server server) {
try {
if (packetTask >= 0)
if (this.packetTask >= 0) {
throw new IllegalStateException("Packet task has already been created");
}
// Attempt to create task
packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
@Override
public void run() {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
this.packetTask = server.getScheduler().scheduleSyncRepeatingTask(this, () -> {
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(tickCounter++, true);
// We KNOW we're on the main thread at the moment
manager.sendProcessedPackets(ProtocolLib.this.tickCounter++, true);
// House keeping
updateConfiguration();
// House keeping
ProtocolLib.this.updateConfiguration();
// Check for updates too
if (!UPDATES_DISABLED && (tickCounter % 20) == 0) {
checkUpdates();
}
// Check for updates too
if (!ProtocolLibrary.updatesDisabled() && (ProtocolLib.this.tickCounter % 20) == 0) {
ProtocolLib.this.checkUpdates();
}
}, ASYNC_MANAGER_DELAY, ASYNC_MANAGER_DELAY);
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
if (packetTask == -1) {
if (this.packetTask == -1) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CREATE_TIMEOUT_TASK).error(e));
}
}
}
private void updateConfiguration() {
if (config != null && config.getModificationCount() != configExpectedMod) {
configExpectedMod = config.getModificationCount();
if (config != null && config.getModificationCount() != this.configExpectedMod) {
this.configExpectedMod = config.getModificationCount();
// Update the debug flag
protocolManager.setDebug(config.isDebug());
@@ -577,58 +525,64 @@ public class ProtocolLib extends JavaPlugin {
private void checkUpdates() {
// Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
try {
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
// Should we update?
if (currentTime > updateTime && !updater.isChecking()) {
if (currentTime > updateTime && !this.updater.isChecking()) {
// Initiate the update as if it came from the console
if (config.isAutoDownload())
commandProtocol.updateVersion(getServer().getConsoleSender(), false);
else if (config.isAutoNotify())
commandProtocol.checkVersion(getServer().getConsoleSender(), false);
else
commandProtocol.updateFinished();
if (config.isAutoDownload()) {
this.commandProtocol.updateVersion(this.getServer().getConsoleSender(), false);
} else if (config.isAutoNotify()) {
this.commandProtocol.checkVersion(this.getServer().getConsoleSender(), false);
} else {
this.commandProtocol.updateFinished();
}
}
} catch (Exception e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(e));
UPDATES_DISABLED = true;
ProtocolLibrary.disableUpdates();
}
}
@Override
public void onDisable() {
if (skipDisable) {
if (this.skipDisable) {
return;
}
// Disable compiler
if (backgroundCompiler != null) {
backgroundCompiler.shutdownAll();
backgroundCompiler = null;
BackgroundCompiler.setInstance(null);
// that reloading the server might break ProtocolLib / plugins depending on it
if (Util.isCurrentlyReloading()) {
logger.severe("╔══════════════════════════════════════════════════════════════════╗");
logger.severe("║ WARNING ║");
logger.severe("║ RELOADING THE SERVER WHILE PROTOCOL LIB IS ENABLED MIGHT ║");
logger.severe("║ LEAD TO UNEXPECTED ERRORS! ║");
logger.severe("║ ║");
logger.severe("║ Consider to cleanly restart your server if you encounter ║");
logger.severe("║ any issues related to Protocol Lib before opening an issue ║");
logger.severe("║ on GitHub! ║");
logger.severe("╚══════════════════════════════════════════════════════════════════╝");
}
// Clean up
if (packetTask >= 0) {
getServer().getScheduler().cancelTask(packetTask);
packetTask = -1;
if (this.packetTask >= 0) {
this.getServer().getScheduler().cancelTask(this.packetTask);
this.packetTask = -1;
}
// And redirect handler too
if (redirectHandler != null) {
logger.removeHandler(redirectHandler);
if (this.redirectHandler != null) {
logger.removeHandler(this.redirectHandler);
}
if (protocolManager != null)
if (protocolManager != null) {
protocolManager.close();
else
} else {
return; // Plugin reloaders!
}
if (unhookTask != null)
unhookTask.close();
protocolManager = null;
statistics = null;
this.statistics = null;
// To clean up global parameters
reporter = new BasicErrorReporter();
@@ -637,15 +591,24 @@ public class ProtocolLib extends JavaPlugin {
/**
* Retrieve the metrics instance used to measure users of this library.
* <p>
* Note that this method may return NULL when the server is reloading or shutting down. It is also
* NULL if metrics has been disabled.
* Note that this method may return NULL when the server is reloading or shutting down. It is also NULL if metrics has
* been disabled.
*
* @return Metrics instance container.
*/
public Statistics getStatistics() {
return statistics;
return this.statistics;
}
public ProtocolConfig getProtocolConfig() {
return config;
}
// Different commands
private enum ProtocolCommand {
FILTER,
PACKET,
PROTOCOL,
LOGGING
}
}
@@ -1,27 +1,24 @@
/**
* 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
* 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 java.util.Arrays;
import java.util.List;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.utility.MinecraftVersion;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
@@ -30,6 +27,7 @@ import org.bukkit.plugin.Plugin;
* @author dmulloy2
*/
public class ProtocolLibrary {
/**
* The minimum version ProtocolLib has been tested with.
*/
@@ -38,17 +36,17 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.18.2";
public static final String MAXIMUM_MINECRAFT_VERSION = MinecraftVersion.LATEST.getVersion();
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.18.2) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.19.2) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2022-02-28";
public static final String MINECRAFT_LAST_RELEASE_DATE = "2022-08-05";
/**
* Plugins that are currently incompatible with ProtocolLib.
*/
public static final List<String> INCOMPATIBLE = Arrays.asList("TagAPI");
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
private static Plugin plugin;
private static ProtocolConfig config;
@@ -107,7 +105,7 @@ public class ProtocolLibrary {
}
/**
* Whether or not updates are currently disabled.
* Whether updates are currently disabled.
* @return True if it is, false if not
*/
public static boolean updatesDisabled() {
@@ -21,7 +21,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.utility.MinecraftVersion;
import org.bukkit.plugin.Plugin;
/**
* @author dmulloy2
@@ -17,16 +17,6 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
@@ -36,81 +26,94 @@ import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Represents an API for accessing the Minecraft protocol.
*
* @author Kristian
*/
public interface ProtocolManager extends PacketStream {
/**
* Retrieve the protocol version of a given player.
* <p>
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot #1628.
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot
* #1628.
*
* @param player - the player.
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
*/
int getProtocolVersion(Player player);
/**
* Send a packet to the given player.
* <p>
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
* to delay a packet until a certain condition has been met.
*
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to
* delay a packet until a certain condition has been met.
*
* @param receiver - the receiver.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException - if an error occurred when sending the packet.
* @param packet - packet to send.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
*/
@Override
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
/**
* Simulate receiving a certain packet from a given player.
* <p>
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()}
* to delay a packet until a certain condition has been met.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to delay
* a packet until a certain condition has been met.
*
* @param sender - the sender.
* @param packet - the packet that was sent.
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
* @throws InvocationTargetException If the reflection machinery failed.
* @throws IllegalAccessException If the underlying method caused an error.
*/
@Override
void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
/**
* Broadcast a given packet to every connected player on the server.
*
* @param packet - the packet to broadcast.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
void broadcastServerPacket(PacketContainer packet);
/**
* Broadcast a packet to every player that is receiving information about a given entity.
* <p>
* This is usually every player in the same world within an observable distance. If the entity is a
* player, it will only be included if <i>includeTracker</i> is TRUE.
* @param packet - the packet to broadcast.
* @param entity - the entity whose trackers we will inform.
* This is usually every player in the same world within an observable distance. If the entity is a player, it will
* only be included if <i>includeTracker</i> is TRUE.
*
* @param packet - the packet to broadcast.
* @param entity - the entity whose trackers we will inform.
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
/**
* Broadcast a packet to every player within the given maximum observer distance.
* @param packet - the packet to broadcast.
* @param origin - the origin to consider when calculating the distance to each observer.
*
* @param packet - the packet to broadcast.
* @param origin - the origin to consider when calculating the distance to each observer.
* @param maxObserverDistance - the maximum distance to the origin.
*/
void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
void broadcastServerPacket(PacketContainer packet, Collection<? extends Player> targetPlayers);
/**
* Retrieves a list of every registered packet listener.
*
* @return Every registered packet listener.
*/
ImmutableSet<PacketListener> getPacketListeners();
@@ -118,9 +121,9 @@ public interface ProtocolManager extends PacketStream {
/**
* Adds a packet listener.
* <p>
* Adding an already registered listener has no effect. If you need to change the packets
* the current listener is observing, you must first remove the packet listener before you
* can register it again.
* Adding an already registered listener has no effect. If you need to change the packets the current listener is
* observing, you must first remove the packet listener before you can register it again.
*
* @param listener - new packet listener.
*/
void addPacketListener(PacketListener listener);
@@ -129,98 +132,109 @@ public interface ProtocolManager extends PacketStream {
* Removes a given packet listener.
* <p>
* Attempting to remove a listener that doesn't exist has no effect.
*
* @param listener - the packet listener to remove.
*/
void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
*
* @param plugin - the plugin to unload.
*/
void removePacketListeners(Plugin plugin);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param type - packet type.
*
* @param type - packet type.
* @return New encapsulated Minecraft packet.
*/
PacketContainer createPacket(PacketType type);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* If set to true, the <i>forceDefaults</i> option will force the system to automatically
* give non-primitive fields in the packet sensible default values. For instance, certain
* packets - like Packet60Explosion - require a List or Set to be non-null. If the
* forceDefaults option is true, the List or Set will be automatically created.
*
* @param type - packet type.
* If set to true, the <i>forceDefaults</i> option will force the system to automatically give non-primitive fields in
* the packet sensible default values. For instance, certain packets - like Packet60Explosion - require a List or Set
* to be non-null. If the forceDefaults option is true, the List or Set will be automatically created.
*
* @param type - packet type.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
PacketContainer createPacket(PacketType type, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param type - the packet type.
*
* @param type - the packet type.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
/**
* Completely resend an entity to a list of clients.
* <p>
* Note that this method is NOT thread safe. If you call this method from anything
* but the main thread, it will throw an exception.
* @param entity - entity to refresh.
* Note that this method is NOT thread safe. If you call this method from anything but the main thread, it will throw
* an exception.
*
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
void updateEntity(Entity entity, List<Player> observers);
/**
* Retrieve the associated entity.
*
* @param container - the world the entity belongs to.
* @param id - the unique ID of the entity.
* @param id - the unique ID of the entity.
* @return The associated entity.
* @throws FieldAccessException Reflection failed.
*/
Entity getEntityFromID(World container, int id) throws FieldAccessException;
Entity getEntityFromID(World container, int id);
/**
* Retrieve every client that is receiving information about a given entity.
*
* @param entity - the entity that is being tracked.
* @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed.
*/
List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
List<Player> getEntityTrackers(Entity entity);
/**
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
*
* @return Every filtered server packet.
*/
Set<PacketType> getSendingFilterTypes();
/**
* Retrieves a immutable set containing the type of the received client packets that will be observed by listeners.
*
* @return Every filtered client packet.
*/
Set<PacketType> getReceivingFilterTypes();
/**
* Retrieve the current Minecraft version.
*
* @return The current version.
*/
MinecraftVersion getMinecraftVersion();
/**
* Determines whether or not this protocol manager has been disabled.
*
* @return TRUE if it has, FALSE otherwise.
*/
boolean isClosed();
/**
* Retrieve the current asynchronous packet manager.
*
* @return Asynchronous packet manager.
*/
AsynchronousManager getAsynchronousManager();
@@ -17,22 +17,24 @@
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
/**
* Used to parse ranges in CommandPacket.
*
* @author Kristian
*/
class RangeParser {
final class RangeParser {
private RangeParser() {}
/**
* Parse a range from a given text.
* @param text - the text.
@@ -40,20 +42,18 @@ class RangeParser {
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
return getRanges(new ArrayDeque<>(Collections.singletonList(text)), legalRange);
}
/**
* Parse ranges from an array of elements.
* @param args - array of elements.
* @param offset - beginning offset.
* @param lastIndex - the last index of the array to read.
* @param input the input to parse the ranges from
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
List<String> tokens = tokenizeInput(input);
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
List<Range<Integer>> ranges = new ArrayList<>();
for (int i = 0; i < tokens.size(); i++) {
Range<Integer> range;
@@ -96,7 +96,7 @@ class RangeParser {
* @return A simplified list of ranges.
*/
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
List<Range<Integer>> result = new ArrayList<>();
boolean[] set = new boolean[maximum + 1];
int start = -1;
@@ -125,7 +125,7 @@ class RangeParser {
}
private static List<String> tokenizeInput(Deque<String> input) {
List<String> tokens = new ArrayList<String>();
List<String> tokens = new ArrayList<>();
// Tokenize the input
while (!input.isEmpty()) {
@@ -36,7 +36,6 @@ import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
@@ -87,7 +86,7 @@ public class AsyncFilterManager implements AsynchronousManager {
// Initialize timeout listeners
this.serverTimeoutListeners = new SortedPacketListenerList();
this.clientTimeoutListeners = new SortedPacketListenerList();
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<>());
this.timeoutListeners = ConcurrentHashMap.newKeySet();
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
@@ -121,16 +121,10 @@ public class AsyncListenerHandler {
}
private void startWarningTask() {
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable() {
@Override
public void run() {
ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
newBuilder(REPORT_HANDLER_NOT_STARTED).
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
build()
);
}
}, 2 * TICKS_PER_SECOND);
warningTask = filterManager.getScheduler().scheduleSyncDelayedTask(getPlugin(), () -> ProtocolLibrary.getErrorReporter().reportWarning(AsyncListenerHandler.this, Report.
newBuilder(REPORT_HANDLER_NOT_STARTED).
messageParam(listener.getPlugin(), AsyncListenerHandler.this).
build()), 2 * TICKS_PER_SECOND);
}
private void stopWarningTask() {
@@ -287,19 +281,16 @@ public class AsyncListenerHandler {
final AsyncRunnable listenerLoop = getListenerLoop();
stopWarningTask();
scheduleAsync(new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
String previousName = thread.getName();
String workerName = getFriendlyWorkerName(listenerLoop.getID());
scheduleAsync(() -> {
Thread thread = Thread.currentThread();
// Add the friendly worker name
thread.setName(workerName);
listenerLoop.run();
thread.setName(previousName);
}
String previousName = thread.getName();
String workerName = getFriendlyWorkerName(listenerLoop.getID());
// Add the friendly worker name
thread.setName(workerName);
listenerLoop.run();
thread.setName(previousName);
});
}
@@ -335,12 +326,7 @@ public class AsyncListenerHandler {
final AsyncRunnable listenerLoop = getListenerLoop();
final Function<AsyncRunnable, Void> delegateCopy = executor;
scheduleAsync(new Runnable() {
@Override
public void run() {
delegateCopy.apply(listenerLoop);
}
});
scheduleAsync(() -> delegateCopy.apply(listenerLoop));
}
private void scheduleAsync(Runnable runnable) {
@@ -423,31 +409,28 @@ public class AsyncListenerHandler {
if (syncTask < 0) {
stopWarningTask();
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), new Runnable() {
@Override
public void run() {
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
while (!cancelled) {
PacketEvent packet = queuedPackets.poll();
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
// Sorry, asynchronous threads!
queuedPackets.add(packet);
// Try again next tick
break;
} else if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onSyncPacket()");
} else {
// No more packets left - wait a tick
break;
}
// Check time here, ensuring that we at least process one packet
if (System.nanoTime() < stopTime)
break;
syncTask = filterManager.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> {
long stopTime = System.nanoTime() + unit.convert(time, TimeUnit.NANOSECONDS);
while (!cancelled) {
PacketEvent packet = queuedPackets.poll();
if (packet == INTERUPT_PACKET || packet == WAKEUP_PACKET) {
// Sorry, asynchronous threads!
queuedPackets.add(packet);
// Try again next tick
break;
} else if (packet != null && packet.getAsyncMarker() != null) {
processPacket(workerID, packet, "onSyncPacket()");
} else {
// No more packets left - wait a tick
break;
}
// Check time here, ensuring that we at least process one packet
if (System.nanoTime() < stopTime)
break;
}
}, tickDelay, tickDelay);
@@ -42,11 +42,11 @@ import com.google.common.primitives.Longs;
* Contains information about the packet that is being processed by asynchronous listeners.
* <p>
* Asynchronous listeners can use this to set packet timeout or transmission order.
*
*
* @author Kristian
*/
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Generated by Eclipse.
*/
@@ -56,52 +56,52 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* Default number of milliseconds until a packet will rejected.
*/
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
/**
* Default number of packets to skip.
*/
public static final int DEFAULT_SENDING_DELTA = 0;
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
/**
* Current list of async packet listeners.
*/
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
// Timeout handling
private long initialTime;
private long timeout;
// Packet order
private long originalSendingIndex;
private long newSendingIndex;
// Used to determine if a packet must be reordered in the sending queue
private Long queuedSendingIndex;
// Whether or not the packet has been processed by the listeners
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
// Whether or not to delay processing
private AtomicInteger processingDelay = new AtomicInteger();
// Used to synchronize processing on the shared PacketEvent
private Object processingLock = new Object();
// Used to identify the asynchronous worker
private transient AsyncListenerHandler listenerHandler;
private transient int workerID;
// Determine if Minecraft processes this packet asynchronously
private volatile static Method isMinecraftAsync;
private volatile static boolean alwaysSync;
@@ -113,18 +113,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
// Timeout
this.initialTime = initialTime;
this.timeout = initialTime + timeoutDelta;
// Sending index
this.originalSendingIndex = sendingIndex;
this.newSendingIndex = sendingIndex;
}
/**
* Retrieve the time the packet was initially queued for asynchronous processing.
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
@@ -140,7 +140,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public long getTimeout() {
return timeout;
}
/**
* Set the time the packet will be forcefully rejected.
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
@@ -219,13 +219,13 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* <p>
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
* using the {@link #getProcessingLock()} method.
*
*
* @return The new processing delay.
*/
public int incrementProcessingDelay() {
return processingDelay.incrementAndGet();
}
/**
* Decrement the number of times this packet must be signalled as done before it's transmitted.
* @return The new processing delay. If zero, the packet should be sent.
@@ -233,7 +233,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
int decrementProcessingDelay() {
return processingDelay.decrementAndGet();
}
/**
* Retrieve the number of times a packet must be signalled to be done before it's sent.
* @return Number of processing delays.
@@ -241,7 +241,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public int getProcessingDelay() {
return processingDelay.get();
}
/**
* Whether or not this packet is or has been queued for processing.
* @return TRUE if it has, FALSE otherwise.
@@ -296,7 +296,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public boolean hasExpired() {
return hasExpired(System.currentTimeMillis());
}
/**
* Determine if this packet has expired given this time.
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
@@ -305,7 +305,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
public boolean hasExpired(long currentTime) {
return timeout < currentTime;
}
/**
* Determine if the asynchronous handling should be cancelled.
* @return TRUE if it should, FALSE otherwise.
@@ -319,7 +319,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* <p>
* This is only relevant during the synchronous processing. Asynchronous
* listeners should use the normal cancel-field to cancel a PacketEvent.
*
*
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
*/
public void setAsyncCancelled(boolean asyncCancelled) {
@@ -369,7 +369,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal;
}
/**
* Set the iterator for the next listener.
* @param listenerTraversal - the new async packet listener iterator.
@@ -377,28 +377,22 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
this.listenerTraversal = listenerTraversal;
}
/**
* Transmit a given packet to the current packet stream.
* @param event - the packet to send.
* @throws IOException If the packet couldn't be sent.
*/
void sendPacket(PacketEvent event) throws IOException {
try {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
}
transmitted = true;
} catch (InvocationTargetException e) {
throw new IOException("Cannot send packet", e);
} catch (IllegalAccessException e) {
throw new IOException("Cannot send packet", e);
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
false);
}
transmitted = true;
}
/**
* Determine if Minecraft allows asynchronous processing of this packet.
* @param event - packet event
@@ -413,7 +407,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
// This will occur in 1.2.5 (or possibly in later versions)
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodListByParameters(boolean.class, new Class[] {});
// Try to look for boolean methods
if (methods.size() == 2) {
isMinecraftAsync = methods.get(1);
@@ -460,7 +454,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
}
}
}
@Override
public int compareTo(AsyncMarker o) {
if (o == null)
@@ -468,7 +462,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
}
@Override
public boolean equals(Object other) {
// Standard equals
@@ -479,7 +473,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
else
return false;
}
@Override
public int hashCode() {
return Longs.hashCode(getNewSendingIndex());
@@ -17,13 +17,9 @@
package com.comphenix.protocol.async;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import org.bukkit.plugin.Plugin;
/**
* Represents a NO OPERATION listener.
@@ -2,16 +2,16 @@
* 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
* 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.
* 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
* 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
*/
@@ -24,81 +24,79 @@ import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.reflect.FieldAccessException;
import org.bukkit.entity.Player;
/**
* Represents packets ready to be transmitted to a client.
*
* @author Kristian
*/
abstract class PacketSendingQueue {
public static final ReportType REPORT_DROPPED_PACKET = new ReportType("Warning: Dropped packet index %s of type %s.");
public static final int INITIAL_CAPACITY = 10;
private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending
private Executor asynchronousSender;
// Whether or not packet transmission must occur on a specific thread
private final boolean notThreadSafe;
private final PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Asynchronous packet sending
private final Executor asynchronousSender;
// Whether or not we've run the cleanup procedure
private boolean cleanedUp = false;
/**
* Create a packet sending queue.
*
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
*/
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.sendingQueue = new PriorityBlockingQueue<>(INITIAL_CAPACITY);
this.notThreadSafe = notThreadSafe;
this.asynchronousSender = asynchronousSender;
}
/**
* Number of packet events in the queue.
*
* @return The number of packet events in the queue.
*/
public int size() {
return sendingQueue.size();
}
/**
* Enqueue a packet for sending.
* Enqueue a packet for sending.
*
* @param packet - packet to queue.
*/
public void enqueue(PacketEvent packet) {
sendingQueue.add(new PacketEventHolder(packet));
}
/**
* Invoked when one of the packets have finished processing.
*
* @param packetUpdated - the packet that has now been updated.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
AsyncMarker marker = packetUpdated.getAsyncMarker();
// Should we reorder the event?
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
// "Cancel" the original event
packetUpdated.setReadOnly(false);
packetUpdated.setCancelled(true);
// Enqueue the copy with the new sending index
enqueue(copy);
}
// Mark this packet as finished
marker.setProcessed(true);
trySendPackets(onMainThread);
@@ -107,83 +105,85 @@ abstract class PacketSendingQueue {
/***
* Invoked when a list of packet IDs are no longer associated with any listeners.
* @param packetsRemoved - packets that no longer have any listeners.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether or not this is occurring on the main thread.
*/
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
Set<PacketType> lookup = new HashSet<PacketType>(packetsRemoved);
Set<PacketType> lookup = new HashSet<>(packetsRemoved);
// Note that this is O(n), so it might be expensive
for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketType())) {
event.getAsyncMarker().setProcessed(true);
}
}
// This is likely to have changed the situation a bit
trySendPackets(onMainThread);
}
/**
* Attempt to send any remaining packets.
*
* @param onMainThread - whether or not this is occuring on the main thread.
*/
public void trySendPackets(boolean onMainThread) {
// Whether or not to continue sending packets
boolean sending = true;
// Transmit as many packets as we can
while (sending) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sending = processPacketHolder(onMainThread, holder);
if (!sending) {
// Add it back again
sendingQueue.add(holder);
}
} else {
// No more packets to send
sending = false;
}
}
}
/**
* Invoked when a packet might be ready for transmission.
*
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
* @param holder - packet container.
* @param holder - packet container.
* @return TRUE to continue sending packets, FALSE otherwise.
*/
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker();
boolean hasExpired = marker.hasExpired();
// Guard in cause the queue is closed
if (cleanedUp) {
return true;
}
// End condition?
if (marker.isProcessed() || hasExpired) {
if (hasExpired) {
// Notify timeout listeners
onPacketTimeout(current);
// Recompute
marker = current.getAsyncMarker();
hasExpired = marker.hasExpired();
// Could happen due to the timeout listeners
if (!marker.isProcessed() && !hasExpired) {
return false;
}
}
// Is it okay to send the packet?
if (!current.isCancelled() && !hasExpired) {
// Make sure we're on the main thread
@@ -191,65 +191,61 @@ abstract class PacketSendingQueue {
try {
boolean wantAsync = marker.isMinecraftAsync(current);
boolean wantSync = !wantAsync;
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
if (!onMainThread && wantSync) {
return false;
}
// Let's give it what it wants
if (onMainThread && wantAsync) {
asynchronousSender.execute(new Runnable() {
@Override
public void run() {
// We know this isn't on the main thread
processPacketHolder(false, holder);
}
});
// We know this isn't on the main thread
asynchronousSender.execute(() -> processPacketHolder(false, holder));
// Scheduler will do the rest
return true;
}
} catch (FieldAccessException e) {
e.printStackTrace();
// Just drop the packet
return true;
}
}
}
// Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
}
}
// Drop the packet
return true;
}
// Add it back and stop sending
return false;
}
/**
* Invoked when a packet has timed out.
*
* @param event - the timed out packet.
*/
protected abstract void onPacketTimeout(PacketEvent event);
private boolean isOnline(Player player) {
return player != null && player.isOnline();
}
/**
* Send every packet, regardless of the processing state.
*/
private void forceSend() {
while (true) {
PacketEventHolder holder = sendingQueue.poll();
if (holder != null) {
sendPacket(holder.getEvent());
} else {
@@ -257,9 +253,10 @@ abstract class PacketSendingQueue {
}
}
}
/**
* Whether or not the packet transmission must synchronize with the main thread.
*
* @return TRUE if it must, FALSE otherwise.
*/
public boolean isSynchronizeMain() {
@@ -268,24 +265,16 @@ abstract class PacketSendingQueue {
/**
* Transmit a packet, if it hasn't already.
*
* @param event - the packet to transmit.
*/
private void sendPacket(PacketEvent event) {
AsyncMarker marker = event.getAsyncMarker();
try {
// Don't send a packet twice
AsyncMarker marker = event.getAsyncMarker();
if (marker != null && !marker.isTransmitted()) {
marker.sendPacket(event);
}
} catch (PlayerLoggedOutException e) {
ProtocolLibrary.getErrorReporter().reportDebug(this, Report.newBuilder(REPORT_DROPPED_PACKET).
messageParam(marker.getOriginalSendingIndex(), event.getPacketType()).
callerParam(event)
);
} catch (IOException e) {
// Just print the error
e.printStackTrace();
@@ -299,7 +288,7 @@ abstract class PacketSendingQueue {
if (!cleanedUp) {
// Note that the cleanup itself will always occur on the main thread
forceSend();
// And we're done
cleanedUp = true;
}
@@ -24,8 +24,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
import com.comphenix.protocol.error.ErrorReporter;
@@ -33,18 +31,20 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.bukkit.entity.Player;
/**
* Contains every sending queue for every player.
*
* @author Kristian
*/
class PlayerSendingHandler {
private ErrorReporter reporter;
private ConcurrentMap<Player, QueueContainer> playerSendingQueues;
private final ErrorReporter reporter;
private final ConcurrentMap<Player, QueueContainer> playerSendingQueues;
// Timeout listeners
private SortedPacketListenerList serverTimeoutListeners;
private SortedPacketListenerList clientTimeoutListeners;
private final SortedPacketListenerList serverTimeoutListeners;
private final SortedPacketListenerList clientTimeoutListeners;
// Asynchronous packet sending
private Executor asynchronousSender;
@@ -58,8 +58,8 @@ class PlayerSendingHandler {
* @author Kristian
*/
private class QueueContainer {
private PacketSendingQueue serverQueue;
private PacketSendingQueue clientQueue;
private final PacketSendingQueue serverQueue;
private final PacketSendingQueue clientQueue;
public QueueContainer() {
// Server packets can be sent concurrently
@@ -174,7 +174,7 @@ class PlayerSendingHandler {
/**
* Immediately send every server packet with the given list of IDs.
* @param types - types of every packet to send immediately.
* @param synchronusOK - whether or not we're running on the main thread.
* @param synchronusOK - whether we're running on the main thread.
*/
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
if (!cleaningUp) {
@@ -186,8 +186,7 @@ class PlayerSendingHandler {
/**
* Immediately send every client packet with the given list of IDs.
* @param ids - ID of every packet to send immediately.
* @param synchronusOK - whether or not we're running on the main thread.
* @param synchronusOK - whether we're running on the main thread.
*/
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
if (!cleaningUp) {
@@ -199,7 +198,7 @@ class PlayerSendingHandler {
/**
* Send any outstanding server packets.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether this is occurring on the main thread.
*/
public void trySendServerPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
@@ -209,7 +208,7 @@ class PlayerSendingHandler {
/**
* Send any outstanding server packets.
* @param onMainThread - whether or not this is occuring on the main thread.
* @param onMainThread - whether this is occurring on the main thread.
*/
public void trySendClientPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
@@ -222,7 +221,7 @@ class PlayerSendingHandler {
* @return Every sever packet queue.
*/
public List<PacketSendingQueue> getServerQueues() {
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
List<PacketSendingQueue> result = new ArrayList<>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getServerQueue());
@@ -234,7 +233,7 @@ class PlayerSendingHandler {
* @return Every client packet queue.
*/
public List<PacketSendingQueue> getClientQueues() {
List<PacketSendingQueue> result = new ArrayList<PacketSendingQueue>();
List<PacketSendingQueue> result = new ArrayList<>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getClientQueue());
@@ -17,13 +17,12 @@
package com.comphenix.protocol.async;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
@@ -8,7 +8,6 @@ import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Ticker;
@@ -49,19 +48,14 @@ public class ExpireHashMap<K, V> {
}
}
private Map<K, ExpireEntry> keyLookup = new HashMap<K, ExpireEntry>();
private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();
private final Map<K, ExpireEntry> keyLookup = new HashMap<>();
private final PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<>();
// View of keyLookup with direct values
private Map<K, V> valueView = Maps.transformValues(keyLookup, new Function<ExpireEntry, V>() {
@Override
public V apply(ExpireEntry entry) {
return entry.expireValue;
}
});
private final Map<K, V> valueView = Maps.transformValues(keyLookup, entry -> entry.expireValue);
// Supplied by the constructor
private Ticker ticker;
private final Ticker ticker;
/**
* Construct a new hash map where each entry may expire at a given time.
@@ -1,10 +1,10 @@
package com.comphenix.protocol.collections;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Represents a very quick integer-based lookup map, with a fixed key space size.
@@ -16,15 +16,6 @@ public class IntegerMap<T> {
private T[] array;
private int size;
/**
* Construct a new integer map.
* @param <T> Parameter type
* @return A new integer map.
*/
public static <T> IntegerMap<T> newMap() {
return new IntegerMap<T>();
}
/**
* Construct a new integer map with a default capacity.
*/
@@ -128,7 +119,7 @@ public class IntegerMap<T> {
* @return The Integer map.
*/
public Map<Integer, Object> toMap() {
Map<Integer, Object> map = Maps.newHashMap();
final Map<Integer, Object> map = new HashMap<>();
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
@@ -2,21 +2,25 @@
* 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
* 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.
* 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
* 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.concurrency;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -24,81 +28,78 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.Iterables;
/**
* A thread-safe implementation of a listener multimap.
*
*
* @author Kristian
*/
public abstract class AbstractConcurrentListenerMultimap<TListener> {
public abstract class AbstractConcurrentListenerMultimap<T> {
// The core of our map
private ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> mapListeners;
private final ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<T>>> mapListeners;
public AbstractConcurrentListenerMultimap() {
mapListeners = new ConcurrentHashMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
this.mapListeners = new ConcurrentHashMap<>();
}
/**
* Adds a listener to its requested list of packet receivers.
* @param listener - listener with a list of packets to receive notifications for.
*
* @param listener - listener with a list of packets to receive notifications for.
* @param whitelist - the packet whitelist to use.
*/
public void addListener(TListener listener, ListeningWhitelist whitelist) {
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
public void addListener(T listener, ListeningWhitelist whitelist) {
PrioritizedListener<T> prioritized = new PrioritizedListener<>(listener, whitelist.getPriority());
for (PacketType type : whitelist.getTypes()) {
addListener(type, prioritized);
this.addListener(type, prioritized);
}
}
// Add the listener to a specific packet notifcation list
private void addListener(PacketType type, PrioritizedListener<TListener> listener) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
private void addListener(PacketType type, PrioritizedListener<T> listener) {
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
// We don't want to create this for every lookup
if (list == null) {
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
// which is a essential feature for our purposes.
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
final SortedCopyOnWriteArray<PrioritizedListener<T>> value = new SortedCopyOnWriteArray<PrioritizedListener<T>>();
// We may end up creating multiple multisets, but we'll agree on which to use
list = mapListeners.putIfAbsent(type, value);
list = this.mapListeners.putIfAbsent(type, value);
if (list == null) {
list = value;
}
}
// Thread safe
list.add(listener);
}
/**
* Removes the given listener from the packet event list.
* @param listener - listener to remove.
*
* @param listener - listener to remove.
* @param whitelist - the packet whitelist that was used.
* @return Every packet ID that was removed due to no listeners.
*/
public List<PacketType> removeListener(TListener listener, ListeningWhitelist whitelist) {
public List<PacketType> removeListener(T listener, ListeningWhitelist whitelist) {
List<PacketType> removedPackets = new ArrayList<PacketType>();
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
for (PacketType type : whitelist.getTypes()) {
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = mapListeners.get(type);
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
// Remove any listeners
if (list != null) {
// Don't remove from newly created lists
if (list.size() > 0) {
// Remove this listener. Note that priority is generally ignored.
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
list.remove(new PrioritizedListener<T>(listener, whitelist.getPriority()));
if (list.size() == 0) {
mapListeners.remove(type);
this.mapListeners.remove(type);
removedPackets.add(type);
}
}
@@ -107,38 +108,41 @@ public abstract class AbstractConcurrentListenerMultimap<TListener> {
}
return removedPackets;
}
/**
* Retrieve the registered listeners, in order from the lowest to the highest priority.
* <p>
* The returned list is thread-safe and doesn't require synchronization.
*
* @param type - packet type.
* @return Registered listeners.
*/
public Collection<PrioritizedListener<TListener>> getListener(PacketType type) {
return mapListeners.get(type);
public Collection<PrioritizedListener<T>> getListener(PacketType type) {
return this.mapListeners.get(type);
}
/**
* Retrieve every listener.
*
* @return Every listener.
*/
public Iterable<PrioritizedListener<TListener>> values() {
return Iterables.concat(mapListeners.values());
public Iterable<PrioritizedListener<T>> values() {
return Iterables.concat(this.mapListeners.values());
}
/**
* Retrieve every registered packet type:
*
* @return Registered packet type.
*/
public Set<PacketType> keySet() {
return mapListeners.keySet();
return this.mapListeners.keySet();
}
/**
* Remove all packet listeners.
*/
protected void clearListeners() {
mapListeners.clear();
this.mapListeners.clear();
}
}
@@ -7,9 +7,9 @@ import com.google.common.base.Preconditions;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.AbstractMap;
@@ -73,7 +73,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingAddress() {
return new ConcurrentPlayerMap<T>(PlayerKey.ADDRESS);
return new ConcurrentPlayerMap<>(PlayerKey.ADDRESS);
}
/**
@@ -82,7 +82,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return Concurrent player map.
*/
public static <T> ConcurrentPlayerMap<T> usingName() {
return new ConcurrentPlayerMap<T>(PlayerKey.NAME);
return new ConcurrentPlayerMap<>(PlayerKey.NAME);
}
/**
@@ -108,7 +108,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return The value map.
*/
private ConcurrentMap<Object, TValue> createValueMap() {
return Maps.newConcurrentMap();
return new ConcurrentHashMap<>();
}
/**
@@ -162,7 +162,7 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
* @return The player with the given key, or NULL if not found.
*/
private Player findOnlinePlayer(Object key) {
for (Player player : Util.getOnlinePlayers()) {
for (Player player : Bukkit.getOnlinePlayers()) {
if (key.equals(keyMethod.apply(player))) {
return player;
}
@@ -1,116 +0,0 @@
/*
* 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.concurrency;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a very quick integer set that uses a lookup table to store membership.
* <p>
* This class is intentionally missing a size method.
* @author Kristian
*/
public class IntegerSet {
private final boolean[] array;
/**
* Initialize a lookup table with the given maximum number of elements.
* <p>
* This creates a set for elements in the range [0, count).
* <p>
* Formally, the current set must be a subset of [0, 1, 2, ..., count - 1].
* @param maximumCount - maximum element value and count.
*/
public IntegerSet(int maximumCount) {
this.array = new boolean[maximumCount];
}
/**
* Initialize a lookup table with a given maximum and value list.
* <p>
* The provided elements must be in the range [0, count).
* @param maximumCount - the maximum element value and count.
* @param values - the elements to add to the set.
*/
public IntegerSet(int maximumCount, Collection<Integer> values) {
this.array = new boolean[maximumCount];
addAll(values);
}
/**
* Determine whether or not the given element exists in the set.
* @param element - the element to check. Must be in the range [0, count).
* @return TRUE if the given element exists, FALSE otherwise.
*/
public boolean contains(int element) {
return array[element];
}
/**
* Add the given element to the set, or do nothing if it already exists.
* @param element - element to add.
* @throws ArrayIndexOutOfBoundsException If the given element is not in the range [0, count).
*/
public void add(int element) {
array[element] = true;
}
/**
* Add the given collection of elements to the set.
* @param packets - elements to add.
*/
public void addAll(Collection<Integer> packets) {
for (Integer id : packets) {
add(id);
}
}
/**
* Remove the given element from the set, or do nothing if it's already removed.
* @param element - element to remove.
*/
public void remove(int element) {
// We don't actually care if the caller tries to remove an element outside the valid set
if (element >= 0 && element < array.length)
array[element] = false;
}
/**
* Remove every element from the set.
*/
public void clear() {
Arrays.fill(array, false);
}
/**
* Convert the current IntegerSet to an equivalent HashSet.
* @return The resulting HashSet.
*/
public Set<Integer> toSet() {
Set<Integer> elements = new HashSet<Integer>();
for (int i = 0; i < array.length; i++) {
if (array[i])
elements.add(i);
}
return elements;
}
}
@@ -1,125 +1,133 @@
package com.comphenix.protocol.concurrency;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a concurrent set of packet types.
*
* @author Kristian
*/
public class PacketTypeSet {
private Set<PacketType> types = Collections.newSetFromMap(Maps.<PacketType, Boolean>newConcurrentMap());
private Set<Class<?>> classes = Collections.newSetFromMap(Maps.<Class<?>, Boolean>newConcurrentMap());
private final Set<PacketType> types;
private final Set<Class<?>> classes;
public PacketTypeSet() {
// Do nothing
this.types = new HashSet<>(16, 0.9f);
this.classes = new HashSet<>(16, 0.9f);
}
public PacketTypeSet(Collection<? extends PacketType> values) {
for (PacketType type : values) {
addType(type);
}
this.types = new HashSet<>(values.size(), 0.9f);
this.classes = new HashSet<>(values.size(), 0.9f);
this.addAll(values);
}
/**
* Add a particular type to the set.
*
* @param type - the type to add.
*/
public synchronized void addType(PacketType type) {
public void addType(PacketType type) {
this.types.add(type);
Class<?> packetClass = type.getPacketClass();
types.add(Preconditions.checkNotNull(type, "type cannot be NULL."));
if (packetClass != null) {
classes.add(type.getPacketClass());
this.classes.add(packetClass);
}
}
/**
* Add the given types to the set of packet types.
*
* @param types - the types to add.
*/
public synchronized void addAll(Iterable<? extends PacketType> types) {
public void addAll(Iterable<? extends PacketType> types) {
for (PacketType type : types) {
addType(type);
this.addType(type);
}
}
/**
* Remove a particular type to the set.
*
* @param type - the type to remove.
*/
public synchronized void removeType(PacketType type) {
public void removeType(PacketType type) {
this.types.remove(type);
Class<?> packetClass = type.getPacketClass();
types.remove(Preconditions.checkNotNull(type, "type cannot be NULL."));
if (packetClass != null) {
classes.remove(packetClass);
this.classes.remove(packetClass);
}
}
/**
* Remove the given types from the set.
*
* @param types Types to remove
*/
public synchronized void removeAll(Iterable<? extends PacketType> types) {
public void removeAll(Iterable<? extends PacketType> types) {
for (PacketType type : types) {
removeType(type);
this.removeType(type);
}
}
/**
* Determine if the given packet type exists in the set.
*
* @param type - the type to find.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean contains(PacketType type) {
return types.contains(type);
return this.types.contains(type);
}
/**
* Determine if a packet type with the given packet class exists in the set.
*
* @param packetClass - the class to find.
* @return TRUE if it does, FALSE otherwise.
*/
public boolean contains(Class<?> packetClass) {
return classes.contains(packetClass);
return this.classes.contains(packetClass);
}
/**
* Determine if the type of a packet is in the current set.
*
* @param packet - the packet.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean containsPacket(Object packet) {
if (packet == null)
return false;
return classes.contains(packet.getClass());
return packet != null && this.classes.contains(packet.getClass());
}
/**
* Retrieve a view of this packet type set.
*
* @return The packet type values.
*/
public Set<PacketType> values() {
return types;
return ImmutableSet.copyOf(this.types);
}
/**
* Retrieve the number of entries in the set.
*
* @return The number of entries.
*/
public int size() {
return types.size();
return this.types.size();
}
public synchronized void clear() {
types.clear();
classes.clear();
public void clear() {
this.types.clear();
this.classes.clear();
}
}
@@ -2,243 +2,236 @@
* 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
* 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.
* 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
* 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.concurrency;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
/**
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
*
* @param <T> - type of the elements in the list.
*/
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
// Prevent reordering
private volatile List<T> list;
/**
* Construct an empty sorted array.
*/
public SortedCopyOnWriteArray() {
list = new ArrayList<T>();
this.list = new ArrayList<>();
}
/**
* Create a sorted array from the given list. The elements will be automatically sorted.
*
* @param wrapped - the collection whose elements are to be placed into the list.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped) {
this.list = new ArrayList<T>(wrapped);
this.list = new ArrayList<>(wrapped);
Collections.sort(this.list);
}
/**
* Create a sorted array from the given list.
* @param wrapped - the collection whose elements are to be placed into the list.
* @param sort - TRUE to automatically sort the collection, FALSE if it is already sorted.
*/
public SortedCopyOnWriteArray(Collection<T> wrapped, boolean sort) {
this.list = new ArrayList<T>(wrapped);
if (sort) {
Collections.sort(list);
}
}
/**
* Inserts the given element in the proper location.
*
* @param value - element to insert.
*/
@Override
public synchronized boolean add(T value) {
// We use NULL as a special marker, so we don't allow it
if (value == null)
throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<T>();
// We use NULL as a special marker, so we don't allow it
if (value == null) throw new IllegalArgumentException("value cannot be NULL");
List<T> copy = new ArrayList<>();
boolean inserted = false;
for (T element : this.list) {
// If the value is now greater than the current element, it should be placed right before it
if (!inserted && value.compareTo(element) < 0) {
copy.add(value);
inserted = true;
}
copy.add(element);
}
// Don't forget to add it
if (!inserted) {
copy.add(value);
}
this.list = copy;
return true;
}
for (T element : list) {
// If the value is now greater than the current element, it should be placed right before it
if (value != null && value.compareTo(element) < 0) {
copy.add(value);
value = null;
}
copy.add(element);
}
// Don't forget to add it
if (value != null)
copy.add(value);
list = copy;
return true;
}
@Override
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
public synchronized boolean addAll(Collection<? extends T> values) {
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0) {
return false;
List<T> copy = new ArrayList<T>();
// Insert the new content and sort it
copy.addAll(list);
copy.addAll(values);
Collections.sort(copy);
list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<T>();
boolean result = false;
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : list) {
if (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
}
List<T> copy = new ArrayList<>(this.list);
// Insert the new content and sort it
copy.addAll(values);
Collections.sort(copy);
this.list = copy;
return true;
}
/**
* Removes from the list by making a new list with every element except the one given.
* <p>
* Objects will be compared using the given objects equals() method.
*
* @param value - value to remove.
*/
@Override
public synchronized boolean remove(Object value) {
List<T> copy = new ArrayList<>();
boolean result = false;
// Note that there's not much to be gained from using BinarySearch, as we
// have to copy (and thus read) the entire list regardless.
// Copy every element except the one given to us.
for (T element : this.list) {
if (!Objects.equal(value, element)) {
copy.add(element);
} else {
result = true;
}
}
this.list = copy;
return result;
}
list = copy;
return result;
}
@Override
public boolean removeAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0) {
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
}
List<T> copy = new ArrayList<>(this.list);
copy.removeAll(values);
list = copy;
this.list = copy;
return true;
}
@Override
public boolean retainAll(Collection<?> values) {
// Special cases
if (values == null)
throw new IllegalArgumentException("values cannot be NULL");
if (values.size() == 0)
return false;
List<T> copy = new ArrayList<T>();
copy.addAll(list);
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
if (values.isEmpty()) return false;
List<T> copy = new ArrayList<>(this.list);
copy.removeAll(values);
list = copy;
this.list = copy;
return true;
}
/**
* Removes from the list by making a copy of every element except the one with the given index.
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<T>(list);
copy.remove(index);
list = copy;
}
/**
* Retrieves an element by index.
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return list.get(index);
}
/**
* Retrieve the size of the list.
* @return Size of the list.
*/
public int size() {
return list.size();
}
/**
* Retrieves an iterator over the elements in the given list.
* Warning: No not attempt to remove elements using the iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(list).iterator();
/**
* Removes from the list by making a copy of every element except the one with the given index.
*
* @param index - index of the element to remove.
*/
public synchronized void remove(int index) {
List<T> copy = new ArrayList<>(this.list);
copy.remove(index);
this.list = copy;
}
/**
* Retrieves an element by index.
*
* @param index - index of element to retrieve.
* @return The element at the given location.
*/
public T get(int index) {
return this.list.get(index);
}
/**
* Retrieve the size of the list.
*
* @return Size of the list.
*/
public int size() {
return this.list.size();
}
/**
* Retrieves an iterator over the elements in the given list. Warning: No not attempt to remove elements using the
* iterator.
*/
public Iterator<T> iterator() {
return Iterables.unmodifiableIterable(this.list).iterator();
}
@Override
public void clear() {
list = new ArrayList<T>();
this.list = new ArrayList<>();
}
@Override
public boolean contains(Object value) {
return list.contains(value);
return this.list.contains(value);
}
@Override
public boolean containsAll(Collection<?> values) {
return list.containsAll(values);
return this.list.containsAll(values);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
return this.list.isEmpty();
}
@Override
public Object[] toArray() {
return list.toArray();
return this.list.toArray();
}
@SuppressWarnings("hiding")
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
return this.list.toArray(a);
}
@Override
public String toString() {
return list.toString();
return this.list.toString();
}
}
@@ -2,11 +2,11 @@ package com.comphenix.protocol.error;
import java.io.PrintStream;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.reflect.PrettyPrinter;
import org.bukkit.plugin.Plugin;
/**
* Represents a basic error reporter that prints error reports to the standard error stream.
* <p>
@@ -1,9 +1,9 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import org.bukkit.plugin.Plugin;
/**
* Construct an error reporter that delegates to another error reporter.
* @author Kristian
@@ -30,11 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.collections.ExpireHashMap;
import com.comphenix.protocol.error.Report.ReportBuilder;
@@ -43,6 +39,11 @@ import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
/**
* Internal class used to handle exceptions.
*
@@ -89,7 +90,7 @@ public class DetailedErrorReporter implements ErrorReporter {
// Reports to ignore
private ExpireHashMap<Report, Boolean> rateLimited = new ExpireHashMap<Report, Boolean>();
private Object rateLock = new Object();
private final Object rateLock = new Object();
/**
* Create a default error reporting system.
@@ -202,14 +203,14 @@ public class DetailedErrorReporter implements ErrorReporter {
// See if we should print the full error
if (errorCount < getMaxErrorCount()) {
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " +
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occurred in " +
methodName + " for " + pluginName, error);
return true;
} else {
// Nope - only print the error count occationally
// Nope - only print the error count occasionally
if (isPowerOfTwo(errorCount)) {
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " +
logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occurred in " +
methodName + " for " + pluginName, error);
}
return false;
@@ -390,9 +391,9 @@ public class DetailedErrorReporter implements ErrorReporter {
writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX));
// Inform of this occurrence
if (ERROR_PERMISSION != null) {
if (ERROR_PERMISSION != null && ProtocolLibrary.getConfig().isChatWarnings()) {
Bukkit.getServer().broadcast(
String.format("Error %s (%s) occured in %s.", report.getReportMessage(), report.getException(), sender),
String.format("Error %s (%s) occurred in %s.", report.getReportMessage(), report.getException(), sender),
ERROR_PERMISSION
);
}
@@ -17,10 +17,10 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import org.bukkit.plugin.Plugin;
/**
* Represents an object that can forward an error {@link Report} to the display and permanent storage.
*
@@ -4,11 +4,11 @@ import java.io.File;
import java.net.URLDecoder;
import java.security.CodeSource;
import com.google.common.base.Preconditions;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.google.common.base.Preconditions;
public final class PluginContext {
// Determine plugin folder
private static File pluginFolder;
@@ -1,10 +1,9 @@
package com.comphenix.protocol.error;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
/**
* Represents a error or warning report.
*
@@ -1,10 +1,10 @@
package com.comphenix.protocol.error;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.google.common.base.Joiner;
import org.bukkit.plugin.Plugin;
/**
* Represents an error reporter that rethrows every exception instead.
* @author Kristian
@@ -1,5 +1,10 @@
package com.comphenix.protocol.events;
import javax.annotation.Nonnull;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
@@ -7,10 +12,12 @@ import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.Sound;
@@ -23,10 +30,6 @@ import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import javax.annotation.Nonnull;
import java.lang.reflect.Array;
import java.util.*;
public abstract class AbstractStructure {
protected transient Object handle;
protected transient StructureModifier<Object> structureModifier;
@@ -294,17 +297,6 @@ public abstract class AbstractStructure {
BukkitConverters.getEntityTypeConverter());
}
/**
* Retrieves a read/write structure for chunk positions.
* @return A modifier for a ChunkPosition.
*/
public StructureModifier<ChunkPosition> getPositionModifier() {
// Convert to and from the Bukkit wrapper
return structureModifier.withType(
MinecraftReflection.getChunkPositionClass(),
ChunkPosition.getConverter());
}
/**
* Retrieves a read/write structure for block positions.
* @return A modifier for a BlockPosition.
@@ -376,21 +368,6 @@ public abstract class AbstractStructure {
);
}
/**
* Retrieves a read/write structure for collections of chunk positions.
* <p>
* This modifier will automatically marshal between the visible ProtocolLib ChunkPosition and the
* internal Minecraft ChunkPosition.
*
* @return A modifier for ChunkPosition list fields.
*/
public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(ChunkPosition.getConverter()));
}
/**
* Retrieves a read/write structure for collections of chunk positions.
* <p>
@@ -420,6 +397,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>
@@ -648,6 +636,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.
@@ -924,6 +926,86 @@ public abstract class AbstractStructure {
);
}
/**
* Retrieve a read/write structure for Instants in (mostly for use in 1.19+)
* @return The Structure Modifier
*/
public StructureModifier<Instant> getInstants() {
return structureModifier.withType(Instant.class);
}
/**
* Retrieve a read/write structure for profile public keys in 1.9
* @return The Structure Modifier
*/
public StructureModifier<WrappedProfilePublicKey> getProfilePublicKeys() {
return structureModifier.withType(
MinecraftReflection.getProfilePublicKeyClass(),
BukkitConverters.getWrappedProfilePublicKeyConverter());
}
/**
* Retrieve a read/write structure for profile public key data in 1.9
* @return The Structure Modifier
*/
public StructureModifier<WrappedProfileKeyData> getProfilePublicKeyData() {
return structureModifier.withType(
MinecraftReflection.getProfilePublicKeyDataClass(),
BukkitConverters.getWrappedPublicKeyDataConverter());
}
/**
* Retrieve a read/write structure for LevelChunkPacketData in 1.18+
*
* @return The Structure Modifier
*/
public StructureModifier<WrappedLevelChunkData.ChunkData> getLevelChunkData() {
return structureModifier.withType(MinecraftReflection.getLevelChunkPacketDataClass(), BukkitConverters.getWrappedChunkDataConverter());
}
/**
* Retrieve a read/write structure for LightUpdatePacketData in 1.18+
*
* @return The Structure Modifier
*/
public StructureModifier<WrappedLevelChunkData.LightData> getLightUpdateData() {
return structureModifier.withType(MinecraftReflection.getLightUpdatePacketDataClass(), BukkitConverters.getWrappedLightDataConverter());
}
/**
* @return read/write structure for login encryption packets
*/
public StructureModifier<Either<byte[], WrappedSaltedSignature>> getLoginSignatures() {
return getEithers(Converters.passthrough(byte[].class), BukkitConverters.getWrappedSignatureConverter());
}
/**
* @return read/writer structure direct access to signature data like chat messages
*/
public StructureModifier<WrappedSaltedSignature> getSignatures() {
return structureModifier.withType(
MinecraftReflection.getSaltedSignatureClass(),
BukkitConverters.getWrappedSignatureConverter()
);
}
/**
* @param leftConverter converter for left values
* @param rightConverter converter for right values
* @return ProtocolLib's read/write structure for Mojang either structures
* @param <L> left data type after converting from NMS
* @param <R> right data type after converting from NMS
*/
public <L, R> StructureModifier<Either<L, R>> getEithers(EquivalentConverter<L> leftConverter,
EquivalentConverter<R> rightConverter) {
return structureModifier.withType(
com.mojang.datafixers.util.Either.class,
BukkitConverters.getEitherConverter(
leftConverter, rightConverter
)
);
}
/**
* Retrieve a read/write structure for the Map class.
* @param keyConverter Converter for map keys
@@ -1000,6 +1082,19 @@ public abstract class AbstractStructure {
structureModifier.getField(index).getType());
}
/**
* Retrieve a read/write structure for an optional, passing
* the value of the optional field through the given converter
* if present.
*
* @param converter Converter for internal element of optional, if present
* @param <T> The inner type of the optional
* @return The modifier
*/
public <T> StructureModifier<Optional<T>> getOptionals(EquivalentConverter<T> converter) {
return structureModifier.withType(Optional.class, Converters.optional(converter));
}
/**
* Represents an equivalent converter for ItemStack arrays.
* @author Kristian
@@ -1,12 +1,11 @@
package com.comphenix.protocol.events;
import java.util.Optional;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.Converters;
import java.util.List;
import java.util.Optional;
public class InternalStructure extends AbstractStructure {
protected InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
@@ -30,6 +29,10 @@ public class InternalStructure extends AbstractStructure {
}
};
public static EquivalentConverter<InternalStructure> getConverter() {
return CONVERTER;
}
public StructureModifier<InternalStructure> getStructures() {
return structureModifier.withType(Object.class, CONVERTER);
}
@@ -2,31 +2,32 @@ package com.comphenix.protocol.events;
import com.comphenix.protocol.injector.GamePhase;
/**
* Represents additional options a listener may require.
*
*
* @author Kristian
*/
public enum ListenerOptions {
/**
* Retrieve the serialized client packet as it appears on the network stream.
*/
INTERCEPT_INPUT_BUFFER,
/**
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when
* a packet ID is known to be transmitted during login.
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
* known to be transmitted during login.
*/
DISABLE_GAMEPHASE_DETECTION,
/**
* Do not verify that the owning plugin has a vaid plugin.yml.
*/
SKIP_PLUGIN_VERIFIER,
/**
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
*/
ASYNC;
ASYNC,
/**
* Notify ProtocolLib that {@link PacketListener#onPacketReceiving(PacketEvent)} must be executed on the main server
* thread.
*/
SYNC
}
@@ -21,16 +21,16 @@ import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
/**
* Determines which packets will be observed by a listener, and with what priority.
*
* @author Kristian
*/
public class ListeningWhitelist {
/**
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
*/
@@ -55,8 +55,72 @@ public class ListeningWhitelist {
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Determine if the given whitelist is empty or not.
*
* @param whitelist - the whitelist to test.
* @return TRUE if the whitelist is empty, FALSE otherwise.
*/
public static boolean isEmpty(ListeningWhitelist whitelist) {
if (whitelist == EMPTY_WHITELIST) {
return true;
} else if (whitelist == null) {
return true;
} else {
return whitelist.getTypes().isEmpty();
}
}
/**
* Construct a new builder of whitelists.
*
* @return New whitelist builder.
*/
public static Builder newBuilder() {
return new Builder(null);
}
/**
* Construct a new builder of whitelists initialized to the same values as the template.
*
* @param template - the template object.
* @return New whitelist builder.
*/
public static Builder newBuilder(ListeningWhitelist template) {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
*
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
}
}
/**
* Construct a copy of a given set.
*
* @param set - the set to copy.
* @return The copied set.
*/
private static <T> Set<T> safeSet(Collection<T> set) {
if (set != null) {
return new HashSet<>(set);
} else {
return Collections.emptySet();
}
}
/**
* Whether or not this whitelist has any enabled packets.
*
* @return TRUE if there are any packets, FALSE otherwise.
*/
public boolean isEnabled() {
@@ -65,6 +129,7 @@ public class ListeningWhitelist {
/**
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
*
* @return Execution order in terms of priority.
*/
public ListenerPriority getPriority() {
@@ -73,22 +138,25 @@ public class ListeningWhitelist {
/**
* Retrieves a set of the packets that will be observed by the listeners.
*
* @return Packet whitelist.
*/
public Set<PacketType> getTypes() {
return types;
}
/**
* Retrieve which game phase this listener is active under.
*
* @return The active game phase.
*/
public GamePhase getGamePhase() {
return gamePhase;
}
/**
* Retrieve every special option associated with this whitelist.
*
* @return Every special option.
*/
public Set<ListenerOptions> getOptions() {
@@ -100,20 +168,6 @@ public class ListeningWhitelist {
return Objects.hashCode(priority, types, gamePhase, options);
}
/**
* Determine if the given whitelist is empty or not.
* @param whitelist - the whitelist to test.
* @return TRUE if the whitelist is empty, FALSE otherwise.
*/
public static boolean isEmpty(ListeningWhitelist whitelist) {
if (whitelist == EMPTY_WHITELIST)
return true;
else if (whitelist == null)
return true;
else
return whitelist.getTypes().isEmpty();
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof ListeningWhitelist) {
@@ -129,67 +183,30 @@ public class ListeningWhitelist {
@Override
public String toString() {
if (this == EMPTY_WHITELIST)
if (this == EMPTY_WHITELIST) {
return "EMPTY_WHITELIST";
else
return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase + ", options=" + options + "]";
}
/**
* Construct a new builder of whitelists.
* @return New whitelist builder.
*/
public static Builder newBuilder() {
return new Builder(null);
}
/**
* Construct a new builder of whitelists initialized to the same values as the template.
* @param template - the template object.
* @return New whitelist builder.
*/
public static Builder newBuilder(ListeningWhitelist template) {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase
+ ", options=" + options + "]";
}
}
/**
* Construct a copy of a given set.
* @param set - the set to copy.
* @return The copied set.
*/
private static <T> Set<T> safeSet(Collection<T> set) {
if (set != null)
return Sets.newHashSet(set);
else
return Collections.emptySet();
}
/**
* Represents a builder of whitelists.
*
* @author Kristian
*/
public static class Builder {
// Default values
private ListenerPriority priority = ListenerPriority.NORMAL;
private Set<PacketType> types = Sets.newHashSet();
private Set<PacketType> types = new HashSet<>();
private GamePhase gamePhase = GamePhase.PLAYING;
private Set<ListenerOptions> options = Sets.newHashSet();
private Set<ListenerOptions> options = new HashSet<>();
/**
* Construct a new listening whitelist template.
*
* @param template - the template.
*/
private Builder(ListeningWhitelist template) {
@@ -200,9 +217,10 @@ public class ListeningWhitelist {
options(template.getOptions());
}
}
/**
* Set the priority to use when constructing new whitelists.
*
* @param priority - the priority.
* @return This builder, for chaining.
*/
@@ -210,49 +228,55 @@ public class ListeningWhitelist {
this.priority = priority;
return this;
}
/**
* Set the priority of the whitelist to monitor.
*
* @return This builder, for chaining.
*/
public Builder monitor() {
return priority(ListenerPriority.MONITOR);
}
/**
* Set the priority of the whitelist to normal.
*
* @return This builder, for chaining.
*/
public Builder normal() {
return priority(ListenerPriority.NORMAL);
}
/**
* Set the priority of the whitelist to lowest.
*
* @return This builder, for chaining.
*/
public Builder lowest() {
return priority(ListenerPriority.LOWEST);
}
/**
* Set the priority of the whitelist to low.
*
* @return This builder, for chaining.
*/
public Builder low() {
return priority(ListenerPriority.LOW);
}
/**
* Set the priority of the whitelist to highest.
*
* @return This builder, for chaining.
*/
public Builder highest() {
return priority(ListenerPriority.HIGHEST);
}
/**
* Set the priority of the whitelist to high.
*
* @return This builder, for chaining.
*/
public Builder high() {
@@ -261,6 +285,7 @@ public class ListeningWhitelist {
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
*
* @param types - the whitelist of packets.
* @return This builder, for chaining.
*/
@@ -268,9 +293,10 @@ public class ListeningWhitelist {
this.types = safeSet(Sets.newHashSet(types));
return this;
}
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
*
* @param types - the whitelist of packets.
* @return This builder, for chaining.
*/
@@ -278,9 +304,10 @@ public class ListeningWhitelist {
this.types = safeSet(types);
return this;
}
/**
* Set the gamephase to use when constructing new whitelists.
*
* @param gamePhase - the gamephase.
* @return This builder, for chaining.
*/
@@ -288,16 +315,19 @@ public class ListeningWhitelist {
this.gamePhase = gamePhase;
return this;
}
/**
* Set the gamephase to {@link GamePhase#BOTH}.
*
* @return This builder, for chaining.
*/
public Builder gamePhaseBoth() {
return gamePhase(GamePhase.BOTH);
}
/**
* Set the options to copy when constructing new whitelists.
*
* @param options - the options.
* @return This builder, for chaining.
*/
@@ -305,9 +335,10 @@ public class ListeningWhitelist {
this.options = safeSet(options);
return this;
}
/**
* Set the options to copy when constructing new whitelists.
*
* @param options - the options.
* @return This builder, for chaining.
*/
@@ -315,9 +346,10 @@ public class ListeningWhitelist {
this.options = safeSet(options);
return this;
}
/**
* Set the options to copy when constructing new whitelists.
*
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
@@ -325,32 +357,36 @@ public class ListeningWhitelist {
this.options = safeSet(Sets.newHashSet(serverOptions));
return this;
}
/**
* Options to merge into the current set of options.
*
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder mergeOptions(ListenerOptions... serverOptions) {
return mergeOptions(Arrays.asList(serverOptions));
}
/**
* Options to merge into the current set of options.
*
* @param serverOptions - the options array.
* @return This builder, for chaining.
*/
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
if (options == null)
if (options == null) {
return options(serverOptions);
}
// Merge the options
this.options.addAll(serverOptions);
return this;
}
/**
* Construct a new whitelist from the values in this builder.
*
* @return The new whitelist.
*/
public ListeningWhitelist build() {
@@ -2,82 +2,76 @@
* 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
* 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.
* 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
* 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.Collection;
import java.util.logging.Logger;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import org.bukkit.plugin.Plugin;
/**
* Represents a listener that is notified of every sent and received packet.
*
*
* @author Kristian
*/
public abstract class MonitorAdapter implements PacketListener {
private Plugin plugin;
private ListeningWhitelist sending = ListeningWhitelist.EMPTY_WHITELIST;
private ListeningWhitelist receiving = ListeningWhitelist.EMPTY_WHITELIST;
private final Plugin plugin;
private final ListeningWhitelist sending;
private final ListeningWhitelist receiving;
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
initialize(plugin, side, getLogger(plugin));
}
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
initialize(plugin, side, logger);
}
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
this.plugin = plugin;
// Recover in case something goes wrong
if (side.isForServer())
this.sending = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getServerPacketTypes()).gamePhaseBoth().build();
if (side.isForClient())
this.receiving = ListeningWhitelist.newBuilder().monitor().types(PacketRegistry.getClientPacketTypes()).gamePhaseBoth().build();
// check the connection side and register the packets for the given side
this.sending = side.isForServer() ? buildWhitelist(PacketRegistry.getServerPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
this.receiving = side.isForClient() ? buildWhitelist(PacketRegistry.getClientPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
}
/**
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
* @param plugin - the plugin to retrieve.
* @return The logger.
*/
private Logger getLogger(Plugin plugin) {
try {
return plugin.getLogger();
} catch (NoSuchMethodError e) {
return Logger.getLogger("Minecraft");
}
@Deprecated
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
this(plugin, side);
}
private static ListeningWhitelist buildWhitelist(Collection<PacketType> packetTypes) {
return ListeningWhitelist.newBuilder().monitor().gamePhaseBoth().types(packetTypes).build();
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sending;
return this.sending;
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receiving;
return this.receiving;
}
@Override
public Plugin getPlugin() {
return plugin;
return this.plugin;
}
@Override
public void onPacketSending(PacketEvent event) {
}
@Override
public void onPacketReceiving(PacketEvent event) {
}
}
@@ -1,421 +1,153 @@
package com.comphenix.protocol.events;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import javax.annotation.Nonnull;
import org.bukkit.entity.Player;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.utility.ByteBufferInputStream;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import org.bukkit.entity.Player;
/**
* Marker containing the serialized packet data seen from the network,
* or output handlers that will serialize the current packet.
*
* Marker containing the serialized packet data seen from the network, or output handlers that will serialize the
* current packet.
*
* @author Kristian
*/
public abstract class NetworkMarker {
public static class EmptyBufferMarker extends NetworkMarker {
public EmptyBufferMarker(@Nonnull ConnectionSide side) {
super(side, (byte[]) null, null);
}
public class NetworkMarker {
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
}
// Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers;
// Post listeners
private List<PacketPostListener> postListeners;
// Post packets
private List<ScheduledPacket> scheduledPackets;
// The input buffer
private ByteBuffer inputBuffer;
private final ConnectionSide side;
// The input data
private final PacketType type;
// Cache serializer too
private StreamSerializer serializer;
/**
* Construct a new network marker.
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.inputBuffer = inputBuffer;
this.type = type;
}
private final ConnectionSide side;
// Post-processing of the packet
private Set<PacketPostListener> postListeners;
private Deque<ScheduledPacket> scheduledPackets;
/**
* Construct a new network marker.
* <p>
* The input buffer is only non-null for client-side packets.
*
* @param side - which side this marker belongs to.
* @param inputBuffer - the read serialized packet data.
* @param type - packet type
*/
public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
public NetworkMarker(@Nonnull ConnectionSide side, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.type = type;
if (inputBuffer != null) {
this.inputBuffer = ByteBuffer.wrap(inputBuffer);
}
}
/**
* Retrieve whether or not this marker belongs to a client or a server side packet.
* @return The side the parent packet belongs to.
*/
public ConnectionSide getSide() {
return side;
}
/**
* Retrieve a utility class for serializing and deserializing Minecraft objects.
* @return Serialization utility class.
*/
public StreamSerializer getSerializer() {
if (serializer == null)
serializer = new StreamSerializer();
return serializer;
}
/**
* Retrieve the serialized packet data (excluding the header by default) from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer() {
return getInputBuffer(true);
}
/**
* Retrieve the serialized packet data from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer != null) {
ByteBuffer result = inputBuffer.asReadOnlyBuffer();
try {
if (excludeHeader)
result = skipHeader(result);
else
result = addHeader(result, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return result;
}
return null;
}
/**
* Retrieve the serialized packet data (excluding the header by default) as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
public DataInputStream getInputStream() {
return getInputStream(true);
}
/**
* Retrieve the serialized packet data as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
@SuppressWarnings("resource")
public DataInputStream getInputStream(boolean excludeHeader) {
if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer == null)
return null;
DataInputStream input = new DataInputStream(
new ByteArrayInputStream(inputBuffer.array())
);
try {
if (excludeHeader)
input = skipHeader(input);
else
input = addHeader(input, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return input;
}
/**
* Whether or not the output handlers have to write a packet header.
* @return TRUE if they do, FALSE otherwise.
*/
public boolean requireOutputHeader() {
return MinecraftReflection.isUsingNetty();
}
/**
* Enqueue the given output handler for managing how the current packet will be written to the network stream.
* <p>
* Note that output handlers are not serialized, as most consumers will probably implement them using anonymous classes.
* It is not safe to serialize anonymous classes, as their name depend on the order in which they are declared in the parent class.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler that will take part in serializing the packet.
* @return TRUE if it was added, FALSE if it has already been added.
*/
public boolean addOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
// Lazy initialization - it's imperative that we save space and time here
if (outputHandlers == null) {
outputHandlers = new PriorityQueue<PacketOutputHandler>(10, new Comparator<PacketOutputHandler>() {
@Override
public int compare(PacketOutputHandler o1, PacketOutputHandler o2) {
return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot());
}
});
}
return outputHandlers.add(handler);
}
/**
* Remove a given output handler from the serialization queue.
* <p>
* This can only be invoked on server side packet events.
* @param handler - the handler to remove.
* @return TRUE if the handler was removed, FALSE otherwise.
*/
public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) {
checkServerSide();
Preconditions.checkNotNull(handler, "handler cannot be NULL.");
if (outputHandlers != null) {
return outputHandlers.remove(handler);
}
return false;
}
/**
* Retrieve every registered output handler in no particular order.
* @return Every registered output handler.
*/
@Nonnull
public Collection<PacketOutputHandler> getOutputHandlers() {
if (outputHandlers != null) {
return outputHandlers;
} else {
return Collections.emptyList();
}
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received
* by the server.
* <p>
* Received packets are not guarenteed to have been fully processed, but packets passed
* to {@link ProtocolManager#recieveClientPacket(Player, PacketContainer)} will be processed after the
* current packet event.
* <p>
* Note that post listeners will be executed asynchronously off the main thread. They are not executed
* in any defined order.
* @param listener - the listener that will be invoked.
* @return TRUE if it was added.
*/
public boolean addPostListener(PacketPostListener listener) {
if (postListeners == null)
postListeners = Lists.newArrayList();
return postListeners.add(listener);
}
/**
* Remove the first instance of the given listener.
* @param listener - listener to remove.
* @return TRUE if it was removed, FALSE otherwise.
*/
public boolean removePostListener(PacketPostListener listener) {
if (postListeners != null) {
return postListeners.remove(listener);
}
return false;
}
/**
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
* @return Every post packet listener. Never NULL.
*/
public List<PacketPostListener> getPostListeners() {
return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.<PacketPostListener>emptyList();
}
/**
* Retrieve a list of packets that will be schedule (in-order) when the current packet has been successfully transmitted.
* <p>
* This list is modifiable.
* @return List of packets that will be scheduled.
*/
public List<ScheduledPacket> getScheduledPackets() {
if (scheduledPackets == null)
scheduledPackets = Lists.newArrayList();
return scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
/**
* Return a byte buffer without the header in the current packet.
* <p>
* It's safe to modify the position of the buffer.
* @param buffer - a read-only byte source.
* @return A byte buffer without the header in the current packet.
* @throws IOException If integer reading fails
*/
protected ByteBuffer skipHeader(ByteBuffer buffer) throws IOException {
skipHeader(new DataInputStream(new ByteBufferInputStream(buffer)));
return buffer;
}
/**
* Return an input stream without the header in the current packet.
* <p>
* It's safe to modify the input stream.
* @param input - input stream
* @return An input stream without the header
* @throws IOException If integer reading fails
*/
protected abstract DataInputStream skipHeader(DataInputStream input) throws IOException;
/**
* Return the byte buffer prepended with the packet header.
* @param buffer - the read-only byte buffer.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract ByteBuffer addHeader(ByteBuffer buffer, PacketType type);
/**
* Return the input stream prepended with the packet header.
* @param input - the input stream.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract DataInputStream addHeader(DataInputStream input, PacketType type);
/**
* Determine if the given marker has any output handlers.
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasOutputHandlers(NetworkMarker marker) {
return marker != null && !marker.getOutputHandlers().isEmpty();
}
/**
* Determine if the given marker has any post listeners.
*
* @param marker - the marker to check.
* @return TRUE if it does, FALSE otherwise.
*/
public static boolean hasPostListeners(NetworkMarker marker) {
return marker != null && !marker.getPostListeners().isEmpty();
}
/**
* Retrieve the byte buffer stored in the given marker.
* @param marker - the marker.
* @return The byte buffer, or NULL if not found.
*/
public static byte[] getByteBuffer(NetworkMarker marker) {
if (marker != null) {
ByteBuffer buffer = marker.getInputBuffer();
if (buffer != null) {
byte[] data = new byte[buffer.remaining()];
buffer.get(data, 0, data.length);
return data;
}
}
return null;
}
/**
* Retrieve the network marker of a particular event without creating it.
* <p>
* This is an internal method that should not be used by API users.
*
* @param event - the event.
* @return The network marker.
*/
public static NetworkMarker getNetworkMarker(PacketEvent event) {
return event.networkMarker;
}
/**
* Retrieve the scheduled packets of a particular network marker without constructing the list.
* <p>
* This is an internal method that should not be used by API users.
*
* @param marker - the marker.
* @return The list, or NULL if not found or initialized.
*/
public static List<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
public static Deque<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
return marker.scheduledPackets;
}
/**
* Retrieve whether or not this marker belongs to a client or a server side packet.
*
* @return The side the parent packet belongs to.
*/
public ConnectionSide getSide() {
return this.side;
}
public PacketType getType() {
return this.type;
}
/**
* Add a listener that is invoked after a packet has been successfully sent to the client, or received by the server.
* <p>
* Received packets are not guarenteed to have been fully processed, but packets passed to {@link
* ProtocolManager#receiveClientPacket(Player, PacketContainer)} will be processed after the current packet event.
* <p>
* Note that post listeners will be executed asynchronously off the main thread. They are not executed in any defined
* order.
*
* @param listener - the listener that will be invoked.
* @return TRUE if it was added.
*/
public boolean addPostListener(PacketPostListener listener) {
if (this.postListeners == null) {
this.postListeners = new HashSet<>();
}
return this.postListeners.add(listener);
}
/**
* Remove the first instance of the given listener.
*
* @param listener - listener to remove.
* @return TRUE if it was removed, FALSE otherwise.
*/
public boolean removePostListener(PacketPostListener listener) {
if (this.postListeners != null) {
return this.postListeners.remove(listener);
}
return false;
}
/**
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
*
* @return Every post packet listener. Never NULL.
*/
public Set<PacketPostListener> getPostListeners() {
return this.postListeners != null ? this.postListeners : Collections.emptySet();
}
/**
* Retrieve a modifiable queue of packets that will be schedule (in-order) when the current packet has been
* successfully transmitted.
*
* @return the queue of packets to schedule after this packet, in order.
*/
public Deque<ScheduledPacket> getScheduledPackets() {
if (this.scheduledPackets == null) {
this.scheduledPackets = new LinkedList<>();
}
return this.scheduledPackets;
}
/**
* Ensure that the packet event is server side.
*/
private void checkServerSide() {
if (this.side.isForClient()) {
throw new IllegalStateException("Must be a server side packet.");
}
}
}
@@ -17,15 +17,13 @@
package com.comphenix.protocol.events;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.bukkit.plugin.Plugin;
@@ -34,69 +32,80 @@ import org.bukkit.plugin.Plugin;
* Represents a packet listener with useful constructors.
* <p>
* Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
*
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {
protected Plugin plugin;
protected ConnectionSide connectionSide;
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
/**
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this builder.
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this
* builder.
*
* @param params - the parameters.
*/
public PacketAdapter(@Nonnull AdapterParameteters params) {
this(
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
params.gamePhase, params.options, params.packets
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
params.gamePhase, params.options, params.packets
);
}
/**
* Initialize a packet listener with the given parameters.
*
* @param plugin - the plugin.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, PacketType... types) {
this(plugin, ListenerPriority.NORMAL, types);
}
/**
* Initialize a packet listener with the given parameters.
*
* @param plugin - the plugin.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
this(params(plugin, Iterables.toArray(types, PacketType.class)));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
*
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
*
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param options - the options.
* @param types - the packet types.
* @param options - the options.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types, ListenerOptions... options) {
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types,
ListenerOptions... options) {
this(
params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
}
/**
* Initialize a packet listener with the given parameters.
* @param plugin - the plugin.
*
* @param plugin - the plugin.
* @param listenerPriority - the priority.
* @param types - the packet types.
* @param types - the packet types.
*/
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
this(params(plugin, types).listenerPriority(listenerPriority));
@@ -106,102 +115,69 @@ public abstract class PacketAdapter implements PacketListener {
private PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
if (plugin == null)
if (plugin == null) {
throw new IllegalArgumentException("plugin cannot be null");
if (connectionSide == null)
throw new IllegalArgumentException("connectionSide cannot be null");
if (listenerPriority == null)
throw new IllegalArgumentException("listenerPriority cannot be null");
if (gamePhase == null)
throw new IllegalArgumentException("gamePhase cannot be NULL");
if (packets == null)
throw new IllegalArgumentException("packets cannot be null");
if (options == null)
throw new IllegalArgumentException("options cannot be null");
ListenerOptions[] serverOptions = options;
ListenerOptions[] clientOptions = options;
// Special case that allows us to specify optionIntercept().
if (connectionSide == ConnectionSide.BOTH) {
serverOptions = except(serverOptions, new ListenerOptions[0],
ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
if (connectionSide == null) {
throw new IllegalArgumentException("connectionSide cannot be null");
}
if (listenerPriority == null) {
throw new IllegalArgumentException("listenerPriority cannot be null");
}
if (gamePhase == null) {
throw new IllegalArgumentException("gamePhase cannot be NULL");
}
if (packets == null) {
throw new IllegalArgumentException("packets cannot be null");
}
if (options == null) {
throw new IllegalArgumentException("options cannot be null");
}
// Add whitelists
if (connectionSide.isForServer())
sendingWhitelist = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(serverOptions).
build();
if (connectionSide.isForClient())
receivingWhitelist = ListeningWhitelist.newBuilder().
priority(listenerPriority).
types(packets).
gamePhase(gamePhase).
options(clientOptions).
build();
if (connectionSide.isForServer()) {
sendingWhitelist = ListeningWhitelist.newBuilder()
.priority(listenerPriority)
.types(packets)
.gamePhase(gamePhase)
.options(options)
.build();
}
if (connectionSide.isForClient()) {
receivingWhitelist = ListeningWhitelist.newBuilder()
.priority(listenerPriority)
.types(packets)
.gamePhase(gamePhase)
.options(options)
.build();
}
this.plugin = plugin;
this.connectionSide = connectionSide;
}
// Remove a given element from an array
private static <T> T[] except(T[] values, T[] buffer, T except) {
List<T> result = Lists.newArrayList(values);
result.remove(except);
return result.toArray(buffer);
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
/**
* Retrieves the name of the plugin that has been associated with the listener.
*
* @param listener - the listener.
* @return Name of the associated plugin.
*/
public static String getPluginName(PacketListener listener) {
return getPluginName(listener.getPlugin());
}
/**
* Retrieves the name of the given plugin.
*
* @param plugin - the plugin.
* @return Name of the given plugin.
*/
public static String getPluginName(Plugin plugin) {
if (plugin == null)
if (plugin == null) {
return "UNKNOWN";
}
try {
return plugin.getName();
@@ -209,20 +185,12 @@ public abstract class PacketAdapter implements PacketListener {
return plugin.toString();
}
}
@Override
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
getPluginName(this),
sendingWhitelist,
receivingWhitelist);
}
/**
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
*
* @return Helper object.
*/
public static AdapterParameteters params() {
@@ -233,32 +201,91 @@ public abstract class PacketAdapter implements PacketListener {
* Construct a helper object for passing parameters to the packet adapter.
* <p>
* This is often simpler and better than passing them directly to each constructor.
* @param plugin - the plugin that spawned this listener.
*
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet types the listener is looking for.
* @return Helper object.
*/
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
return new AdapterParameteters().plugin(plugin).types(packets);
}
/**
* Determine if the required parameters are set.
*/
private static AdapterParameteters checkValidity(AdapterParameteters params) {
if (params == null) {
throw new IllegalArgumentException("params cannot be NULL.");
}
if (params.plugin == null) {
throw new IllegalStateException("Plugin was never set in the parameters.");
}
if (params.connectionSide == null) {
throw new IllegalStateException("Connection side was never set in the parameters.");
}
if (params.packets == null) {
throw new IllegalStateException("Packet IDs was never set in the parameters.");
}
return params;
}
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
}
@Override
public ListeningWhitelist getReceivingWhitelist() {
return receivingWhitelist;
}
@Override
public ListeningWhitelist getSendingWhitelist() {
return sendingWhitelist;
}
@Override
public Plugin getPlugin() {
return plugin;
}
@Override
public String toString() {
// This is used by the error reporter
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
getPluginName(this),
sendingWhitelist,
receivingWhitelist);
}
/**
* Represents a builder for passing parameters to the packet adapter constructor.
* <p>
* Note: Never make spelling mistakes in a public API!
*
* @author Kristian
*/
public static class AdapterParameteters {
private Plugin plugin;
private ConnectionSide connectionSide;
private PacketType[] packets;
// Parameters with default values
private GamePhase gamePhase = GamePhase.PLAYING;
private ListenerOptions[] options = new ListenerOptions[0];
private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
/**
* Set the plugin that spawned this listener. This parameter is required.
*
* @param plugin - the plugin.
* @return This builder, for chaining.
*/
@@ -266,9 +293,10 @@ public abstract class PacketAdapter implements PacketListener {
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
return this;
}
/**
* Set the packet types this listener is looking for. This parameter is required.
*
* @param connectionSide - the new packet type.
* @return This builder, for chaining.
*/
@@ -276,27 +304,30 @@ public abstract class PacketAdapter implements PacketListener {
this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
return this;
}
/**
* Set this adapter to also look for client-side packets.
*
* @return This builder, for chaining.
*/
public AdapterParameteters clientSide() {
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
}
/**
* Set this adapter to also look for server-side packets.
*
* @return This builder, for chaining.
*/
public AdapterParameteters serverSide() {
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
}
/**
* Set the the event priority, where the execution is in ascending order from lowest to highest.
* <p>
* Default is {@link ListenerPriority#NORMAL}.
*
* @param listenerPriority - the new event priority.
* @return This builder, for chaining.
*/
@@ -304,11 +335,13 @@ public abstract class PacketAdapter implements PacketListener {
this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
return this;
}
/**
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login packets.
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login
* packets.
* <p>
* Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
*
* @param gamePhase - the new game phase.
* @return This builder, for chaining.
*/
@@ -316,19 +349,22 @@ public abstract class PacketAdapter implements PacketListener {
this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
return this;
}
/**
* Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
*
* @return This builder, for chaining.
*/
public AdapterParameteters loginPhase() {
return gamePhase(GamePhase.LOGIN);
}
/**
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
* stream.
* <p>
* The default is to disable this raw packet interception.
*
* @param options - every option to use.
* @return This builder, for chaining.
*/
@@ -338,9 +374,11 @@ public abstract class PacketAdapter implements PacketListener {
}
/**
* Set listener options that decide whether or not to intercept the raw packet data as read from the network stream.
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
* stream.
* <p>
* The default is to disable this raw packet interception.
*
* @param options - every option to use.
* @return This builder, for chaining.
*/
@@ -349,9 +387,10 @@ public abstract class PacketAdapter implements PacketListener {
this.options = options.toArray(new ListenerOptions[0]);
return this;
}
/**
* Add a given option to the current builder.
*
* @param option - the option to add.
* @return This builder, for chaining.
*/
@@ -359,34 +398,38 @@ public abstract class PacketAdapter implements PacketListener {
if (options == null) {
return options(option);
} else {
Set<ListenerOptions> current = Sets.newHashSet(options);
final Set<ListenerOptions> current = Sets.newHashSet(options);
current.add(option);
return options(current);
}
}
/**
* Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream.
* @return This builder, for chaining.
*/
public AdapterParameteters optionIntercept() {
return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
/**
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our listener is thread safe.
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our outbound listener is thread safe.
* <p>
* This allows ProtocolLib to perform certain optimizations.
*
* @return This builder, for chaining.
*/
public AdapterParameteters optionAsync() {
return addOption(ListenerOptions.ASYNC);
}
/**
* Set the listener option to {@link ListenerOptions#SYNC}, indicating that our inbound listener must be executed
* on the main server thread.
*
* @return This builder, for chaining.
*/
public AdapterParameteters optionSync() {
return addOption(ListenerOptions.SYNC);
}
/**
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
*
* @param packets - the packet types to look for.
* @return This builder, for chaining.
*/
@@ -398,16 +441,18 @@ public abstract class PacketAdapter implements PacketListener {
}
}
this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
if (packets.length == 0)
if (packets.length == 0) {
throw new IllegalArgumentException("Passed an empty packet type array.");
}
return this;
}
/**
* Set the packet types the listener is looking for.
* <p>
* This parameter is required.
*
* @param packets - a set of packet types to look for.
* @return This builder, for chaining.
*/
@@ -415,19 +460,4 @@ public abstract class PacketAdapter implements PacketListener {
return types(packets.toArray(new PacketType[0]));
}
}
/**
* Determine if the required parameters are set.
*/
private static AdapterParameteters checkValidity(AdapterParameteters params) {
if (params == null)
throw new IllegalArgumentException("params cannot be NULL.");
if (params.plugin == null)
throw new IllegalStateException("Plugin was never set in the parameters.");
if (params.connectionSide == null)
throw new IllegalStateException("Connection side was never set in the parameters.");
if (params.packets == null)
throw new IllegalStateException("Packet IDs was never set in the parameters.");
return params;
}
}
@@ -21,61 +21,49 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Function;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.*;
import com.comphenix.protocol.reflect.cloning.*;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
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.MethodAccessor;
import com.comphenix.protocol.reflect.cloning.AggregateCloner;
import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters;
import com.comphenix.protocol.reflect.cloning.BukkitCloner;
import com.comphenix.protocol.reflect.cloning.Cloner;
import com.comphenix.protocol.reflect.cloning.CollectionCloner;
import com.comphenix.protocol.reflect.cloning.FieldCloner;
import com.comphenix.protocol.reflect.cloning.GuavaOptionalCloner;
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
import com.comphenix.protocol.reflect.cloning.JavaOptionalCloner;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.MinecraftGenerator;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.EnumWrappers.*;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.comphenix.protocol.wrappers.Converters;
import com.google.common.collect.Sets;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import io.netty.util.ReferenceCountUtil;
import javax.annotation.Nullable;
/**
* Represents a Minecraft packet indirectly.
*
*
* @author Kristian
*/
@SuppressWarnings("unused")
@@ -85,8 +73,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
private PacketType type;
// Support for serialization
private static ConcurrentMap<Class<?>, Method> writeMethods = Maps.newConcurrentMap();
private static ConcurrentMap<Class<?>, Method> readMethods = Maps.newConcurrentMap();
private static final Map<PacketType, Function<Object, Object>> PACKET_DESERIALIZER_METHODS = new ConcurrentHashMap<>();
// Used to clone packets
private static final AggregateCloner DEEP_CLONER = AggregateCloner
@@ -99,7 +86,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
.andThen(CollectionCloner.class)
.andThen(getSpecializedDeepClonerFactory())
.build();
private static final AggregateCloner SHALLOW_CLONER = AggregateCloner
.newBuilder()
.instanceProvider(StructureCache::newPacket)
@@ -113,7 +100,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
}};
})
.build();
// Packets that cannot be cloned by our default deep cloner
private static final Set<PacketType> FAST_CLONE_UNSUPPORTED = Sets.newHashSet(
PacketType.Play.Server.BOSS,
@@ -129,7 +116,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
public PacketContainer(PacketType type) {
this(type, StructureCache.newPacket(type));
}
/**
* Creates a packet container for an existing packet.
* @param type - Type of the given packet.
@@ -138,7 +125,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
public PacketContainer(PacketType type, Object handle) {
this(type, handle, StructureCache.getStructure(type).withTarget(handle));
}
/**
* Creates a packet container for an existing packet.
* @param type - Type of the given packet.
@@ -147,7 +134,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
*/
public PacketContainer(PacketType type, Object handle, StructureModifier<Object> structure) {
super(handle, structure);
this.type = type;
setDefaults();
@@ -170,13 +157,13 @@ public class PacketContainer extends AbstractStructure implements Serializable {
PacketType type = PacketType.fromClass(packet.getClass());
return new PacketContainer(type, packet);
}
/**
* For serialization.
*/
protected PacketContainer() {
}
/**
* Retrieves the underlying Minecraft packet.
* @return Underlying Minecraft packet.
@@ -184,7 +171,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
public Object getHandle() {
return handle;
}
/**
* Retrieves the generic structure modifier for this packet.
* @return Structure modifier.
@@ -216,20 +203,20 @@ public class PacketContainer extends AbstractStructure implements Serializable {
public PacketType getType() {
return type;
}
/**
* Create a shallow copy of the current packet.
* <p>
* This merely writes the content of each field to the new class directly,
* without performing any expensive copies.
*
*
* @return A shallow copy of the current packet.
*/
public PacketContainer shallowClone() {
Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
return new PacketContainer(getType(), clonedPacket);
}
/**
* Create a deep copy of the current packet.
* <p>
@@ -237,32 +224,40 @@ public class PacketContainer extends AbstractStructure implements Serializable {
* known immutable objects and primitive types.
* <p>
* Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
*
*
* @return A deep copy of the current packet.
*/
public PacketContainer deepClone() {
Object clonedPacket = null;
Object handle = this.getHandle();
PacketType packetType = this.getType();
if (handle == null || packetType == null) {
// nothing to clone, just carry on (this should normally not happen)
return this;
}
if (!FAST_CLONE_UNSUPPORTED.contains(type)) {
// try fast cloning first
if (!FAST_CLONE_UNSUPPORTED.contains(packetType)) {
try {
clonedPacket = DEEP_CLONER.clone(getHandle());
Object cloned = DEEP_CLONER.clone(handle);
return new PacketContainer(packetType, cloned);
} catch (Exception ex) {
FAST_CLONE_UNSUPPORTED.add(type);
FAST_CLONE_UNSUPPORTED.add(packetType);
}
}
// Fall back on the slower alternative method of reading and writing back the packet
if (clonedPacket == null) {
clonedPacket = SerializableCloner.clone(this).getHandle();
}
Object serialized = this.serializeToBuffer();
Object deserialized = deserializeFromBuffer(packetType, serialized);
return new PacketContainer(getType(), clonedPacket);
// ensure that we don't leak memory
ReferenceCountUtil.safeRelease(serialized);
return new PacketContainer(packetType, deserialized);
}
// To save space, we'll skip copying the inflated buffers in packet 51 and 56
private static Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
private static com.google.common.base.Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
// Look at what you've made me do Java, look at it!!
return new Function<BuilderParameters, Cloner>() {
return new com.google.common.base.Function<BuilderParameters, Cloner>() {
@Override
public Cloner apply(@Nullable BuilderParameters param) {
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
@@ -281,97 +276,124 @@ public class PacketContainer extends AbstractStructure implements Serializable {
}
};
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
// Default serialization
output.defaultWriteObject();
// We'll take care of NULL packets as well
output.writeBoolean(handle != null);
try {
ByteBuf buffer = createPacketBuffer();
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, buffer);
// serialize the packet
ByteBuf buffer = (ByteBuf) this.serializeToBuffer();
if (buffer != null) {
output.writeBoolean(true);
output.writeInt(buffer.readableBytes());
buffer.readBytes(output, buffer.readableBytes());
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not serialize Minecraft packet.", e);
// ensure that we don't leak memory
ReferenceCountUtil.safeRelease(buffer);
} else {
output.writeBoolean(false);
}
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
// Default deserialization
input.defaultReadObject();
// Get structure modifier
structureModifier = StructureCache.getStructure(type);
// Don't read NULL packets
if (input.readBoolean()) {
ByteBuf buffer = createPacketBuffer();
buffer.writeBytes(input, input.readInt());
// Create a default instance of the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
// Deserialize the packet from the stream (if present)
this.structureModifier = StructureCache.getStructure(this.type);
if (input.readBoolean()) {
int dataLength = input.readInt();
try {
handle = type.getPacketClass()
.getConstructor(MinecraftReflection.getPacketDataSerializerClass())
.newInstance(serializer);
} catch (ReflectiveOperationException ex) {
// they might have a static method to create them instead
Method method = FuzzyReflection.fromClass(type.getPacketClass(), true)
.getMethod(FuzzyMethodContract
.newBuilder()
.requireModifier(Modifier.STATIC)
.returnTypeExact(type.getPacketClass())
.parameterExactArray(MinecraftReflection.getPacketDataSerializerClass())
.build());
try {
handle = method.invoke(null, serializer);
} catch (ReflectiveOperationException ignored) {
throw new RuntimeException("Failed to construct packet for " + type, ex);
}
}
} else {
handle = StructureCache.newPacket(type);
ByteBuf byteBuf = (ByteBuf) MinecraftReflection.createPacketDataSerializer(dataLength);
while (true) {
// ObjectInputStream only reads a specific amount of bytes before moving the cursor forwards and
// allows reading the next byte chunk. So we need to read until the data is gone from the stream and
// fully transferred into the buffer.
int transferredBytes = byteBuf.writeBytes(input, dataLength);
// Call the read method
try {
MinecraftMethods.getPacketReadByteBufMethod().invoke(handle, buffer);
} catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e);
} catch (InvocationTargetException e) {
throw new IOException("Could not deserialize Minecraft packet.", e);
// check if we reached the end of the stream, or if the stream has no more data available
dataLength -= transferredBytes;
if (dataLength <= 0 || transferredBytes <= 0) {
break;
}
}
// And we're done
structureModifier = structureModifier.withTarget(handle);
}
// deserialize & ensure that we don't leak memory
Object packet = deserializeFromBuffer(this.type, byteBuf);
ReferenceCountUtil.safeRelease(byteBuf);
this.handle = packet;
this.structureModifier = this.structureModifier.withTarget(packet);
}
}
/**
* Construct a new packet data serializer.
* @return The packet data serializer.
* @deprecated use {@link MinecraftReflection#createPacketDataSerializer(int)} instead
*/
@Deprecated
public static ByteBuf createPacketBuffer() {
ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();
Class<?> packetSerializer = MinecraftReflection.getPacketDataSerializerClass();
return (ByteBuf) MinecraftReflection.createPacketDataSerializer(0);
}
try {
return (ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer);
} catch (Exception e) {
throw new RuntimeException("Cannot construct packet serializer.", e);
// ---- Cloning
public static Object deserializeFromBuffer(PacketType packetType, Object buffer) {
if (buffer == null) {
return null;
}
Function<Object, Object> deserializer = PACKET_DESERIALIZER_METHODS.computeIfAbsent(packetType, type -> {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
// best guess - a constructor which takes a buffer as the only argument
ConstructorAccessor bufferConstructor = Accessors.getConstructorAccessorOrNull(
type.getPacketClass(),
MinecraftReflection.getPacketDataSerializerClass());
if (bufferConstructor != null) {
return bufferConstructor::invoke;
}
// they might have a static method to create them instead
List<Method> methods = FuzzyReflection.fromClass(type.getPacketClass(), true)
.getMethodList(FuzzyMethodContract.newBuilder()
.requireModifier(Modifier.STATIC)
.returnTypeExact(type.getPacketClass())
.parameterExactArray(MinecraftReflection.getPacketDataSerializerClass())
.build());
if (!methods.isEmpty()) {
MethodAccessor accessor = Accessors.getMethodAccessor(methods.get(0));
return buf -> accessor.invoke(null, buf);
}
}
// try to construct a packet instance using a no-args constructor and invoke the read method
MethodAccessor readMethod = MinecraftMethods.getPacketReadByteBufMethod();
Objects.requireNonNull(readMethod,
"Unable to find the Packet#read(ByteBuf) method, cannot deserialize " + type);
Object checkInstance = DefaultInstances.DEFAULT.create(type.getPacketClass());
Objects.requireNonNull(checkInstance, "Unable to construct empty packet, cannot deserialize " + type);
// okay, Packet#read exists
return buf -> {
Object packet = DefaultInstances.DEFAULT.create(type.getPacketClass());
readMethod.invoke(packet, buf);
return packet;
};
});
return deserializer.apply(buffer);
}
public Object serializeToBuffer() {
Object handle = this.getHandle();
if (handle == null) {
return null;
}
Object targetBuffer = MinecraftReflection.createPacketDataSerializer(0);
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
return targetBuffer;
}
// ---- Metadata
@@ -419,7 +441,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
private Method getMethodLazily(ConcurrentMap<Class<?>, Method> lookup,
Class<?> handleClass, String methodName, Class<?> parameterClass) {
Method method = lookup.get(handleClass);
// Atomic operation
if (method == null) {
Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(
@@ -429,13 +451,13 @@ public class PacketContainer extends AbstractStructure implements Serializable {
returnTypeVoid().
build());
method = lookup.putIfAbsent(handleClass, initialized);
// Use our version if we succeeded
if (method == null) {
method = initialized;
}
}
return method;
}
@@ -23,58 +23,56 @@ import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.EventObject;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.error.PluginContext;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.TemporaryPlayer;
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
/**
* 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.
* 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.
*
* @author Kristian
*/
public class PacketEvent extends EventObject implements Cancellable {
public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
Multimaps.synchronizedSetMultimap(HashMultimap.create());
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
// Network input and output handlers
NetworkMarker networkMarker;
private transient WeakReference<Player> playerReference;
private PacketContainer packet;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
// Network input and output handlers
NetworkMarker networkMarker;
// Whether or not a packet event is read only
private boolean readOnly;
private boolean filtered;
/**
* Use the static constructors to create instances of this event.
*
* @param source - the event source.
*/
public PacketEvent(Object source) {
@@ -85,8 +83,9 @@ public class PacketEvent extends EventObject implements Cancellable {
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
this(source, packet, null, player, serverPacket, true);
}
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered) {
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
boolean filtered) {
super(source);
this.packet = packet;
this.playerReference = new WeakReference<>(player);
@@ -94,7 +93,7 @@ public class PacketEvent extends EventObject implements Cancellable {
this.serverPacket = serverPacket;
this.filtered = filtered;
}
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
super(origial.source);
this.packet = origial.packet;
@@ -109,6 +108,7 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* Creates an event representing a client packet transmission.
*
* @param source - the event source.
* @param packet - the packet.
* @param client - the client that sent the packet.
@@ -117,9 +117,10 @@ public class PacketEvent extends EventObject implements Cancellable {
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
return new PacketEvent(source, packet, client, false);
}
/**
* Creates an event representing a client packet transmission.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
@@ -129,85 +130,94 @@ public class PacketEvent extends EventObject implements Cancellable {
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
return new PacketEvent(source, packet, marker, client, false, true);
}
/**
* Creates an event representing a client 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 client - the client that sent the packet.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param client - the client that sent the packet.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @return The event.
*/
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, boolean filtered) {
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
boolean filtered) {
return new PacketEvent(source, packet, marker, client, false, filtered);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
*
* @param source - the event source.
* @param packet - the packet.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
return new PacketEvent(source, packet, recipient, true);
}
/**
* Creates an event representing a server packet transmission.
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
*
* @param source - the event source.
* @param packet - the packet.
* @param marker - the network marker.
* @param recipient - the client that will receieve the packet.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
return new PacketEvent(source, packet, marker, recipient, true, true);
}
/**
* 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 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 or not this packet event is processed by every packet listener.
* @param filtered - whether or not this packet event is processed by every packet listener.
* @return The event.
*/
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered) {
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
boolean filtered) {
return new PacketEvent(source, packet, marker, recipient, true, filtered);
}
/**
* Create an asynchronous packet event from a synchronous event and a async marker.
* @param event - the original synchronous event.
*
* @param event - the original synchronous event.
* @param marker - the asynchronous marker.
* @return The new packet event.
*/
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
return new PacketEvent(event, marker);
}
/**
* Determine if we are executing the packet event in an asynchronous thread.
* <p>
* If so, you must synchronize all calls to the Bukkit API.
* <p>
* Generally, most server packets are executed on the main thread, whereas client packets
* are all executed asynchronously.
* Generally, most server packets are executed on the main thread, whereas client packets are all executed
* asynchronously.
*
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isAsync() {
return !Bukkit.isPrimaryThread();
}
/**
* Retrieves the packet that will be sent to the player.
*
* @return Packet to send to the player.
*/
public PacketContainer getPacket() {
@@ -216,14 +226,17 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* Replace the packet that will be sent to the player.
*
* @param packet - the packet that will be sent instead.
*/
public void setPacket(PacketContainer packet) {
if (readOnly)
if (readOnly) {
throw new IllegalStateException("The packet event is read-only.");
if (packet == null)
}
if (packet == null) {
throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
}
// Change warnings
final PacketType oldType = this.packet.getType();
final PacketType newType = packet.getType();
@@ -232,53 +245,77 @@ public class PacketEvent extends EventObject implements Cancellable {
if (CHANGE_WARNINGS.put(oldType, newType)) {
ProtocolLibrary.getErrorReporter().reportWarning(this,
Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
build());
messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
build());
}
}
this.packet = packet;
}
/**
* Retrieves the packet ID.
* <p>
* Deprecated: Use {@link #getPacketType()} instead.
*
* @return The current packet ID.
*/
@Deprecated
public int getPacketID() {
return packet.getId();
}
/**
* Retrieve the packet type.
*
* @return The type.
*/
public PacketType getPacketType() {
return packet.getType();
}
/**
* Retrieves whether or not the packet should be cancelled.
*
* @return TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public boolean isCancelled() {
return cancel;
}
/**
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
* <p>
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
* packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with
* the main thread.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
*
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public void setCancelled(boolean cancel) {
if (readOnly) {
throw new IllegalStateException("The packet event is read-only.");
}
this.cancel = cancel;
}
/**
* Retrieve the object responsible for managing the serialized input and output of a packet.
* <p>
* Note that the serialized input data is only available for client-side packets, and the output handlers
* can only be applied to server-side packets.
* Note that the serialized input data is only available for client-side packets, and the output handlers can only be
* applied to server-side packets.
*
* @return The network manager.
*/
public NetworkMarker getNetworkMarker() {
if (networkMarker == null) {
if (isServerPacket()) {
networkMarker = new NetworkMarker.EmptyBufferMarker(
serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE);
networkMarker = new NetworkMarker(serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, this.getPacketType());
} else {
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
}
@@ -290,31 +327,12 @@ public class PacketEvent extends EventObject implements Cancellable {
* Update the network manager.
* <p>
* This method is internal - do not call.
*
* @param networkMarker - the new network manager.
*/
public void setNetworkMarker(NetworkMarker networkMarker) {
this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
}
/**
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
* <p>
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
* packet manager if you need to perform extensive processing. It should also be used
* if you need to synchronize with the main thread.
* <p>
* This ensures that other plugins can work with the same packet.
* <p>
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
*
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
*/
@Override
public void setCancelled(boolean cancel) {
if (readOnly)
throw new IllegalStateException("The packet event is read-only.");
this.cancel = cancel;
}
private WeakReference<Player> getPlayerReference() {
Player player = playerReference.get();
@@ -332,6 +350,7 @@ public class PacketEvent extends EventObject implements Cancellable {
/**
* Retrieves the player that has sent the packet or is receiving it.
*
* @return The player associated with this event.
*/
public Player getPlayer() {
@@ -350,7 +369,7 @@ public class PacketEvent extends EventObject implements Cancellable {
* <li>kickPlayer</li>
* <li>isOnline</li>
* </ul>
*
* <p>
* Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when
* dealing with packets early in the login sequence or if you get the aforementioned exception.
*
@@ -359,27 +378,29 @@ public class PacketEvent extends EventObject implements Cancellable {
public boolean isPlayerTemporary() {
return getPlayer() instanceof TemporaryPlayer;
}
/**
* Determine if this packet is filtered by every packet listener.
* <p>
* If not, it will only be intercepted by monitor packets.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isFiltered() {
return filtered;
}
/**
* Whether or not this packet was created by the server.
* <p>
* Most listeners can deduce this by noting which listener method was invoked.
*
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
*/
public boolean isServerPacket() {
return serverPacket;
}
/**
* Retrieve the asynchronous marker.
* <p>
@@ -387,54 +408,62 @@ public class PacketEvent extends EventObject implements Cancellable {
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
*
* @return The current asynchronous marker, or NULL.
*/
public AsyncMarker getAsyncMarker() {
return asyncMarker;
}
/**
* Set the asynchronous marker.
* <p>
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
* to be processed asynchronously with the given settings.
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be
* processed asynchronously with the given settings.
* <p>
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
*
* @param asyncMarker - the new asynchronous marker, or NULL.
* @throws IllegalStateException If the current event is asynchronous.
*/
public void setAsyncMarker(AsyncMarker asyncMarker) {
if (isAsynchronous())
if (isAsynchronous()) {
throw new IllegalStateException("The marker is immutable for asynchronous events");
if (readOnly)
}
if (readOnly) {
throw new IllegalStateException("The packet event is read-only.");
}
this.asyncMarker = asyncMarker;
}
/**
* Determine if the current packet event is read only.
* <p>
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However,
* it is still possible to modify the packet itself, as it would require too many resources to verify its integrity.
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is
* still possible to modify the packet itself, as it would require too many resources to verify its integrity.
* <p>
* Thus, the packet is considered immutable if the packet event is read only.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set the read-only state of this packet event.
* <p>
* This will be reset for every packet listener.
*
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Determine if the packet event has been executed asynchronously or not.
*
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
*/
public boolean isAsynchronous() {
@@ -445,14 +474,16 @@ public class PacketEvent extends EventObject implements Cancellable {
* Schedule a packet for sending or receiving after the current packet event is successful.
* <p>
* The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
*
* @param scheduled - the packet to transmit or receive.
*/
public void schedule(ScheduledPacket scheduled) {
getNetworkMarker().getScheduledPackets().add(scheduled);
}
/**
* Unschedule a specific packet.
*
* @param scheduled - the scheduled packet.
* @return TRUE if it was unscheduled, FALSE otherwise.
*/
@@ -462,9 +493,9 @@ public class PacketEvent extends EventObject implements Cancellable {
}
return false;
}
private void writeObject(ObjectOutputStream output) throws IOException {
// Default serialization
// Default serialization
output.defaultWriteObject();
// Write the name of the player (or NULL if it's not set)
@@ -473,17 +504,17 @@ public class PacketEvent extends EventObject implements Cancellable {
}
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
// Default deserialization
// Default deserialization
input.defaultReadObject();
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
// Better than nothing
if (serialized != null) {
// Store it, to prevent weak reference from cleaning up the reference
Player offlinePlayer = serialized.getPlayer();
playerReference = new WeakReference<>(offlinePlayer);
}
if (serialized != null) {
// Store it, to prevent weak reference from cleaning up the reference
Player offlinePlayer = serialized.getPlayer();
playerReference = new WeakReference<>(offlinePlayer);
}
}
@Override
@@ -23,49 +23,53 @@ import org.bukkit.plugin.Plugin;
* Represents a listener that receives notifications when packets are sending or being received.
* <p>
* Use {@link PacketAdapter} for a simple wrapper around this interface.
*
* @author Kristian
*/
public interface PacketListener {
/**
* Invoked right before a packet is transmitted from the server to the client.
* <p>
* Note that the packet may be replaced, if needed.
* <p>
* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use.
* <p>
* In Minecraft 1.7.2 and later, this method MAY be executed asynchronously, but only if {@link ListenerOptions#ASYNC}
* have been specified in the listener. This is off by default.
* This method is executed on the main server thread by default. However, some spigot forks (like paper) schedule
* specific packets off the main thread. If the {@link ListenerOptions#ASYNC} option is not specified any invocation
* of this method will be on the main server thread.
*
* @param event - the packet that should be sent.
*/
public void onPacketSending(PacketEvent event);
void onPacketSending(PacketEvent event);
/**
* Invoked right before a received packet from a client is being processed.
* <p>
* <b>WARNING</b>: <br>
* This method will be called <i>asynchronously</i>! You should synchronize with the main
* thread using {@link org.bukkit.scheduler.BukkitScheduler#scheduleSyncDelayedTask(Plugin, Runnable, long) scheduleSyncDelayedTask}
* if you need to call the Bukkit API.
* This method will be called asynchronously (or on the netty event loop) by default. If the
* {@link ListenerOptions#SYNC} option is specified, the invocation of this method will be synced to the main server
* thread which might cause issues due to delayed packets.
*
* @param event - the packet that has been received.
*/
public void onPacketReceiving(PacketEvent event);
void onPacketReceiving(PacketEvent event);
/**
* Retrieve which packets sent by the server this listener will observe.
*
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getSendingWhitelist();
ListeningWhitelist getSendingWhitelist();
/**
* Retrieve which packets sent by the client this listener will observe.
*
* @return List of server packets to observe, along with the priority.
*/
public ListeningWhitelist getReceivingWhitelist();
ListeningWhitelist getReceivingWhitelist();
/**
* Retrieve the plugin that created list packet listener.
*
* @return The plugin, or NULL if not available.
*/
public Plugin getPlugin();
Plugin getPlugin();
}
@@ -1,142 +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 com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang.Validate;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 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;
}
}
@@ -1,9 +1,9 @@
package com.comphenix.protocol.events;
import org.bukkit.plugin.Plugin;
import com.google.common.base.Preconditions;
import org.bukkit.plugin.Plugin;
/**
* Represents an adapter version of a post listener.
* @author Kristian
@@ -1,14 +1,12 @@
package com.comphenix.protocol.events;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.base.Preconditions;
import org.bukkit.entity.Player;
/**
* Represents a packet that is scheduled for transmission at a later stage.
* @author Kristian
@@ -121,17 +119,11 @@ public class ScheduledPacket {
*/
public void schedule(PacketStream stream) {
Preconditions.checkNotNull(stream, "stream cannot be NULL");
try {
if (getSender() == Sender.CLIENT) {
stream.recieveClientPacket(getTarget(), getPacket(), isFiltered());
} else {
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
}
} catch (InvocationTargetException e) {
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot send packet " + this + " to " + stream);
if (getSender() == Sender.CLIENT) {
stream.receiveClientPacket(getTarget(), getPacket(), isFiltered());
} else {
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
}
}
@@ -26,7 +26,16 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
@@ -36,19 +45,13 @@ import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Pipe;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;
import com.comphenix.protocol.utility.ByteBuddyFactory;
/**
* Represents a player object that can be serialized by Java.
*
@@ -163,6 +166,11 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public void setStatistic(Statistic statistic, EntityType entityType, int i) { }
@Override
public Location getLastDeathLocation() {
return null;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
@@ -284,18 +292,33 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
methodNames[idx] = offlinePlayerMethods[idx].getName();
final Map<Method, MethodAccessor> accessorCache = new ConcurrentHashMap<>();
final ElementMatcher.Junction<ByteCodeElement> forwardedMethods = ElementMatchers.namedOneOf(methodNames);
try {
final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration()
.withBinders(Pipe.Binder.install(Function.class))
.to(new Object() {
@RuntimeType
public Object intercept(@Pipe Function<OfflinePlayer, Object> pipe,
@FieldValue("offlinePlayer") OfflinePlayer proxy) {
return pipe.apply(proxy);
final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration().to(new Object() {
@RuntimeType
public Object intercept(
@Origin Method calledMethod,
@AllArguments Object[] args,
@FieldValue("offlinePlayer") OfflinePlayer proxy
) {
MethodAccessor accessor = accessorCache.computeIfAbsent(calledMethod, method -> {
// special case - some methods (like getName) are defined in OfflinePlayer as well
// as the online Player class. This causes cast exceptions if we try to invoke the method on
// the online player with our proxy. Prevent that
if (OfflinePlayer.class.isAssignableFrom(method.getDeclaringClass())) {
return Accessors.getMethodAccessor(
OfflinePlayer.class,
method.getName(),
method.getParameterTypes());
} else {
return Accessors.getMethodAccessor(method);
}
});
return accessor.invoke(proxy, args);
}
});
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
throw new UnsupportedOperationException(
@@ -17,25 +17,23 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.primitives.Primitives;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
@@ -45,123 +43,136 @@ import com.google.common.primitives.Primitives;
* <li>org.bukkit.entity.Player to net.minecraft.server.EntityPlayer</li>
* <li>org.bukkit.World to net.minecraft.server.WorldServer</li>
* </ul>
*
*
* @author Kristian
*/
public class BukkitUnwrapper implements Unwrapper {
private static BukkitUnwrapper DEFAULT;
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
private static final Map<Class<?>, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap<Class<?>, Unwrapper>();
private static BukkitUnwrapper DEFAULT;
// The current error reporter
private final ErrorReporter reporter;
/**
* Retrieve the default instance of the Bukkit unwrapper.
* @return The default instance.
*/
public static BukkitUnwrapper getInstance() {
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
// Also recreate the unwrapper if the error reporter has changed
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
DEFAULT = new BukkitUnwrapper(currentReporter);
}
return DEFAULT;
}
/**
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
*/
public BukkitUnwrapper() {
this(ProtocolLibrary.getErrorReporter());
}
/**
* Construct a new Bukkit unwrapper with the given error reporter.
*
* @param reporter - the error reporter to use.
*/
public BukkitUnwrapper(ErrorReporter reporter) {
this.reporter = reporter;
this.reporter = reporter;
}
/**
* Retrieve the default instance of the Bukkit unwrapper.
*
* @return The default instance.
*/
public static BukkitUnwrapper getInstance() {
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
// Also recreate the unwrapper if the error reporter has changed
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
DEFAULT = new BukkitUnwrapper(currentReporter);
}
return DEFAULT;
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
@SuppressWarnings("unchecked")
@Override
public Object unwrapItem(Object wrappedObject) {
// Special case
if (wrappedObject == null)
if (wrappedObject == null) {
return null;
}
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
// No need to unwrap primitives
if (currentClass.isPrimitive() || currentClass.equals(String.class))
if (currentClass.isPrimitive() || currentClass.equals(String.class)) {
return null;
}
// Next, check for types that doesn't have a getHandle()
if (wrappedObject instanceof Collection) {
return handleCollection((Collection<Object>) wrappedObject);
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
return null;
}
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
// Retrieve the handle
if (specificUnwrapper != null)
if (specificUnwrapper != null) {
return specificUnwrapper.unwrapItem(wrappedObject);
else
} else {
return null;
}
}
// Handle a collection of items
private Object handleCollection(Collection<Object> wrappedObject) {
@SuppressWarnings("unchecked")
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
if (copy != null) {
// Unwrap every element
for (Object element : wrappedObject) {
copy.add(unwrapItem(element));
}
return copy;
} else {
// Impossible
return null;
}
}
/**
* Retrieve a cached class unwrapper for the given class.
*
* @param type - the type of the class.
* @return An unwrapper for the given class.
*/
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
// See if we're already determined this
if (unwrapperCache.containsKey(type)) {
if (UNWRAPPER_CACHE.containsKey(type)) {
// We will never remove from the cache, so this ought to be thread safe
return unwrapperCache.get(type);
return UNWRAPPER_CACHE.get(type);
}
try {
final Method find = type.getMethod("getHandle");
// It's thread safe, as getMethod should return the same handle
Unwrapper methodUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
if (wrappedObject instanceof Class) {
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
}
return find.invoke(wrappedObject);
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
@@ -173,14 +184,14 @@ public class BukkitUnwrapper implements Unwrapper {
// This is really bad
throw new RuntimeException("Minecraft error.", e);
}
return null;
}
};
unwrapperCache.put(type, methodUnwrapper);
UNWRAPPER_CACHE.put(type, methodUnwrapper);
return methodUnwrapper;
} catch (SecurityException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
@@ -188,16 +199,18 @@ public class BukkitUnwrapper implements Unwrapper {
} catch (NoSuchMethodException e) {
// Maybe it's a proxy?
Unwrapper proxyUnwrapper = getProxyUnwrapper(type);
if (proxyUnwrapper != null)
if (proxyUnwrapper != null) {
return proxyUnwrapper;
}
// Try getting the field unwrapper too
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
if (fieldUnwrapper != null)
if (fieldUnwrapper != null) {
return fieldUnwrapper;
else
} else {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
}
}
// Default method
@@ -211,22 +224,19 @@ public class BukkitUnwrapper implements Unwrapper {
if (Player.class.isAssignableFrom(type)) {
final Method getHandle = MinecraftReflection.getCraftPlayerClass().getMethod("getHandle");
Unwrapper unwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrapped) {
Unwrapper unwrapper = wrapped -> {
try {
return getHandle.invoke(((Player) wrapped).getPlayer());
} catch (Throwable ex) {
try {
return getHandle.invoke(((Player) wrapped).getPlayer());
} catch (Throwable ex) {
try {
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
} catch (ReflectiveOperationException ex1) {
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
}
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
} catch (ReflectiveOperationException ex1) {
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
}
}
};
unwrapperCache.put(type, unwrapper);
UNWRAPPER_CACHE.put(type, unwrapper);
return unwrapper;
}
} catch (Throwable ignored) {
@@ -237,46 +247,42 @@ public class BukkitUnwrapper implements Unwrapper {
/**
* Retrieve a cached unwrapper using the handle field.
*
* @param type - a cached field unwrapper.
* @return The cached field unwrapper.
*/
private Unwrapper getFieldUnwrapper(final Class<?> type) {
final Field find = FieldUtils.getField(type, "handle", true);
// See if we succeeded
if (find != null) {
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(type, "handle", null);
if (accessor != null) {
Unwrapper fieldUnwrapper = new Unwrapper() {
@Override
public Object unwrapItem(Object wrappedObject) {
try {
if (wrappedObject instanceof Class)
return checkClass((Class<?>) wrappedObject, type, find.getType());
return FieldUtils.readField(find, wrappedObject, true);
} catch (IllegalAccessException e) {
if (wrappedObject instanceof Class) {
return checkClass((Class<?>) wrappedObject, type, accessor.getField().getType());
}
return accessor.get(wrappedObject);
} catch (IllegalStateException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e)
.callerParam(wrappedObject, accessor.getField())
);
return null;
}
}
};
unwrapperCache.put(type, fieldUnwrapper);
UNWRAPPER_CACHE.put(type, fieldUnwrapper);
return fieldUnwrapper;
} else {
// Inform about this too
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(type)
);
return null;
}
}
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
if (expected.isAssignableFrom(input)) {
return result;
}
return null;
}
}
@@ -1,149 +0,0 @@
/*
* 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.injector;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
/**
* Represents a single delayed task.
*
* @author Kristian
*/
public class DelayedSingleTask {
protected int taskID = -1;
protected Plugin plugin;
protected BukkitScheduler scheduler;
protected boolean closed;
/**
* Create a single task scheduler.
* @param plugin - owner plugin.
*/
public DelayedSingleTask(Plugin plugin) {
this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler();
}
/**
* Create a single task scheduler.
* @param plugin - owner plugin.
* @param scheduler - specialized scheduler.
*/
public DelayedSingleTask(Plugin plugin, BukkitScheduler scheduler) {
this.plugin = plugin;
this.scheduler = scheduler;
}
/**
* Schedule a single task for execution.
* <p>
* Any previously scheduled task will be automatically cancelled.
* <p>
* Note that a tick delay of zero will execute the task immediately.
*
* @param ticksDelay - number of ticks before the task is executed.
* @param task - the task to schedule.
* @return TRUE if the task was successfully scheduled or executed, FALSE otherwise.
*/
public boolean schedule(long ticksDelay, Runnable task) {
if (ticksDelay < 0)
throw new IllegalArgumentException("Tick delay cannot be negative.");
if (task == null)
throw new IllegalArgumentException("task cannot be NULL");
if (closed)
return false;
// Special case
if (ticksDelay == 0) {
task.run();
return true;
}
// Boilerplate, boilerplate
final Runnable dispatch = task;
// Don't run multiple tasks!
cancel();
taskID = scheduler.scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
dispatch.run();
taskID = -1;
}
}, ticksDelay);
return isRunning();
}
/**
* Whether or not a future task is scheduled to be executed.
* @return TRUE if a current task has been scheduled for execution, FALSE otherwise.
*/
public boolean isRunning() {
return taskID >= 0;
}
/**
* Cancel a future task from being executed.
* @return TRUE if a task was cancelled, FALSE otherwise.
*/
public boolean cancel() {
if (isRunning()) {
scheduler.cancelTask(taskID);
taskID = -1;
return true;
} else {
return false;
}
}
/**
* Retrieve the raw task ID.
* @return Raw task ID, or negative one if no task has been scheduled.
*/
public int getTaskID() {
return taskID;
}
/**
* Retrieve the plugin this task belongs to.
* @return The plugin scheduling the current taks.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Stop the current task and all future tasks scheduled by this instance.
*/
public synchronized void close() {
if (!closed) {
cancel();
plugin = null;
scheduler = null;
closed = true;
}
}
@Override
protected void finalize() throws Throwable {
close();
}
}
@@ -17,10 +17,6 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
@@ -32,7 +28,16 @@ import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.Validate;
import org.bukkit.World;
@@ -45,54 +50,106 @@ import org.bukkit.entity.Player;
* @author Kristian
*/
class EntityUtilities {
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private static final EntityUtilities INSTANCE = new EntityUtilities();
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
private final Map<Class<?>, MethodAccessor> scanPlayersMethods = new ConcurrentHashMap<>();
private final Map<Class<?>, FieldAccessor> trackedEntityFields = new ConcurrentHashMap<>();
private FieldAccessor chunkMapField;
private FieldAccessor entityTrackerField;
private FieldAccessor trackedPlayersField;
private FieldAccessor trackedEntitiesField;
private MethodAccessor getEntity;
private MethodAccessor getChunkProvider;
private EntityUtilities() {
}
public static EntityUtilities getInstance() {
return INSTANCE;
}
private EntityUtilities() { }
private FieldAccessor entityTrackerField;
private FieldAccessor trackedEntitiesField;
private FieldAccessor trackedPlayersField;
private Map<Class<?>, MethodAccessor> scanPlayersMethods = new HashMap<>();
public void updateEntity(Entity entity, List<Player> observers) {
if (entity == null || !entity.isValid()) {
return;
}
Collection<?> trackedPlayers = getTrackedPlayers(entity);
List<Object> nmsPlayers = unwrapBukkit(observers);
List<Object> removingEntries = MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ?
getPlayerConnections(nmsPlayers) : nmsPlayers;
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
List<Object> nmsPlayers = this.unwrapBukkit(observers);
List<Object> removingEntries =
MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ? this.getPlayerConnections(nmsPlayers) : nmsPlayers;
trackedPlayers.removeAll(removingEntries);
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
// there can be multiple different entity tracker entry impls, see GH-732....
scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers).invoke(trackerEntry, nmsPlayers);
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
this.scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers)
.invoke(trackerEntry, nmsPlayers);
}
public Entity getEntity(World world, int id) {
// new tracker registers the entity before sending out any packets - we can use the supported getter method here
if (NEW_TRACKER) {
Object level = BukkitUnwrapper.getInstance().unwrapItem(world);
if (this.getEntity == null) {
Method entityGetter = FuzzyReflection.fromObject(level).getMethodByReturnTypeAndParameters(
"getEntity",
MinecraftReflection.getEntityClass(),
int.class);
this.getEntity = Accessors.getMethodAccessor(entityGetter);
}
Object entity = this.getEntity.invoke(level, id);
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
// old tracker - now the pain begins
Object trackerEntry = this.getEntityTrackerEntry(world, id);
if (trackerEntry == null) {
// not tracked
return null;
}
FieldAccessor trackedEntityAccessor = this.trackedEntityFields.computeIfAbsent(trackerEntry.getClass(), clz -> {
try {
// try to get the first entity field from the tracker class
Field entityField = FuzzyReflection.fromClass(clz, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getEntityClass())
.build());
return Accessors.getFieldAccessor(entityField);
} catch (IllegalArgumentException exception) {
// no such field, try the base tracker class instead
Class<?> trackerClass = MinecraftReflection.getEntityTrackerClass();
Field entityField = FuzzyReflection.fromClass(trackerClass, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getEntityClass())
.build());
return Accessors.getFieldAccessor(entityField);
}
});
Object entity = trackedEntityAccessor.get(trackerEntry);
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
private MethodAccessor findScanPlayers(Class<?> trackerClass) {
MethodAccessor candidate = Accessors.getMethodAcccessorOrNull(trackerClass, "scanPlayers");
MethodAccessor candidate = Accessors.getMethodAccessorOrNull(trackerClass, "scanPlayers");
if (candidate != null) {
return candidate;
}
FuzzyReflection fuzzy = FuzzyReflection.fromClass(trackerClass, true);
return Accessors.getMethodAccessor(
fuzzy.getMethod(
FuzzyMethodContract.newBuilder().returnTypeVoid().parameterExactArray(List.class).build()));
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeVoid()
.parameterExactArray(List.class)
.build()));
}
/**
* Retrieve every client that is receiving information about a given entity.
*
* @param entity - the entity that is being tracked.
* @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed.
@@ -103,7 +160,7 @@ class EntityUtilities {
}
List<Player> result = new ArrayList<>();
Collection<?> trackedPlayers = getTrackedPlayers(entity);
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
// Wrap every player - we also ensure that the underlying tracker list is immutable
for (Object tracker : trackedPlayers) {
@@ -120,68 +177,63 @@ class EntityUtilities {
private Collection<?> getTrackedPlayers(Entity entity) {
Validate.notNull(entity, "entity cannot be null");
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
Validate.notNull(trackerEntry, "Could not find entity trackers for " + entity);
if (trackedPlayersField == null) {
trackedPlayersField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
if (this.trackedPlayersField == null) {
this.trackedPlayersField = Accessors.getFieldAccessor(
FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
}
Validate.notNull(trackedPlayersField, "Could not find trackedPlayers field");
Validate.notNull(this.trackedPlayersField, "Could not find trackedPlayers field");
Object value = trackedPlayersField.get(trackerEntry);
Object value = this.trackedPlayersField.get(trackerEntry);
if (value instanceof Collection) {
return (Collection<?>) value;
} else if (value instanceof Map) {
return ((Map<?, ?>) value).keySet();
} else {
// Please. No more changes.
throw new IllegalStateException("trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass());
throw new IllegalStateException(
"trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass());
}
}
private MethodAccessor getChunkProvider;
private FieldAccessor chunkMapField;
@SuppressWarnings("unchecked")
private Object getNewEntityTracker(Object worldServer, int entityId) {
if (getChunkProvider == null) {
if (this.getChunkProvider == null) {
Class<?> chunkProviderClass = MinecraftReflection.getChunkProviderServer();
getChunkProvider = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(worldServer.getClass(), false).getMethod(
FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
this.getChunkProvider = Accessors.getMethodAccessor(FuzzyReflection.fromClass(worldServer.getClass(), false)
.getMethod(FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
}
Object chunkProvider = getChunkProvider.invoke(worldServer);
Object chunkProvider = this.getChunkProvider.invoke(worldServer);
if (chunkMapField == null) {
if (this.chunkMapField == null) {
Class<?> chunkMapClass = MinecraftReflection.getPlayerChunkMap();
chunkMapField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(chunkProvider.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
this.chunkMapField = Accessors.getFieldAccessor(FuzzyReflection.fromClass(chunkProvider.getClass(), false)
.getField(FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
}
Object playerChunkMap = chunkMapField.get(chunkProvider);
Object playerChunkMap = this.chunkMapField.get(chunkProvider);
if (trackedEntitiesField == null) {
if (this.trackedEntitiesField == null) {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), true).getField(
FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requirePublic()
.typeExact(MinecraftReflection.getInt2ObjectMapClass())
.build()
)
);
this.trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), true)
.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requirePublic()
.typeExact(MinecraftReflection.getInt2ObjectMapClass())
.build()));
} else {
trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
this.trackedEntitiesField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
}
}
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) trackedEntitiesField.get(playerChunkMap);
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) this.trackedEntitiesField.get(playerChunkMap);
return trackedEntities.get(entityId);
}
@@ -190,88 +242,28 @@ class EntityUtilities {
Object worldServer = unwrapper.unwrapItem(world);
if (NEW_TRACKER) {
return getNewEntityTracker(worldServer, entityID);
return this.getNewEntityTracker(worldServer, entityID);
}
if (entityTrackerField == null)
entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
if (this.entityTrackerField == null) {
this.entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
}
// Get the tracker
Object tracker = entityTrackerField.get(worldServer);
Object tracker = this.entityTrackerField.get(worldServer);
// Looking for an IntHashMap in the tracker entry
if (trackedEntitiesField == null) {
trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
if (this.trackedEntitiesField == null) {
this.trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
.getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass()));
}
// Read the map
Object trackedEntities = trackedEntitiesField.get(tracker);
Object trackedEntities = this.trackedEntitiesField.get(tracker);
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
}
private Map<Class<?>, FieldAccessor> trackerFields = new ConcurrentHashMap<>();
private MethodAccessor getEntityFromId;
/**
* Retrieve entity from a ID, even it it's newly created.
* @return The associated entity.
* @throws FieldAccessException Reflection error.
*/
public Entity getEntityFromID(World world, int entityID) {
Validate.notNull(world, "world cannot be null");
Validate.isTrue(entityID >= 0, "entityID cannot be negative");
try {
// first, try to read from the world
// this should be good enough for most cases, but only exists in 1.14+
if (NEW_TRACKER) {
Object worldServer = BukkitUnwrapper.getInstance().unwrapItem(world);
if (getEntityFromId == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(worldServer.getClass(), false);
getEntityFromId = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(int.class)
.returnTypeExact(MinecraftReflection.getEntityClass())
.build()));
}
Object entity = getEntityFromId.invoke(worldServer, entityID);
if (entity != null) {
return (Entity) MinecraftReflection.getBukkitEntity(entity);
}
}
// then go into the trackers
Object trackerEntry = getEntityTrackerEntry(world, entityID);
Object tracker = null;
if (trackerEntry != null) {
// plugins like citizens will use their own tracker class, so cache the result
FieldAccessor trackerField = trackerFields.computeIfAbsent(trackerEntry.getClass(), x -> {
// get the first entity field
try {
return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntry.getClass(), true)
.getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build()));
} catch (Exception ex) {
// try with the default class
Class<?> trackerEntryClass = MinecraftReflection.getEntityTrackerClass();
return Accessors.getFieldAccessor(FuzzyReflection.fromClass(trackerEntryClass, true)
.getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityClass()).build()));
}
});
tracker = trackerField.get(trackerEntry);
}
// If the tracker is NULL, we'll just assume this entity doesn't exist
return tracker != null ? (Entity) MinecraftReflection.getBukkitEntity(tracker) : null;
} catch (Exception e) {
throw new FieldAccessException("Cannot find entity from ID " + entityID + ".", e);
}
}
private List<Object> getPlayerConnections(List<Object> nmsPlayers) {
List<Object> connections = new ArrayList<>(nmsPlayers.size());
nmsPlayers.forEach(nmsPlayer -> connections.add(MinecraftFields.getPlayerConnection(nmsPlayer)));
@@ -279,17 +271,18 @@ class EntityUtilities {
}
private List<Object> unwrapBukkit(List<Player> players) {
List<Object> output = Lists.newArrayList();
final List<Object> output = new ArrayList<>();
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
// Get the NMS equivalent
for (Player player : players) {
Object result = unwrapper.unwrapItem(player);
if (result != null)
if (result != null) {
output.add(result);
else
} else {
throw new IllegalArgumentException("Cannot unwrap item " + player);
}
}
return output;
@@ -2,16 +2,16 @@
* 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
* 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.
* 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
* 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
*/
@@ -19,7 +19,7 @@ package com.comphenix.protocol.injector;
/**
* The current player phase. This is used to limit the number of different injections.
*
*
* @author Kristian
*/
public enum GamePhase {
@@ -27,27 +27,29 @@ public enum GamePhase {
* Only listen for packets sent or received before a player has logged in.
*/
LOGIN,
/**
* Only listen for packets sent or received after a player has logged in.
*/
PLAYING,
/**
* Listen for every sent and received packet.
*/
BOTH;
/**
* Determine if the current value represents the login phase.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasLogin() {
return this == LOGIN || this == BOTH;
}
/**
* Determine if the current value represents the playing phase.
*
* @return TRUE if it does, FALSE otherwise.
*/
public boolean hasPlaying() {
@@ -1,49 +1,40 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.ProtocolManager;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.ProtocolManager;
/**
* Yields access to the internal hook configuration.
*
*
* @author Kristian
*/
public interface InternalManager extends ProtocolManager {
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook();
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook);
/**
* Register this protocol manager on Bukkit.
*
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
* @param plugin - the parent plugin.
*/
public void registerEvents(PluginManager manager, final Plugin plugin);
void registerEvents(PluginManager manager, final Plugin plugin);
/**
* Called when ProtocolLib is closing.
*/
public void close();
void close();
/**
* Determine if debug mode is enabled.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isDebug();
boolean isDebug();
/**
* Set whether or not debug mode is enabled.
*
* @param debug - TRUE if it is, FALSE otherwise.
*/
public void setDebug(boolean debug);
void setDebug(boolean debug);
}
@@ -2,16 +2,16 @@
* 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
* 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.
* 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
* 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
*/
@@ -22,25 +22,28 @@ import com.comphenix.protocol.events.PacketEvent;
/**
* Represents an object that initiate the packet listeners.
*
*
* @author Kristian
*/
public interface ListenerInvoker {
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
void invokePacketRecieving(PacketEvent event);
void invokePacketReceiving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated type of a packet.
*
* @param packet - the packet.
* @return The packet type.
*/
@@ -1,108 +1,74 @@
package com.comphenix.protocol.injector;
import java.util.List;
import java.util.PriorityQueue;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketOutputHandler;
import com.comphenix.protocol.events.PacketPostListener;
import com.comphenix.protocol.events.ScheduledPacket;
import java.util.Deque;
/**
* Represents a processor for network markers.
*
* @author Kristian
*/
public class NetworkProcessor {
private ErrorReporter reporter;
private final ErrorReporter reporter;
/**
* Construct a new network processor.
*
* @param reporter - the reporter.
*/
public NetworkProcessor(ErrorReporter reporter) {
this.reporter = reporter;
}
/**
* Process the serialized packet byte array with the given network marker.
* @param event - current packet event.
* @param marker - the network marker.
* @param input - the input array.
* @return The output array.
*/
public byte[] processOutput(PacketEvent event, NetworkMarker marker, final byte[] input) {
// Bit of a hack - but we need the performance
PriorityQueue<PacketOutputHandler> handlers = (PriorityQueue<PacketOutputHandler>)
marker.getOutputHandlers();
byte[] output = input;
// Let each handler prepare the actual output
while (!handlers.isEmpty()) {
PacketOutputHandler handler = handlers.poll();
try {
byte[] changed = handler.handle(event, output);
// Don't break just because a plugin returned NULL
if (changed != null) {
output = changed;
} else {
throw new IllegalStateException("Handler cannot return a NULL array.");
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e);
}
}
return output;
}
/**
* Invoke the post listeners and packet transmission, if any.
* @param event - PacketEvent
*
* @param event - PacketEvent
* @param marker - the network marker, or NULL.
*/
public void invokePostEvent(PacketEvent event, NetworkMarker marker) {
if (marker == null)
if (marker == null) {
return;
if (NetworkMarker.hasPostListeners(marker)) {
}
if (event != null && NetworkMarker.hasPostListeners(marker)) {
// Invoke every sent listener
for (PacketPostListener listener : marker.getPostListeners()) {
try {
listener.onPostEvent(event);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
} catch (OutOfMemoryError | ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
}
}
}
sendScheduledPackets(marker);
this.sendScheduledPackets(marker);
}
/**
* Send any scheduled packets.
*
* @param marker - the network marker.
*/
private void sendScheduledPackets(NetworkMarker marker) {
// Next, invoke post packet transmission
List<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
Deque<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
if (scheduled != null) {
for (ScheduledPacket packet : scheduled) {
ScheduledPacket packet;
while ((packet = scheduled.poll()) != null) {
packet.schedule(manager);
}
}
}
}
}
@@ -17,11 +17,6 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Optional;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.RethrowErrorReporter;
import com.comphenix.protocol.events.PacketContainer;
@@ -31,70 +26,117 @@ import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.Primitives;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* A packet constructor that uses an internal Minecraft.
* @author Kristian
*
* @author Kristian
*/
public class PacketConstructor {
/**
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
* <p>
* Remember to call withPacket().
*/
public static PacketConstructor DEFAULT = new PacketConstructor(null);
// The constructor method that's actually responsible for creating the packet
private Constructor<?> constructorMethod;
private final Constructor<?> constructorMethod;
// Used to unwrap Bukkit objects
private final List<Unwrapper> unwrappers;
// The packet ID
private PacketType type;
// Used to unwrap Bukkit objects
private List<Unwrapper> unwrappers;
// Parameters that need to be unwrapped
private Unwrapper[] paramUnwrapper;
private PacketConstructor(Constructor<?> constructorMethod) {
this.constructorMethod = constructorMethod;
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter() ));
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter()));
this.unwrappers.addAll(BukkitConverters.getUnwrappers());
}
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> unwrappers, Unwrapper[] paramUnwrapper) {
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> unwrappers,
Unwrapper[] paramUnwrapper) {
this.type = type;
this.constructorMethod = constructorMethod;
this.unwrappers = unwrappers;
this.paramUnwrapper = paramUnwrapper;
}
// Determine if a method with the types 'params' can be called with 'types'
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
Class<?> inputType = types[i];
Class<?> paramType = params[i];
// The input type is always wrapped
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
// Wrap it
paramType = Primitives.wrap(paramType);
}
// Compare assignability
if (!paramType.isAssignableFrom(inputType)) {
return false;
}
}
return true;
}
// Parameter count must match
return false;
}
/**
* Retrieve the class of an object, or just the class if it already is a class object.
*
* @param obj - the object.
* @return The class of an object.
*/
public static Class<?> getClass(Object obj) {
if (obj instanceof Class) {
return (Class<?>) obj;
}
return obj.getClass();
}
public ImmutableList<Unwrapper> getUnwrappers() {
return ImmutableList.copyOf(unwrappers);
}
/**
* Retrieve the id of the packets this constructor creates.
* <p>
* Deprecated: Use {@link #getType()} instead.
*
* @return The ID of the packets this constructor will create.
*/
@Deprecated
public int getPacketID() {
return type.getCurrentId();
}
/**
* Retrieve the type of the packets this constructor creates.
*
* @return The type of the created packets.
*/
public PacketType getType() {
return type;
}
/**
* Return a copy of the current constructor with a different list of unwrappers.
*
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
* @return A constructor with a different set of unwrappers.
*/
@@ -106,7 +148,8 @@ public class PacketConstructor {
* Create a packet constructor that creates packets using the given types.
* <p>
* Note that if you pass a Class as a value, it will use its type directly.
* @param type - the type of the packet to create.
*
* @param type - the type of the packet to create.
* @param values - the values that will match each parameter in the desired constructor.
* @return A packet constructor with these types.
* @throws IllegalArgumentException If no packet constructor could be created with these types.
@@ -115,15 +158,15 @@ public class PacketConstructor {
Class<?>[] types = new Class<?>[values.length];
Throwable lastException = null;
Unwrapper[] paramUnwrapper = new Unwrapper[values.length];
for (int i = 0; i < types.length; i++) {
// Default type
if (values[i] != null) {
types[i] = PacketConstructor.getClass(values[i]);
for (Unwrapper unwrapper : unwrappers) {
Object result = null;
try {
result = unwrapper.unwrapItem(values[i]);
} catch (OutOfMemoryError e) {
@@ -133,7 +176,7 @@ public class PacketConstructor {
} catch (Throwable e) {
lastException = e;
}
// Update type we're searching for
if (result != null) {
types[i] = PacketConstructor.getClass(result);
@@ -141,7 +184,7 @@ public class PacketConstructor {
break;
}
}
} else {
// Try it
types[i] = Object.class;
@@ -161,14 +204,15 @@ public class PacketConstructor {
}
throw new IllegalArgumentException("No suitable constructor could be found.", lastException);
}
/**
* Construct a packet using the special builtin Minecraft constructors.
*
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
* @return The created packet.
* @throws FieldAccessException Failure due to a security limitation.
* @throws FieldAccessException Failure due to a security limitation.
* @throws IllegalArgumentException Arguments doesn't match the constructor.
* @throws RuntimeException Minecraft threw an exception.
* @throws RuntimeException Minecraft threw an exception.
*/
public PacketContainer createPacket(Object... values) throws FieldAccessException {
try {
@@ -178,10 +222,10 @@ public class PacketConstructor {
values[i] = paramUnwrapper[i].unwrapItem(values[i]);
}
}
Object nmsPacket = constructorMethod.newInstance(values);
return new PacketContainer(type, nmsPacket);
} catch (IllegalArgumentException e) {
throw e;
} catch (InstantiationException e) {
@@ -192,57 +236,20 @@ public class PacketConstructor {
throw new RuntimeException("Minecraft error.", e);
}
}
// Determine if a method with the types 'params' can be called with 'types'
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
// Determine if the types are similar
if (params.length == types.length) {
for (int i = 0; i < params.length; i++) {
Class<?> inputType = types[i];
Class<?> paramType = params[i];
// The input type is always wrapped
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
// Wrap it
paramType = Primitives.wrap(paramType);
}
// Compare assignability
if (!paramType.isAssignableFrom(inputType)) {
return false;
}
}
return true;
}
// Parameter count must match
return false;
}
/**
* Retrieve the class of an object, or just the class if it already is a class object.
* @param obj - the object.
* @return The class of an object.
*/
public static Class<?> getClass(Object obj) {
if (obj instanceof Class)
return (Class<?>) obj;
return obj.getClass();
}
/**
* Represents a unwrapper for a constructor parameter.
*
*
* @author Kristian
*/
public static interface Unwrapper {
/**
* Convert the given wrapped object to the equivalent net.minecraft.server object.
* <p>
* Note that we may pass in a class instead of object - in that case, the unwrapper should
* return the equivalent NMS class.
* Note that we may pass in a class instead of object - in that case, the unwrapper should return the equivalent NMS
* class.
*
* @param wrappedObject - wrapped object or class.
* @return The equivalent net.minecraft.server object or class.
*/
@@ -1,191 +1,122 @@
package com.comphenix.protocol.injector;
import javax.annotation.Nonnull;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.utility.MinecraftVersion;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
public class PacketFilterBuilder {
private ClassLoader classLoader;
private Server server;
private Plugin library;
private MinecraftVersion mcVersion;
private DelayedSingleTask unhookTask;
private ErrorReporter reporter;
// Whether or not we need to enable Netty
private AsyncFilterManager asyncManager;
private boolean nettyEnabled;
/**
* Update the current class loader.
* @param classLoader - current class loader.
* @return This builder, for chaining.
*/
public PacketFilterBuilder classLoader(@Nonnull ClassLoader classLoader) {
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
this.classLoader = classLoader;
return this;
}
/**
* Set the current server.
*
* @param server - current server.
* @return This builder, for chaining.
*/
public PacketFilterBuilder server(@Nonnull Server server) {
if (server == null)
throw new IllegalArgumentException("server cannot be NULL.");
this.server = server;
return this;
}
/**
* Set a reference to the plugin instance of ProtocolLib.
*
* @param library - plugin instance.
* @return This builder, for chaining.
*/
public PacketFilterBuilder library(@Nonnull Plugin library) {
if (library == null)
throw new IllegalArgumentException("library cannot be NULL.");
this.library = library;
return this;
}
/**
* Set the current Minecraft version.
*
* @param mcVersion - Minecraft version.
* @return This builder, for chaining.
* @return This builder, for chaining.
*/
public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) {
if (mcVersion == null)
throw new IllegalArgumentException("minecraftVersion cannot be NULL.");
this.mcVersion = mcVersion;
return this;
}
/**
* Set the task used to delay unhooking when ProtocolLib is no in use.
* @param unhookTask - the unhook task.
* @return This builder, for chaining.
*/
public PacketFilterBuilder unhookTask(@Nonnull DelayedSingleTask unhookTask) {
if (unhookTask == null)
throw new IllegalArgumentException("unhookTask cannot be NULL.");
this.unhookTask = unhookTask;
return this;
}
/**
* Set the error reporter.
*
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) {
if (reporter == null)
throw new IllegalArgumentException("reporter cannot be NULL.");
this.reporter = reporter;
return this;
}
/**
* Determine if we should prepare to hook Netty in Spigot.
* <p>
* This is calculated in the {@link #build()} method.
* @return TRUE if we should, FALSE otherwise.
*/
public boolean isNettyEnabled() {
return nettyEnabled;
}
/**
* Retrieve the class loader set in this builder.
* @return The class loader.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
/**
* Retrieve the current CraftBukkit server.
*
* @return Current server.
*/
public Server getServer() {
return server;
return this.server;
}
/**
* Retrieve a reference to the current ProtocolLib instance.
*
* @return ProtocolLib.
*/
public Plugin getLibrary() {
return library;
return this.library;
}
/**
* Retrieve the current Minecraft version.
*
* @return Current version.
*/
public MinecraftVersion getMinecraftVersion() {
return mcVersion;
}
/**
* Retrieve the task that is used to delay unhooking when ProtocolLib is no in use.
* @return The unhook task.
*/
public DelayedSingleTask getUnhookTask() {
return unhookTask;
return this.mcVersion;
}
/**
* Retrieve the error reporter.
*
* @return Error reporter.
*/
public ErrorReporter getReporter() {
return reporter;
return this.reporter;
}
/**
* Retrieve the asynchronous manager.
* <p>
* This is first constructed the {@link #build()} method.
*
* @return The asynchronous manager.
*/
public AsyncFilterManager getAsyncManager() {
return asyncManager;
return this.asyncManager;
}
/**
* Create a new packet filter manager.
*
* @return A new packet filter manager.
*/
public InternalManager build() {
if (reporter == null)
if (this.reporter == null) {
throw new IllegalArgumentException("reporter cannot be NULL.");
if (classLoader == null)
throw new IllegalArgumentException("classLoader cannot be NULL.");
asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
nettyEnabled = false;
}
return buildInternal();
}
/**
* Construct a new packet filter manager without checking for Netty.
* @return A new packet filter manager.
*/
private PacketFilterManager buildInternal() {
PacketFilterManager manager = new PacketFilterManager(this);
// It's a cyclic reference, but it's too late to fix now
asyncManager.setManager(manager);
return manager;
this.asyncManager = new AsyncFilterManager(this.reporter, this.server.getScheduler());
return new PacketFilterManager(this);
}
}
File diff suppressed because it is too large. Load diff
@@ -1,31 +0,0 @@
package com.comphenix.protocol.injector;
/**
* Sets the inject hook type. Different types allow for maximum compatibility.
* @author Kristian
*/
public enum PlayerInjectHooks {
/**
* The injection hook that does nothing. Set when every other inject hook fails.
*/
NONE,
/**
* Override the network handler object itself. Only works in 1.3.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_MANAGER_OBJECT,
/**
* Override the packet queue lists in NetworkHandler.
* <p>
* Cannot intercept MapChunk packets.
*/
NETWORK_HANDLER_FIELDS,
/**
* Override the server handler object. Versatile, but a tad slower.
*/
NETWORK_SERVER_OBJECT;
}
@@ -1,58 +0,0 @@
/*
* 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.injector;
/**
* Invoked when attempting to use a player that has already logged out.
*
* @author Kristian
*/
public class PlayerLoggedOutException extends RuntimeException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 4889257862160145234L;
public PlayerLoggedOutException() {
// Default error message
super("Cannot inject a player that has already logged out.");
}
public PlayerLoggedOutException(String message, Throwable cause) {
super(message, cause);
}
public PlayerLoggedOutException(String message) {
super(message);
}
public PlayerLoggedOutException(Throwable cause) {
super(cause);
}
/**
* Construct an exception from a formatted message.
* @param message - the message to format.
* @param params - parameters.
* @return The formated exception
*/
public static PlayerLoggedOutException fromFormat(String message, Object... params) {
return new PlayerLoggedOutException(String.format(message, params));
}
}
@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import com.google.common.collect.ImmutableSet;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLoadOrder;
@@ -55,12 +56,12 @@ class PluginVerifier {
/**
* Contains a list of plugins that will detect and use ProtocolLib dynamically, instead of relying on the dependency system.
*/
private static final Set<String> DYNAMIC_DEPENDENCY = Sets.newHashSet("mcore", "MassiveCore");
private static final Set<String> DYNAMIC_DEPENDENCY = ImmutableSet.of("mcore", "MassiveCore");
/**
* Set of plugins that have been loaded after ProtocolLib.
*/
private final Set<String> loadedAfter = new HashSet<String>();
private final Set<String> loadedAfter = new HashSet<>();
/**
* Reference to ProtocolLib.
@@ -201,7 +202,7 @@ class PluginVerifier {
if (list == null)
return Collections.emptySet();
else
return Sets.newHashSet(list);
return new HashSet<>(list);
}
// Avoid cycles. DFS.
@@ -50,7 +50,7 @@ public class PrioritizedListener<TListener> implements Comparable<PrioritizedLis
@Override
public boolean equals(Object obj) {
// We only care about the listener - priority itself should not make a difference
if(obj instanceof PrioritizedListener){
if (obj instanceof PrioritizedListener) {
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
return Objects.equal(listener, other.listener);
} else {
@@ -17,77 +17,93 @@
package com.comphenix.protocol.injector;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import java.security.PublicKey;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
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.StructureModifier;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.ZeroBuffer;
import io.netty.buffer.ByteBuf;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.google.common.base.Preconditions;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import io.netty.buffer.ByteBuf;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
/**
* Caches structure modifiers.
*
* @author Kristian
*/
public class StructureCache {
// Structure modifiers
private static final ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers = new ConcurrentHashMap<>();
// invocation cache for packets
private static final ConcurrentMap<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
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 boolean trickTried;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER;
private static boolean TRICK_TRIED = false;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
private static final Set<PacketType> compiling = new HashSet<>();
public static Object newPacket(Class<?> clazz) {
Object result = DefaultInstances.DEFAULT.create(clazz);
if (result == null) {
return PACKET_INSTANCE_CREATORS.computeIfAbsent(clazz, $ -> {
ConstructorAccessor accessor = Accessors.getConstructorAccessorOrNull(clazz, MinecraftReflection.getPacketDataSerializerClass());
if (accessor != null) {
return () -> {
public static Object newPacket(Class<?> packetClass) {
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
// prefer construction via PacketDataSerializer constructor on 1.17 and above
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
clazz,
MinecraftReflection.getPacketDataSerializerClass());
if (serializerAccessor != null) {
// check if the method is possible
if (tryInitTrickDataSerializer()) {
try {
return accessor.invoke(MinecraftReflection.getPacketDataSerializer(new ZeroBuffer()));
} catch (Exception exception) {
// try trick nms around as they want a non-null compound in the map_chunk packet constructor
ConstructorAccessor trickyDataSerializerAccessor = getTrickDataSerializerOrNull();
if (trickyDataSerializerAccessor != null) {
return accessor.invoke(trickyDataSerializerAccessor.invoke(new ZeroBuffer()));
}
// the tricks are over
throw new IllegalArgumentException("Unable to create packet " + clazz, exception);
}
};
}
throw new IllegalArgumentException("No matching constructor to create packet in class " + clazz);
}).get();
}
// first try with the base accessor
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
serializerAccessor.invoke(serializer); // throwaway instance, for testing
return result;
// 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
}
}
}
}
}
// try via DefaultInstances as fallback
return () -> {
Object packetInstance = DefaultInstances.DEFAULT.create(clazz);
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + clazz);
return packetInstance;
};
});
return packetConstructor.get();
}
/**
* Creates an empty Minecraft packet of the given type.
*
* @param type - packet type.
* @return Created packet.
*/
@@ -95,106 +111,92 @@ public class StructureCache {
return newPacket(PacketRegistry.getPacketClassFromType(type));
}
/**
* Retrieve a cached structure modifier for the given packet type.
* @param type - packet type.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(PacketType type) {
// Compile structures by default
return getStructure(type, true);
}
/**
* Retrieve a cached structure modifier given a packet type.
*
* @param packetType - packet type.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(Class<?> packetType) {
// Compile structures by default
return getStructure(packetType, true);
}
/**
* Retrieve a cached structure modifier given a packet type.
* @param packetType - packet type.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
// Get the ID from the class
PacketType type = PacketRegistry.getPacketType(packetType);
Preconditions.checkNotNull(type, "No packet type associated with " + packetType);
return getStructure(type, compile);
return getStructure(type);
}
/**
* Retrieve a cached structure modifier for the given packet type.
* @param type - packet type.
* @param compile - whether or not to asynchronously compile the structure modifier.
*
* @param packetType - packet type.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(final PacketType type, boolean compile) {
Preconditions.checkNotNull(type, "type cannot be null");
StructureModifier<Object> result = structureModifiers.get(type);
public static StructureModifier<Object> getStructure(final PacketType packetType) {
Preconditions.checkNotNull(packetType, "type cannot be null");
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<>(
PacketRegistry.getPacketClassFromType(type), MinecraftReflection.getPacketClass(), true);
result = structureModifiers.putIfAbsent(type, value);
// We may end up creating multiple modifiers, but we'll agree on which to use
if (result == null) {
result = value;
}
}
// Automatically compile the structure modifier
if (compile && !(result instanceof CompiledStructureModifier)) {
// Compilation is many orders of magnitude slower than synchronization
synchronized (compiling) {
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
if (!compiling.contains(type) && compiler != null) {
compiler.scheduleCompilation(result,
compiledModifier -> structureModifiers.put(type, compiledModifier));
compiling.add(type);
}
}
}
return result;
return STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);
});
}
/**
* 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.
* Returns a new mocked null data serializer instance, if possible.
*
* @return a new null data serializer instance.
*/
private static ConstructorAccessor getTrickDataSerializerOrNull() {
if (TRICKED_DATA_SERIALIZER == null && !trickTried) {
// ensure that we only try once to create the class
trickTried = true;
try {
// create an empty instance of a nbt tag compound that we can re-use when needed
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()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializer")
.method(ElementMatchers.returns(MinecraftReflection.getNBTCompoundClass())
.and(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())))
.intercept(FixedValue.value(compound))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
TRICKED_DATA_SERIALIZER = Accessors.getConstructorAccessor(generatedClass, ByteBuf.class);
} catch (Exception ignored) {
// can happen if unsupported
}
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.
*/
public static boolean tryInitTrickDataSerializer() {
if (TRICK_TRIED) {
return TRICKED_DATA_SERIALIZER_BASE != null;
}
return TRICKED_DATA_SERIALIZER;
// prevent double init
TRICK_TRIED = true;
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();
// base builder which intercepts a few methods
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.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))
.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_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
// 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) {
}
// didn't work
return false;
}
}
@@ -1,54 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.injector.packet.PacketInjector;
public abstract class AbstractPacketInjector implements PacketInjector {
private PacketTypeSet reveivedFilters;
public AbstractPacketInjector(PacketTypeSet reveivedFilters) {
this.reveivedFilters = reveivedFilters;
}
@Override
public boolean isCancelled(Object packet) {
// No, it's never cancelled
return false;
}
@Override
public void setCancelled(Object packet, boolean cancelled) {
throw new UnsupportedOperationException();
}
@Override
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
reveivedFilters.addType(type);
return true;
}
@Override
public boolean removePacketHandler(PacketType type) {
reveivedFilters.removeType(type);
return true;
}
@Override
public boolean hasPacketHandler(PacketType type) {
return reveivedFilters.contains(type);
}
@Override
public Set<PacketType> getPacketHandlers() {
return reveivedFilters.values();
}
@Override
public void cleanupAll() {
reveivedFilters.clear();
}
}
@@ -1,83 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.io.DataInputStream;
import java.util.Set;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
public abstract class AbstractPlayerHandler implements PlayerInjectionHandler {
protected PacketTypeSet sendingFilters;
public AbstractPlayerHandler(PacketTypeSet sendingFilters) {
this.sendingFilters = sendingFilters;
}
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
sendingFilters.addType(type);
}
@Override
public void removePacketHandler(PacketType type) {
sendingFilters.removeType(type);
}
@Override
public Set<PacketType> getSendingFilters() {
return sendingFilters.values();
}
@Override
public void close() {
sendingFilters.clear();
}
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public boolean canRecievePackets() {
return true;
}
@Override
public PlayerInjectHooks getPlayerHook() {
// Pretend that we do
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
@Override
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
throw new UnsupportedOperationException("This is not needed in Spigot.");
}
@Override
public void checkListener(PacketListener listener) {
// They're all fine!
}
@Override
public void checkListener(Set<PacketListener> listeners) {
// Yes, really
}
}
@@ -1,234 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Callable;
import com.google.common.collect.Lists;
public class BootstrapList implements List<Object> {
private List<Object> delegate;
private ChannelHandler handler;
/**
* Construct a new bootstrap list.
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public BootstrapList(List<Object> delegate, ChannelHandler handler) {
this.delegate = delegate;
this.handler = handler;
// Process all existing bootstraps
for (Object item : this) {
processElement(item);
}
}
@Override
public synchronized boolean add(Object element) {
processElement(element);
return delegate.add(element);
}
@Override
public synchronized boolean addAll(Collection<? extends Object> collection) {
List<Object> copy = Lists.newArrayList(collection);
// Process the collection before we pass it on
for (Object element : copy) {
processElement(element);
}
return delegate.addAll(copy);
}
@Override
public synchronized Object set(int index, Object element) {
Object old = delegate.set(index, element);
// Handle the old future, and the newly inserted future
if (old != element) {
unprocessElement(old);
processElement(element);
}
return old;
}
/**
* Process a single element.
* @param element - the element.
*/
protected void processElement(Object element) {
if (element instanceof ChannelFuture) {
processBootstrap((ChannelFuture) element);
}
}
/**
* Unprocess a single element.
* @param element - the element to unprocess.
*/
protected void unprocessElement(Object element) {
if (element instanceof ChannelFuture) {
unprocessBootstrap((ChannelFuture) element);
}
}
/**
* Process a single channel future.
* @param future - the future.
*/
protected void processBootstrap(ChannelFuture future) {
// Important: Must be addFirst()
future.channel().pipeline().addFirst(handler);
}
/**
* Revert any changes we made to the channel future.
* @param future - the future.
*/
protected void unprocessBootstrap(ChannelFuture future) {
final Channel channel = future.channel();
// For thread safety - see ChannelInjector.close()
channel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
channel.pipeline().remove(handler);
return null;
}
});
}
/**
* Close and revert all changes.
*/
public synchronized void close() {
for (Object element : this)
unprocessElement(element);
}
// Boiler plate
@Override
public synchronized int size() {
return delegate.size();
}
@Override
public synchronized boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public synchronized Iterator<Object> iterator() {
return delegate.iterator();
}
@Override
public synchronized Object[] toArray() {
return delegate.toArray();
}
@Override
public synchronized <T> T[] toArray(T[] a) {
return delegate.toArray(a);
}
@Override
public synchronized boolean remove(Object o) {
return delegate.remove(o);
}
@Override
public synchronized boolean containsAll(Collection<?> c) {
return delegate.containsAll(c);
}
@Override
public synchronized boolean addAll(int index, Collection<? extends Object> c) {
return delegate.addAll(index, c);
}
@Override
public synchronized boolean removeAll(Collection<?> c) {
return delegate.removeAll(c);
}
@Override
public synchronized boolean retainAll(Collection<?> c) {
return delegate.retainAll(c);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@Override
public synchronized Object get(int index) {
return delegate.get(index);
}
@Override
public synchronized void add(int index, Object element) {
delegate.add(index, element);
}
@Override
public synchronized Object remove(int index) {
return delegate.remove(index);
}
@Override
public synchronized int indexOf(Object o) {
return delegate.indexOf(o);
}
@Override
public synchronized int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}
@Override
public synchronized ListIterator<Object> listIterator() {
return delegate.listIterator();
}
@Override
public synchronized ListIterator<Object> listIterator(int index) {
return delegate.listIterator(index);
}
@Override
public synchronized List<Object> subList(int fromIndex, int toIndex) {
return delegate.subList(fromIndex, toIndex);
}
// End boiler plate
}
File diff suppressed because it is too large. Load diff
@@ -1,66 +1,70 @@
package com.comphenix.protocol.injector.netty;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
/**
* Represents a listener for received or sent packets.
*
* @author Kristian
*/
public interface ChannelListener {
/**
* Invoked when a packet is being sent to the client.
* <p>
* This is invoked on the main thread.
*
* @param injector - the channel injector.
* @param packet - the packet.
* @param marker - the network marker.
* @param packet - the packet.
* @param marker - the network marker.
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
*/
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
/**
* Invoked when a packet is being received from a client.
* <p>
* This is invoked on an asynchronous worker thread.
*
* @param injector - the channel injector.
* @param packet - the packet.
* @param marker - the associated network marker, if any.
* @param packet - the packet.
* @param marker - the associated network marker, if any.
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
*/
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
/**
* Determine if there is a packet listener for the given packet.
*
* @param packetClass - the packet class to check.
* @return TRUE if there is such a listener, FALSE otherwise.
*/
public boolean hasListener(Class<?> packetClass);
boolean hasListener(Class<?> packetClass);
/**
* Determine if there is a server packet listener that must be executed on the main thread.
*
* @param packetClass - the packet class to check.
* @return TRUE if there is, FALSE otherwise.
*/
public boolean hasMainThreadListener(Class<?> packetClass);
/**
* Determine if we need the buffer data of a given client side packet.
* @param packetClass - the packet class.
* @return TRUE if we do, FALSE otherwise.
*/
public boolean includeBuffer(Class<?> packetClass);
boolean hasMainThreadListener(Class<?> packetClass);
boolean hasMainThreadListener(PacketType type);
/**
* Retrieve the current error reporter.
*
* @return The error reporter.
*/
public ErrorReporter getReporter();
/**
* Determine if debug mode is enabled.
* @return TRUE if it is, FALSE otherwise.
*/
ErrorReporter getReporter();
/**
* Determine if debug mode is enabled.
*
* @return TRUE if it is, FALSE otherwise.
*/
boolean isDebug();
}
@@ -1,352 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import java.lang.reflect.Field;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.Callable;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.google.common.collect.Maps;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
public abstract class ChannelProxy implements Channel {
// Mark that a certain object does not contain a message field
private static final FieldAccessor MARK_NO_MESSAGE = new FieldAccessor() {
@Override
public void set(Object instance, Object value) { }
@Override
public Object get(Object instance) { return null; }
@Override
public Field getField() { return null; };
};
// Looking up packets in inner classes
private static Map<Class<?>, FieldAccessor> MESSAGE_LOOKUP = Maps.newConcurrentMap();
// The underlying channel
protected Channel delegate;
protected Class<?> messageClass;
// Event loop proxy
private transient EventLoopProxy loopProxy;
public ChannelProxy(Channel delegate, Class<?> messageClass) {
this.delegate = delegate;
this.messageClass = messageClass;
}
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param <T> Type
* @param callable - callable to schedule for execution.
* @param packetAccessor - accessor for modifying the packet in the callable.
* @return The callable that will be scheduled, or NULL to cancel.
*/
protected abstract <T> Callable<T> onMessageScheduled(Callable<T> callable, FieldAccessor packetAccessor);
/**
* Invoked when a packet is scheduled for transmission in the event loop.
* @param runnable - the runnable that contains a packet to be scheduled.
* @param packetAccessor - accessor for modifying the packet in the runnable.
* @return The runnable that will be scheduled, or NULL to cancel.
*/
protected abstract Runnable onMessageScheduled(Runnable runnable, FieldAccessor packetAccessor);
@Override
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
return delegate.attr(paramAttributeKey);
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress) {
return delegate.bind(paramSocketAddress);
}
@Override
public ChannelPipeline pipeline() {
return delegate.pipeline();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress) {
return delegate.connect(paramSocketAddress);
}
@Override
public ByteBufAllocator alloc() {
return delegate.alloc();
}
@Override
public ChannelPromise newPromise() {
return delegate.newPromise();
}
@Override
public EventLoop eventLoop() {
if (loopProxy == null) {
loopProxy = new EventLoopProxy() {
@Override
protected EventLoop getDelegate() {
return delegate.eventLoop();
}
@Override
protected Runnable schedulingRunnable(final Runnable runnable) {
final FieldAccessor accessor = getMessageAccessor(runnable);
if (accessor != null) {
Runnable result = onMessageScheduled(runnable, accessor);;
return result != null ? result : getEmptyRunnable();
}
return runnable;
}
@Override
protected <T> Callable<T> schedulingCallable(Callable<T> callable) {
FieldAccessor accessor = getMessageAccessor(callable);
if (accessor != null) {
Callable<T> result = onMessageScheduled(callable, accessor);;
return result != null ? result : EventLoopProxy.<T>getEmptyCallable();
}
return callable;
}
};
}
return loopProxy;
}
/**
* Retrieve a way to access the packet field of an object.
* @param value - the object.
* @return The packet field accessor, or NULL if not found.
*/
private FieldAccessor getMessageAccessor(Object value) {
Class<?> clazz = value.getClass();
FieldAccessor accessor = MESSAGE_LOOKUP.get(clazz);
if (accessor == null) {
try {
accessor = Accessors.getFieldAccessor(clazz, messageClass, true);
} catch (IllegalArgumentException e) {
accessor = MARK_NO_MESSAGE;
}
// Save the result
MESSAGE_LOOKUP.put(clazz, accessor);
}
return accessor != MARK_NO_MESSAGE ? accessor : null;
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
}
@Override
public ChannelProgressivePromise newProgressivePromise() {
return delegate.newProgressivePromise();
}
@Override
public Channel parent() {
return delegate.parent();
}
@Override
public ChannelConfig config() {
return delegate.config();
}
@Override
public ChannelFuture newSucceededFuture() {
return delegate.newSucceededFuture();
}
@Override
public boolean isOpen() {
return delegate.isOpen();
}
@Override
public ChannelFuture disconnect() {
return delegate.disconnect();
}
@Override
public boolean isRegistered() {
return delegate.isRegistered();
}
@Override
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
return delegate.newFailedFuture(paramThrowable);
}
@Override
public ChannelFuture close() {
return delegate.close();
}
@Override
public boolean isActive() {
return delegate.isActive();
}
@Override
@Deprecated
public ChannelFuture deregister() {
return delegate.deregister();
}
@Override
public ChannelPromise voidPromise() {
return delegate.voidPromise();
}
@Override
public ChannelMetadata metadata() {
return delegate.metadata();
}
@Override
public ChannelFuture bind(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.bind(paramSocketAddress, paramChannelPromise);
}
@Override
public SocketAddress localAddress() {
return delegate.localAddress();
}
@Override
public SocketAddress remoteAddress() {
return delegate.remoteAddress();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress, paramChannelPromise);
}
@Override
public ChannelFuture closeFuture() {
return delegate.closeFuture();
}
@Override
public boolean isWritable() {
return delegate.isWritable();
}
@Override
public Channel flush() {
return delegate.flush();
}
@Override
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
}
@Override
public Channel read() {
return delegate.read();
}
@Override
public Unsafe unsafe() {
return delegate.unsafe();
}
@Override
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
return delegate.disconnect(paramChannelPromise);
}
@Override
public ChannelFuture close(ChannelPromise paramChannelPromise) {
return delegate.close(paramChannelPromise);
}
@Override
@Deprecated
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
return delegate.deregister(paramChannelPromise);
}
@Override
public ChannelFuture write(Object paramObject) {
return delegate.write(paramObject);
}
@Override
public ChannelFuture write(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.write(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject, ChannelPromise paramChannelPromise) {
return delegate.writeAndFlush(paramObject, paramChannelPromise);
}
@Override
public ChannelFuture writeAndFlush(Object paramObject) {
return delegate.writeAndFlush(paramObject);
}
@Override
public int compareTo(Channel o) {
return delegate.compareTo(o);
}
/* Added in Netty 4.1, seem to be unused
public long bytesBeforeUnwritable() {
return delegate.bytesBeforeUnwritable();
}
public long bytesBeforeWritable() {
return delegate.bytesBeforeWritable();
}
public ChannelId id() {
return delegate.id();
}
public <T> boolean hasAttr(AttributeKey<T> key) {
return delegate.hasAttr(key);
}
*/
}
@@ -1,265 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* An event loop proxy.
* @author Kristian.
*/
abstract class EventLoopProxy implements EventLoop {
private static final Runnable EMPTY_RUNNABLE = new Runnable() {
@Override
public void run() {
// Do nothing
}
};
private static final Callable<?> EMPTY_CALLABLE = new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
};
};
/**
* Retrieve the underlying event loop.
* @return The event loop.
*/
protected abstract EventLoop getDelegate();
/**
* Retrieve a callable that does nothing but return NULL.
* @return The empty callable.
*/
@SuppressWarnings("unchecked")
public static <T> Callable<T> getEmptyCallable() {
return (Callable<T>) EMPTY_CALLABLE;
}
/**
* Retrieve a runnable that does nothing.
* @return A NO-OP runnable.
*/
public static Runnable getEmptyRunnable() {
return EMPTY_RUNNABLE;
}
/**
* Invoked when a runnable is being scheduled.
* @param runnable - the runnable that is scheduling.
* @return The runnable to schedule instead. Cannot be NULL.
*/
protected abstract Runnable schedulingRunnable(Runnable runnable);
/**
* Invoked when a callable is being scheduled.
* @param runnable - the callable that is scheduling.
* @return The callable to schedule instead. Cannot be NULL.
*/
protected abstract <T> Callable<T> schedulingCallable(Callable<T> callable);
@Override
public void execute(Runnable command) {
getDelegate().execute(schedulingRunnable(command));
}
@Override
public <T> Future<T> submit(Callable<T> action) {
return getDelegate().submit(schedulingCallable(action));
}
@Override
public <T> Future<T> submit(Runnable action, T arg1) {
return getDelegate().submit(schedulingRunnable(action), arg1);
}
@Override
public Future<?> submit(Runnable action) {
return getDelegate().submit(schedulingRunnable(action));
}
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingCallable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> schedule(Runnable action, long arg1, TimeUnit arg2) {
return getDelegate().schedule(schedulingRunnable(action), arg1, arg2);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleAtFixedRate(schedulingRunnable(action), arg1, arg2, arg3);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable action, long arg1, long arg2, TimeUnit arg3) {
return getDelegate().scheduleWithFixedDelay(schedulingRunnable(action), arg1, arg2, arg3);
}
// Boiler plate:
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return getDelegate().awaitTermination(timeout, unit);
}
@Override
public boolean inEventLoop() {
return getDelegate().inEventLoop();
}
@Override
public boolean inEventLoop(Thread arg0) {
return getDelegate().inEventLoop(arg0);
}
@Override
public boolean isShutdown() {
return getDelegate().isShutdown();
}
@Override
public boolean isTerminated() {
return getDelegate().isTerminated();
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return getDelegate().invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return getDelegate().invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException {
return getDelegate().invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return getDelegate().invokeAny(tasks, timeout, unit);
}
@Override
public boolean isShuttingDown() {
return getDelegate().isShuttingDown();
}
@Override
public Iterator<EventExecutor> iterator() {
return getDelegate().iterator();
}
@Override
public <V> Future<V> newFailedFuture(Throwable arg0) {
return getDelegate().newFailedFuture(arg0);
}
@Override
public EventLoop next() {
return ((EventLoopGroup) getDelegate()).next();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return getDelegate().newProgressivePromise();
}
@Override
public <V> Promise<V> newPromise() {
return getDelegate().newPromise();
}
@Override
public <V> Future<V> newSucceededFuture(V arg0) {
return getDelegate().newSucceededFuture(arg0);
}
@Override
public EventLoopGroup parent() {
return getDelegate().parent();
}
@Override
public ChannelFuture register(Channel arg0, ChannelPromise arg1) {
return getDelegate().register(arg0, arg1);
}
@Override
public ChannelFuture register(Channel arg0) {
return getDelegate().register(arg0);
}
@Override
public Future<?> shutdownGracefully() {
return getDelegate().shutdownGracefully();
}
@Override
public Future<?> shutdownGracefully(long arg0, long arg1, TimeUnit arg2) {
return getDelegate().shutdownGracefully(arg0, arg1, arg2);
}
@Override
public Future<?> terminationFuture() {
return getDelegate().terminationFuture();
}
@Override
@Deprecated
public void shutdown() {
getDelegate().shutdown();
}
@Override
@Deprecated
public List<Runnable> shutdownNow() {
return getDelegate().shutdownNow();
}
/*
public ChannelFuture register(ChannelPromise promise) {
return getDelegate().register(promise);
}
*/
}
@@ -1,236 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.injector.netty.ChannelInjector.ChannelSocketInjector;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* Represents an injector factory.
* <p>
* Note that the factory will return {@link ClosedInjector} when the factory is closed.
* @author Kristian
*/
public class InjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
// Whether or not the factory is closed
private volatile boolean closed;
// The current plugin
private final Plugin plugin;
public InjectionFactory(Plugin plugin) {
this.plugin = plugin;
}
/**
* Retrieve the main plugin associated with this injection factory.
* @return The main plugin.
*/
public Plugin getPlugin() {
return plugin;
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
if (closed)
return new ClosedInjector(player);
Injector injector = playerLookup.get(player);
// Find a temporary injector as well
if (injector == null)
injector = getTemporaryInjector(player);
if (injector != null && !injector.isClosed())
return injector;
Object networkManager = MinecraftFields.getNetworkManager(player);
// Must be a temporary Bukkit player
if (networkManager == null) {
return fromName(player.getName(), player);
}
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
// See if a channel has already been created
injector = (ChannelInjector) ChannelInjector.findChannelHandler(channel, ChannelInjector.class);
if (injector != null) {
// Update the player instance
playerLookup.remove(injector.getPlayer());
injector.setPlayer(player);
} else {
injector = new ChannelInjector(player, networkManager, channel, listener, this);
}
// Cache injector and return
cacheInjector(player, injector);
return injector;
}
/**
* Retrieve a cached injector from a name.
* <p>
* The injector may be NULL if the plugin has been reloaded during a player login.
* @param name - the name.
* @param player - the player.
* @return The cached injector, or a closed injector if it could not be found.
*/
public Injector fromName(String name, Player player) {
if (!closed) {
Injector injector = nameLookup.get(name);
// We can only retrieve cached injectors
if (injector != null) {
// Update instance
injector.setUpdatedPlayer(player);
return injector;
}
}
return new ClosedInjector(player);
}
/**
* Construct a new channel injector for the given channel.
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (closed)
return new ClosedInjector(null);
Object networkManager = findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer());
ChannelInjector injector = new ChannelInjector(temporaryPlayer, networkManager, channel, listener, this);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector));
return injector;
}
/**
* Invalidate a cached injector.
* @param player - the associated player.
* @return The cached injector, or NULL if nothing was cached.
*/
public Injector invalidate(Player player) {
Injector injector = playerLookup.remove(player);
nameLookup.remove(player.getName());
return injector;
}
/**
* Cache an injector by player.
* @param player - the player.
* @param injector - the injector to cache.
* @return The previously cached injector.
*/
public Injector cacheInjector(Player player, Injector injector) {
nameLookup.put(player.getName(), injector);
return playerLookup.put(player, injector);
}
/**
* Cache an injector by name alone.
* @param name - the name to lookup.
* @param injector - the injector.
* @return The cached injector.
*/
public Injector cacheInjector(String name, Injector injector) {
return nameLookup.put(name, injector);
}
/**
* Retrieve the associated channel injector.
* @param player - the temporary player, or normal Bukkit player.
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private ChannelInjector getTemporaryInjector(Player player) {
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector != null) {
return ((ChannelSocketInjector) injector).getChannelInjector();
}
return null;
}
/**
* Find the network manager in a channel's pipeline.
* @param channel - the channel.
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = ChannelInjector.findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass());
if (networkManager != null)
return networkManager;
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
}
/**
* Determine if the factory is closed.
* <p>
* If it is, all new injectors will be closed by default.
* @return TRUE if it is closed, FALSE otherwise.
*/
public boolean isClosed() {
return closed;
}
/**
* Close all injectors created by this factory, and cease the creation of new injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
// Close everything
for (Injector injector : playerLookup.values())
injector.close();
for (Injector injector : nameLookup.values())
injector.close();
}
}
}
@@ -1,97 +1,100 @@
package com.comphenix.protocol.injector.netty;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import org.bukkit.entity.Player;
/**
* Represents an injected client connection.
*
* @author Kristian
*/
public interface Injector {
/**
* Retrieve the current protocol version of the player.
*
* @return Protocol version.
*/
public abstract int getProtocolVersion();
int getProtocolVersion();
/**
* Inject the current channel.
* <p>
* Note that only active channels can be injected.
*
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
*/
public abstract boolean inject();
boolean inject();
void uninject();
/**
* Close the current injector.
*/
public abstract void close();
void close();
/**
* Send a packet to a player's client.
* @param packet - the packet to send.
* @param marker - the network marker.
*
* @param packet - the packet to send.
* @param marker - the network marker.
* @param filtered - whether or not the packet is filtered.
*/
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
/**
* Recieve a packet on the server.
* @param packet - the (NMS) packet to send.
*/
public abstract void recieveClientPacket(Object packet);
void receiveClientPacket(Object packet);
/**
* Retrieve the current protocol state.
*
* @return The current protocol.
*/
public abstract Protocol getCurrentProtocol();
Protocol getCurrentProtocol();
/**
* Retrieve the network marker associated with a given packet.
*
* @param packet - the packet.
* @return The network marker.
*/
public abstract NetworkMarker getMarker(Object packet);
NetworkMarker getMarker(Object packet);
/**
* Associate a given network marker with a specific packet.
*
* @param packet - the NMS packet.
* @param marker - the associated marker.
*/
public abstract void saveMarker(Object packet, NetworkMarker marker);
void saveMarker(Object packet, NetworkMarker marker);
/**
* Retrieve the current player or temporary player associated with the injector.
*
* @return The current player.
*/
public abstract Player getPlayer();
Player getPlayer();
/**
* Set the current player instance.
*
* @param player - the current player.
*/
public abstract void setPlayer(Player player);
void setPlayer(Player player);
void disconnect(String message);
/**
* Determine if the channel has already been injected.
*
* @return TRUE if it has, FALSE otherwise.
*/
public abstract boolean isInjected();
boolean isInjected();
/**
* Determine if this channel has been closed and cleaned up.
*
* @return TRUE if it has, FALSE otherwise.
*/
public abstract boolean isClosed();
/**
* Set the updated player instance.
* <p>
* This will not replace the current instance, but it will allow PacketEvent to provide additional player information.
* @param player - the more up-to-date player.
*/
public abstract void setUpdatedPlayer(Player player);
boolean isClosed();
}
@@ -1,25 +1,26 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* 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.injector.netty;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.io.ByteStreams;
import io.netty.buffer.AbstractByteBuf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
@@ -33,35 +34,31 @@ import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.WritableByteChannel;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.io.ByteStreams;
/**
* Construct a ByteBuf around an input stream and an output stream.
* <p>
* Note that as streams usually don't support seeking, this implementation will ignore
* all indexing in the byte buffer.
* Note that as streams usually don't support seeking, this implementation will ignore all indexing in the byte buffer.
*
* @author Kristian
*/
@SuppressWarnings("unused")
public class NettyByteBufAdapter extends AbstractByteBuf {
private DataInputStream input;
private DataOutputStream output;
private static final int CAPACITY = Integer.MAX_VALUE;
// For modifying the reader or writer index
private static FieldAccessor READER_INDEX;
private static FieldAccessor WRITER_INDEX;
private static final int CAPACITY = Integer.MAX_VALUE;
private final DataInputStream input;
private final DataOutputStream output;
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
// Just pick a figure
super(CAPACITY);
this.input = input;
this.output = output;
// Prepare accessors
try {
if (READER_INDEX == null) {
@@ -73,32 +70,37 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
} catch (Exception e) {
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
}
// "Infinite" reading/writing
if (input == null)
if (input == null) {
READER_INDEX.set(this, Integer.MAX_VALUE);
if (output == null)
}
if (output == null) {
WRITER_INDEX.set(this, Integer.MAX_VALUE);
}
}
/**
* Construct a new Minecraft packet serializer using the current byte buf adapter.
*
* @param input - the input stream.
* @return A packet serializer with a wrapped byte buf adapter.
*/
public static ByteBuf packetReader(DataInputStream input) {
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
}
/**
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
*
* @param output - the output stream.
* @return A packet serializer with a wrapped byte buf adapter.
*/
public static ByteBuf packetWriter(DataOutputStream output) {
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
}
@Override
public int refCnt() {
return 1;
@@ -117,7 +119,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected byte _getByte(int paramInt) {
try {
return input.readByte();
return this.input.readByte();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -126,7 +128,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected short _getShort(int paramInt) {
try {
return input.readShort();
return this.input.readShort();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -135,7 +137,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected int _getUnsignedMedium(int paramInt) {
try {
return input.readUnsignedShort();
return this.input.readUnsignedShort();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -144,7 +146,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected int _getInt(int paramInt) {
try {
return input.readInt();
return this.input.readInt();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -153,7 +155,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected long _getLong(int paramInt) {
try {
return input.readLong();
return this.input.readLong();
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -162,7 +164,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setByte(int index, int value) {
try {
output.writeByte(value);
this.output.writeByte(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -171,7 +173,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setShort(int index, int value) {
try {
output.writeShort(value);
this.output.writeShort(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -180,7 +182,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setMedium(int index, int value) {
try {
output.writeShort(value);
this.output.writeShort(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -189,7 +191,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setInt(int index, int value) {
try {
output.writeInt(value);
this.output.writeInt(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -198,7 +200,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
protected void _setLong(int index, long value) {
try {
output.writeLong(value);
this.output.writeLong(value);
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
}
@@ -238,7 +240,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
try {
for (int i = 0; i < length; i++) {
dst.setByte(dstIndex + i, input.read());
dst.setByte(dstIndex + i, this.input.read());
}
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
@@ -249,7 +251,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
try {
input.read(dst, dstIndex, length);
this.input.read(dst, dstIndex, length);
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -259,7 +261,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf getBytes(int index, ByteBuffer dst) {
try {
dst.put(ByteStreams.toByteArray(input));
dst.put(ByteStreams.toByteArray(this.input));
} catch (IOException e) {
throw new RuntimeException("Cannot read input.", e);
}
@@ -268,14 +270,14 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
ByteStreams.copy(ByteStreams.limit(input, length), dst);
ByteStreams.copy(ByteStreams.limit(this.input, length), dst);
return this;
}
@Override
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(input, length));
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(this.input, length));
out.write(ByteBuffer.wrap(data));
return data.length;
}
@@ -284,9 +286,9 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
byte[] buffer = new byte[length];
src.getBytes(srcIndex, buffer);
try {
output.write(buffer);
this.output.write(buffer);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
@@ -296,7 +298,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
try {
output.write(src, srcIndex, length);
this.output.write(src, srcIndex, length);
return this;
} catch (IOException e) {
throw new RuntimeException("Cannot write output.", e);
@@ -306,7 +308,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public ByteBuf setBytes(int index, ByteBuffer src) {
try {
WritableByteChannel channel = Channels.newChannel(output);
WritableByteChannel channel = Channels.newChannel(this.output);
channel.write(src);
return this;
@@ -318,15 +320,15 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
@Override
public int setBytes(int index, InputStream in, int length) throws IOException {
InputStream limit = ByteStreams.limit(in, length);
ByteStreams.copy(limit, output);
ByteStreams.copy(limit, this.output);
return length - limit.available();
}
@Override
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(length);
WritableByteChannel channel = Channels.newChannel(output);
WritableByteChannel channel = Channels.newChannel(this.output);
int count = in.read(buffer);
channel.write(buffer);
return count;
@@ -424,7 +426,7 @@ public class NettyByteBufAdapter extends AbstractByteBuf {
return 0;
}
public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) throws IOException {
public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) {
return 0;
}
@@ -1,40 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
public class NettyNetworkMarker extends NetworkMarker {
public NettyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer) {
super(side, inputBuffer, null);
}
public NettyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer) {
super(side, inputBuffer, null);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// Skip the variable int containing the packet ID
getSerializer().deserializeVarInt(input);
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
// We don't have to add anything - it's already there
return buffer;
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
// As above
return input;
}
}
@@ -1,83 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* Stores packets that need to be sent without being handled by the listeners (filtered=false).
* When other packets sent after sending the packet are removed, the packet is removed as well
* to prevent a memory leak, assuming a consistent send order is in place.
*
* @author bergerkiller
*/
public class PacketFilterQueue {
private Queue<Object> queue = new ArrayDeque<>();
/**
* Adds a packet to this queue, indicating further on that it should not be filtered.
*
* @param packet
*/
public synchronized void add(Object packet) {
queue.add(packet);
}
/**
* Checks whether a packet is contained inside this queue, indicating
* it should not be filtered.
*
* @param packet
* @return True if contained and packet should not be filtered (filtered=false)
*/
public synchronized boolean contains(Object packet) {
return queue.contains(packet);
}
/**
* Checks whether a packet is contained inside this queue and removes it if so.
* Other packets marked in this queue that were sent before this packet are
* removed from the queue also, avoiding memory leaks because of dropped packets.
*
* @param packet
* @return True if contained and packet should not be filtered (filtered=false)
*/
public synchronized boolean remove(Object packet) {
if (queue.isEmpty()) {
// Nothing in the queue
return false;
} else if (queue.peek() == packet) {
// First in the queue (expected)
queue.poll();
return true;
} else if (!queue.contains(packet)) {
// There are unfiltered packets, but this one is not
return false;
} else {
// We have skipped over some packets (unexpected)
// Poll packets until we find it
while (queue.poll() != packet) {
if (queue.isEmpty()) {
// This should never happen! But to avoid infinite loop.
return false;
}
}
return true;
}
}
}
@@ -1,388 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import io.netty.channel.*;
import io.netty.util.concurrent.EventExecutorGroup;
/**
* A pipeline proxy.
* @author Kristian
*/
public class PipelineProxy implements ChannelPipeline {
protected final ChannelPipeline pipeline;
protected final Channel channel;
public PipelineProxy(ChannelPipeline pipeline, Channel channel) {
this.pipeline = pipeline;
this.channel = channel;
}
@Override
public ChannelPipeline addAfter(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addAfter(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addAfter(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addAfter(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addBefore(EventExecutorGroup arg0, String arg1, String arg2, ChannelHandler arg3) {
pipeline.addBefore(arg0, arg1, arg2, arg3);
return this;
}
@Override
public ChannelPipeline addBefore(String arg0, String arg1, ChannelHandler arg2) {
pipeline.addBefore(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(ChannelHandler... arg0) {
pipeline.addFirst(arg0);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addFirst(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addFirst(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addFirst(String arg0, ChannelHandler arg1) {
pipeline.addFirst(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(ChannelHandler... arg0) {
pipeline.addLast(arg0);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, ChannelHandler... arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup arg0, String arg1, ChannelHandler arg2) {
pipeline.addLast(arg0, arg1, arg2);
return this;
}
@Override
public ChannelPipeline addLast(String arg0, ChannelHandler arg1) {
pipeline.addLast(arg0, arg1);
return this;
}
@Override
public ChannelFuture bind(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.bind(arg0, arg1);
}
@Override
public ChannelFuture bind(SocketAddress arg0) {
return pipeline.bind(arg0);
}
@Override
public Channel channel() {
return channel;
}
@Override
public ChannelFuture close() {
return pipeline.close();
}
@Override
public ChannelFuture close(ChannelPromise arg0) {
return pipeline.close(arg0);
}
@Override
public ChannelFuture connect(SocketAddress arg0, ChannelPromise arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1, ChannelPromise arg2) {
return pipeline.connect(arg0, arg1, arg2);
}
@Override
public ChannelFuture connect(SocketAddress arg0, SocketAddress arg1) {
return pipeline.connect(arg0, arg1);
}
@Override
public ChannelFuture connect(SocketAddress arg0) {
return pipeline.connect(arg0);
}
@Override
public ChannelHandlerContext context(ChannelHandler arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(Class<? extends ChannelHandler> arg0) {
return pipeline.context(arg0);
}
@Override
public ChannelHandlerContext context(String arg0) {
return pipeline.context(arg0);
}
// We have to call the depreciated methods to properly implement the proxy
@Override
public ChannelFuture deregister() {
return pipeline.deregister();
}
@Override
public ChannelFuture deregister(ChannelPromise arg0) {
return pipeline.deregister(arg0);
}
@Override
public ChannelPipeline fireChannelUnregistered() {
pipeline.fireChannelUnregistered();
return this;
}
@Override
public ChannelFuture disconnect() {
return pipeline.disconnect();
}
@Override
public ChannelFuture disconnect(ChannelPromise arg0) {
return pipeline.disconnect(arg0);
}
@Override
public ChannelPipeline fireChannelActive() {
pipeline.fireChannelActive();
return this;
}
@Override
public ChannelPipeline fireChannelInactive() {
pipeline.fireChannelInactive();
return this;
}
@Override
public ChannelPipeline fireChannelRead(Object arg0) {
pipeline.fireChannelRead(arg0);
return this;
}
@Override
public ChannelPipeline fireChannelReadComplete() {
pipeline.fireChannelReadComplete();
return this;
}
@Override
public ChannelPipeline fireChannelRegistered() {
pipeline.fireChannelRegistered();
return this;
}
@Override
public ChannelPipeline fireChannelWritabilityChanged() {
pipeline.fireChannelWritabilityChanged();
return this;
}
@Override
public ChannelPipeline fireExceptionCaught(Throwable arg0) {
pipeline.fireExceptionCaught(arg0);
return this;
}
@Override
public ChannelPipeline fireUserEventTriggered(Object arg0) {
pipeline.fireUserEventTriggered(arg0);
return this;
}
@Override
public ChannelHandler first() {
return pipeline.first();
}
@Override
public ChannelHandlerContext firstContext() {
return pipeline.firstContext();
}
@Override
public ChannelPipeline flush() {
pipeline.flush();
return this;
}
@Override
public <T extends ChannelHandler> T get(Class<T> arg0) {
return pipeline.get(arg0);
}
@Override
public ChannelHandler get(String arg0) {
return pipeline.get(arg0);
}
@Override
public Iterator<Entry<String, ChannelHandler>> iterator() {
return pipeline.iterator();
}
@Override
public ChannelHandler last() {
return pipeline.last();
}
@Override
public ChannelHandlerContext lastContext() {
return pipeline.lastContext();
}
@Override
public List<String> names() {
return pipeline.names();
}
@Override
public ChannelPipeline read() {
pipeline.read();
return this;
}
@Override
public ChannelPipeline remove(ChannelHandler arg0) {
pipeline.remove(arg0);
return this;
}
@Override
public <T extends ChannelHandler> T remove(Class<T> arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler remove(String arg0) {
return pipeline.remove(arg0);
}
@Override
public ChannelHandler removeFirst() {
return pipeline.removeFirst();
}
@Override
public ChannelHandler removeLast() {
return pipeline.removeLast();
}
@Override
public ChannelPipeline replace(ChannelHandler arg0, String arg1, ChannelHandler arg2) {
pipeline.replace(arg0, arg1, arg2);
return this;
}
@Override
public <T extends ChannelHandler> T replace(Class<T> arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public ChannelHandler replace(String arg0, String arg1, ChannelHandler arg2) {
return pipeline.replace(arg0, arg1, arg2);
}
@Override
public Map<String, ChannelHandler> toMap() {
return pipeline.toMap();
}
@Override
public ChannelFuture write(Object arg0, ChannelPromise arg1) {
return pipeline.write(arg0, arg1);
}
@Override
public ChannelFuture write(Object arg0) {
return pipeline.write(arg0);
}
@Override
public ChannelFuture writeAndFlush(Object arg0, ChannelPromise arg1) {
return pipeline.writeAndFlush(arg0, arg1);
}
@Override
public ChannelFuture writeAndFlush(Object arg0) {
return pipeline.writeAndFlush(arg0);
}
/* Added in Netty 4.1, seem to be unused
public ChannelFuture newFailedFuture(Throwable ex) {
return pipeline.newFailedFuture(ex);
}
public ChannelProgressivePromise newProgressivePromise() {
return pipeline.newProgressivePromise();
}
public ChannelPromise newPromise() {
return pipeline.newPromise();
}
public ChannelFuture newSucceededFuture() {
return pipeline.newSucceededFuture();
}
public ChannelPromise voidPromise() {
return pipeline.voidPromise();
}
*/
}
@@ -1,467 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Set;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.NettyVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
public class ProtocolInjector implements ChannelListener {
public static final ReportType REPORT_CANNOT_INJECT_INCOMING_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private volatile boolean injected;
private volatile boolean closed;
// The temporary player factory
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
private List<VolatileField> bootstrapFields = Lists.newArrayList();
// The channel injector factory
private InjectionFactory injectionFactory;
// List of network managers
private volatile List<Object> networkManagers;
// Different sending filters
private PacketTypeSet sendingFilters = new PacketTypeSet();
private PacketTypeSet reveivedFilters = new PacketTypeSet();
// Packets that must be executed on the main thread
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
// Which packets are buffered
private PacketTypeSet bufferedPackets = new PacketTypeSet();
private ListenerInvoker invoker;
// Handle errors
private ErrorReporter reporter;
private boolean debug;
public ProtocolInjector(Plugin plugin, ListenerInvoker invoker, ErrorReporter reporter) {
this.injectionFactory = new InjectionFactory(plugin);
this.invoker = invoker;
this.reporter = reporter;
}
@Override
public boolean isDebug() {
return debug;
}
/**
* Set whether or not the debug mode is enabled.
* @param debug - TRUE if it is, FALSE otherwise.
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Inject into the spigot connection class.
*/
@SuppressWarnings("unchecked")
public synchronized void inject() {
if (injected)
throw new IllegalStateException("Cannot inject twice.");
try {
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
List<Method> serverConnectionMethods = fuzzyServer.getMethodListByParameters(MinecraftReflection.getServerConnectionClass(), new Class[] {});
// Get the server connection
Object server = fuzzyServer.getSingleton();
Object serverConnection = null;
for (Method method : serverConnectionMethods) {
try {
serverConnection = method.invoke(server);
// Continue until we get a server connection
if (serverConnection != null) {
break;
}
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception invoking " + method, ex);
}
}
if (serverConnection == null) {
throw new ReflectiveOperationException("Failed to obtain server connection");
}
// Handle connected channels
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(final Channel channel) throws Exception {
try {
synchronized (networkManagers) {
// For some reason it needs to be delayed when using netty 4.1.24 (minecraft 1.12) or newer,
// but the delay breaks older minecraft versions
// TODO I see this more as a temporary hotfix than a permanent solution
// Check if the netty version is greater than 4.1.24, that's the version bundled with spigot 1.12
NettyVersion ver = NettyVersion.getVersion();
if ((ver.isValid() && ver.isGreaterThan(4,1,24)) ||
MinecraftVersion.getCurrentVersion().getMinor() >= 12) { // fallback if netty version couldn't be detected
channel.eventLoop().submit(() ->
injectionFactory.fromChannel(channel, ProtocolInjector.this, playerFactory).inject());
} else {
injectionFactory.fromChannel(channel, ProtocolInjector.this, playerFactory).inject();
}
}
} catch (Exception ex) {
reporter.reportDetailed(ProtocolInjector.this, Report.newBuilder(REPORT_CANNOT_INJECT_INCOMING_CHANNEL).messageParam(channel).error(ex));
}
}
};
// This is executed before Minecraft's channel handler
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
// Our only job is to add init protocol
channel.pipeline().addLast(endInitProtocol);
}
};
// Add our handler to newly created channels
final ChannelHandler connectionHandler = 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);
}
@Override
public boolean isSharable() {
// Needed if multiple objects are stored in the bootstrap list
return true;
}
};
FuzzyReflection fuzzy = FuzzyReflection.fromObject(serverConnection, true);
try {
Field field = fuzzy.getParameterizedField(List.class, MinecraftReflection.getNetworkManagerClass());
field.setAccessible(true);
networkManagers = (List<Object>) field.get(serverConnection);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception checking list fields", ex);
Method method = fuzzy.getMethodByParameters("getNetworkManagers", List.class,
new Class<?>[] { serverConnection.getClass() });
method.setAccessible(true);
networkManagers = (List<Object>) method.invoke(null, serverConnection);
}
if (networkManagers == null) {
throw new ReflectiveOperationException("Failed to obtain list of network managers");
}
// Insert ProtocolLib's connection interceptor
bootstrapFields = getBootstrapFields(serverConnection);
for (VolatileField field : bootstrapFields) {
final List<Object> list = (List<Object>) field.getValue();
// We don't have to override this list
if (list == networkManagers) {
continue;
}
// Synchronize with each list before we attempt to replace them.
field.setValue(new BootstrapList(list, connectionHandler));
}
injected = true;
} catch (Exception e) {
throw new RuntimeException("Unable to inject channel futures.", e);
}
}
@Override
public boolean hasListener(Class<?> packetClass) {
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
}
@Override
public boolean hasMainThreadListener(Class<?> packetClass) {
return mainThreadFilters.contains(packetClass);
}
@Override
public ErrorReporter getReporter() {
return reporter;
}
/**
* Inject our packet handling into a specific player.
* @param player Player to inject into
*/
public void injectPlayer(Player player) {
injectionFactory.fromPlayer(player, this).inject();
}
private List<VolatileField> getBootstrapFields(Object serverConnection) {
List<VolatileField> result = Lists.newArrayList();
// Find and (possibly) proxy every list
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
VolatileField volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) volatileField.getValue();
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
result.add(volatileField);
}
}
return result;
}
/**
* Clean up any remaning injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
for (VolatileField field : bootstrapFields) {
Object value = field.getValue();
// Undo the processed channels, if any
if (value instanceof BootstrapList) {
((BootstrapList) value).close();
}
field.revertValue();
}
// Uninject all the players
injectionFactory.close();
}
}
@Override
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (sendingFilters.contains(clazz) || marker != null) {
try {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetQueued(container, injector.getPlayer(), marker);
} catch (LinkageError e) {
System.err.println("[ProtocolLib] Encountered a LinkageError (likely a misbehaving wrapper), please report this!");
e.printStackTrace();
}
}
// Don't change anything
return null;
}
@Override
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
Class<?> clazz = packet.getClass();
if (reveivedFilters.contains(clazz) || marker != null) {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetReceived(container, injector.getPlayer(), marker);
}
// Don't change anything
return null;
}
@Override
public boolean includeBuffer(Class<?> packetClass) {
return bufferedPackets.contains(packetClass);
}
/**
* Called to inform the event listeners of a queued packet.
* @param packet - the packet that is to be sent.
* @param receiver - the receiver of this packet.
* @return The packet event that was used.
*/
private PacketEvent packetQueued(PacketContainer packet, Player receiver, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver);
invoker.invokePacketSending(event);
return event;
}
/**
* Called to inform the event listeners of a received packet.
* @param packet - the packet that has been receieved.
* @param sender - the client packet.
* @param marker - the network marker.
* @return The packet event that was used.
*/
private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
// Server side
public PlayerInjectionHandler getPlayerInjector() {
return new AbstractPlayerHandler(sendingFilters) {
private ChannelListener listener = ProtocolInjector.this;
@Override
public int getProtocolVersion(Player player) {
return injectionFactory.fromPlayer(player, listener).getProtocolVersion();
}
@Override
public void updatePlayer(Player player) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
injectionFactory.fromPlayer(player, listener).inject();
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
// Ignore this too
return true;
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
if (!type.isAsyncForced() && (options == null || !options.contains(ListenerOptions.ASYNC)))
mainThreadFilters.addType(type);
super.addPacketHandler(type, options);
}
@Override
public void removePacketHandler(PacketType type) {
mainThreadFilters.removeType(type);
super.removePacketHandler(type);
}
@Override
public boolean uninjectPlayer(Player player) {
// Just let Netty clean this up
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injectionFactory.fromPlayer(receiver, listener).sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return mainThreadFilters.contains(type);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injectionFactory.fromPlayer(player, listener).recieveClientPacket(mcPacket);
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Ignore this
return null;
}
@Override
public void handleDisconnect(Player player) {
injectionFactory.fromPlayer(player, listener).close();
}
@Override
public Channel getChannel(Player player) {
Injector injector = injectionFactory.fromPlayer(player, listener);
if (injector instanceof ChannelInjector) {
return ((ChannelInjector) injector).getChannel();
}
return null;
}
};
}
/**
* Retrieve a view of this protocol injector as a packet injector.
* @return The packet injector.
*/
// Client side
public PacketInjector getPacketInjector() {
return new AbstractPacketInjector(reveivedFilters) {
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
injectionFactory.fromPlayer(client, ProtocolInjector.this).saveMarker(packet.getHandle(), marker);
return packetReceived(packet, client, marker);
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
bufferedPackets = new PacketTypeSet(set);
}
};
}
}
@@ -1,268 +0,0 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.SocketChannel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
/**
* This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides
* all methods in {@link Socket} to ensure that calls are not mistakingly made
* to the unsupported super socket. All operations that can be sanely applied to
* a {@link Channel} are implemented here. Those which cannot will throw an
* {@link UnsupportedOperationException}.
*/
// Thanks MD5. :)
public class SocketAdapter extends Socket {
private final SocketChannel ch;
private SocketAdapter(SocketChannel ch) {
this.ch = ch;
}
public static SocketAdapter adapt(Channel ch) {
if (!(ch instanceof SocketChannel)) {
return null;
}
return new SocketAdapter((SocketChannel) ch);
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
ch.bind(bindpoint).syncUninterruptibly();
}
@Override
public synchronized void close() throws IOException {
ch.close().syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint) throws IOException {
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
ch.config().setConnectTimeoutMillis(timeout);
ch.connect(endpoint).syncUninterruptibly();
}
@Override
public boolean equals(Object obj) {
return obj instanceof SocketAdapter && ch.equals(((SocketAdapter) obj).ch);
}
@Override
public java.nio.channels.SocketChannel getChannel() {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public InetAddress getInetAddress() {
return ch.remoteAddress().getAddress();
}
@Override
public InputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getKeepAlive() throws SocketException {
return ch.config().getOption(ChannelOption.SO_KEEPALIVE);
}
@Override
public InetAddress getLocalAddress() {
return ch.localAddress().getAddress();
}
@Override
public int getLocalPort() {
return ch.localAddress().getPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return ch.localAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public int getPort() {
return ch.remoteAddress().getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_RCVBUF);
}
@Override
public SocketAddress getRemoteSocketAddress() {
return ch.remoteAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return ch.config().getOption(ChannelOption.SO_REUSEADDR);
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return ch.config().getOption(ChannelOption.SO_SNDBUF);
}
@Override
public int getSoLinger() throws SocketException {
return ch.config().getOption(ChannelOption.SO_LINGER);
}
@Override
public synchronized int getSoTimeout() throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return ch.config().getOption(ChannelOption.TCP_NODELAY);
}
@Override
public int getTrafficClass() throws SocketException {
return ch.config().getOption(ChannelOption.IP_TOS);
}
@Override
public int hashCode() {
return ch.hashCode();
}
@Override
public boolean isBound() {
return ch.localAddress() != null;
}
@Override
public boolean isClosed() {
return !ch.isOpen();
}
@Override
public boolean isConnected() {
return ch.isActive();
}
@Override
public boolean isInputShutdown() {
return ch.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return ch.isOutputShutdown();
}
@Override
public void sendUrgentData(int data) throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_KEEPALIVE, on);
}
@Override
public void setOOBInline(boolean on) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_RCVBUF, size);
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.SO_REUSEADDR, on);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
ch.config().setOption(ChannelOption.SO_SNDBUF, size);
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
ch.config().setOption(ChannelOption.SO_LINGER, linger);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
ch.config().setOption(ChannelOption.TCP_NODELAY, on);
}
@Override
public void setTrafficClass(int tc) throws SocketException {
ch.config().setOption(ChannelOption.IP_TOS, tc);
}
@Override
public void shutdownInput() throws IOException {
throw new UnsupportedOperationException("Operation not supported on Channel wrapper.");
}
@Override
public void shutdownOutput() throws IOException {
ch.shutdownOutput().syncUninterruptibly();
}
@Override
public String toString() {
return ch.toString();
}
}
@@ -1,263 +1,241 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Method;
import java.util.Arrays;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* A packet represented only by its id and bytes.
* @author dmulloy2
*/
public class WirePacket {
private final int id;
private final byte[] bytes;
/**
* Constructs a new WirePacket with a given type and contents
* @param type Type of the packet
* @param bytes Contents of the packet
*/
public WirePacket(PacketType type, byte[] bytes) {
this.id = checkNotNull(type, "type cannot be null").getCurrentId();
this.bytes = bytes;
}
/**
* Constructs a new WirePacket with a given id and contents
* @param id ID of the packet
* @param bytes Contents of the packet
*/
public WirePacket(int id, byte[] bytes) {
this.id = id;
this.bytes = bytes;
}
/**
* Gets this packet's ID
* @return The ID
*/
public int getId() {
return id;
}
/**
* Gets this packet's contents as a byte array
* @return The contents
*/
public byte[] getBytes() {
return bytes;
}
/**
* Writes the id of this packet to a given output
* @param output Output to write to
*/
public void writeId(ByteBuf output) {
writeVarInt(output, id);
}
/**
* Writes the contents of this packet to a given output
* @param output Output to write to
*/
public void writeBytes(ByteBuf output) {
checkNotNull(output, "output cannot be null!");
output.writeBytes(bytes);
}
/**
* Fully writes the ID and contents of this packet to a given output
* @param output Output to write to
*/
public void writeFully(ByteBuf output) {
writeId(output);
writeBytes(output);
}
/**
* Serializes this packet into a byte buffer
* @return The buffer
*/
public ByteBuf serialize() {
ByteBuf buffer = Unpooled.buffer();
writeFully(buffer);
return buffer;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof WirePacket) {
WirePacket that = (WirePacket) obj;
return this.id == that.id &&
Arrays.equals(this.bytes, that.bytes);
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(bytes);
result = prime * result + id;
return result;
}
@Override
public String toString() {
return "WirePacket[id=" + id + ", bytes=" + Arrays.toString(bytes) + "]";
}
private static byte[] getBytes(ByteBuf buffer) {
byte[] array = new byte[buffer.readableBytes()];
buffer.readBytes(array);
return array;
}
/**
* Creates a WirePacket from an existing PacketContainer
* @param packet Existing packet
* @return The resulting WirePacket
*/
public static WirePacket fromPacket(PacketContainer packet) {
int id = packet.getType().getCurrentId();
return new WirePacket(id, bytesFromPacket(packet));
}
/**
* Creates a byte array from an existing PacketContainer containing all the
* bytes from that packet
*
* @param packet Existing packet
* @return the byte array
*/
public static byte[] bytesFromPacket(PacketContainer packet) {
checkNotNull(packet, "packet cannot be null!");
ByteBuf buffer = PacketContainer.createPacketBuffer();
ByteBuf store = PacketContainer.createPacketBuffer();
// Read the bytes once
Method write = MinecraftMethods.getPacketWriteByteBufMethod();
try {
write.invoke(packet.getHandle(), buffer);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to read packet contents.", ex);
}
byte[] bytes = getBytes(buffer);
buffer.release();
// Rewrite them to the packet to avoid issues with certain packets
if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD
|| packet.getType() == PacketType.Play.Client.CUSTOM_PAYLOAD) {
// Make a copy of the array before writing
byte[] ret = Arrays.copyOf(bytes, bytes.length);
store.writeBytes(bytes);
Method read = MinecraftMethods.getPacketReadByteBufMethod();
try {
read.invoke(packet.getHandle(), store);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to rewrite packet contents.", ex);
}
return ret;
}
store.release();
return bytes;
}
/**
* Creates a WirePacket from an existing Minecraft packet
* @param packet Existing Minecraft packet
* @return The resulting WirePacket
* @throws IllegalArgumentException If the packet is null or not a Minecraft packet
*/
public static WirePacket fromPacket(Object packet) {
checkNotNull(packet, "packet cannot be null!");
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
PacketType type = PacketType.fromClass(packet.getClass());
int id = type.getCurrentId();
ByteBuf buffer = PacketContainer.createPacketBuffer();
Method write = MinecraftMethods.getPacketWriteByteBufMethod();
try {
write.invoke(packet, buffer);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to serialize packet contents.", ex);
}
byte[] bytes = getBytes(buffer);
buffer.release();
return new WirePacket(id, bytes);
}
public static void writeVarInt(ByteBuf output, int i) {
checkNotNull(output, "output cannot be null!");
while ((i & -128) != 0) {
output.writeByte(i & 127 | 128);
i >>>= 7;
}
output.writeByte(i);
}
public static int readVarInt(ByteBuf input) {
checkNotNull(input, "input cannot be null!");
int i = 0;
int j = 0;
byte b0;
do {
b0 = input.readByte();
i |= (b0 & 127) << j++ * 7;
if (j > 5) {
throw new RuntimeException("VarInt too big");
}
} while ((b0 & 128) == 128);
return i;
}
}
/**
* 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.injector.netty;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
import java.util.Arrays;
/**
* A packet represented only by its id and bytes.
*
* @author dmulloy2
*/
@SuppressWarnings("deprecation") // yea we need to do that :/
public class WirePacket {
private final int id;
private final byte[] bytes;
/**
* Constructs a new WirePacket with a given type and contents
*
* @param type Type of the packet
* @param bytes Contents of the packet
*/
public WirePacket(PacketType type, byte[] bytes) {
this.id = checkNotNull(type, "type cannot be null").getCurrentId();
this.bytes = bytes;
}
/**
* Constructs a new WirePacket with a given id and contents
*
* @param id ID of the packet
* @param bytes Contents of the packet
*/
public WirePacket(int id, byte[] bytes) {
this.id = id;
this.bytes = bytes;
}
/**
* Creates a WirePacket from an existing PacketContainer
*
* @param packet Existing packet
* @return The resulting WirePacket
*/
public static WirePacket fromPacket(PacketContainer packet) {
int id = packet.getType().getCurrentId();
return new WirePacket(id, bytesFromPacket(packet));
}
/**
* Creates a byte array from an existing PacketContainer containing all the bytes from that packet
*
* @param packet Existing packet
* @return the byte array
*/
public static byte[] bytesFromPacket(PacketContainer packet) {
checkNotNull(packet, "packet cannot be null!");
ByteBuf buffer = PacketContainer.createPacketBuffer();
ByteBuf store = PacketContainer.createPacketBuffer();
// Read the bytes once
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
write.invoke(packet.getHandle(), buffer);
byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer);
// Rewrite them to the packet to avoid issues with certain packets
if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD
|| packet.getType() == PacketType.Play.Client.CUSTOM_PAYLOAD) {
// Make a copy of the array before writing
byte[] ret = Arrays.copyOf(bytes, bytes.length);
store.writeBytes(bytes);
MethodAccessor read = MinecraftMethods.getPacketReadByteBufMethod();
read.invoke(packet.getHandle(), store);
bytes = ret;
}
ReferenceCountUtil.safeRelease(store);
return bytes;
}
/**
* Creates a WirePacket from an existing Minecraft packet
*
* @param packet Existing Minecraft packet
* @return The resulting WirePacket
* @throws IllegalArgumentException If the packet is null or not a Minecraft packet
*/
public static WirePacket fromPacket(Object packet) {
checkNotNull(packet, "packet cannot be null!");
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
ByteBuf buffer = PacketContainer.createPacketBuffer();
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
write.invoke(packet, buffer);
byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer);
int id = PacketType.fromClass(packet.getClass()).getCurrentId();
return new WirePacket(id, bytes);
}
public static void writeVarInt(ByteBuf output, int value) {
while (true) {
if ((value & ~0x7F) == 0) {
output.writeByte(value);
break;
} else {
output.writeByte((value & 0x7F) | 0x80);
value >>>= 7;
}
}
}
public static int readVarInt(ByteBuf input) {
int result = 0;
for (byte j = 0; j < 5; j++) {
int nextByte = input.readByte();
result |= (nextByte & 0x7F) << j * 7;
if ((nextByte & 0x80) != 128) {
return result;
}
}
throw new RuntimeException("VarInt is too big");
}
/**
* Gets this packet's ID
*
* @return The ID
*/
public int getId() {
return this.id;
}
/**
* Gets this packet's contents as a byte array
*
* @return The contents
*/
public byte[] getBytes() {
return this.bytes;
}
/**
* Writes the id of this packet to a given output
*
* @param output Output to write to
*/
public void writeId(ByteBuf output) {
writeVarInt(output, this.id);
}
/**
* Writes the contents of this packet to a given output
*
* @param output Output to write to
*/
public void writeBytes(ByteBuf output) {
checkNotNull(output, "output cannot be null!");
output.writeBytes(this.bytes);
}
/**
* Fully writes the ID and contents of this packet to a given output
*
* @param output Output to write to
*/
public void writeFully(ByteBuf output) {
this.writeId(output);
this.writeBytes(output);
}
/**
* Serializes this packet into a byte buffer
*
* @return The buffer
*/
public ByteBuf serialize() {
ByteBuf buffer = Unpooled.buffer();
this.writeFully(buffer);
return buffer;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof WirePacket) {
WirePacket that = (WirePacket) obj;
return this.id == that.id && Arrays.equals(this.bytes, that.bytes);
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(this.bytes);
result = prime * result + this.id;
return result;
}
@Override
public String toString() {
return "WirePacket[id=" + this.id + ", bytes=" + Arrays.toString(this.bytes) + "]";
}
}
@@ -1,43 +1,44 @@
package com.comphenix.protocol.injector.netty;
import org.bukkit.entity.Player;
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.netty.Injector;
import org.bukkit.entity.Player;
final class EmptyInjector implements Injector {
public static final Injector WITHOUT_PLAYER = new EmptyInjector(null);
/**
* Represents a closed injector.
* @author Kristian
*/
public class ClosedInjector implements Injector {
private Player player;
/**
* Construct a new injector that is always closed.
* @param player - the associated player.
*/
public ClosedInjector(Player player) {
public EmptyInjector(Player player) {
this.player = player;
}
@Override
public int getProtocolVersion() {
return Integer.MIN_VALUE;
}
@Override
public boolean inject() {
return false;
}
@Override
public void uninject() {
}
@Override
public void close() {
// Do nothing
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
// Do nothing
}
@Override
public void recieveClientPacket(Object packet) {
// Do nothing
public void receiveClientPacket(Object packet) {
}
@Override
@@ -52,24 +53,22 @@ public class ClosedInjector implements Injector {
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player player) {
// Do nothing
}
@Override
public Player getPlayer() {
return player;
return this.player;
}
@Override
public void setPlayer(Player player) {
this.player = player;
}
@Override
public void disconnect(String message) {
}
@Override
public boolean isInjected() {
return false;
@@ -79,9 +78,4 @@ public class ClosedInjector implements Injector {
public boolean isClosed() {
return true;
}
@Override
public int getProtocolVersion() {
return Integer.MIN_VALUE;
}
}
@@ -0,0 +1,44 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.utility.MinecraftReflection;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter {
private final NettyChannelInjector injector;
private final ChannelListener channelListener;
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
this.injector = injector;
this.channelListener = listener;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Class<?> messageClass = msg.getClass();
if (this.shouldInterceptMessage(messageClass)) {
// process the login if the packet is one before posting the packet to any handler to provide "real" data
// the method invocation will do nothing if the packet is not a login packet
this.injector.tryProcessLogin(msg);
// check if there are any listeners bound for the packet - if not just post the packet down the pipeline
if (!this.channelListener.hasListener(messageClass)) {
ctx.fireChannelRead(msg);
return;
}
// call all inbound listeners
this.injector.processInboundPacket(ctx, msg, messageClass);
} else {
// just pass the message down the pipeline
ctx.fireChannelRead(msg);
}
}
private boolean shouldInterceptMessage(Class<?> messageClass) {
// only intercept minecraft packets and no garbage from other stuff in the channel
return MinecraftReflection.getPacketClass().isAssignableFrom(messageClass);
}
}
@@ -0,0 +1,291 @@
/**
* 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.injector.netty.channel;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Represents an injector factory.
* <p>
* Note that the factory will return {@link EmptyInjector} when the factory is closed.
*
* @author Kristian
*/
public class InjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
// bukkit stuff
private final Plugin plugin;
private final Server server;
// protocol lib stuff
private final ErrorReporter errorReporter;
// state of the factory
private boolean closed;
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
this.plugin = plugin;
this.server = server;
this.errorReporter = errorReporter;
}
/**
* Retrieve the main plugin associated with this injection factory.
*
* @return The main plugin.
*/
public Plugin getPlugin() {
return this.plugin;
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
*
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
if (this.closed) {
return new EmptyInjector(player);
}
// try to get the injector using the player reference first
Injector injector = this.playerLookup.get(player);
if (injector == null) {
injector = this.getTemporaryInjector(player);
}
// check if we found an injector
if (injector != null && !injector.isClosed()) {
return injector;
}
// check if a network manager is present, if not maybe we cached the player temporarily
Object networkManager = MinecraftFields.getNetworkManager(player);
if (networkManager == null) {
return this.fromName(player.getName(), player);
}
// get the channel of the player and check if we already hooked into it
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
injector = NettyChannelInjector.findInjector(channel);
if (injector != null) {
// check if the new player is not the old one, this saves us a bit when many calls to the method are made
if (injector.getPlayer() != player || !this.playerLookup.containsKey(player)) {
this.playerLookup.remove(injector.getPlayer());
this.cacheInjector(player, injector);
// re-set the player of the injection
injector.setPlayer(player);
}
} else {
// construct a new injector as it seems like we have none yet
injector = new NettyChannelInjector(
player,
this.server,
networkManager,
channel,
listener,
this,
this.errorReporter);
this.cacheInjector(player, injector);
}
// definitely not null
return injector;
}
/**
* Retrieve a cached injector from a name.
* <p>
* The injector may be NULL if the plugin has been reloaded during a player login.
*
* @param name - the name.
* @param player - the player.
* @return The cached injector, or a closed injector if it could not be found.
*/
public Injector fromName(String name, Player player) {
if (this.closed) {
return new EmptyInjector(player);
}
// check if we have a player with that name cached
Injector injector = this.nameLookup.get(name);
if (injector != null) {
injector.setPlayer(player);
return injector;
}
return new EmptyInjector(player);
}
/**
* Construct a new channel injector for the given channel.
*
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (this.closed) {
return EmptyInjector.WITHOUT_PLAYER;
}
Object netManager = this.findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
NettyChannelInjector injector = new NettyChannelInjector(
temporaryPlayer,
this.server,
netManager,
channel,
listener,
this,
this.errorReporter);
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
return injector;
}
/**
* Invalidate a cached injector.
*
* @param player - the associated player.
* @return The cached injector, or NULL if nothing was cached.
*/
public Injector invalidate(Player player, String name) {
Injector injector = null;
// try the name first, more unsafe but works 99% of the time
if (name != null) {
injector = this.nameLookup.remove(name);
}
// if we have a player then use that as the safe removal way
if (player != null) {
injector = this.playerLookup.remove(player);
}
return injector;
}
/**
* Cache an injector by player.
*
* @param player - the player.
* @param injector - the injector to cache.
* @return The previously cached injector.
*/
public Injector cacheInjector(Player player, Injector injector) {
this.nameLookup.put(player.getName(), injector);
return this.playerLookup.put(player, injector);
}
/**
* Cache an injector by name alone.
*
* @param name - the name to lookup.
* @param injector - the injector.
* @return The cached injector.
*/
public Injector cacheInjector(String name, Injector injector) {
return this.nameLookup.put(name, injector);
}
/**
* Retrieve the associated channel injector.
*
* @param player - the temporary player, or normal Bukkit player.
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelMinimalInjector) {
return ((NettyChannelMinimalInjector) injector).getInjector();
}
return null;
}
/**
* Find the network manager in a channel's pipeline.
*
* @param channel - the channel.
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
MinecraftReflection.getNetworkManagerClass());
if (networkManager != null) {
return networkManager;
}
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
}
/**
* Determine if the factory is closed.
* <p>
* If it is, all new injectors will be closed by default.
*
* @return TRUE if it is closed, FALSE otherwise.
*/
public boolean isClosed() {
return this.closed;
}
/**
* Close all injectors created by this factory, and cease the creation of new injections.
*/
public void close() {
if (!this.closed) {
this.closed = true;
// Close everything
for (Injector injector : this.playerLookup.values()) {
injector.close();
}
for (Injector injector : this.nameLookup.values()) {
injector.close();
}
}
}
}
@@ -0,0 +1,662 @@
package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
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.wrappers.WrappedGameProfile;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.util.AttributeKey;
import org.bukkit.Server;
import org.bukkit.entity.Player;
public class NettyChannelInjector implements Injector {
// an accessor used when we're unable to retrieve the actual packet field in an outbound packet send
private static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
@Override
public Object get(Object instance) {
return null;
}
@Override
public void set(Object instance, Object value) {
}
@Override
public Field getField() {
return null;
}
};
private static final String INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
private static final String WIRE_PACKET_ENCODER_NAME = "protocol_lib_wire_packet_encoder";
// all registered channel handlers to easier make sure we unregister them all from the pipeline
private static final String[] PROTOCOL_LIB_HANDLERS = new String[]{
WIRE_PACKET_ENCODER_NAME, INTERCEPTOR_NAME
};
private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
private static final WirePacketEncoder WIRE_PACKET_ENCODER = new WirePacketEncoder();
private static final Map<Class<?>, FieldAccessor> PACKET_ACCESSORS = new ConcurrentHashMap<>(16, 0.9f);
private static final Class<?> LOGIN_PACKET_START_CLASS = PacketType.Login.Client.START.getPacketClass();
private static final Class<?> PACKET_PROTOCOL_CLASS = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass();
private static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey());
private static final AttributeKey<NettyChannelInjector> INJECTOR = AttributeKey.valueOf(getRandomKey());
// lazy initialized fields, if we don't need them we don't bother about them
private static FieldAccessor LOGIN_PROFILE_ACCESSOR;
private static FieldAccessor PROTOCOL_VERSION_ACCESSOR;
// bukkit stuff
private final Server server;
// protocol lib stuff we need
private final ErrorReporter errorReporter;
private final NetworkProcessor networkProcessor;
// references
private final Object networkManager;
private final Channel wrappedChannel;
private final ChannelListener channelListener;
private final InjectionFactory injectionFactory;
private final FieldAccessor channelField;
// packet marking
private final Map<Object, NetworkMarker> savedMarkers = new WeakHashMap<>(16, 0.9f);
private final Set<Object> skippedPackets = Collections.synchronizedSet(new HashSet<>());
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
// status of this injector
private volatile boolean closed = false;
private volatile boolean injected = false;
// information about the player belonging to this injector
private String playerName;
private Player resolvedPlayer;
// lazy initialized fields, if we don't need them we don't bother about them
private Object playerConnection;
private FieldAccessor protocolAccessor;
public NettyChannelInjector(
Player player,
Server server,
Object netManager,
Channel channel,
ChannelListener listener,
InjectionFactory injector,
ErrorReporter errorReporter
) {
// bukkit stuff
this.server = server;
this.resolvedPlayer = player;
// protocol lib stuff
this.errorReporter = errorReporter;
this.networkProcessor = new NetworkProcessor(errorReporter);
// references
this.networkManager = netManager;
this.wrappedChannel = channel;
this.channelListener = listener;
this.injectionFactory = injector;
// register us into the channel
this.wrappedChannel.attr(INJECTOR).set(this);
// read the channel field from the network manager given to this method
// we re-read this field every time as plugins/spigot forks might give us different network manager types
Field channelField = FuzzyReflection.fromObject(netManager, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(Channel.class)
.banModifier(Modifier.STATIC)
.build());
this.channelField = Accessors.getFieldAccessor(channelField);
// hook here into the close future to be 100% sure that this injector gets closed when the channel we wrap gets closed
// normally we listen to the disconnect event, but there is a very small period of time, between the login and actual
// join that is not covered by the disconnect event and may lead to unexpected injector states...
this.wrappedChannel.closeFuture().addListener(future -> this.close());
}
static NettyChannelInjector findInjector(Channel channel) {
return channel.attr(INJECTOR).get();
}
static Object findChannelHandler(Channel channel, Class<?> type) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (type.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
return null;
}
private static String getRandomKey() {
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
}
private static boolean hasProtocolLibHandler(Channel channel) {
for (String handler : PROTOCOL_LIB_HANDLERS) {
if (channel.pipeline().get(handler) != null) {
return true;
}
}
return false;
}
@Override
public int getProtocolVersion() {
Integer protocolVersion = this.wrappedChannel.attr(PROTOCOL_VERSION).get();
return protocolVersion == null ? MinecraftProtocolVersion.getCurrentVersion() : protocolVersion;
}
@Override
public boolean inject() {
// we only do this on the channel event loop to prevent blocking the main server thread
// and to be sure that the netty pipeline view we get is up-to-date
if (this.wrappedChannel.eventLoop().inEventLoop()) {
// ensure that we should actually inject into the channel
if (this.closed || this.wrappedChannel instanceof ByteBuddyGenerated || !this.wrappedChannel.isActive()) {
return false;
}
// check here if we need to rewrite the channel field and do so
// minecraft overrides the channel field when the channel actually becomes active, so we need to ensure that our
// proxied channel is always on that field - therefore this rewrite is event before we check if we're already
// injected into the channel
this.rewriteChannelField();
// check if we already injected into the channel
if (hasProtocolLibHandler(this.wrappedChannel)) {
return false;
}
// inject our handlers
this.wrappedChannel.pipeline().addAfter("encoder", WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER);
this.wrappedChannel.pipeline().addAfter(
"decoder",
INTERCEPTOR_NAME,
new InboundPacketInterceptor(this, this.channelListener));
this.injected = true;
return true;
} else {
// re-run in event loop, return false as we cannot be sure if the injection actually worked
this.ensureInEventLoop(this::inject);
return false;
}
}
@Override
public void uninject() {
// ensure that we injected into the channel before trying to remove anything from it
if (this.injected) {
// uninject on the event loop to ensure the instant visibility of the change and prevent blocks of other threads
if (this.wrappedChannel.eventLoop().inEventLoop()) {
this.injected = false;
// remove known references to us
this.wrappedChannel.attr(INJECTOR).remove();
this.channelField.set(this.networkManager, this.wrappedChannel);
for (String handler : PROTOCOL_LIB_HANDLERS) {
try {
this.wrappedChannel.pipeline().remove(handler);
} catch (NoSuchElementException ignored) {
// ignore that one, probably an edge case
}
}
} else {
this.ensureInEventLoop(this::uninject);
}
}
}
@Override
public void close() {
// ensure that the injector wasn't close before
if (!this.closed) {
this.closed = true;
// remove all of our references from the channel
this.uninject();
// cleanup
this.savedMarkers.clear();
this.skippedPackets.clear();
// wipe this injector completely
this.injectionFactory.invalidate(this.getPlayer(), this.playerName);
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
// do not send the packet if this injector was already closed / is not injected yet
if (this.closed || !this.injected) {
return;
}
// register the packet as filtered if we shouldn't post it to any listener
if (!filtered) {
this.skippedPackets.add(packet);
}
// save the given packet marker and send the packet
this.saveMarker(packet, marker);
try {
if (this.resolvedPlayer instanceof ByteBuddyGenerated) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet);
} else {
// ensure that the player is properly connected before sending
Object playerConnection = this.getPlayerConnection();
if (playerConnection != null) {
MinecraftMethods.getSendPacketMethod().invoke(playerConnection, packet);
}
}
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
}
}
@Override
public void receiveClientPacket(Object packet) {
// do not do that if we're not injected or this injector was closed
if (this.closed || !this.injected) {
return;
}
Runnable receiveAction = () -> {
try {
// try to invoke the method, this should normally not fail
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet);
} catch (Exception exception) {
// 99% the user gave wrong information to the server
this.errorReporter.reportMinimal(this.injectionFactory.getPlugin(), "receiveClientPacket", exception);
}
};
// execute the action on the event loop rather than any thread which we should potentially not block
if (this.wrappedChannel.eventLoop().inEventLoop()) {
receiveAction.run();
} else {
this.ensureInEventLoop(receiveAction);
}
}
@Override
public Protocol getCurrentProtocol() {
// ensure that the accessor to the protocol field is available
if (this.protocolAccessor == null) {
this.protocolAccessor = Accessors.getFieldAccessor(
this.networkManager.getClass(),
MinecraftReflection.getEnumProtocolClass(),
true);
}
Object nmsProtocol = this.protocolAccessor.get(this.networkManager);
return Protocol.fromVanilla((Enum<?>) nmsProtocol);
}
@Override
public NetworkMarker getMarker(Object packet) {
return this.savedMarkers.get(packet);
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null && !this.closed) {
this.savedMarkers.put(packet, marker);
}
}
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.resolvedPlayer != null) {
return this.resolvedPlayer;
}
// check if the name of the player is already known to the injector
if (this.playerName != null) {
this.resolvedPlayer = this.server.getPlayerExact(this.playerName);
}
// either we resolved it or we didn't...
return this.resolvedPlayer;
}
@Override
public void setPlayer(Player player) {
this.resolvedPlayer = player;
this.playerName = player.getName();
}
@Override
public void disconnect(String message) {
// we're still during pre-login, just close the connection
if (this.playerConnection == null || this.resolvedPlayer instanceof ByteBuddyGenerated) {
this.wrappedChannel.disconnect();
} else {
try {
// try to call the disconnect method on the player
MinecraftMethods.getDisconnectMethod(this.playerConnection.getClass()).invoke(this.playerConnection, message);
} catch (Exception exception) {
throw new IllegalArgumentException("Unable to disconnect the current injector", exception);
}
}
}
@Override
public boolean isInjected() {
return this.injected;
}
@Override
public boolean isClosed() {
return this.closed;
}
void tryProcessLogin(Object packet) {
// check if the given packet is a login packet
if (LOGIN_PACKET_START_CLASS != null && LOGIN_PACKET_START_CLASS.equals(packet.getClass())) {
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
// 1.19 removed the profile from the packet and now sends the plain username directly
// ensure that the game profile accessor is available
if (LOGIN_PROFILE_ACCESSOR == null) {
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(LOGIN_PACKET_START_CLASS, String.class, true);
}
// get the username from the packet
String username = (String) LOGIN_PROFILE_ACCESSOR.get(packet);
// cache the injector and the player name
this.playerName = username;
this.injectionFactory.cacheInjector(username, this);
} else {
// ensure that the game profile accessor is available
if (LOGIN_PROFILE_ACCESSOR == null) {
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(
LOGIN_PACKET_START_CLASS,
MinecraftReflection.getGameProfileClass(),
true);
}
// the client only sends the name but the server wraps it into a GameProfile, so here we are
WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_PROFILE_ACCESSOR.get(packet));
// cache the injector and the player name
this.playerName = profile.getName();
this.injectionFactory.cacheInjector(profile.getName(), this);
}
return;
}
// protocol version begin
if (PACKET_PROTOCOL_CLASS != null && PACKET_PROTOCOL_CLASS.equals(packet.getClass())) {
// ensure the protocol version accessor is available
if (PROTOCOL_VERSION_ACCESSOR == null) {
try {
Field ver = FuzzyReflection.fromClass(PACKET_PROTOCOL_CLASS, true).getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.typeExact(int.class)
.build());
PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver);
} catch (IllegalArgumentException exception) {
// unable to resolve that field, continue no-op
PROTOCOL_VERSION_ACCESSOR = NO_OP_ACCESSOR;
}
}
// read the protocol version from the field if available
if (PROTOCOL_VERSION_ACCESSOR != NO_OP_ACCESSOR) {
int protocolVersion = (int) PROTOCOL_VERSION_ACCESSOR.get(packet);
this.wrappedChannel.attr(PROTOCOL_VERSION).set(protocolVersion);
}
}
}
private void rewriteChannelField() {
// check if we need to rewrite the channel or if the channel is already correct (prevent wrapping a wrapped channel)
Object currentChannel = this.channelField.get(this.networkManager);
if (currentChannel instanceof NettyChannelProxy) {
return;
}
// the field is not correct, rewrite now to our handler
Channel ch = new NettyChannelProxy(this.wrappedChannel, new NettyEventLoopProxy(this.wrappedChannel.eventLoop(), this) {
@Override
protected Runnable doProxyRunnable(Runnable original) {
return NettyChannelInjector.this.processOutbound(original);
}
@Override
protected <T> Callable<T> doProxyCallable(Callable<T> original) {
return NettyChannelInjector.this.processOutbound(original);
}
}, this);
this.channelField.set(this.networkManager, ch);
}
private void ensureInEventLoop(Runnable runnable) {
this.ensureInEventLoop(this.wrappedChannel.eventLoop(), runnable);
}
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
} else {
eventLoop.execute(runnable);
}
}
void processInboundPacket(ChannelHandlerContext ctx, Object packet, Class<?> packetClass) {
if (this.channelListener.hasMainThreadListener(packetClass) && !this.server.isPrimaryThread()) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
this.server.getScheduler().runTask(
this.injectionFactory.getPlugin(),
() -> this.processInboundPacket(ctx, packet, packetClass));
return;
}
// call packet handlers, a null result indicates that we shouldn't change anything
PacketEvent interceptionResult = this.channelListener.onPacketReceiving(this, packet, null);
if (interceptionResult == null) {
this.ensureInEventLoop(ctx.channel().eventLoop(), () -> ctx.fireChannelRead(packet));
return;
}
// fire the intercepted packet down the pipeline if it wasn't cancelled
if (!interceptionResult.isCancelled()) {
this.ensureInEventLoop(
ctx.channel().eventLoop(),
() -> ctx.fireChannelRead(interceptionResult.getPacket().getHandle()));
// check if there were any post events added the packet after we fired it down the pipeline
// we use this way as we don't want to construct a new network manager accidentally
NetworkMarker marker = NetworkMarker.getNetworkMarker(interceptionResult);
if (marker != null) {
this.networkProcessor.invokePostEvent(interceptionResult, marker);
}
}
}
<T> T processOutbound(T action) {
// get the accessor to the packet field
// if we are unable to look up the accessor then just return the runnable, probably nothing of our business
FieldAccessor packetAccessor = this.lookupPacketAccessor(action);
if (packetAccessor == NO_OP_ACCESSOR) {
return action;
}
// get the packet field and ensure that the field is actually present (should always be, just to be sure)
Object packet = packetAccessor.get(action);
if (packet == null) {
return action;
}
// filter out all packets which were explicitly send to not be processed by any event
// pre-checking isEmpty will reduce the need of hashing packets which don't override the
// hashCode method; this presents calls to the very slow identityHashCode default implementation
NetworkMarker marker = this.savedMarkers.isEmpty() ? null : this.savedMarkers.remove(packet);
if (!this.skippedPackets.isEmpty() && this.skippedPackets.remove(packet)) {
// if a marker was set there might be scheduled packets to execute after the packet send
// for this to work we need to proxy the input action to provide access to them
if (marker != null) {
return this.proxyAction(action, null, marker);
}
// nothing special, just no processing
return action;
}
// no listener and no marker - no magic :)
if (!this.channelListener.hasListener(packet.getClass()) && marker == null) {
return action;
}
// ensure that we are on the main thread if we need to
if (this.channelListener.hasMainThreadListener(packet.getClass()) && !this.server.isPrimaryThread()) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
this.server.getScheduler().scheduleSyncDelayedTask(
this.injectionFactory.getPlugin(),
() -> this.sendServerPacket(packet, null, true));
return null;
}
// call all listeners which are listening to the outbound packet, if any
// null indicates that no listener was affected by the packet, meaning that we can directly send the original packet
PacketEvent event = this.channelListener.onPacketSending(this, packet, marker);
if (event == null) {
return action;
}
// if the event wasn't cancelled by this action we must recheck if the packet changed during the method call
if (!event.isCancelled()) {
// rewrite the packet in the given action if the packet was changed during the event call
Object interceptedPacket = event.getPacket().getHandle();
if (interceptedPacket != packet) {
packetAccessor.set(action, interceptedPacket);
}
// this is essential to do this way as a call to getMarker on the event will construct a new marker instance if needed
// we just want to know here if there is a marker to proceed correctly
// if the marker is null we can just schedule the action as we don't need to do anything after the packet was sent
NetworkMarker eventMarker = NetworkMarker.getNetworkMarker(event);
if (eventMarker == null) {
return action;
}
// we need to wrap the action to call the listeners set in the marker
return this.proxyAction(action, event, eventMarker);
}
// return null if the event was cancelled to schedule a no-op event
return null;
}
@SuppressWarnings("unchecked")
private <T> T proxyAction(T action, PacketEvent event, NetworkMarker marker) {
// hack - we only know that the given action is either a runnable or callable, but we need to work out which thing
// it is exactly to proceed correctly here.
if (action instanceof Runnable) {
// easier thing to do - just wrap the runnable in a new one
return (T) (Runnable) () -> {
// notify the outbound handler that the packets are processed
this.processedPackets.set(Boolean.TRUE);
// execute the action & invoke the post event
((Runnable) action).run();
this.networkProcessor.invokePostEvent(event, marker);
};
} else if (action instanceof Callable<?>) {
// okay this is a bit harder now - we need to wrap the action and return the value of it
return (T) (Callable<Object>) () -> {
// notify the outbound handler that the packets are processed
this.processedPackets.set(Boolean.TRUE);
// execute the action & invoke the post event
Object value = ((Callable<Object>) action).call();
this.networkProcessor.invokePostEvent(event, marker);
return value;
};
} else {
throw new IllegalStateException("Unexpected input action of type " + action.getClass());
}
}
private FieldAccessor lookupPacketAccessor(Object action) {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> {
try {
// find the field
Field packetField = FuzzyReflection.fromClass(clazz, true).getField(FuzzyFieldContract
.newBuilder()
.typeSuperOf(MinecraftReflection.getPacketClass())
.build());
return Accessors.getFieldAccessor(packetField);
} catch (IllegalArgumentException exception) {
// no such field found :(
return NO_OP_ACCESSOR;
}
});
}
private Object getPlayerConnection() {
// resolve the player connection if needed
if (this.playerConnection == null) {
Player target = this.getPlayer();
if (target == null) {
return null;
}
this.playerConnection = MinecraftFields.getPlayerConnection(target);
}
// cannot be null at this point
return this.playerConnection;
}
public Channel getWrappedChannel() {
return this.wrappedChannel;
}
}
@@ -0,0 +1,44 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
final class NettyChannelMinimalInjector implements MinimalInjector {
private final NettyChannelInjector injector;
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
this.injector = injector;
}
@Override
public SocketAddress getAddress() {
return this.injector.getWrappedChannel().remoteAddress();
}
@Override
public void disconnect(String message) {
this.injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
this.injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return this.injector.getPlayer();
}
@Override
public boolean isConnected() {
return this.injector.getWrappedChannel().isActive();
}
public NettyChannelInjector getInjector() {
return this.injector;
}
}
@@ -0,0 +1,271 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.utility.MinecraftReflection;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.net.SocketAddress;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
/**
* A netty channel which has all methods delegated to another given channel except for the event loop which is proxied
* separately. This class can not extend from AbstractChannel as the {@code newUnsafe()} method is protected and can
* therefore not be called in the delegate channel.
*/
final class NettyChannelProxy implements Channel {
private final Channel delegate;
private final EventLoop eventLoop;
private final NettyChannelInjector injector;
public NettyChannelProxy(Channel delegate, EventLoop eventLoop, NettyChannelInjector injector) {
this.delegate = delegate;
this.eventLoop = eventLoop;
this.injector = injector;
}
@Override
public EventLoop eventLoop() {
return this.eventLoop;
}
@Override
public Channel parent() {
return this.delegate.parent();
}
@Override
public ChannelConfig config() {
return this.delegate.config();
}
@Override
public boolean isOpen() {
return this.delegate.isOpen();
}
@Override
public boolean isRegistered() {
return this.delegate.isRegistered();
}
@Override
public boolean isActive() {
return this.delegate.isActive();
}
@Override
public ChannelMetadata metadata() {
return this.delegate.metadata();
}
@Override
public SocketAddress localAddress() {
return this.delegate.localAddress();
}
@Override
public SocketAddress remoteAddress() {
return this.delegate.remoteAddress();
}
@Override
public ChannelFuture closeFuture() {
return this.delegate.closeFuture();
}
@Override
public boolean isWritable() {
return this.delegate.isWritable();
}
@Override
public Unsafe unsafe() {
return this.delegate.unsafe();
}
@Override
public ChannelPipeline pipeline() {
return this.delegate.pipeline();
}
@Override
public ByteBufAllocator alloc() {
return this.delegate.alloc();
}
@Override
public ChannelPromise newPromise() {
return this.delegate.newPromise();
}
@Override
public ChannelProgressivePromise newProgressivePromise() {
return this.delegate.newProgressivePromise();
}
@Override
public ChannelFuture newSucceededFuture() {
return this.delegate.newSucceededFuture();
}
@Override
public ChannelFuture newFailedFuture(Throwable cause) {
return this.delegate.newFailedFuture(cause);
}
@Override
public ChannelPromise voidPromise() {
return this.delegate.voidPromise();
}
@Override
public ChannelFuture bind(SocketAddress localAddress) {
return this.delegate.bind(localAddress);
}
@Override
public ChannelFuture connect(SocketAddress remoteAddress) {
return this.delegate.connect(remoteAddress);
}
@Override
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
return this.delegate.connect(remoteAddress, localAddress);
}
@Override
public ChannelFuture disconnect() {
return this.delegate.disconnect();
}
@Override
public ChannelFuture close() {
return this.delegate.close();
}
@Override
public ChannelFuture deregister() {
return this.delegate.deregister();
}
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return this.delegate.bind(localAddress, promise);
}
@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return this.delegate.connect(remoteAddress, promise);
}
@Override
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) {
return this.delegate.connect(remoteAddress, localAddress, promise);
}
@Override
public ChannelFuture disconnect(ChannelPromise promise) {
return this.delegate.disconnect(promise);
}
@Override
public ChannelFuture close(ChannelPromise promise) {
return this.delegate.close(promise);
}
@Override
public ChannelFuture deregister(ChannelPromise promise) {
return this.delegate.deregister(promise);
}
@Override
public Channel read() {
return this.delegate.read();
}
@Override
public ChannelFuture write(Object msg) {
return this.write(msg, this.newPromise());
}
@Override
public ChannelFuture write(Object msg, ChannelPromise promise) {
// only need to do our special handling if we are in the event loop
if (this.isPacketEventCallNeeded(msg)) {
this.processPacketOutbound(msg, packet -> this.delegate.write(packet, promise));
return promise;
} else {
// no special handling needed, just write the packet
return this.delegate.write(msg, promise);
}
}
@Override
public Channel flush() {
return this.delegate.flush();
}
@Override
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
// only need to do our special handling if we are in the event loop
if (this.isPacketEventCallNeeded(msg)) {
this.processPacketOutbound(msg, packet -> this.delegate.writeAndFlush(packet, promise));
return promise;
} else {
// no special handling needed, just write the packet
return this.delegate.writeAndFlush(msg, promise);
}
}
@Override
public ChannelFuture writeAndFlush(Object msg) {
return this.writeAndFlush(msg, this.newPromise());
}
@Override
public <T> Attribute<T> attr(AttributeKey<T> key) {
return this.delegate.attr(key);
}
@Override
public int compareTo(@NotNull Channel o) {
return this.delegate.compareTo(o);
}
private boolean isPacketEventCallNeeded(Object msg) {
if (MinecraftReflection.isPacketClass(msg)) {
// check if any packet was marked as processed before during the current execution
// then reset the thread local as there will always be only one packet per write op (if needed)
Boolean hasProcessedPacket = this.injector.processedPackets.get();
if (hasProcessedPacket == Boolean.TRUE) {
// at least one packet was processed before, so no need for us to process as well
this.injector.processedPackets.set(Boolean.FALSE);
return false;
} else {
// no packet was processed in the current context, we need to process
return true;
}
} else {
// not a packet, just ignore
return false;
}
}
private void processPacketOutbound(Object packet, Consumer<Object> delegateActionHandler) {
Runnable action = this.injector.processOutbound(() -> delegateActionHandler.accept(packet));
if (action != null) {
action.run();
}
}
}
@@ -0,0 +1,277 @@
package com.comphenix.protocol.injector.netty.channel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
* An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which
* schedule something on the event loop to methods which decide what should happen to the scheduled task.
*/
abstract class NettyEventLoopProxy implements EventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
};
private final EventLoop delegate;
private final NettyChannelInjector injector;
public NettyEventLoopProxy(EventLoop delegate, NettyChannelInjector injector) {
this.delegate = delegate;
this.injector = injector;
}
private Runnable proxyRunnable(Runnable original) {
// execute the proxy and check if we need to do anything
Runnable proxied = this.doProxyRunnable(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
this.injector.processedPackets.set(Boolean.TRUE);
original.run();
};
} else {
// either the action was not executed, or the proxy will set the packet as processes
return proxied;
}
}
private <T> Callable<T> proxyCallable(Callable<T> original) {
// execute the proxy and check if we need to do anything
Callable<T> proxied = this.doProxyCallable(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
this.injector.processedPackets.set(Boolean.TRUE);
return proxied.call();
};
} else {
// either the action was not executed, or the proxy will set the packet as processes
return proxied;
}
}
/**
* Proxies the given runnable. The returned runnable will be executed instead of the original. If this method returns
* null a no-op runnable will be scheduled instead, preventing the original action from happening.
*
* @param original the runnable to proxy.
* @return the runnable to execute instead, null to execute no action.
*/
protected abstract Runnable doProxyRunnable(Runnable original);
/**
* Proxies the given callable. The returned callable will be executed instead of the original. If this method returns
* null a callable which always returns null will be scheduled instead, preventing the original action from
* happening.
*
* @param original the callable to proxy.
* @param <T> the return type of the original callable.
* @return the callable to execute instead of the original, null to use a no-op callable instead.
*/
protected abstract <T> Callable<T> doProxyCallable(Callable<T> original);
@Override
public EventLoopGroup parent() {
return this.delegate.parent();
}
@Override
public EventLoop next() {
return this.delegate.next();
}
@Override
public boolean inEventLoop() {
return this.delegate.inEventLoop();
}
@Override
public boolean inEventLoop(Thread thread) {
return this.delegate.inEventLoop(thread);
}
@Override
public <V> Promise<V> newPromise() {
return this.delegate.newPromise();
}
@Override
public <V> ProgressivePromise<V> newProgressivePromise() {
return this.delegate.newProgressivePromise();
}
@Override
public <V> Future<V> newSucceededFuture(V result) {
return this.delegate.newSucceededFuture(result);
}
@Override
public <V> Future<V> newFailedFuture(Throwable cause) {
return this.delegate.newFailedFuture(cause);
}
@Override
public boolean isShuttingDown() {
return this.delegate.isShuttingDown();
}
@Override
public Future<?> shutdownGracefully() {
return this.delegate.shutdownGracefully();
}
@Override
public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) {
return this.delegate.shutdownGracefully(quietPeriod, timeout, unit);
}
@Override
public Future<?> terminationFuture() {
return this.delegate.terminationFuture();
}
@Override
@Deprecated
public void shutdown() {
this.delegate.shutdown();
}
@Override
@Deprecated
public List<Runnable> shutdownNow() {
return this.delegate.shutdownNow();
}
@Override
public Iterator<EventExecutor> iterator() {
return this.delegate.iterator();
}
@Override
public Future<?> submit(Runnable task) {
Runnable proxied = this.proxyRunnable(task);
return this.delegate.submit(proxied == null ? EMPTY_RUNNABLE : proxied);
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
Runnable proxied = this.proxyRunnable(task);
return this.delegate.submit(proxied == null ? EMPTY_RUNNABLE : proxied, result);
}
@Override
@SuppressWarnings("unchecked")
public <T> Future<T> submit(Callable<T> task) {
Callable<T> proxied = this.proxyCallable(task);
return this.delegate.submit(proxied == null ? (Callable<T>) EMPTY_CALLABLE : proxied);
}
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
Runnable proxied = this.proxyRunnable(command);
return this.delegate.schedule(proxied == null ? EMPTY_RUNNABLE : proxied, delay, unit);
}
@Override
@SuppressWarnings("unchecked")
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
Callable<V> proxied = this.proxyCallable(callable);
return this.delegate.schedule(callable == null ? (Callable<V>) EMPTY_CALLABLE : proxied, delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
Runnable proxied = this.proxyRunnable(command);
return this.delegate.scheduleAtFixedRate(proxied == null ? EMPTY_RUNNABLE : proxied, initialDelay, period, unit);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
Runnable proxied = this.proxyRunnable(command);
return this.delegate.scheduleWithFixedDelay(proxied == null ? EMPTY_RUNNABLE : proxied, initialDelay, delay, unit);
}
@Override
public boolean isShutdown() {
return this.delegate.isShutdown();
}
@Override
public boolean isTerminated() {
return this.delegate.isTerminated();
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return this.delegate.awaitTermination(timeout, unit);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return this.delegate.invokeAll(tasks);
}
@Override
public <T> List<java.util.concurrent.Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
TimeUnit unit) throws InterruptedException {
return this.delegate.invokeAll(tasks, timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
return this.delegate.invokeAny(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return this.delegate.invokeAny(tasks, timeout, unit);
}
@Override
public void execute(Runnable command) {
Runnable proxied = this.proxyRunnable(command);
if (proxied != null) {
this.delegate.execute(proxied);
}
}
@Override
public void forEach(Consumer<? super EventExecutor> action) {
this.delegate.forEach(action);
}
@Override
public Spliterator<EventExecutor> spliterator() {
return this.delegate.spliterator();
}
@Override
public ChannelFuture register(Channel channel) {
return this.delegate.register(channel);
}
@Override
public ChannelFuture register(Channel channel, ChannelPromise promise) {
return this.delegate.register(channel, promise);
}
}
Loaded 100 of 276 files, more files were not shown because too many files have changed in this diff. Show more