Compare commits

..
100 Commits
Author SHA1 Message Date
Dan Mulloy 95a884974a Only set chat UUID if not already set
Fixes #1310
2021-07-08 23:33:17 -04:00
Dan Mulloy 153dd61994 Update to 1.17.1
Fixes #1315
2021-07-08 23:22:56 -04:00
Dan Mulloy 263ec8a36e Add FLYING packet back for backwards compat
Fixes #1275
2021-06-27 18:13:40 -04:00
Julian af46ba4d1a Fix support for Minecraft versions under the bee update (#1276) 2021-06-27 17:57:14 -04:00
Dan Mulloy dd85904642 Metrics: only track each plugin once 2021-06-26 21:22:52 -04:00
Dan Mulloy 7a8fce224e Set mc object regex in 1.17
Fixes #1271
2021-06-26 20:57:39 -04:00
Dan Mulloy 9ca7c91a76 Simplify packet registry 2021-06-24 17:14:00 -04:00
Dan Mulloy 466354cd2c Cache list constructors to save on exceptions 2021-06-24 17:13:40 -04:00
Dan Mulloy 99504dab8f Abstract out structure modifiers to allow internal structures to be read 2021-06-24 17:13:05 -04:00
Dan Mulloy 9b6603e2eb Use fuzzy reflection for effect types 2021-06-21 16:10:58 -04:00
Dan Mulloy 4fc476a125 Use right int 2 object map class for Paper
Addresses #1217
2021-06-20 12:42:41 -04:00
Dan Mulloy 583ed4b58a Update scoreboard team class
Addresses #1232
2021-06-20 12:35:03 -04:00
Dan Mulloy 638e81b9ce Merge branch 'master' of https://github.com/dmulloy2/ProtocolLib 2021-06-20 12:08:52 -04:00
Dan Mulloy 9a0703d05d Fix attribute builder in 1.17
Addresses #1224
2021-06-20 12:08:47 -04:00
Pasqual Koschmieder 9de096f783 Fix sdk command for jitpack builds (#1241) 2021-06-20 11:51:55 -04:00
Dan Mulloy c54a99945d Try fixing some of the unit tests 2021-06-19 22:45:17 -04:00
Pasqual Koschmieder 90a38cc15c Restore backwards compatibility (#1235)
* Remove usages of net.minecraft and craftbukkit
* Restore packet type backward compatibility (tested on 1.8)
* Re-add last removed packets
* Fix sub class naming for newer minecraft versions
2021-06-19 22:20:54 -04:00
Dan Mulloy 76930ae6e8 Remove travis
It's gonna be a lot of work to build, so we're just gonna use GitHub actions
2021-06-19 22:15:03 -04:00
Pasqual Koschmieder d745cfb184 Switch to GH-Workflow & use java 16 on jitpack builds (#1236)
* Add github actions workflow
* Add jitpack.yml to configure java version
2021-06-19 22:11:12 -04:00
Dan Mulloy 4be2bf38ff Switch from travis-ci.org to .com 2021-06-18 10:53:17 -04:00
Pasqual Koschmieder 0a32f24f08 Fix EntityUseAction & Hand read for minecraft 1.17 (#1230) 2021-06-18 10:47:48 -04:00
Dan Mulloy 1c2bc274dd Fix differently mapped fastutil classes in Paper
Fixes #1228
2021-06-16 18:22:53 -04:00
Dan Mulloy 9b54794f6b Fix cloning in 1.17
Addresses #1222
2021-06-15 23:58:40 -04:00
Dan Mulloy 42bec5a858 Fix entity trackers in 1.17
Addresses #1217
2021-06-15 18:39:34 -04:00
Dan Mulloy fa317c1167 Working on live server 2021-06-14 16:36:22 -04:00
Dan Mulloy cc17b9ee6e Finish up unit tests 2021-06-13 17:18:36 -04:00
Dan Mulloy 190ca1ff6a Almost there, /theoretically/ 2021-06-13 15:57:23 -04:00
Dan Mulloy c51930121f Fix a bunch more classpaths
Also worked on fixing some wrappers
2021-06-13 11:36:44 -04:00
Dan Mulloy c7a8d734d4 Fix some more class paths 2021-06-12 23:53:09 -04:00
Dan Mulloy b446cf2183 Update unit tests to 1.17, fix packet lookups 2021-06-12 15:28:01 -04:00
Dan Mulloy f11c246276 Deprecate SocketInjector#getSocket (see #1188) 2021-06-04 18:24:30 -04:00
Camotoy 6f91bd23de Remove inferences of SocketChannel presence in temporary player (#1188)
To note: this is yet another compatibility change for my Geyser work, but https://github.com/PaperMC/Paper/pull/5611 will also break without these changes as Unix domain sockets don't implement SocketChannel.

The temporary player method delegation directed the isOnline and getName methods to functions that require the channel to be an instance of SocketChannel, when this won't always be the case. To solve this, this PR redirects `getSocket().getRemoteSocketAddress()` to `injector.getAddress()` which returns the same value. To determine if the player is online, a new method is created in SocketInjector to determine if a connection is online (which also returns the same value as before this commit).
2021-06-04 18:21:48 -04:00
Pim van der Loos 45c293df7d Update ByteBuddy dependency (#1156)
- Updated ByteBuddy dependency to add support for Java 17 (introduced in 1.10.21) and to take advantage of any fixes and improvements made between versions.
2021-05-29 13:01:45 -04:00
Camotoy 5acdb2b3c5 Mark connectionHandler as sharable (#1179)
Fixes #1170.

The reasoning for this PR is largely outlined in the above issue. As the ChannelInboundHandlerAdapter has no private class, a race condition cannot occur and therefore marking the class as sharable should incur no cost.
2021-05-29 13:00:58 -04:00
Camotoy 72c1f3e26c Redirect #mcdevs link to libera.chat (#1181)
The #mcdevs IRC channel is in the process of migrating away from Freenode.
2021-05-29 12:59:26 -04:00
Minecrell b3ccf82597 WrappedServerPing: Properly translate MotD to components for RGB colors (#1152)
In previous Minecraft versions, using WrappedServerPing.setMotD(String)
behaved exactly like using Bukkit's ServerListPingEvent.setMotd(String).

With the addition of RGB colors in Minecraft 1.16, Spigot's
ServerListPingEvent was patched to translate the MotD string to the
chat component equivalent to make it possible to use RGB colors in MotDs.
In general, using raw legacy color codes (e.g. §c) within a (JSON) text
component tends to cause weird issues on newer Minecraft versions,
so it's better to translate them to the JSON equivalents on the server.

However, the WrappedServerPing implementation in ProtocolLib was never
updated with the same change, which makes it behave differently from
Spigot's ServerListPingEvent now. Using ServerListPingEvent RGB color
codes work, using ProtocolLib they do not work.

To fix this, this commit changes WrappedServerPing.setMotD(String) to
use the same method as Spigot for translating the legacy text to the
JSON/chat component equivalent.

This allows for example ServerListPlus to use Spigot's RGB color codes
(e.g. &x&7&9&b&8&f&fHello) without requiring any changes in ServerListPlus.
2021-05-02 17:43:32 -04:00
LewUwU 0c01a11755 Fixed protocol injection with latest netty on minecraft 1.11 and below (#1067) 2021-05-01 15:51:52 -04:00
Pim van der Loos 7ce3f471bf Add support for Java 16 (#1120)
Switched from the now-unavailable ClassLoader::defineClass method to the java.lang.invoke.MethodHandles.Lookup::defineClass. This is available on Java 9+.
2021-04-04 23:03:05 -04:00
Dan Mulloy 97972acee8 Add converter for game state ids
Fixes #1041
2021-02-27 15:38:05 -05:00
LOOHP a0bb11e1bd Add support for PaperMC's new Adventure API Component (#1103) 2021-02-27 14:15:43 -05:00
Dan Mulloy ba74fceed6 Update version to 4.6.1-SNAPSHOT 2021-02-27 14:15:01 -05:00
Dan Mulloy ab0faab396 Update version to 4.6.0 for release 2021-02-15 13:59:14 -05:00
Dan Mulloy 765fd9e987 Broaden throwable for misbehaving metrics plugins (#1078)
Fixes #1078
2021-02-03 09:39:04 -05:00
Dan Mulloy 8f7b530613 Tested with 1.16.5 2021-01-24 17:14:32 -05:00
Dan Mulloy 30fe81d366 Move getPacketDescription from the command to HexDumper as a public API
Related to filoghost/HolographicDisplays#385
2021-01-04 13:35:58 -05:00
PimvanderLoos b54dd49426 Replace CGLib with ByteBuddy (#984)
- The gclib dependency in the EnchancerFactory has been removed. All classes that used the actual factory part of it have been updated to use bytebuddy instead. This class will have to be removed at some point, but at the moment it is still used for accessing its class loader.
- Renamed EnhancerFactory to ByteBuddyFactory. All ByteBuddy actions should go through this now. Every subclass created here implements the ByteBuddyGenerated interface. This makes it possible to recognize classes generated using ByteBuddy (by default, it doesn't leave such a trace).
- Removed the method DefaultInstances#forEnhancer(Enhancer). This method isn't used anywhere; the last trace of usage of the method I could find was in 2013 (in the NetworkServerInjector). External plugins (I couldn't find any that used it), they should really have their own implementation, given that they already require an instance of an Enchancer. As such, I feel it is safe to remove rather than update it.
2021-01-04 00:24:34 -05:00
Dan Mulloy 26274fed52 ✨backwards compat✨ 2021-01-02 23:59:13 -05:00
Jan Lindner aa555f792e add sound converter for versions greater 1.16.4 (#1027) 2021-01-02 23:55:23 -05:00
Dan Mulloy 0d50905754 Merge branch 'master' of https://github.com/dmulloy2/ProtocolLib 2021-01-02 23:19:28 -05:00
Dan Mulloy b7132196fb Fix ChatExtensions exception
Fixes #1045
2021-01-02 23:19:20 -05:00
Dan Mulloy eb54b99856 Update funding info 2020-12-30 15:31:39 -06:00
Dan Mulloy 13b3d8679d Update ChatExtensions for 1.16.4
Fixes #1044
2020-12-27 00:04:11 -05:00
dependabot[bot] 1545a88e06 Bump junit from 4.13 to 4.13.1 (#989)
Bumps [junit](https://github.com/junit-team/junit4) from 4.13 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.13...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-23 14:59:46 -05:00
Thibaut Gautier 2b22999b94 Fixed "protocollib_encoder" attempting to read non-nms packets (#959)
Added an override to the encoder as such would process any non-wire packets as NMS packets. In the case of the use of different APIs, such as Artemis Packet API, such would cause a conflict and would spit out casting exceptions. It is quite easy to resolve by exclusively accepting Wire Packets and packets which are assignable to the packet class. This solves the issue and tada happy ending.
2020-12-23 14:59:07 -05:00
PimvanderLoosandMark Vainomaa bbb053aa4e Fix Java 15 (#1025)
- Implemented a fix for the incompatibility with Java 15. This incompatibility was caused by the fact that the lambda generated in the NMS.NetworkManager is a hidden class in J15. Starting in Java 15, final fields in hidden classes can no longer be modified regardless of the 'accessible' flag. (see https://openjdk.java.net/jeps/371 "Using a hidden class", point 3). To circumvent this issue, this retrieves the data from the existing fields in the hidden class (a runnable or a callable) other than the packet. It then retrieves the constructor of the hidden class and instantiates it using the previously-retrieved data and the modified packet instance (this code is only used if the packet instance changed).
- Introduced a new ObjectReconstructor class that does all the fields/constructor discovering/accessing etc. The Runnable and Callable methods each get one instance of this class so that we can avoid having to get the fields/constructors and set them accessible every time we want to replace a packet.

Co-authored-by: Mark Vainomaa <mikroskeem@mikroskeem.eu>
2020-12-23 14:57:16 -05:00
Aurora 7bac4ec634 Update to 1.16.4 (#1007) 2020-11-09 15:11:48 -05:00
Dan Mulloy 4bc9e8b7b7 Fix a class cast exception with array wrappers
(Kinda surprised there isn't an issue to link here)
2020-10-19 16:30:42 -04:00
Dan Mulloy f381f0a2f7 Fix multi block change cloning
Fixes #990
2020-10-19 11:04:11 -04:00
Dan Mulloy 553e4b6813 Update to 1.16.3 2020-10-19 11:02:32 -04:00
Dan Mulloy bdaa843f2d Update to 1.16.2
Fixes #941
Fixes #944
Fixes #934
2020-08-24 13:19:49 -04:00
Dan Mulloy 13f5c14599 Fix bytecode breakage with StructureModifier
Fixes #935
2020-08-24 11:40:47 -04:00
Dan Mulloy b871eb3d54 Fix data watcher cloning in 1.16
Fixes #925
2020-08-06 14:26:27 -04:00
Dan Mulloy 12e3a895b3 Remove stray debug 2020-08-06 13:54:16 -04:00
Dan Mulloy 8c5fbe3298 Merge remote-tracking branch 'origin/master' 2020-08-04 19:04:22 -04:00
Dan Mulloy 0512215007 Add modifiers for world keys and moving block position
Fixes #906
Fixes #927
2020-08-04 19:03:59 -04:00
Tarrant 80f4c7b9a7 Support for MerchantRecipeList (#894)
* Added support for MerchantRecipeList found in OPEN_WINDOW_MERCHANT of MC versions 1.13+
2020-07-26 19:33:05 -04:00
Dan Mulloy 8c51b175c4 Merge remote-tracking branch 'origin/master' 2020-07-05 23:47:25 -04:00
Dan Mulloy 8d991ad5a7 Support dimensions in 1.16 (#893)
Fixes #893
2020-07-05 23:47:06 -04:00
Dan Mulloy f19bfc613e Add support for entity equipment pair in 1.16 2020-07-05 13:29:52 -04:00
Lewys Davies a7e702899a Implementing EntityPose (#873) 2020-06-30 23:18:04 -04:00
RERERE deb192b04d Support 1.16 PlayerDigType enumeration variable change (#889) 2020-06-30 23:16:52 -04:00
Dan Mulloy fbe46f7bac Force all chat packets to have empty UUID 2020-06-29 20:35:41 -04:00
Dan Mulloy fd93c1c553 Try to fix reload, give more detailed error (#874)
Fixes #874
2020-06-28 17:06:12 -04:00
Dan Mulloy b2f6a56843 Update tile entities for 1.16 (#880)
Ended up being simpler than I thought
2020-06-28 17:02:22 -04:00
Dan Mulloy 658da31d46 Initial support for 1.16 (#880)
Note that things may not initially work as expected. There are known issues (see #880) relating to dimensions, chat packets, and tile entities. There were also internal changes to attributes, though hopefully those are fixed. As always, report issues on GitHub.
2020-06-28 15:59:30 -04:00
xxDark 77feaa857e Cache perm gen bean (#876) 2020-06-23 16:26:30 -04:00
Niklas Seyfarth 5f204d798c StreamSerializer: Reduce duplicated code and allow (de)serialization to and from byte arrays. (#875) 2020-06-21 12:58:37 -04:00
NewbieOrange 5183bd53b5 Add MCP packet names for Bukkit-Forge hybrid server (#862)
Adds mappings to support MCP packets

Fixes #858
2020-06-16 13:35:38 -04:00
Dan Mulloy fdd30a7b87 Remove a bunch of legacy (<1.8) code
Shouldn't break any servers running 1.8+, but this version is all about code cleanup baby
2020-06-06 15:13:29 -04:00
Dan Mulloy 7ac4ac696f Fold BukkitExecutors into ProtocolLib (#721)
Fixes #721
2020-06-06 13:49:26 -04:00
Dan Mulloy c203fda391 Update version to 4.6.0-SNAPSHOT for development 2020-06-06 13:38:11 -04:00
Dan Mulloy 9a65ddbc43 Update version to 4.5.1 for release 2020-06-03 21:21:31 -04:00
Dan Mulloy 54c252a354 Improve WrappedDataWatcher hasIndex performance
Use the map to check for indices instead of getting the object

Addresses #850
2020-05-27 21:56:22 -04:00
Dan Mulloy e92abda187 Try to fix forge compatibility
Addresses #825
2020-05-27 21:41:56 -04:00
Dan Mulloy 3f7b7f4bb3 Make sure all Spigot forks are included for updater purposes
Addresses #835
2020-05-24 20:35:25 -04:00
Andrew Steinborn 944b3f8280 Cache PacketType#hashCode (#818)
This should result in a general performance improvement, as PacketType is often used as map keys across ProtocolLib.
2020-05-24 15:45:42 -04:00
Dan Mulloy b04fca8324 Fix packet interception error with ViaVersion (#724)
Thanks to @KennyTV and @MedicOP for their help in tracking this bug down. Essentially the decode method we were using could be different depending on when the player logged in, which clashed with PL's static handling of it.

Fixes #724
Fixes #791
Fixes #803
Fixes #811
Fixes #813
Fixes #819
...and probably some others...
2020-05-24 15:29:01 -04:00
Dan Mulloy bfa0eee91e Fix direction and dimension enums 2020-05-03 15:59:55 -04:00
Dan Mulloy 308e3d3417 Update to 1.15.2 2020-05-03 13:15:42 -04:00
Dan Mulloy 42e48aa9b8 Be more specific in which sendPacket method we choose
Related to PaperMC/Paper#3282
2020-05-03 12:44:59 -04:00
Dan Mulloy 18d0193288 Merge remote-tracking branch 'origin/master' 2020-04-30 18:47:06 -04:00
Dan Mulloy 0ab552a6ef Fix issue with legacy block data (#809)
Fixes #809
2020-04-30 18:46:29 -04:00
Dan Mulloy d9fef94bc6 Merge pull request #782 from bergerkiller/master
Replace ThreadLocal scheduleProcessPackets with queue, fixes #763
2020-03-03 09:15:24 -05:00
Irmo van den Berge ab5fb40f8f Replace ThreadLocal scheduleProcessPackets with queue, fixes #763 2020-03-01 13:07:14 +01:00
libraryaddict 4baa4aa724 AttributeModifier$Operation only to be fetched if viable (#773) 2020-02-18 16:12:24 -05:00
Dan Mulloy 7a4a285935 Add some more useful metrics 2020-01-11 13:49:36 -05:00
Dan Mulloy f38c393d6f Use new Spigot updater
Fixes #746
2020-01-11 13:47:49 -05:00
Dan Mulloy 22c2a4abcc Fix compatibility with CraftBukkit
Still recommend Spigot, for what it's worth
2019-12-26 11:39:23 -05:00
Dan Mulloy 1a434e9ea2 Try changing visibility on ProtocolLogger#init
Addresses #740
2019-12-26 11:34:33 -05:00
Dan Mulloy 25a5f1d07f Update version to 4.5.1-SNAPSHOT 2019-12-26 11:28:14 -05:00
154 changed files with 8550 additions and 13089 deletions

No files matched your search

+2 -2
View File
@@ -19,7 +19,7 @@ This page will detail specific things that must be done if you intend to contrib
#### Ready? Let's get started!
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [freenode.net](http://freenode.net)
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [libera.chat](http://libera.chat)
or #spigot on [irc.spi.gt](http://irc.spi.gt) ([webchat](https://irc.spi.gt/iris/?channels=spigot)).
2. Search for usages of the now-defunct NMS package guard and change them.
3. The class `com.comphenix.protocol.PacketType` contains a list of all the packets. If any packets were added or removed
@@ -35,4 +35,4 @@ and the release date.
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
If the protocol version has been incremented, add a new line to the map.
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
the build fails, debug!
the build fails, debug!
+2
View File
@@ -0,0 +1,2 @@
github: [dmulloy2]
custom: ["https://paypal.me/dmulloy2"]
+26
View File
@@ -0,0 +1,26 @@
name: ProtocolLib full build lifecycle
on:
- push
- pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup java
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '16'
check-latest: true
- name: Run maven build lifecycle
run: mvn --batch-mode clean test package
- name: Upload plugin file
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: target/ProtocolLib.jar
-11
View File
@@ -1,11 +0,0 @@
language: java
jdk:
- openjdk8
script: mvn clean test
install: true
notifications:
email:
recipients:
- "dmulloy2@live.com"
on_success: change
on_failure: always
+5 -5
View File
@@ -1,4 +1,4 @@
# ProtocolLib [![Travis Status](https://travis-ci.org/dmulloy2/ProtocolLib.svg?branch=master)](https://travis-ci.org/dmulloy2/ProtocolLib)
# 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
@@ -44,7 +44,7 @@ You can also add ProtocolLib as a Maven dependency:
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
...
</repositories>
@@ -53,7 +53,7 @@ You can also add ProtocolLib as a Maven dependency:
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.5.0</version>
<version>4.6.0</version>
</dependency>
</dependencies>
````
@@ -62,11 +62,11 @@ Or use the maven dependency with gradle:
```gradle
repositories {
maven { url "https://repo.dmulloy2.net/nexus/repository/public/" }
maven { url "https://repo.dmulloy2.net/repository/public/" }
}
dependencies {
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.5.0";
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.6.0";
}
```
+5
View File
@@ -0,0 +1,5 @@
before_install:
- source "$HOME/.sdkman/bin/sdkman-init.sh"
- sdk update
- sdk install java 16.0.1.hs-adpt
- sdk use java 16.0.1.hs-adpt
+30 -27
View File
@@ -4,7 +4,7 @@
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name>
<version>4.5.0</version>
<version>4.7.0-SNAPSHOT</version>
<description>Provides read/write access to the Minecraft protocol.</description>
<url>https://github.com/dmulloy2/ProtocolLib</url>
@@ -16,8 +16,8 @@
<project.build.number></project.build.number>
<project.fullVersion>${project.version}</project.fullVersion>
<powermock.version>2.0.4</powermock.version>
<spigot.version>1.15.1-R0.1-SNAPSHOT</spigot.version>
<powermock.version>2.0.7</powermock.version>
<spigot.version>1.17.1-R0.1-SNAPSHOT</spigot.version>
</properties>
<build>
@@ -38,7 +38,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
@@ -51,8 +51,8 @@
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
<pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
</relocation>
</relocations>
@@ -98,20 +98,25 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M4</version>
<version>3.0.0-M5</version>
<configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties>
<property>
<name>projectVersion</name>
<value>${project.version}</value>
</property>
</systemProperties>
<!-- TODO figure out a better way to do this before Java 17 -->
<!-- We're currently waiting on powermock, but we may need to switch -->
<argLine>--illegal-access=permit</argLine>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<version>3.2.0</version>
<configuration>
<failOnError>false</failOnError>
<encoding>ISO-8859-1</encoding>
@@ -145,8 +150,8 @@
</activation>
<properties>
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
<!--<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>-->
<project.fullVersion>${project.version}</project.fullVersion>
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
<!--<project.fullVersion>${project.version}</project.fullVersion>-->
</properties>
</profile>
@@ -166,7 +171,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
@@ -180,7 +185,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -252,18 +257,18 @@
<distributionManagement>
<repository>
<id>dmulloy2-releases</id>
<url>https://repo.dmulloy2.net/nexus/repository/releases/</url>
<url>https://repo.dmulloy2.net/repository/releases/</url>
</repository>
<snapshotRepository>
<id>dmulloy2-snapshots</id>
<url>https://repo.dmulloy2.net/nexus/repository/snapshots/</url>
<url>https://repo.dmulloy2.net/repository/snapshots/</url>
</snapshotRepository>
</distributionManagement>
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
<repository>
<id>spigot-repo</id>
@@ -292,31 +297,29 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
<scope>compile</scope>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.5.1</version>
<scope>provided</scope>
</dependency>
<!-- TODO fold BukkitExecutors into ProtocolLib -->
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>com.comphenix.executors</groupId>
<artifactId>BukkitExecutors</artifactId>
<version>1.1-SNAPSHOT</version>
<scope>compile</scope>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.11.0</version>
</dependency>
<!-- Testing dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.2.0</version>
<version>3.5.10</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -42,83 +42,63 @@ public interface AsynchronousManager {
* @param listener - the packet listener that will receive these asynchronous events.
* @return An asynchronous handler.
*/
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
AsyncListenerHandler registerAsyncHandler(PacketListener listener);
/**
* Unregisters and closes the given asynchronous handler.
* @param handler - asynchronous handler.
*/
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
void unregisterAsyncHandler(AsyncListenerHandler handler);
/**
* Unregisters and closes the first asynchronous handler associated with the given listener.
* @param listener - asynchronous listener
*/
public abstract void unregisterAsyncHandler(PacketListener listener);
void unregisterAsyncHandler(PacketListener listener);
/**
* Unregisters every asynchronous handler associated with this plugin.
* @param plugin - the original plugin.
*/
public void unregisterAsyncHandlers(Plugin plugin);
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be
* observed by the asynchronous listeners.
* <p>
* Deprecated: Use {@link #getSendingTypes()} instead.
* @return Every filtered server packet.
*/
@Deprecated
public abstract Set<Integer> getSendingFilters();
void unregisterAsyncHandlers(Plugin plugin);
/**
* Retrieves a immutable set containing the types of the sent server packets that will be
* observed by the asynchronous listeners.
* @return Every filtered server packet.
*/
public abstract Set<PacketType> getSendingTypes();
/**
* Retrieves a immutable set containing the ID of the recieved client packets that will be
* <p>
* Deprecated: Use {@link #getReceivingTypes()} instead.
* observed by the asynchronous listeners.
* @return Every filtered client packet.
*/
@Deprecated
public abstract Set<Integer> getReceivingFilters();
Set<PacketType> getSendingTypes();
/**
* Retrieves a immutable set containing the types of the received client packets that will be
* observed by the asynchronous listeners.
* @return Every filtered client packet.
*/
public abstract Set<PacketType> getReceivingTypes();
Set<PacketType> getReceivingTypes();
/**
* Determine if a given synchronous packet has asynchronous listeners.
* @param packet - packet to test.
* @return TRUE if it does, FALSE otherwise.
*/
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
boolean hasAsynchronousListeners(PacketEvent packet);
/**
* Retrieve the default packet stream.
* @return Default packet stream.
*/
public abstract PacketStream getPacketStream();
PacketStream getPacketStream();
/**
* Retrieve the default error reporter.
* @return Default reporter.
*/
public abstract ErrorReporter getErrorReporter();
ErrorReporter getErrorReporter();
/**
* Remove listeners, close threads and transmit every delayed packet.
*/
public abstract void cleanupAll();
void cleanupAll();
/**
* Signal that a packet is ready to be transmitted.
@@ -127,29 +107,29 @@ public interface AsynchronousManager {
* has been called previously.
* @param packet - packet to signal.
*/
public abstract void signalPacketTransmission(PacketEvent packet);
void signalPacketTransmission(PacketEvent packet);
/**
* Register a synchronous listener that handles packets when they time out.
* @param listener - synchronous listener that will handle timed out packets.
*/
public abstract void registerTimeoutHandler(PacketListener listener);
void registerTimeoutHandler(PacketListener listener);
/**
* Unregisters a given timeout listener.
* @param listener - the timeout listener to unregister.
*/
public abstract void unregisterTimeoutHandler(PacketListener listener);
void unregisterTimeoutHandler(PacketListener listener);
/**
* Get a immutable set of every registered timeout handler.
* @return Set of every registered timeout handler.
*/
public abstract Set<PacketListener> getTimeoutHandlers();
Set<PacketListener> getTimeoutHandlers();
/**
* Get an immutable set of every registered asynchronous packet listener.
* @return Set of every asynchronous packet listener.
*/
public abstract Set<PacketListener> getAsyncHandlers();
Set<PacketListener> getAsyncHandlers();
}
@@ -1,170 +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;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import com.comphenix.protocol.async.AsyncListenerHandler;
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.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
/**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
*
* @author Kristian
*/
class CleanupStaticMembers {
// Reports
public final static ReportType REPORT_CANNOT_RESET_FIELD = new ReportType("Unable to reset field %s: %s");
public final static ReportType REPORT_CANNOT_UNLOAD_CLASS = new ReportType("Unable to unload class %s.");
private ClassLoader loader;
private ErrorReporter reporter;
public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) {
this.loader = loader;
this.reporter = reporter;
}
/**
* Ensure that the previous ClassLoader is not leaking.
*/
public void resetAll() {
// This list must always be updated
@SuppressWarnings("deprecation")
Class<?>[] publicClasses = {
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class,
ObjectWriter.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class,
AbstractInputStreamLookup.class, TemporaryPlayerFactory.class, SpigotPacketInjector.class,
MinecraftReflection.class, NbtBinarySerializer.class
};
String[] internalClasses = {
"com.comphenix.protocol.events.SerializedOfflinePlayer",
"com.comphenix.protocol.injector.player.InjectedServerConnection",
"com.comphenix.protocol.injector.player.NetworkFieldInjector",
"com.comphenix.protocol.injector.player.NetworkObjectInjector",
"com.comphenix.protocol.injector.player.NetworkServerInjector",
"com.comphenix.protocol.injector.player.PlayerInjector",
"com.comphenix.protocol.injector.EntityUtilities",
"com.comphenix.protocol.injector.packet.PacketRegistry",
"com.comphenix.protocol.injector.packet.PacketInjector",
"com.comphenix.protocol.injector.packet.ReadPacketModifier",
"com.comphenix.protocol.injector.StructureCache",
"com.comphenix.protocol.reflect.compiler.BoxingHelper",
"com.comphenix.protocol.reflect.compiler.MethodDescriptor",
"com.comphenix.protocol.wrappers.nbt.WrappedElement",
};
resetClasses(publicClasses);
resetClasses(getClasses(loader, internalClasses));
}
private void resetClasses(Class<?>[] classes) {
// Reset each class one by one
for (Class<?> clazz : classes) {
resetClass(clazz);
}
}
private void resetClass(Class<?> clazz) {
for (Field field : clazz.getFields()) {
Class<?> type = field.getType();
// Only check static non-primitive fields. We also skip strings.
if (Modifier.isStatic(field.getModifiers()) &&
!type.isPrimitive() && !type.equals(String.class) &&
!type.equals(ReportType.class)) {
try {
setFinalStatic(field, null);
} catch (IllegalAccessException e) {
// Just inform the player
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_RESET_FIELD).error(e).messageParam(field.getName(), e.getMessage())
);
e.printStackTrace();
}
}
}
}
// HACK! HAACK!
private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException {
int modifier = field.getModifiers();
boolean isFinal = Modifier.isFinal(modifier);
Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null;
// We have to remove the final field first
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true);
}
// Now we can safely modify the field
FieldUtils.writeStaticField(field, newValue, true);
// Revert modifier
if (isFinal) {
FieldUtils.writeField(modifiersField, field, modifier, true);
}
}
private Class<?>[] getClasses(ClassLoader loader, String[] names) {
List<Class<?>> output = new ArrayList<Class<?>>();
for (String name : names) {
try {
output.add(loader.loadClass(name));
} catch (ClassNotFoundException e) {
// Warn the user
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_UNLOAD_CLASS).error(e).messageParam(name));
}
}
return output.toArray(new Class<?>[0]);
}
}
@@ -29,13 +29,12 @@ import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.ChatColor;
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;
@@ -77,11 +76,6 @@ class CommandPacket extends CommandBase {
*/
public static final int PAGE_LINE_COUNT = 9;
/**
* Number of bytes before we do a hex dump.
*/
private static final int HEX_DUMP_THRESHOLD = 256;
private Plugin plugin;
private Logger logger;
private ProtocolManager manager;
@@ -371,7 +365,7 @@ class CommandPacket extends CommandBase {
logger.info("Initial packet:\n" + original + " -> ");
}
logger.info(shortDescription + ":\n" + getPacketDescription(
logger.info(shortDescription + ":\n" + HexDumper.getPacketDescription(
event.getPacket())
);
} catch (IllegalAccessException e) {
@@ -428,7 +422,7 @@ class CommandPacket extends CommandBase {
*/
private void savePacketState(PacketEvent event) {
try {
originalPackets.put(event, getPacketDescription(event.getPacket()));
originalPackets.put(event, HexDumper.getPacketDescription(event.getPacket()));
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot read packet.", e);
}
@@ -450,68 +444,6 @@ class CommandPacket extends CommandBase {
}
};
}
/**
* Retrieve a detailed string representation of the given packet.
* @param packetContainer - the packet to describe.
* @return The detailed description.
* @throws IllegalAccessException An error occured.
*/
public String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
Object packet = packetContainer.getHandle();
Class<?> clazz = packet.getClass();
// Get the first Minecraft super class
while (clazz != null && clazz != Object.class &&
(!MinecraftReflection.isMinecraftClass(clazz) ||
Factory.class.isAssignableFrom(clazz))) {
clazz = clazz.getSuperclass();
}
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(), PrettyPrinter.RECURSE_DEPTH, new ObjectPrinter() {
@Override
public boolean print(StringBuilder output, Object value) {
// Special case
if (value instanceof byte[]) {
byte[] data = (byte[]) value;
if (data.length > HEX_DUMP_THRESHOLD) {
output.append("[");
HexDumper.defaultDumper().appendTo(output, data);
output.append("]");
return true;
}
} else if (value != null) {
EquivalentConverter<Object> converter = findConverter(value.getClass());
if (converter != null) {
output.append(converter.getSpecific(value));
return true;
}
}
return false;
}
});
}
/**
* Retrieve the closest equivalent converter to a specific class.
* @param clazz - the class.
* @return The closest converter, or NULL if not found,
*/
private EquivalentConverter<Object> findConverter(Class<?> clazz) {
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
while (clazz != null) {
EquivalentConverter<Object> result = converters.get(clazz);
if (result != null)
return result;
else
clazz = clazz.getSuperclass();
}
return null;
}
public PacketListener updatePacketListener() {
if (listener != null) {
@@ -6,24 +6,22 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
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.Objects;
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 com.google.common.util.concurrent.Futures;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
/**
* Represents the type of a packet in a specific protocol.
@@ -54,7 +52,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType SET_PROTOCOL = new PacketType(PROTOCOL, SENDER, 0x00, 0x00, "SetProtocol");
public static final PacketType SET_PROTOCOL = new PacketType(PROTOCOL, SENDER, 0x00, "SetProtocol", "C00Handshake");
private final static Client INSTANCE = new Client();
@@ -105,99 +103,109 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.SERVER;
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x00, 0xFF, "SpawnEntity");
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x01, 0xFF, "SpawnEntityExperienceOrb");
public static final PacketType SPAWN_ENTITY_WEATHER = new PacketType(PROTOCOL, SENDER, 0x02, 0xFF, "SpawnEntityWeather");
public static final PacketType SPAWN_ENTITY_LIVING = new PacketType(PROTOCOL, SENDER, 0x03, 0xFF, "SpawnEntityLiving");
public static final PacketType SPAWN_ENTITY_PAINTING = new PacketType(PROTOCOL, SENDER, 0x04, 0xFF, "SpawnEntityPainting");
public static final PacketType NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 0x05, 0xFF, "NamedEntitySpawn");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, 0xFF, "Animation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x07, 0xFF, "Statistic");
public static final PacketType BLOCK_BREAK = new PacketType(PROTOCOL, SENDER, 0x08, 0xFF, "BlockBreak");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x09, 0xFF, "BlockBreakAnimation");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x0A, 0xFF, "TileEntityData");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x0B, 0xFF, "BlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x0C, 0xFF, "BlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0D, 0xFF, "Boss");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0E, 0xFF, "ServerDifficulty");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x0F, 0xFF, "Chat");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x10, 0xFF, "MultiBlockChange");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x11, 0xFF, "TabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x12, 0xFF, "Commands");
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x13, 0xFF, "Transaction");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x14, 0xFF, "CloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x15, 0xFF, "WindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x16, 0xFF, "WindowData");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x17, 0xFF, "SetSlot");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x18, 0xFF, "SetCooldown");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x19, 0xFF, "CustomPayload");
public static final PacketType CUSTOM_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x1A, 0xFF, "CustomSoundEffect");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1B, 0xFF, "KickDisconnect");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1C, 0xFF, "EntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1D, 0xFF, "Explosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1E, 0xFF, "UnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x1F, 0xFF, "GameStateChange");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x20, 0xFF, "OpenWindowHorse");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x21, 0xFF, "KeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x22, 0xFF, "MapChunk");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x23, 0xFF, "WorldEvent");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x24, 0xFF, "WorldParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x25, 0xFF, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x26, 0xFF, "Login");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x27, 0xFF, "Map");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x28, 0xFF, "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x29, 0xFF, "Entity$RelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2A, 0xFF, "Entity$RelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x2B, 0xFF, "Entity$EntityLook");
public static final PacketType ENTITY = new PacketType(PROTOCOL, SENDER, 0x2C, 0xFF, "Entity");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2D, 0xFF, "VehicleMove");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x2E, 0xFF, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x2F, 0xFF, "OpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x30, 0xFF, "OpenSignEditor");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x31, 0xFF, "AutoRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x32, 0xFF, "Abilities");
public static final PacketType COMBAT_EVENT = new PacketType(PROTOCOL, SENDER, 0x33, 0xFF, "CombatEvent");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x34, 0xFF, "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x35, 0xFF, "LookAt");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x36, 0xFF, "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x37, 0xFF, "Recipes");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x38, 0xFF, "EntityDestroy");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x39, 0xFF, "RemoveEntityEffect");
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 0x3A, 0xFF, "ResourcePackSend");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x3B, 0xFF, "Respawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x3C, 0xFF, "EntityHeadRotation");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x3D, 0xFF, "SelectAdvancementTab");
public static final PacketType WORLD_BORDER = new PacketType(PROTOCOL, SENDER, 0x3E, 0xFF, "WorldBorder");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x3F, 0xFF, "Camera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x40, 0xFF, "HeldItemSlot");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x41, 0xFF, "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x42, 0xFF, "ViewDistance");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x43, 0xFF, "ScoreboardDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x44, 0xFF, "EntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x45, 0xFF, "AttachEntity");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x46, 0xFF, "EntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x47, 0xFF, "EntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x48, 0xFF, "Experience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x49, 0xFF, "UpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x4A, 0xFF, "ScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x4B, 0xFF, "Mount");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x4C, 0xFF, "ScoreboardTeam");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x4D, 0xFF, "ScoreboardScore");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x4E, 0xFF, "SpawnPosition");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x4F, 0xFF, "UpdateTime");
public static final PacketType TITLE = new PacketType(PROTOCOL, SENDER, 0x50, 0xFF, "Title");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x51, 0xFF, "EntitySound");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x52, 0xFF, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x53, 0xFF, "StopSound");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x54, 0xFF, "PlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x55, 0xFF, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x56, 0xFF, "Collect");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x57, 0xFF, "EntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x58, 0xFF, "Advancements");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x59, 0xFF, "UpdateAttributes");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x5A, 0xFF, "EntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x5B, 0xFF, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x5C, 0xFF, "Tags");
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");
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 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");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x3A, "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x3B, "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 0x3C, "ResourcePackSend", "SPacketResourcePackSend");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x3D, "Respawn", "SPacketRespawn");
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 SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x57, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x58, "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x59, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x5A, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x5B, "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x5C, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x5D, "StopSound");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x5E, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x5F, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x60, "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x61, "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x62, "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x63, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x64, "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x65, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x66, "Tags");
// ---- Removed in 1.9
@@ -205,19 +213,19 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @deprecated Removed in 1.9
*/
@Deprecated
public static final PacketType MAP_CHUNK_BULK = new PacketType(PROTOCOL, SENDER, 255, 255, "MapChunkBulk");
public static final PacketType MAP_CHUNK_BULK = new PacketType(PROTOCOL, SENDER, 255, "MapChunkBulk");
/**
* @deprecated Removed in 1.9
*/
@Deprecated
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 254, 254, "SetCompression");
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 254, "SetCompression");
/**
* @deprecated Removed in 1.9
*/
@Deprecated
public static final PacketType UPDATE_ENTITY_NBT = new PacketType(PROTOCOL, SENDER, 253, 253, "UpdateEntityNBT");
public static final PacketType UPDATE_ENTITY_NBT = new PacketType(PROTOCOL, SENDER, 253, "UpdateEntityNBT");
// ----- Renamed packets
@@ -251,7 +259,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @deprecated Replaced by {@link #TILE_ENTITY_DATA}
*/
@Deprecated
public static final PacketType UPDATE_SIGN = MinecraftReflection.signUpdateExists() ? new PacketType(PROTOCOL, SENDER, 252, 252, "UpdateSign") :
public static final PacketType UPDATE_SIGN = MinecraftReflection.signUpdateExists() ? new PacketType(PROTOCOL, SENDER, 252, "UpdateSign") :
TILE_ENTITY_DATA.clone();
// ---- Removed in 1.14
@@ -260,7 +268,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @deprecated Removed in 1.14
*/
@Deprecated
public static final PacketType BED = new PacketType(PROTOCOL, SENDER, 0x33, 0x33, "Bed");
public static final PacketType BED = new PacketType(PROTOCOL, SENDER, 251, "Bed", "SPacketUseBed");
/**
* @deprecated Renamed to {@link #BED}
@@ -268,6 +276,42 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType USE_BED = BED.clone();
/**
* @deprecated Removed in 1.16
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_WEATHER = new PacketType(PROTOCOL, SENDER, 250, "SpawnEntityWeather", "SPacketSpawnGlobalEntity");
/**
* @deprecated Removed in 1.17, split into separate packets
*/
@Deprecated
public static final PacketType TITLE = new PacketType(PROTOCOL, SENDER, 249, "Title");
/**
* @deprecated Removed in 1.17, split into separate packets
*/
@Deprecated
public static final PacketType WORLD_BORDER = new PacketType(PROTOCOL, SENDER, 248, "WorldBorder");
/**
* @deprecated Removed in 1.17, split into separate packets
*/
@Deprecated
public static final PacketType COMBAT_EVENT = new PacketType(PROTOCOL, SENDER, 247, "CombatEvent");
/**
* @deprecated Removed in 1.17
*/
@Deprecated
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 246, "Transaction", "SPacketConfirmTransaction");
/**
* @deprecated Made abstract in 1.17, no actual packet anymore
*/
@Deprecated
public static final PacketType ENTITY = new PacketType(PROTOCOL, SENDER, 245, "Entity", "SPacketEntity");
private final static Server INSTANCE = new Server();
// Prevent accidental construction
@@ -288,52 +332,66 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, 0xFF, "TeleportAccept");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, 0xFF, "TileNBTQuery");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x02, 0xFF, "DifficultyChange");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x03, 0xFF, "Chat");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, 0xFF, "ClientCommand");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x05, 0xFF, "Settings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x06, 0xFF, "TabComplete");
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x07, 0xFF, "Transaction");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x08, 0xFF, "EnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x09, 0xFF, "WindowClick");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0A, 0xFF, "CloseWindow");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x0B, 0xFF, "CustomPayload");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x0C, 0xFF, "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x0D, 0xFF, "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x0E, 0xFF, "UseEntity");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x0F, 0xFF, "KeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x10, 0xFF, "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x11, 0xFF, "Flying$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x12, 0xFF, "Flying$PositionLook");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x13, 0xFF, "Flying$Look");
public static final PacketType FLYING = new PacketType(PROTOCOL, SENDER, 0x14, 0xFF, "Flying");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x15, 0xFF, "VehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x16, 0xFF, "BoatMove");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x17, 0xFF, "PickItem");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x18, 0xFF, "AutoRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x19, 0xFF, "Abilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x1A, 0xFF, "BlockDig");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x1B, 0xFF, "EntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x1C, 0xFF, "SteerVehicle");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x1D, 0xFF, "RecipeDisplayed");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x1E, 0xFF, "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x1F, 0xFF, "ResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x20, 0xFF, "Advancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x21, 0xFF, "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x22, 0xFF, "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x23, 0xFF, "HeldItemSlot");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x24, 0xFF, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x25, 0xFF, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x26, 0xFF, "SetCreativeSlot");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x27, 0xFF, "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x28, 0xFF, "Struct");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x29, 0xFF, "UpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2A, 0xFF, "ArmAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x2B, 0xFF, "Spectate");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x2C, 0xFF, "UseItem");
public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x2D, 0xFF, "BlockPlace");
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");
/**
* @deprecated Removed in 1.17
*/
@Deprecated
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 255, "Transaction", "CPacketConfirmTransaction");
/**
* @deprecated Removed in 1.17
*/
@Deprecated
public static final PacketType FLYING = new PacketType(PROTOCOL, SENDER, 254, "Flying", "CPacketPlayer");
private final static Client INSTANCE = new Client();
@@ -368,8 +426,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private final static Sender SENDER = Sender.SERVER;
@ForceAsync
public static final PacketType SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, 0xFF, "ServerInfo");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x01, 0xFF, "Pong");
public static final PacketType SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, "ServerInfo", "SPacketServerInfo");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x01, "Pong", "SPacketPong");
/**
* @deprecated Renamed to {@link #SERVER_INFO}
@@ -398,8 +456,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, 0xFF, "Start");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x01, 0xFF, "Ping");
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Start", "CPacketServerQuery");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x01, "Ping", "CPacketPing");
private final static Client INSTANCE = new Client();
@@ -433,11 +491,11 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.SERVER;
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, 0xFF, "Disconnect");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, 0xFF, "EncryptionBegin");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, 0xFF, "Success");
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, 0xFF, "SetCompression");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, 0xFF, "CustomPayload");
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, "Disconnect", "SPacketDisconnect");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "EncryptionBegin", "SPacketEncryptionRequest");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "Success", "SPacketLoginSuccess");
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, "SetCompression", "SPacketEnableCompression");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, "CustomPayload", "SPacketCustomPayload");
private final static Server INSTANCE = new Server();
@@ -459,86 +517,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, 0xFF, "Start");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, 0xFF, "EncryptionBegin");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x02, 0xFF, "CustomPayload");
private final static Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(); }
public static Sender getSender() {
return SENDER;
}
public static Client getInstance() {
return INSTANCE;
}
}
public static Protocol getProtocol() {
return PROTOCOL;
}
}
/**
* Contains every packet Minecraft 1.6.4 packet removed in Minecraft 1.7.2.
* @author Kristian
*/
public static class Legacy {
private static final Protocol PROTOCOL = Protocol.LEGACY;
/**
* Outgoing packets.
* @author Kristian
*/
// Missing server packets: [10, 11, 12, 21, 107, 252]
public static class Server extends PacketTypeEnum {
private final static Sender SENDER = Sender.SERVER;
public static final PacketType PLAYER_FLYING = PacketType.newLegacy(SENDER, 10);
public static final PacketType PLAYER_POSITION = PacketType.newLegacy(SENDER, 11);
public static final PacketType PLAYER_POSITON_LOOK = PacketType.newLegacy(SENDER, 12);
/**
* Removed in Minecraft 1.4.6.
*/
public static final PacketType PICKUP_SPAWN = PacketType.newLegacy(SENDER, 21);
/**
* Removed in Minecraft 1.7.2
*/
public static final PacketType SET_CREATIVE_SLOT = PacketType.newLegacy(SENDER, 107);
/**
* Removed in Minecraft 1.7.2
*/
public static final PacketType KEY_RESPONSE = PacketType.newLegacy(SENDER, 252);
private final static Server INSTANCE = new Server();
// Prevent accidental construction
private Server() {
super();
}
public static Sender getSender() {
return SENDER;
}
public static Server getInstance() {
return INSTANCE;
}
}
/**
* Incoming packets.
* @author Kristian
*/
// Missing client packets: [1, 9, 255]
public static class Client extends PacketTypeEnum {
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType LOGIN = PacketType.newLegacy(SENDER, 1);
public static final PacketType RESPAWN = PacketType.newLegacy(SENDER, 9);
public static final PacketType DISCONNECT = PacketType.newLegacy(SENDER, 255);
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Start", "CPacketLoginStart");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "EncryptionBegin", "CPacketEncryptionResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x02, "CustomPayload", "CPacketCustomPayload");
private final static Client INSTANCE = new Client();
@@ -563,15 +544,23 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @author Kristian
*/
public enum Protocol {
HANDSHAKING,
PLAY,
STATUS,
LOGIN,
HANDSHAKING("Handshaking", "handshake"),
PLAY("Play", "game"),
STATUS("Status", "status"),
LOGIN("Login", "login"),
/**
* Only for packets removed in Minecraft 1.7.2
*/
LEGACY;
LEGACY("", "");
private String packetName;
private String mojangName;
Protocol(String packetName, String mojangName) {
this.packetName = packetName;
this.mojangName = mojangName;
}
/**
* Retrieve the correct protocol enum from a given vanilla enum instance.
@@ -593,7 +582,15 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
}
public String getPacketName() {
return WordUtils.capitalize(name().toLowerCase(Locale.ENGLISH));
return packetName;
}
public String getMojangName() {
return mojangName;
}
public String getMcpPacketName() {
return name().toLowerCase(Locale.ENGLISH);
}
}
@@ -606,12 +603,22 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
/**
* Indicates that packets of this type will be sent by connected clients.
*/
CLIENT,
CLIENT("Serverbound", "In", "client"),
/**
* Indicate that packets of this type will be sent by the current server.
*/
SERVER;
SERVER("Clientbound", "Out", "server");
private String mojangName;
private String packetName;
private String mcpName;
Sender(String mojangName, String packetName, String mcpName) {
this.mojangName = mojangName;
this.packetName = packetName;
this.mcpName = mcpName;
}
/**
* Retrieve the equivialent connection side.
@@ -622,7 +629,15 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
}
public String getPacketName() {
return this == CLIENT ? "In" : "Out";
return packetName;
}
public String getMcpPacketName() {
return mcpName;
}
public String getMojangName() {
return mojangName;
}
}
@@ -639,14 +654,13 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
/**
* Protocol version of all the current IDs.
*/
private static final MinecraftVersion PROTOCOL_VERSION = MinecraftVersion.VILLAGE_UPDATE;
private static final MinecraftVersion PROTOCOL_VERSION = Constants.CURRENT_VERSION;
private final Protocol protocol;
private final Sender sender;
private final int currentId;
private final int legacyId;
private final MinecraftVersion version;
private final String[] classNames;
private final List<String> classNames;
String[] names;
private String name;
@@ -654,6 +668,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private boolean forceAsync;
private boolean dynamic;
private int hashCode;
/**
* Retrieve the current packet/legacy lookup.
@@ -669,9 +684,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
addPacketTypes(Status.Client.getInstance()).
addPacketTypes(Status.Server.getInstance()).
addPacketTypes(Login.Client.getInstance()).
addPacketTypes(Login.Server.getInstance()).
addPacketTypes(Legacy.Client.getInstance()).
addPacketTypes(Legacy.Server.getInstance());
addPacketTypes(Login.Server.getInstance());
}
return LOOKUP;
}
@@ -691,11 +704,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
sources.add(Login.Client.getInstance());
sources.add(Login.Server.getInstance());
// Add the missing types in earlier versions
if (!MinecraftReflection.isUsingNetty()) {
sources.add(Legacy.Client.getInstance());
sources.add(Legacy.Server.getInstance());
}
return Iterables.concat(sources);
}
@@ -782,7 +790,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
}
}
private static String formatMojangClassName(Protocol protocol, Sender sender, String name) {
return "net.minecraft.network.protocol." + protocol.getMojangName() + "." + sender.getMojangName()
+ name + "Packet";
}
private static String formatClassName(Protocol protocol, Sender sender, String name) {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
return "net.minecraft.network.protocol." + protocol.getMojangName() + ".Packet"
+ protocol.getPacketName() + sender.getPacketName() + name;
}
String base = MinecraftReflection.getMinecraftPackage() + ".Packet";
if (name.startsWith(base)) {
return name;
@@ -799,6 +817,14 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
return base + protocol.getPacketName() + sender.getPacketName() + WordUtils.capitalize(name);
}
private static boolean isMcpPacketName(String packetName) {
return packetName.startsWith("C00") || packetName.startsWith("CPacket") || packetName.startsWith("SPacket");
}
private static String formatMcpClassName(Protocol protocol, Sender sender, String name) {
return "net.minecraft.network." + protocol.getMcpPacketName() + "." + sender.getMcpPacketName() + "." + name;
}
/**
* Determine if the given packet exists.
* @param protocol - the protocol.
@@ -810,31 +836,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
return getLookup().getFromCurrent(protocol, sender, packetId) != null;
}
/**
* Retrieve a packet type from a legacy ID.
* <p>
* If no associated packet type could be found, a new will be registered under LEGACY.
* @param id - the legacy ID.
* @param sender - the sender of the packet, or NULL if unknown.
* @return The packet type.
* @throws IllegalArgumentException If the sender is NULL and the packet doesn't exist.
* @deprecated Legacy IDs haven't functioned properly for some time
*/
@Deprecated
public static PacketType fromLegacy(int id, Sender sender) {
PacketType type = getLookup().getFromLegacy(id, sender);
if (type == null) {
if (sender == null)
throw new IllegalArgumentException("Cannot find legacy packet " + id);
type = newLegacy(sender, id);
// As below
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
}
return type;
}
/**
* Retrieve a packet type from a protocol, sender and packet ID, for pre-1.8.
* <p>
@@ -849,7 +850,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
PacketType type = getLookup().getFromCurrent(protocol, sender, packetId);
if (type == null) {
type = new PacketType(protocol, sender, packetId, -1, PROTOCOL_VERSION, packetClass.getName());
type = new PacketType(protocol, sender, packetId, PROTOCOL_VERSION, packetClass.getName());
type.dynamic = true;
// Many may be scheduled, but only the first will be executed
@@ -880,7 +881,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
PacketType type = find(map, className);
if (type == null) {
// Guess we don't support this packet :/
type = new PacketType(protocol, sender, packetId, -1, PROTOCOL_VERSION, className);
type = new PacketType(protocol, sender, packetId, PROTOCOL_VERSION, className);
type.dynamic = true;
// Many may be scheduled, but only the first will be executed
@@ -899,8 +900,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
// Check any aliases
for (PacketType check : map.values()) {
String[] aliases = check.getClassNames();
if (aliases.length > 1) {
List<String> aliases = check.getClassNames();
if (aliases.size() > 1) {
for (String alias : aliases) {
if (alias.equals(clazz)) {
// We have a match!
@@ -941,7 +942,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* Determine if a given class represents a packet class.
* @param packetClass - the class to lookup.
* @return TRUE if this is a packet class, FALSE otherwise.
* @deprecated Doesn't really have a purpose
*/
@Deprecated
public static boolean hasClass(Class<?> packetClass) {
return PacketRegistry.getPacketType(packetClass) != null;
}
@@ -952,34 +955,29 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* Note that the registration will be performed on the main thread.
* @param type - the type to register.
* @param name - the name of the packet.
* @return A future telling us if our instance was registered.
*/
public static Future<Boolean> scheduleRegister(final PacketType type, final String name) {
Callable<Boolean> callable = new Callable<Boolean>() {
public static void scheduleRegister(final PacketType type, final String name) {
BukkitRunnable runnable = new BukkitRunnable() {
@Override
public Boolean call() throws Exception {
public void run() {
PacketTypeEnum objEnum;
// A bit ugly, but performance is critical
objEnum = getObjectEnum(type);
if (objEnum.registerMember(type, name)) {
getLookup().addPacketTypes(Arrays.asList(type));
return true;
getLookup().addPacketTypes(Collections.singletonList(type));
}
return false;
}
};
// Execute in the main thread if possible
if (Bukkit.getServer() == null || Bukkit.isPrimaryThread()) {
try {
return Futures.immediateFuture(callable.call());
} catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
runnable.run();
} catch (Exception ignored) { }
} else {
runnable.runTaskLater(ProtocolLibrary.getPlugin(), 0);
}
return ProtocolLibrary.getExecutorSync().submit(callable);
}
/**
@@ -997,8 +995,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
return type.isClient() ? Status.Client.getInstance() : Status.Server.getInstance();
case LOGIN:
return type.isClient() ? Login.Client.getInstance() : Login.Server.getInstance();
case LEGACY:
return type.isClient() ? Legacy.Client.getInstance() : Legacy.Server.getInstance();
default:
throw new IllegalStateException("Unexpected protocol: " + type.getProtocol());
}
@@ -1009,10 +1005,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @param protocol - the current protocol.
* @param sender - client or server.
* @param currentId - the current packet ID, or
* @param legacyId - the legacy packet ID.
*/
public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId, String... names) {
this(protocol, sender, currentId, legacyId, PROTOCOL_VERSION, names);
public PacketType(Protocol protocol, Sender sender, int currentId, String... names) {
this(protocol, sender, currentId, PROTOCOL_VERSION, names);
}
/**
@@ -1020,34 +1015,27 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @param protocol - the current protocol.
* @param sender - client or server.
* @param currentId - the current packet ID.
* @param legacyId - the legacy packet ID.
* @param version - the version of the current ID.
*/
public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId, MinecraftVersion version, String... names) {
public PacketType(Protocol protocol, Sender sender, int currentId, MinecraftVersion version, String... names) {
this.protocol = Preconditions.checkNotNull(protocol, "protocol cannot be NULL");
this.sender = Preconditions.checkNotNull(sender, "sender cannot be NULL");
this.currentId = currentId;
this.legacyId = legacyId;
this.version = version;
this.classNames = new String[names.length];
for (int i = 0; i < classNames.length; i++) {
classNames[i] = formatClassName(protocol, sender, names[i]);
this.classNames = new ArrayList<>();
for (int i = 0; i < names.length; i++) {
if (isMcpPacketName(names[i])) { // Minecraft MCP packets
classNames.add(formatMcpClassName(protocol, sender, names[i]));
} else {
classNames.add(formatClassName(protocol, sender, names[i]));
classNames.add(formatMojangClassName(protocol, sender, names[i]));
}
}
this.names = names;
}
/**
* Construct a legacy packet type.
* @param sender - client or server.
* @param legacyId - the legacy packet ID.
* @return Legacy packet type
*/
public static PacketType newLegacy(Sender sender, int legacyId) {
return new PacketType(Protocol.LEGACY, sender, PacketType.UNKNOWN_PACKET, legacyId, MinecraftVersion.WORLD_UPDATE);
}
/**
* Determine if this packet is supported on the current server.
* @return Whether or not the packet is supported.
@@ -1102,7 +1090,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
return currentId;
}
public String[] getClassNames() {
public List<String> getClassNames() {
return classNames;
}
@@ -1111,11 +1099,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @return The packet class, or NULL if not found.
*/
public Class<?> getPacketClass() {
try {
return PacketRegistry.getPacketClassFromType(this);
} catch (Exception e) {
return null;
}
return PacketRegistry.tryGetPacketClass(this).orElse(null);
}
// Only used by Enum processor
@@ -1169,18 +1153,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
return version;
}
/**
* Retrieve the legacy (1.6.4 or below) protocol ID of the packet type.
* <p>
* This ID is globally unique.
* @return The legacy ID, or {@link #UNKNOWN_PACKET} if unknown.
* @deprecated Legacy IDs haven't functioned properly for some time
*/
@Deprecated
public int getLegacyId() {
return legacyId;
}
/**
* Whether or not this packet was dynamically created (i.e. we don't have it registered)
* @return True if dnyamic, false if not.
@@ -1191,7 +1163,14 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Override
public int hashCode() {
return Objects.hashCode(protocol, sender, currentId);
int hash = hashCode;
if (hash == 0) {
hash = protocol.hashCode();
hash = 31 * hash + sender.hashCode();
hash = 31 * hash + Integer.hashCode(currentId);
hashCode = hash;
}
return hash;
}
@Override
@@ -1222,7 +1201,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
Class<?> clazz = getPacketClass();
if (clazz == null)
return name() + "[" + protocol + ", " + sender + ", " + currentId + ", classNames: " + Arrays.toString(classNames) + " (unregistered)]";
return name() + "[" + protocol + ", " + sender + ", " + currentId + ", classNames: " + classNames + " (unregistered)]";
else
return name() + "[class=" + clazz.getSimpleName() + ", id=" + currentId + "]";
}
@@ -110,22 +110,6 @@ public class PacketTypeEnum implements Iterable<PacketType> {
public boolean hasMember(PacketType member) {
return members.contains(member);
}
/**
* Retrieve a member by name,
* @param name - name of member to retrieve.
* @return The member, or NULL if not found.
* @deprecated Don't use this
*/
@Deprecated
public PacketType valueOf(String name) {
for (PacketType member : members) {
if (member.name().equals(name))
return member;
}
return null;
}
/**
* Retrieve every registered member.
@@ -105,20 +105,10 @@ class PacketTypeLookup {
Preconditions.checkNotNull(types, "types cannot be NULL");
for (PacketType type : types) {
int legacy = type.getLegacyId();
// Skip unknown legacy packets
if (legacy != PacketType.UNKNOWN_PACKET) {
if (type.isServer())
serverLookup.put(type.getLegacyId(), type);
if (type.isClient())
clientLookup.put(type.getLegacyId(), type);
legacyLookup.put(type.getLegacyId(), type);
}
// Skip unknown current packets
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames()[0], type);
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames().get(0), type);
}
nameLookup.put(type.name(), type);
}
@@ -1,300 +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;
import com.comphenix.protocol.reflect.IntEnum;
/**
* List of known packet IDs since 1.3.2.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public final class Packets {
/**
* The highest possible packet ID. It's unlikely that this value will ever change.
*/
public static final int MAXIMUM_PACKET_ID = 255;
/**
* The maximum number of unique packet IDs. It's unlikely this will ever change.
*/
public static final int PACKET_COUNT = 256;
/**
* List of packets sent only by the server.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public static final class Server extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
private static Server INSTANCE = new Server();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int CHAT = 3;
public static final int UPDATE_TIME = 4;
public static final int ENTITY_EQUIPMENT = 5;
public static final int SPAWN_POSITION = 6;
public static final int UPDATE_HEALTH = 8;
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
/**
* Made bi-directional in 1.4.6.
*/
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ENTITY_LOCATION_ACTION = 17;
public static final int ARM_ANIMATION = 18;
public static final int NAMED_ENTITY_SPAWN = 20;
/**
* Removed in 1.4.6 and replaced with VEHICLE_SPAWN.
* @see <a href="http://www.wiki.vg/Protocol_History#2012-12-20">Protocol History - MinecraftCoalition</a>
*/
@Deprecated()
public static final int PICKUP_SPAWN = 21;
public static final int COLLECT = 22;
public static final int VEHICLE_SPAWN = 23;
public static final int MOB_SPAWN = 24;
public static final int ENTITY_PAINTING = 25;
public static final int ADD_EXP_ORB = 26;
public static final int ENTITY_VELOCITY = 28;
public static final int DESTROY_ENTITY = 29;
public static final int ENTITY = 30;
public static final int REL_ENTITY_MOVE = 31;
public static final int ENTITY_LOOK = 32;
public static final int REL_ENTITY_MOVE_LOOK = 33;
public static final int ENTITY_TELEPORT = 34;
public static final int ENTITY_HEAD_ROTATION = 35;
public static final int ENTITY_STATUS = 38;
public static final int ATTACH_ENTITY = 39;
/**
* Sent when an entities DataWatcher is updated.
* <p>
* Remember to clone the packet if you are modifying it.
*/
public static final int ENTITY_METADATA = 40;
public static final int MOB_EFFECT = 41;
public static final int REMOVE_MOB_EFFECT = 42;
public static final int SET_EXPERIENCE = 43;
public static final int UPDATE_ATTRIBUTES = 44;
public static final int MAP_CHUNK = 51;
public static final int MULTI_BLOCK_CHANGE = 52;
public static final int BLOCK_CHANGE = 53;
public static final int PLAY_NOTE_BLOCK = 54;
public static final int BLOCK_BREAK_ANIMATION = 55;
public static final int MAP_CHUNK_BULK = 56;
public static final int EXPLOSION = 60;
public static final int WORLD_EVENT = 61;
public static final int NAMED_SOUND_EFFECT = 62;
public static final int WORLD_PARTICLES = 63;
public static final int BED = 70;
public static final int WEATHER = 71;
public static final int OPEN_WINDOW = 100;
public static final int CLOSE_WINDOW = 101;
public static final int SET_SLOT = 103;
public static final int WINDOW_ITEMS = 104;
public static final int CRAFT_PROGRESS_BAR = 105;
public static final int TRANSACTION = 106;
public static final int SET_CREATIVE_SLOT = 107;
public static final int UPDATE_SIGN = 130;
public static final int ITEM_DATA = 131;
/**
* Sent the first time a tile entity (chest inventory, etc.) is withing range of the player, or has been updated.
* <p>
* Remember to clone the packet if you are modifying it.
*/
public static final int TILE_ENTITY_DATA = 132;
public static final int OPEN_TILE_ENTITY = 133;
public static final int STATISTIC = 200;
public static final int PLAYER_INFO = 201;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int SCOREBOARD_OBJECTIVE = 206;
public static final int UPDATE_SCORE = 207;
public static final int DISPLAY_SCOREBOARD = 208;
public static final int TEAMS = 209;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int KEY_REQUEST = 253;
public static final int KICK_DISCONNECT = 255;
/**
* This packet was introduced in 1.7.2.
*/
public static final int PING_TIME = 230;
/**
* This packet was introduced in 1.7.2.
*/
public static final int LOGIN_SUCCESS = 232;
/**
* A registry that parses between names and packet IDs.
* @return The current server registry.
*/
public static Server getRegistry() {
return INSTANCE;
}
// We only allow a single instance of this class
private Server() {
super();
}
}
/**
* List of packets sent by the client.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @author Kristian
*/
@Deprecated
public static class Client extends IntEnum {
/**
* The singleton instance. Can also be retrieved from the parent class.
*/
private static Client INSTANCE = new Client();
public static final int KEEP_ALIVE = 0;
public static final int LOGIN = 1;
public static final int HANDSHAKE = 2;
public static final int CHAT = 3;
public static final int USE_ENTITY = 7;
/**
* Since 1.3.1, the client no longer sends a respawn packet. Moved to CLIENT_COMMAND.
*/
@Deprecated
public static final int RESPAWN = 9;
public static final int FLYING = 10;
public static final int PLAYER_POSITION = 11;
public static final int PLAYER_LOOK = 12;
public static final int PLAYER_LOOK_MOVE = 13;
public static final int BLOCK_DIG = 14;
public static final int PLACE = 15;
public static final int BLOCK_ITEM_SWITCH = 16;
public static final int ARM_ANIMATION = 18;
public static final int ENTITY_ACTION = 19;
public static final int PLAYER_INPUT = 27;
public static final int CLOSE_WINDOW = 101;
public static final int WINDOW_CLICK = 102;
public static final int TRANSACTION = 106;
public static final int SET_CREATIVE_SLOT = 107;
public static final int BUTTON_CLICK = 108;
public static final int UPDATE_SIGN = 130;
public static final int ABILITIES = 202;
public static final int TAB_COMPLETE = 203;
public static final int LOCALE_AND_VIEW_DISTANCE = 204;
public static final int CLIENT_COMMAND = 205;
public static final int CUSTOM_PAYLOAD = 250;
public static final int KEY_RESPONSE = 252;
public static final int GET_INFO = 254;
public static final int KICK_DISCONNECT = 255;
/**
* This packet was introduced in 1.7.2.
*/
public static final int PING_TIME = 230;
/**
* This packet was introduced in 1.7.2.
*/
public static final int LOGIN_START = 231;
/**
* A registry that parses between names and packet IDs.
* @return The current client registry.
*/
public static Client getRegistry() {
return INSTANCE;
}
// Like above
private Client() {
super();
}
}
/**
* A registry that parses between names and packet IDs.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @return The current client registry.
*/
@Deprecated
public static Server getServerRegistry() {
return Server.getRegistry();
}
/**
* A registry that parses between names and packet IDs.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @return The current server registry.
*/
@Deprecated
public static Client getClientRegistry() {
return Client.INSTANCE;
}
/**
* Find a packet by name. Must be capitalized and use underscores.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @param name - name of packet to find.
* @return The packet ID found.
*/
@Deprecated
public static int valueOf(String name) {
Integer serverAttempt = Server.INSTANCE.valueOf(name);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.valueOf(name);
}
/**
* Retrieves the name of a packet.
* <p>
* Deprecated: Use {@link PacketType} instead.
* @param packetID - packet to retrieve name.
* @return The name, or NULL if unable to find such a packet.
*/
@Deprecated
public static String getDeclaredName(int packetID) {
String serverAttempt = Server.INSTANCE.getDeclaredName(packetID);
if (serverAttempt != null)
return serverAttempt;
else
return Client.INSTANCE.getDeclaredName(packetID);
}
}
@@ -26,21 +26,8 @@ import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.executors.BukkitExecutors;
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.error.*;
import com.comphenix.protocol.injector.DelayedSingleTask;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
@@ -50,12 +37,19 @@ 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.EnhancerFactory;
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 com.google.common.util.concurrent.ListeningScheduledExecutorService;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The main entry point for ProtocolLib.
@@ -112,10 +106,6 @@ public class ProtocolLib extends JavaPlugin {
// Metrics and statistics
private Statistics statistics;
// Executors
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
// Structure compiler
private BackgroundCompiler backgroundCompiler;
@@ -155,11 +145,7 @@ public class ProtocolLib extends JavaPlugin {
ProtocolLogger.init(this);
// Initialize enhancer factory
EnhancerFactory.getInstance().setClassLoader(getClassLoader());
// Initialize executors
executorAsync = BukkitExecutors.newAsynchronous(this);
executorSync = BukkitExecutors.newSynchronous(this);
ByteBuddyFactory.getInstance().setClassLoader(getClassLoader());
// Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);
@@ -185,7 +171,11 @@ 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);
@@ -213,7 +203,7 @@ public class ProtocolLib extends JavaPlugin {
.build();
// Initialize the API
ProtocolLibrary.init(this, config, protocolManager, reporter, executorAsync, executorSync);
ProtocolLibrary.init(this, config, protocolManager, reporter);
// Setup error reporter
detailedReporter.addGlobalParameter("manager", protocolManager);
@@ -19,12 +19,11 @@ package com.comphenix.protocol;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
/**
* The main entry point for ProtocolLib.
@@ -39,12 +38,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.15.1";
public static final String MAXIMUM_MINECRAFT_VERSION = "1.17.1";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.14.4) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.17.1) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2019-12-17";
public static final String MINECRAFT_LAST_RELEASE_DATE = "2021-07-06";
/**
* Plugins that are currently incompatible with ProtocolLib.
@@ -56,21 +55,15 @@ public class ProtocolLibrary {
private static ProtocolManager manager;
private static ErrorReporter reporter = new BasicErrorReporter();
private static ListeningScheduledExecutorService executorAsync;
private static ListeningScheduledExecutorService executorSync;
private static boolean updatesDisabled;
private static boolean initialized;
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager, ErrorReporter reporter,
ListeningScheduledExecutorService executorAsync, ListeningScheduledExecutorService executorSync) {
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager, ErrorReporter reporter) {
Validate.isTrue(!initialized, "ProtocolLib has already been initialized.");
ProtocolLibrary.plugin = plugin;
ProtocolLibrary.config = config;
ProtocolLibrary.manager = manager;
ProtocolLibrary.reporter = reporter;
ProtocolLibrary.executorAsync = executorAsync;
ProtocolLibrary.executorSync = executorSync;
initialized = true;
}
@@ -106,26 +99,6 @@ public class ProtocolLibrary {
return reporter;
}
/**
* Retrieve an executor service for performing asynchronous tasks on the behalf of ProtocolLib.
* <p>
* Note that this service is NULL if ProtocolLib has not been initialized yet.
* @return The executor service, or NULL.
*/
public static ListeningScheduledExecutorService getExecutorAsync() {
return executorAsync;
}
/**
* Retrieve an executor service for performing synchronous tasks (main thread) on the behalf of ProtocolLib.
* <p>
* Note that this service is NULL if ProtocolLib has not been initialized yet.
* @return The executor service, or NULL.
*/
public static ListeningScheduledExecutorService getExecutorSync() {
return executorSync;
}
/**
* Disables the ProtocolLib update checker.
*/
@@ -20,6 +20,7 @@ import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.comphenix.protocol.utility.MinecraftVersion;
import org.bukkit.plugin.Plugin;
/**
@@ -29,7 +30,12 @@ public class ProtocolLogger {
private static boolean debugEnabled = false;
private static Logger logger = Logger.getLogger("Minecraft");
protected static void init(Plugin plugin) {
/**
* Don't call this method from any plugin. Currently only public to test if it fixes a weird error.
* See GH-740
* @param plugin ProtocolLib
*/
public static void init(ProtocolLib plugin) {
logger = plugin.getLogger();
try {
@@ -77,4 +83,10 @@ public class ProtocolLogger {
logger.log(Level.WARNING, "[Debug] " + message, ex);
}
}
public static void warnAbove(MinecraftVersion version, String message, Object... args) {
if (version.atOrAbove()) {
log(Level.WARNING, message, args);
}
}
}
@@ -49,7 +49,7 @@ public interface ProtocolManager extends PacketStream {
* @param player - the player.
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
*/
public int getProtocolVersion(Player player);
int getProtocolVersion(Player player);
/**
* Send a packet to the given player.
@@ -63,7 +63,7 @@ public interface ProtocolManager extends PacketStream {
* @throws InvocationTargetException - if an error occurred when sending the packet.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters)
throws InvocationTargetException;
/**
@@ -79,7 +79,7 @@ public interface ProtocolManager extends PacketStream {
* @throws IllegalAccessException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
throws IllegalAccessException, InvocationTargetException;
/**
@@ -87,7 +87,7 @@ public interface ProtocolManager extends PacketStream {
* @param packet - the packet to broadcast.
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
*/
public void broadcastServerPacket(PacketContainer packet);
void broadcastServerPacket(PacketContainer packet);
/**
* Broadcast a packet to every player that is receiving information about a given entity.
@@ -99,7 +99,7 @@ public interface ProtocolManager extends PacketStream {
* @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.
*/
public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
/**
* Broadcast a packet to every player within the given maximum observer distance.
@@ -107,13 +107,13 @@ public interface ProtocolManager extends PacketStream {
* @param origin - the origin to consider when calculating the distance to each observer.
* @param maxObserverDistance - the maximum distance to the origin.
*/
public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
/**
* Retrieves a list of every registered packet listener.
* @return Every registered packet listener.
*/
public ImmutableSet<PacketListener> getPacketListeners();
ImmutableSet<PacketListener> getPacketListeners();
/**
* Adds a packet listener.
@@ -123,7 +123,7 @@ public interface ProtocolManager extends PacketStream {
* can register it again.
* @param listener - new packet listener.
*/
public void addPacketListener(PacketListener listener);
void addPacketListener(PacketListener listener);
/**
* Removes a given packet listener.
@@ -131,47 +131,20 @@ public interface ProtocolManager extends PacketStream {
* Attempting to remove a listener that doesn't exist has no effect.
* @param listener - the packet listener to remove.
*/
public void removePacketListener(PacketListener listener);
void removePacketListener(PacketListener listener);
/**
* Removes every listener associated with the given plugin.
* @param plugin - the plugin to unload.
*/
public void removePacketListeners(Plugin plugin);
void removePacketListeners(Plugin plugin);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* <p>
* Deprecated: Use {@link #createPacket(PacketType)} instead.
* @param id - packet ID.
* @return New encapsulated Minecraft packet.
*/
@Deprecated
public PacketContainer createPacket(int id);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
* @param type - packet type.
* @return New encapsulated Minecraft packet.
*/
public 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.
* <p>
* Deprecated: Use {@link #createPacket(PacketType, boolean)} instead.
*
* @param id - packet ID.
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults);
PacketContainer createPacket(PacketType type);
/**
* Constructs a new encapsulated Minecraft packet with the given ID.
@@ -185,18 +158,7 @@ public interface ProtocolManager extends PacketStream {
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
* @return New encapsulated Minecraft packet.
*/
public PacketContainer createPacket(PacketType type, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
* <p>
* Deprecated: Use {@link #createPacketConstructor(PacketType, Object...)} instead.
* @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments);
PacketContainer createPacket(PacketType type, boolean forceDefaults);
/**
* Construct a packet using the special builtin Minecraft constructors.
@@ -204,7 +166,7 @@ public interface ProtocolManager extends PacketStream {
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
/**
* Completely resend an entity to a list of clients.
@@ -214,7 +176,7 @@ public interface ProtocolManager extends PacketStream {
* @param entity - entity to refresh.
* @param observers - the clients to update.
*/
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
/**
* Retrieve the associated entity.
@@ -223,7 +185,7 @@ public interface ProtocolManager extends PacketStream {
* @return The associated entity.
* @throws FieldAccessException Reflection failed.
*/
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
Entity getEntityFromID(World container, int id) throws FieldAccessException;
/**
* Retrieve every client that is receiving information about a given entity.
@@ -231,55 +193,37 @@ public interface ProtocolManager extends PacketStream {
* @return Every client/player that is tracking the given entity.
* @throws FieldAccessException If reflection failed.
*/
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
/**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* <p>
* Deprecated: Use {@link #getSendingFilterTypes()} instead.
* @return Every filtered server packet.
*/
@Deprecated
public Set<Integer> getSendingFilters();
List<Player> getEntityTrackers(Entity entity) throws FieldAccessException;
/**
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
* @return Every filtered server packet.
*/
public Set<PacketType> getSendingFilterTypes();
/**
* Retrieves a immutable set containing the ID of the received client packets that will be observed by listeners.
* <p>
* Deprecated: Use {@link #getReceivingFilterTypes()} instead.
* @return Every filtered client packet.
*/
@Deprecated
public Set<Integer> getReceivingFilters();
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.
*/
public Set<PacketType> getReceivingFilterTypes();
Set<PacketType> getReceivingFilterTypes();
/**
* Retrieve the current Minecraft version.
* @return The current version.
*/
public MinecraftVersion getMinecraftVersion();
MinecraftVersion getMinecraftVersion();
/**
* Determines whether or not this protocol manager has been disabled.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
boolean isClosed();
/**
* Retrieve the current asynchronous packet manager.
* @return Asynchronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
AsynchronousManager getAsynchronousManager();
public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
}
@@ -23,10 +23,6 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
@@ -37,12 +33,15 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PrioritizedListener;
import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.comphenix.protocol.injector.packet.PacketRegistry;
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;
import org.bukkit.scheduler.BukkitScheduler;
/**
* Represents a filter manager for asynchronous packets.
* <p>
@@ -88,7 +87,7 @@ public class AsyncFilterManager implements AsynchronousManager {
// Initialize timeout listeners
this.serverTimeoutListeners = new SortedPacketListenerList();
this.clientTimeoutListeners = new SortedPacketListenerList();
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
this.timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap<>());
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
@@ -327,22 +326,12 @@ public class AsyncFilterManager implements AsynchronousManager {
getProcessingQueue(syncPacket).enqueue(newEvent, true);
}
}
@Override
public Set<Integer> getSendingFilters() {
return PacketRegistry.toLegacy(serverProcessingQueue.keySet());
}
@Override
public Set<PacketType> getReceivingTypes() {
return serverProcessingQueue.keySet();
}
@Override
public Set<Integer> getReceivingFilters() {
return PacketRegistry.toLegacy(clientProcessingQueue.keySet());
}
@Override
public Set<PacketType> getSendingTypes() {
return clientProcessingQueue.keySet();
@@ -32,11 +32,11 @@ public class PacketTypeSet {
* @param type - the type to add.
*/
public synchronized void addType(PacketType type) {
Class<?> packetClass = getPacketClass(type);
Class<?> packetClass = type.getPacketClass();
types.add(Preconditions.checkNotNull(type, "type cannot be NULL."));
if (packetClass != null) {
classes.add(getPacketClass(type));
classes.add(type.getPacketClass());
}
}
@@ -55,11 +55,11 @@ public class PacketTypeSet {
* @param type - the type to remove.
*/
public synchronized void removeType(PacketType type) {
Class<?> packetClass = getPacketClass(type);
Class<?> packetClass = type.getPacketClass();
types.remove(Preconditions.checkNotNull(type, "type cannot be NULL."));
if (packetClass != null) {
classes.remove(getPacketClass(type));
classes.remove(packetClass);
}
}
@@ -72,16 +72,7 @@ public class PacketTypeSet {
removeType(type);
}
}
/**
* Retrieve the packet class associated with a particular type.
* @param type - the packet type.
* @return The associated packet type.
*/
protected Class<?> getPacketClass(PacketType type) {
return PacketRegistry.getPacketClassFromType(type);
}
/**
* Determine if the given packet type exists in the set.
* @param type - the type to find.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,45 @@
package com.comphenix.protocol.events;
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) {
super(handle, structureModifier);
}
protected static final EquivalentConverter<InternalStructure> CONVERTER = new EquivalentConverter<InternalStructure>() {
@Override
public Object getGeneric(InternalStructure specific) {
return specific.handle;
}
@Override
public InternalStructure getSpecific(Object generic) {
return new InternalStructure(generic, new StructureModifier<>(generic.getClass()).withTarget(generic));
}
@Override
public Class<InternalStructure> getSpecificType() {
return InternalStructure.class;
}
};
public StructureModifier<InternalStructure> getStructures() {
return structureModifier.withType(Object.class, CONVERTER);
}
public StructureModifier<Optional<InternalStructure>> getOptionalStructures() {
return structureModifier.withType(Optional.class, Converters.optional(CONVERTER));
}
@Override
public String toString() {
return "InternalStructure[handle=" + handle + " (" + handle.getClass().getSimpleName() + ")]";
}
}
@@ -17,11 +17,7 @@
package com.comphenix.protocol.events;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
@@ -44,93 +40,21 @@ public class ListeningWhitelist {
private final GamePhase gamePhase;
private final Set<ListenerOptions> options;
private final Set<PacketType> types;
// Cache whitelist
private transient Set<Integer> intWhitelist;
private ListeningWhitelist(Builder builder) {
this.priority = builder.priority;
this.types = builder.types;
this.gamePhase = builder.gamePhase;
this.options = builder.options;
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist) {
this(priority, whitelist, GamePhase.PLAYING);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - set of IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Set<Integer> whitelist, GamePhase gamePhase) {
private ListeningWhitelist(ListenerPriority priority) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
this.gamePhase = gamePhase;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist of a given priority for a list of packets.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer... whitelist) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.types = new HashSet<>();
this.gamePhase = GamePhase.PLAYING;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = gamePhase;
this.options = EnumSet.noneOf(ListenerOptions.class);
}
/**
* Creates a packet whitelist for a given priority with a set of packet IDs and options.
* <p>
* Deprecated: Use {@link #newBuilder()} instead.
* @param priority - the listener priority.
* @param whitelist - list of packet IDs to observe/enable.
* @param gamePhase - which game phase to receieve notifications on.
* @param options - every special option associated with this whitelist.
*/
@Deprecated
public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase, ListenerOptions... options) {
this.priority = priority;
this.types = PacketRegistry.toPacketTypes(Sets.newHashSet(whitelist));
this.gamePhase = gamePhase;
this.options = safeEnumSet(Arrays.asList(options), ListenerOptions.class);
}
/**
* Whether or not this whitelist has any enabled packets.
* @return TRUE if there are any packets, FALSE otherwise.
@@ -147,19 +71,6 @@ public class ListeningWhitelist {
return priority;
}
/**
* Retrieves the list of packets that will be observed by the listeners.
* <p>
* Deprecated: Use {@link #getTypes()} instead.
* @return Packet whitelist.
*/
@Deprecated
public Set<Integer> getWhitelist() {
if (intWhitelist == null)
intWhitelist = PacketRegistry.toLegacy(types);
return intWhitelist;
}
/**
* Retrieves a set of the packets that will be observed by the listeners.
* @return Packet whitelist.
@@ -189,23 +100,6 @@ public class ListeningWhitelist {
return Objects.hashCode(priority, types, gamePhase, options);
}
/**
* Determine if any of the given IDs can be found in the whitelist.
* @param whitelist - whitelist to test.
* @param idList - list of packet IDs to find.
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
*/
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
if (whitelist != null) {
for (int i = 0; i < idList.length; i++) {
if (whitelist.getWhitelist().contains(idList[i]))
return true;
}
}
return false;
}
/**
* Determine if the given whitelist is empty or not.
* @param whitelist - the whitelist to test.
@@ -273,7 +167,7 @@ public class ListeningWhitelist {
/**
* Construct a copy of a given set.
* @param list - the set to copy.
* @param set - the set to copy.
* @return The copied set.
*/
private static <T> Set<T> safeSet(Collection<T> set) {
@@ -364,20 +258,7 @@ public class ListeningWhitelist {
public Builder high() {
return priority(ListenerPriority.HIGH);
}
/**
* Set the whitelist of packet IDs to copy when constructing new whitelists.
* <p>
* Deprecated: Use {@link #types(Collection)} instead.
* @param whitelist - the whitelist of packets.
* @return This builder, for chaining.
*/
@Deprecated
public Builder whitelist(Collection<Integer> whitelist) {
this.types = PacketRegistry.toPacketTypes(safeSet(whitelist));
return this;
}
/**
* Set the whitelist of packet types to copy when constructing new whitelists.
* @param types - the whitelist of packets.
@@ -17,15 +17,11 @@
package com.comphenix.protocol.events;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import org.bukkit.plugin.Plugin;
/**
* Represents a listener that is notified of every sent and received packet.
@@ -45,26 +41,17 @@ public abstract class MonitorAdapter implements PacketListener {
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
initialize(plugin, side, logger);
}
@SuppressWarnings("deprecation")
private void initialize(Plugin plugin, ConnectionSide side, Logger logger) {
this.plugin = plugin;
// Recover in case something goes wrong
try {
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();
} catch (FieldAccessException e) {
if (side.isForServer())
this.sending = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Server.getRegistry().values(), GamePhase.BOTH);
if (side.isForClient())
this.receiving = new ListeningWhitelist(ListenerPriority.MONITOR, Packets.Client.getRegistry().values(), GamePhase.BOTH);
logger.log(Level.WARNING, "Defaulting to 1.3 packets.", e);
}
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();
}
/**
* Retrieve a logger, even if we're running in a CraftBukkit version that doesn't support it.
* @param plugin - the plugin to retrieve.
@@ -19,19 +19,17 @@ package com.comphenix.protocol.events;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.packet.PacketRegistry;
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;
/**
* Represents a packet listener with useful constructors.
* <p>
@@ -103,151 +101,7 @@ public abstract class PacketAdapter implements PacketListener {
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
this(params(plugin, types).listenerPriority(listenerPriority));
}
/**
* Initialize a packet listener with default priority.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Set<Integer> packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, packets.toArray(new Integer[0]));
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, Integer... packets) {
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerOptions[] options, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, options, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, new ListenerOptions[0], packets);
}
/**
* Initialize a packet listener for a single connection side.
* <p>
* The game phase is used to optimize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
* <p>
* Listener options must be specified in order for {@link NetworkMarker#getInputBuffer()} to function correctly.
* <p>
* Deprecated: Use {@link #params()} instead.
* @param plugin - the plugin that spawned this listener.
* @param connectionSide - the packet type the listener is looking for.
* @param listenerPriority - the event priority.
* @param gamePhase - which game phase this listener is active under.
* @param options - which listener options to use.
* @param packets - the packet IDs the listener is looking for.
*/
@Deprecated
public PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
GamePhase gamePhase, ListenerOptions[] options, Integer... packets) {
this(plugin, connectionSide, listenerPriority, gamePhase, options,
PacketRegistry.toPacketTypes(Sets.newHashSet(packets), connectionSide.getSender()).toArray(new PacketType[0])
);
}
// For internal use only
private PacketAdapter(
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
@@ -374,20 +228,6 @@ public abstract class PacketAdapter implements PacketListener {
public static AdapterParameteters params() {
return new AdapterParameteters();
}
/**
* 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.
* Deprecated: Use {@link #params(Plugin, PacketType...)} instead.
* @param plugin - the plugin that spawned this listener.
* @param packets - the packet IDs the listener is looking for.
* @return Helper object.
*/
@Deprecated
public static AdapterParameteters params(Plugin plugin, Integer... packets) {
return new AdapterParameteters().plugin(plugin).packets(packets);
}
/**
* Construct a helper object for passing parameters to the packet adapter.
@@ -532,17 +372,7 @@ public abstract class PacketAdapter implements PacketListener {
public AdapterParameteters optionIntercept() {
return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER);
}
/**
* Set the listener option to {@link ListenerOptions#DISABLE_GAMEPHASE_DETECTION}, causing ProtocolLib to ignore automatic game phase detection.
* <p>
* This is no longer relevant in 1.7.2.
* @return This builder, for chaining.
*/
public AdapterParameteters optionManualGamePhase() {
return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION);
}
/**
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our listener is thread safe.
* <p>
@@ -552,40 +382,7 @@ public abstract class PacketAdapter implements PacketListener {
public AdapterParameteters optionAsync() {
return addOption(ListenerOptions.ASYNC);
}
/**
* Set the packet IDs of the packets the listener is looking for.
* <p>
* This parameter is required.
* <p>
* Deprecated: Use {@link #types(PacketType...)} instead.
* @param packets - the packet IDs to look for.
* @return This builder, for chaining.
*/
@Deprecated
public AdapterParameteters packets(@Nonnull Integer... packets) {
Preconditions.checkNotNull(packets, "packets cannot be NULL");
PacketType[] types = new PacketType[packets.length];
for (int i = 0; i < types.length; i++) {
types[i] = PacketType.findLegacy(packets[i]);
}
this.packets = types;
return this;
}
/**
* Set the packet IDs of the packets the listener is looking for.
* <p>
* This parameter is required.
* @param packets - a set of the packet IDs to look for.
* @return This builder, for chaining.
*/
@Deprecated
public AdapterParameteters packets(@Nonnull Set<Integer> packets) {
return packets(packets.toArray(new Integer[0]));
}
/**
* Set the packet types the listener is looking for.
* <p>
File diff suppressed because it is too large. Load diff
@@ -247,7 +247,7 @@ public class PacketEvent extends EventObject implements Cancellable {
*/
@Deprecated
public int getPacketID() {
return packet.getID();
return packet.getId();
}
/**
@@ -21,22 +21,32 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
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.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.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.ByteBuddyFactory;
/**
* Represents a player object that can be serialized by Java.
@@ -62,9 +72,8 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private boolean playedBefore;
private boolean online;
private boolean whitelisted;
// Proxy helper
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
/**
* Constructor used by serialization.
@@ -109,6 +118,50 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return bedSpawnLocation;
}
// TODO do we need to implement this?
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { }
public void incrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
public void setStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
public int getStatistic(Statistic statistic) throws IllegalArgumentException {
return 0;
}
public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { }
public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
return 0;
}
public void incrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
public void setStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { }
public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
return 0;
}
public void incrementStatistic(Statistic statistic, EntityType entityType, int i) throws IllegalArgumentException { }
public void decrementStatistic(Statistic statistic, EntityType entityType, int i) { }
public void setStatistic(Statistic statistic, EntityType entityType, int i) { }
@Override
public long getFirstPlayed() {
return firstPlayed;
@@ -201,42 +254,78 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
/**
* Retrieve a player object that implements OfflinePlayer by refering to this object.
* Retrieve a player object that implements OfflinePlayer by referring to this object.
* <p>
* All other methods cause an exception.
* @return Proxy object.
*/
public Player getProxyPlayer() {
// Remember to initialize the method filter
if (lookup.size() == 0) {
// Add all public methods
for (Method method : OfflinePlayer.class.getMethods()) {
lookup.put(method.getName(), method);
}
try {
return (Player) proxyPlayerConstructor.newInstance(this);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
// MORE CGLIB magic!
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// There's no overloaded methods, so we don't care
Method offlineMethod = lookup.get(method.getName());
// Ignore all other methods
if (offlineMethod == null) {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
}
}
// Invoke our on method
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
}
});
return (Player) ex.create();
private static Constructor<? extends Player> setupProxyPlayerConstructor()
{
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final String[] methodNames = new String[offlinePlayerMethods.length];
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
methodNames[idx] = offlinePlayerMethods[idx].getName();
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 InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
});
return ByteBuddyFactory.getInstance()
.createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(SerializedOfflinePlayer.class.getPackage().getName() + ".PlayerInvocationHandler")
.defineField("offlinePlayer", OfflinePlayer.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameters(OfflinePlayer.class)
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("offlinePlayer").setsArgumentAt(0)))
.method(forwardedMethods)
.intercept(forwarding)
.method(ElementMatchers.not(forwardedMethods))
.intercept(throwException)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(OfflinePlayer.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Player constructor!", e);
}
}
/**
* This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
* looks at OfflinePlayer's methods first while still being a Player.
*/
private interface PlayerUnion extends OfflinePlayer, Player
{
}
}
@@ -1,518 +0,0 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
/**
* A protocol manager that delays all packet listener registrations and unregistrations until
* an underlying protocol manager can be constructed.
*
* @author Kristian
*/
public class DelayedPacketManager implements InternalManager {
// Registering packet IDs that are not supported
public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
public static final ReportType REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET = new ReportType("Cannot send queued wire packet %s.");
public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
private volatile InternalManager delegate;
// Queued actions
private final List<Runnable> queuedActions = Collections.synchronizedList(Lists.<Runnable>newArrayList());
private final List<PacketListener> queuedListeners = Collections.synchronizedList(Lists.<PacketListener>newArrayList());
private AsynchronousManager asyncManager;
private ErrorReporter reporter;
// The current hook
private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// If we have been closed
private boolean closed;
private boolean debug;
// Queued registration
private PluginManager queuedManager;
private Plugin queuedPlugin;
private MinecraftVersion version;
public DelayedPacketManager(@Nonnull ErrorReporter reporter, @Nonnull MinecraftVersion version) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
Preconditions.checkNotNull(version, "version cannot be NULL.");
this.reporter = reporter;
this.version = version;
}
/**
* Retrieve the underlying protocol manager.
* @return The underlying manager.
*/
public InternalManager getDelegate() {
return delegate;
}
@Override
public int getProtocolVersion(Player player) {
if (delegate != null)
return delegate.getProtocolVersion(player);
else
return Integer.MIN_VALUE;
}
@Override
public MinecraftVersion getMinecraftVersion() {
if (delegate != null)
return delegate.getMinecraftVersion();
else
return version;
}
/**
* Update the delegate to the underlying manager.
* <p>
* This will prompt this packet manager to immediately transmit and
* register all queued packets an listeners.
* @param delegate - delegate to the new manager.
*/
protected void setDelegate(InternalManager delegate) {
this.delegate = delegate;
if (delegate != null) {
// Update the hook if needed
if (!Objects.equal(delegate.getPlayerHook(), hook)) {
delegate.setPlayerHook(hook);
}
// Register events as well
if (queuedManager != null && queuedPlugin != null) {
delegate.registerEvents(queuedManager, queuedPlugin);
}
// And update the debug mode
delegate.setDebug(debug);
// Add any pending listeners
synchronized (queuedListeners) {
for (PacketListener listener : queuedListeners) {
try {
delegate.addPacketListener(listener);
} catch (IllegalArgumentException e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
callerParam(delegate).messageParam(listener).error(e));
}
}
}
// Execute any delayed actions
synchronized (queuedActions) {
for (Runnable action : queuedActions) {
action.run();
}
}
// Don't keep this around anymore
queuedListeners.clear();
queuedActions.clear();
}
}
private Runnable queuedAddPacket(final ConnectionSide side, final Player player, final PacketContainer packet,
final NetworkMarker marker, final boolean filtered) {
return new Runnable() {
@Override
public void run() {
try {
// Attempt to send it now
switch (side) {
case CLIENT_SIDE:
delegate.recieveClientPacket(player, packet, marker, filtered);
break;
case SERVER_SIDE:
delegate.sendServerPacket(player, packet, marker, filtered);
break;
default:
throw new IllegalArgumentException("side cannot be " + side);
}
} catch (Exception e) {
// Inform about this plugin error
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
callerParam(delegate).messageParam(packet).error(e));
}
}
};
}
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.hook = playerHook;
}
@Override
public PlayerInjectHooks getPlayerHook() {
return hook;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, true);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, boolean filters) throws InvocationTargetException {
sendServerPacket(receiver, packet, null, filters);
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
if (delegate != null) {
delegate.sendServerPacket(receiver, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.SERVER_SIDE, receiver, packet, marker, filters));
}
}
@Override
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException {
WirePacket packet = new WirePacket(id, bytes);
sendWirePacket(receiver, packet);
}
@Override
public void sendWirePacket(final Player receiver, final WirePacket packet) throws InvocationTargetException {
if (delegate != null) {
delegate.sendWirePacket(receiver, packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
try {
delegate.sendWirePacket(receiver, packet);
} catch (Throwable ex) {
// Inform about this plugin error
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET)
.callerParam(delegate)
.messageParam(packet)
.error(ex));
}
}
});
}
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, true);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, filters);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException {
if (delegate != null) {
delegate.recieveClientPacket(sender, packet, marker, filters);
} else {
queuedActions.add(queuedAddPacket(ConnectionSide.CLIENT_SIDE, sender, packet, marker, filters));
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Entity entity, final boolean includeTracker) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, entity, includeTracker);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, entity, includeTracker);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet, final Location origin, final int maxObserverDistance) {
if (delegate != null) {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet, origin, maxObserverDistance);
}
});
}
}
@Override
public void broadcastServerPacket(final PacketContainer packet) {
if (delegate != null) {
delegate.broadcastServerPacket(packet);
} else {
queuedActions.add(new Runnable() {
@Override
public void run() {
delegate.broadcastServerPacket(packet);
}
});
}
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
if (delegate != null)
return delegate.getPacketListeners();
else
return ImmutableSet.copyOf(queuedListeners);
}
@Override
public void addPacketListener(PacketListener listener) {
if (delegate != null)
delegate.addPacketListener(listener);
else
queuedListeners.add(listener);
}
@Override
public void removePacketListener(PacketListener listener) {
if (delegate != null)
delegate.removePacketListener(listener);
else
queuedListeners.remove(listener);
}
@Override
public void removePacketListeners(Plugin plugin) {
if (delegate != null) {
delegate.removePacketListeners(plugin);
} else {
for (Iterator<PacketListener> it = queuedListeners.iterator(); it.hasNext(); ) {
// Remove listeners of the same plugin
if (Objects.equal(it.next().getPlugin(), plugin)) {
it.remove();
}
}
}
}
@Override
@Deprecated
public PacketContainer createPacket(int id) {
if (delegate != null)
return delegate.createPacket(id);
return createPacket(id, true);
}
@Override
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults) {
if (delegate != null) {
return delegate.createPacket(id);
} else {
// Fallback implementation
PacketContainer packet = new PacketContainer(id);
// Use any default values if possible
if (forceDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
return packet;
}
}
@SuppressWarnings("deprecation")
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(id, arguments);
else
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(type, arguments);
else
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
@Deprecated
public Set<Integer> getSendingFilters() {
if (delegate != null) {
return delegate.getSendingFilters();
} else {
// Linear scan is fast enough here
Set<Integer> sending = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
sending.addAll(listener.getSendingWhitelist().getWhitelist());
}
return sending;
}
}
@Override
@Deprecated
public Set<Integer> getReceivingFilters() {
if (delegate != null) {
return delegate.getReceivingFilters();
} else {
Set<Integer> recieving = Sets.newHashSet();
for (PacketListener listener : queuedListeners) {
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
}
return recieving;
}
}
@Override
public PacketContainer createPacket(PacketType type) {
return createPacket(type.getLegacyId());
}
@Override
public PacketContainer createPacket(PacketType type, boolean forceDefaults) {
return createPacket(type.getLegacyId(), forceDefaults);
}
@Override
public Set<PacketType> getSendingFilterTypes() {
return PacketRegistry.toPacketTypes(getSendingFilters(), Sender.SERVER);
}
@Override
public Set<PacketType> getReceivingFilterTypes() {
return PacketRegistry.toPacketTypes(getReceivingFilters(), Sender.CLIENT);
}
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
if (delegate != null)
delegate.updateEntity(entity, observers);
else
EntityUtilities.getInstance().updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityFromID(container, id);
else
return EntityUtilities.getInstance().getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
if (delegate != null)
return delegate.getEntityTrackers(entity);
else
return EntityUtilities.getInstance().getEntityTrackers(entity);
}
@Override
public boolean isClosed() {
return closed || (delegate != null && delegate.isClosed());
}
@Override
public AsynchronousManager getAsynchronousManager() {
if (delegate != null)
return delegate.getAsynchronousManager();
else
return asyncManager;
}
@Override
public boolean isDebug() {
return debug;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
if (delegate != null) {
delegate.setDebug(debug);
}
}
/**
* Update the asynchronous manager. This must be set.
* @param asyncManager - the asynchronous manager.
*/
public void setAsynchronousManager(AsynchronousManager asyncManager) {
this.asyncManager = asyncManager;
}
@Override
public void registerEvents(PluginManager manager, Plugin plugin) {
if (delegate != null) {
delegate.registerEvents(manager, plugin);
} else {
queuedManager = manager;
queuedPlugin = plugin;
}
}
@Override
public void close() {
if (delegate != null)
delegate.close();
closed = true;
}
@Override
public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
for (PacketType type : whitelist.getTypes()) {
if (type == null) {
throw new IllegalArgumentException(String.format("Packet type in in listener %s was NULL.",
PacketAdapter.getPluginName(listener))
);
}
}
}
}
@@ -17,6 +17,7 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -139,7 +140,7 @@ class EntityUtilities {
@SuppressWarnings("unchecked")
private Object getNewEntityTracker(Object worldServer, int entityId) {
if (getChunkProvider == null) {
Class<?> chunkProviderClass = MinecraftReflection.getMinecraftClass("ChunkProviderServer");
Class<?> chunkProviderClass = MinecraftReflection.getChunkProviderServer();
getChunkProvider = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(worldServer.getClass(), false).getMethod(
FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
@@ -148,7 +149,7 @@ class EntityUtilities {
Object chunkProvider = getChunkProvider.invoke(worldServer);
if (chunkMapField == null) {
Class<?> chunkMapClass = MinecraftReflection.getMinecraftClass("PlayerChunkMap");
Class<?> chunkMapClass = MinecraftReflection.getPlayerChunkMap();
chunkMapField = Accessors.getFieldAccessor(
FuzzyReflection.fromClass(chunkProvider.getClass(), false).getField(
FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
@@ -157,9 +158,21 @@ class EntityUtilities {
Object playerChunkMap = chunkMapField.get(chunkProvider);
if (trackedEntitiesField == null) {
trackedEntitiesField = Accessors.getFieldAccessor(
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()
)
);
} else {
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);
@@ -229,14 +242,17 @@ class EntityUtilities {
Object tracker = null;
if (trackerEntry != null) {
// plugins like citizens will use their own tracker
// 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(trackerEntry.getClass(), "tracker", true);
} catch (Exception e) {
// Assume it's the first entity field then
return Accessors.getFieldAccessor(FuzzyReflection.fromObject(trackerEntry, true)
.getFieldByType("tracker", MinecraftReflection.getEntityClass()));
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()));
}
});
@@ -19,7 +19,6 @@ package com.comphenix.protocol.injector;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
/**
* Represents an object that initiate the packet listeners.
@@ -32,63 +31,18 @@ public interface ListenerInvoker {
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketRecieving(PacketEvent event);
void invokePacketRecieving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated ID of a packet.
* @param packet - the packet.
* @return The packet ID.
*/
@Deprecated
public abstract int getPacketID(Object packet);
void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated type of a packet.
* @param packet - the packet.
* @return The packet type.
*/
public abstract PacketType getPacketType(Object packet);
/**
* Retrieve the object responsible for intercepting write packets.
* @return Object that intercepts write packets.
*/
public InterceptWritePacket getInterceptWritePacket();
/**
* Determine if a given packet requires input buffering.
* @param packetId - the packet to check.
* @return TRUE if it does, FALSE otherwise.
*/
@Deprecated
public boolean requireInputBuffer(int packetId);
/**
* Associate a given class with the given packet ID. Internal method.
* @param clazz - class to associate.
*/
public abstract void unregisterPacketClass(Class<?> clazz);
/**
* Register a given class in the packet registry. Internal method.
* @param clazz - class to register.
* @param packetID - the the new associated packet ID.
*/
@Deprecated
public abstract void registerPacketClass(Class<?> clazz, int packetID);
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
@Deprecated
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
PacketType getPacketType(Object packet);
}
@@ -1,92 +0,0 @@
package com.comphenix.protocol.injector;
import org.bukkit.Bukkit;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Packets that are known to be transmitted during login.
* <p>
* This may be dynamically extended later.
* @author Kristian
*/
class LoginPackets {
private IntegerSet clientSide = new IntegerSet(Packets.PACKET_COUNT);
private IntegerSet serverSide = new IntegerSet(Packets.PACKET_COUNT);
@SuppressWarnings("deprecation")
public LoginPackets(MinecraftVersion version) {
// Ordinary login
clientSide.add(Packets.Client.HANDSHAKE);
serverSide.add(Packets.Server.KEY_REQUEST);
clientSide.add(Packets.Client.KEY_RESPONSE);
serverSide.add(Packets.Server.KEY_RESPONSE);
clientSide.add(Packets.Client.CLIENT_COMMAND);
serverSide.add(Packets.Server.LOGIN);
// List ping
clientSide.add(Packets.Client.GET_INFO);
// In 1.6.2, Minecraft started sending CUSTOM_PAYLOAD in the server list protocol
// MCPC+/Cauldron contains Forge, which uses CUSTOM_PAYLOAD during login
if (version.isAtLeast(MinecraftVersion.HORSE_UPDATE) || isCauldronOrMCPC()) {
clientSide.add(Packets.Client.CUSTOM_PAYLOAD);
}
if (isCauldronOrMCPC()) {
serverSide.add(Packets.Server.CUSTOM_PAYLOAD);
}
serverSide.add(Packets.Server.KICK_DISCONNECT);
}
/**
* Determine if we are running MCPC or Cauldron.
* @return TRUE if we are, FALSE otherwise.
*/
private static boolean isCauldronOrMCPC() {
String version = Bukkit.getServer().getVersion();
return version.contains("MCPC") || version.contains("Cauldron");
}
/**
* Determine if a packet may be sent during login from a given direction.
* @param packetId - the ID of the packet.
* @param side - the direction.
* @return TRUE if it may, FALSE otherwise.
*/
@Deprecated
public boolean isLoginPacket(int packetId, ConnectionSide side) {
switch (side) {
case CLIENT_SIDE:
return clientSide.contains(packetId);
case SERVER_SIDE:
return serverSide.contains(packetId);
case BOTH:
return clientSide.contains(packetId) ||
serverSide.contains(packetId);
default:
throw new IllegalArgumentException("Unknown connection side: " + side);
}
}
/**
* Determine if a given packet may be sent during login.
* @param type - the packet type.
* @return TRUE if it may, FALSE otherwise.
*/
public boolean isLoginPacket(PacketType type) {
if (!MinecraftReflection.isUsingNetty())
return isLoginPacket(type.getLegacyId(), type.getSender().toSide());
return PacketType.Login.Client.getInstance().hasMember(type) ||
PacketType.Login.Server.getInstance().hasMember(type) ||
PacketType.Status.Client.getInstance().hasMember(type) ||
PacketType.Status.Server.getInstance().hasMember(type);
}
}
@@ -20,6 +20,7 @@ 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;
@@ -81,7 +82,7 @@ public class PacketConstructor {
*/
@Deprecated
public int getPacketID() {
return type.getLegacyId();
return type.getCurrentId();
}
/**
@@ -101,22 +102,6 @@ public class PacketConstructor {
return new PacketConstructor(type, constructorMethod, unwrappers, paramUnwrapper);
}
/**
* Create a packet constructor that creates packets using the given ID.
* <p>
* Note that if you pass a Class as a value, it will use its type directly.
* <p>
* Deprecated: Use {@link #withPacket(PacketType, Object[])} instead.
* @param id - legacy (1.6.4) packet ID.
* @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.
*/
@Deprecated
public PacketConstructor withPacket(int id, Object[] values) {
return withPacket(PacketType.findLegacy(id), values);
}
/**
* Create a packet constructor that creates packets using the given types.
* <p>
@@ -162,13 +147,11 @@ public class PacketConstructor {
types[i] = Object.class;
}
}
Class<?> packetType = PacketRegistry.getPacketClassFromType(type, true);
if (packetType == null)
throw new IllegalArgumentException("Could not find a packet by the type " + type);
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
// Find the correct constructor
for (Constructor<?> constructor : packetType.getConstructors()) {
for (Constructor<?> constructor : packetClass.getConstructors()) {
Class<?>[] params = constructor.getParameterTypes();
if (isCompatible(types, params)) {
@@ -2,26 +2,14 @@ package com.comphenix.protocol.injector;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.plugin.Plugin;
import com.comphenix.executors.BukkitFutures;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.player.InjectedServerConnection;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
public class PacketFilterBuilder {
public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event.");
public static final ReportType REPORT_SPIGOT_IS_DELAYING_INJECTOR = new ReportType("Delaying due to Spigot.");
private ClassLoader classLoader;
private Server server;
private Plugin library;
@@ -185,63 +173,10 @@ public class PacketFilterBuilder {
asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
nettyEnabled = false;
// Spigot
if (SpigotPacketInjector.canUseSpigotListener()) {
// If the server hasn't loaded yet - wait
if (InjectedServerConnection.getServerConnection(reporter, server) == null) {
// We need to delay this until we know if Netty is enabled
final DelayedPacketManager delayed = new DelayedPacketManager(reporter, mcVersion);
// They must reference each other
delayed.setAsynchronousManager(asyncManager);
asyncManager.setManager(delayed);
Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class),
new FutureCallback<WorldInitEvent>() {
@Override
public void onSuccess(WorldInitEvent event) {
// Nevermind
if (delayed.isClosed())
return;
try {
registerSpigot(delayed);
} catch (Exception e) {
onFailure(e);
}
}
@Override
public void onFailure(Throwable error) {
reporter.reportWarning(PacketFilterBuilder.this, Report
.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error));
}
});
reporter.reportWarning(this, Report.newBuilder(REPORT_SPIGOT_IS_DELAYING_INJECTOR));
// Let plugins use this version instead
return delayed;
} else {
nettyEnabled = !MinecraftReflection.isMinecraftObject(
InjectedServerConnection.getServerConnection(reporter, server));
}
}
// Otherwise - construct the packet filter manager right away
return buildInternal();
}
private void registerSpigot(DelayedPacketManager delayed) {
// Use netty if we have a non-standard ServerConnection class
nettyEnabled = !MinecraftReflection.isMinecraftObject(
InjectedServerConnection.getServerConnection(reporter, server));
// Switch to the standard manager
delayed.setDelegate(buildInternal());
}
/**
* Construct a new packet filter manager without checking for Netty.
* @return A new packet filter manager.
@@ -24,7 +24,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import java.util.function.Predicate;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
@@ -37,25 +37,21 @@ import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.netty.ProtocolInjector;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.player.PlayerInjector.ServerHandlerNull;
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import io.netty.channel.Channel;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
@@ -72,8 +68,6 @@ import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import io.netty.channel.Channel;
public final class PacketFilterManager implements ListenerInvoker, InternalManager {
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
@@ -106,8 +100,7 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
private DelayedSingleTask unhookTask;
// Create a concurrent set
private Set<PacketListener> packetListeners =
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
private Set<PacketListener> packetListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
// Packet injection
private PacketInjector packetInjector;
@@ -115,9 +108,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
// Different injection types per game phase
private PlayerInjectionHandler playerInjection;
// Intercepting write packet methods
private InterceptWritePacket interceptWritePacket;
// Whether or not a packet must be input buffered
private volatile Set<PacketType> inputBufferedPackets = Sets.newHashSet();
@@ -154,9 +144,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
// Whether or not plugins are using the send/receive methods
private AtomicBoolean packetCreation = new AtomicBoolean();
// Spigot listener, if in use
private SpigotPacketInjector spigotInjector;
// Netty injector (for 1.7.2)
private ProtocolInjector nettyInjector;
@@ -169,9 +156,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
// The current Minecraft version
private MinecraftVersion minecraftVersion;
// Login packets
private LoginPackets loginPackets;
// Debug mode
private boolean debug;
@@ -181,18 +165,15 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
*/
public PacketFilterManager(PacketFilterBuilder builder) {
// Used to determine if injection is needed
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
@Override
public boolean apply(@Nullable GamePhase phase) {
boolean result = true;
Predicate<GamePhase> isInjectionNecessary = phase -> {
boolean result = true;
if (phase.hasLogin())
result &= getPhaseLoginCount() > 0;
// Note that we will still hook players if the unhooking has been delayed
if (phase.hasPlaying())
result &= getPhasePlayingCount() > 0 || unhookTask.isRunning();
return result;
}
if (phase.hasLogin())
result &= getPhaseLoginCount() > 0;
// Note that we will still hook players if the unhooking has been delayed
if (phase.hasPlaying())
result &= getPhasePlayingCount() > 0 || unhookTask.isRunning();
return result;
};
// Listener containers
@@ -217,47 +198,11 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
// Prepare version
this.minecraftVersion = builder.getMinecraftVersion();
this.loginPackets = new LoginPackets(minecraftVersion);
// The write packet interceptor
this.interceptWritePacket = new InterceptWritePacket(reporter);
this.nettyInjector = new ProtocolInjector(builder.getLibrary(), this, reporter);
this.playerInjection = nettyInjector.getPlayerInjector();
this.packetInjector = nettyInjector.getPacketInjector();
// Use the correct injection type
if (MinecraftReflection.isUsingNetty()) {
this.nettyInjector = new ProtocolInjector(builder.getLibrary(), this, reporter);
this.playerInjection = nettyInjector.getPlayerInjector();
this.packetInjector = nettyInjector.getPacketInjector();
} else if (builder.isNettyEnabled()) {
this.spigotInjector = new SpigotPacketInjector(reporter, this, server);
this.playerInjection = spigotInjector.getPlayerHandler();
this.packetInjector = spigotInjector.getPacketInjector();
// Set real injector, in case we need it
spigotInjector.setProxyPacketInjector(PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
playerInjection(playerInjection).
buildInjector()
);
} else {
// Initialize standard injection mangers
this.playerInjection = PlayerInjectorBuilder.newBuilder().
invoker(this).
server(server).
reporter(reporter).
packetListeners(packetListeners).
injectionFilter(isInjectionNecessary).
version(builder.getMinecraftVersion()).
buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
playerInjection(playerInjection).
buildInjector();
}
this.asyncFilterManager = builder.getAsyncManager();
this.library = builder.getLibrary();
@@ -331,11 +276,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
return ImmutableSet.copyOf(packetListeners);
}
@Override
public InterceptWritePacket getInterceptWritePacket() {
return interceptWritePacket;
}
/**
* Warn of common programming mistakes.
* @param plugin - plugin to check.
@@ -410,13 +350,25 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
}
}
/**
* Determine if a given packet may be sent during login.
* @param type - the packet type.
* @return TRUE if it may, FALSE otherwise.
*/
public boolean isLoginPacket(PacketType type) {
return PacketType.Login.Client.getInstance().hasMember(type) ||
PacketType.Login.Server.getInstance().hasMember(type) ||
PacketType.Status.Client.getInstance().hasMember(type) ||
PacketType.Status.Server.getInstance().hasMember(type);
}
private GamePhase processPhase(ListeningWhitelist whitelist) {
// Determine if this is a login packet, ensuring that gamephase detection is enabled
if (!whitelist.getGamePhase().hasLogin() &&
!whitelist.getOptions().contains(ListenerOptions.DISABLE_GAMEPHASE_DETECTION)) {
for (PacketType type : whitelist.getTypes()) {
if (loginPackets.isLoginPacket(type)) {
if (isLoginPacket(type)) {
return GamePhase.BOTH;
}
}
@@ -565,11 +517,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
}
}
@Override
public boolean requireInputBuffer(int packetId) {
return inputBufferedPackets.contains(PacketType.findLegacy(packetId, Sender.CLIENT));
}
/**
* Handle a packet sending or receiving event.
* <p>
@@ -858,23 +805,11 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
}
}
@Override
@Deprecated
public PacketContainer createPacket(int id) {
return createPacket(PacketType.findLegacy(id), true);
}
@Override
public PacketContainer createPacket(PacketType type) {
return createPacket(type, true);
}
@Override
@Deprecated
public PacketContainer createPacket(int id, boolean forceDefaults) {
return createPacket(PacketType.findLegacy(id), forceDefaults);
}
@Override
public PacketContainer createPacket(PacketType type, boolean forceDefaults) {
PacketContainer packet = new PacketContainer(type);
@@ -891,27 +826,11 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
return packet;
}
@Override
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
@Deprecated
public Set<Integer> getSendingFilters() {
return PacketRegistry.toLegacy(playerInjection.getSendingFilters());
}
@Override
public Set<Integer> getReceivingFilters() {
return PacketRegistry.toLegacy(packetInjector.getPacketHandlers());
}
@Override
public Set<PacketType> getSendingFilterTypes() {
return Collections.unmodifiableSet(playerInjection.getSendingFilters());
@@ -964,8 +883,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
*/
@Override
public void registerEvents(PluginManager manager, final Plugin plugin) {
if (spigotInjector != null && !spigotInjector.register(plugin))
throw new IllegalArgumentException("Spigot has already been registered.");
if (nettyInjector != null)
nettyInjector.inject();
@@ -1012,8 +929,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
// Let's clean up the other injection first.
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
} catch (ServerHandlerNull e) {
// Caused by logged out players, or fake login events in MCPC+/Cauldron. Ignore it.
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_INJECT_PLAYER).callerParam(event).error(e)
@@ -1064,12 +979,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
return phaseLoginCount.get();
}
@Override
@Deprecated
public int getPacketID(Object packet) {
return PacketRegistry.getPacketID(packet.getClass());
}
@Override
public PacketType getPacketType(Object packet) {
if (packet == null)
@@ -1087,44 +996,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
}
}
@Override
@Deprecated
public void registerPacketClass(Class<?> clazz, int packetID) {
PacketRegistry.getPacketToID().put(clazz, packetID);
}
@Override
@Deprecated
public void unregisterPacketClass(Class<?> clazz) {
PacketRegistry.getPacketToID().remove(clazz);
}
@Override
@Deprecated
public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) {
return PacketRegistry.getPacketClassFromID(packetID, forceVanilla);
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getServerPackets() throws FieldAccessException {
return PacketRegistry.getServerPackets();
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getClientPackets() throws FieldAccessException {
return PacketRegistry.getClientPackets();
}
/**
* Retrieves the current plugin class loader.
* @return Class loader.
@@ -1147,8 +1018,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
// Remove packet handlers
if (packetInjector != null)
packetInjector.cleanupAll();
if (spigotInjector != null)
spigotInjector.cleanupAll();
if (nettyInjector != null)
nettyInjector.close();
@@ -1161,9 +1030,6 @@ public final class PacketFilterManager implements ListenerInvoker, InternalManag
recievedListeners = null;
sendingListeners = null;
// Also cleanup the interceptor for the write packet method
interceptWritePacket.cleanup();
// Clean up async handlers. We have to do this last.
asyncFilterManager.cleanupAll();
}
@@ -19,17 +19,29 @@ package com.comphenix.protocol.injector;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.packet.PacketRegistry;
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.CompileListener;
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.ZeroBuffer;
import io.netty.buffer.ByteBuf;
import com.google.common.base.Preconditions;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.matcher.ElementMatchers;
/**
* Caches structure modifiers.
@@ -37,21 +49,41 @@ import com.comphenix.protocol.utility.MinecraftReflection;
*/
public class StructureCache {
// Structure modifiers
private static ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<PacketType, StructureModifier<Object>>();
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<>();
// packet data serializer which always returns an empty nbt tag compound
private static boolean trickTried;
private static ConstructorAccessor TRICKED_DATA_SERIALIZER;
private static Set<PacketType> compiling = new HashSet<PacketType>();
private static final Set<PacketType> compiling = new HashSet<>();
/**
* Creates an empty Minecraft packet of the given id.
* <p>
* Decreated: Use {@link #newPacket(PacketType)} instead.
* @param legacyId - legacy (1.6.4) packet id.
* @return Created packet.
*/
@Deprecated
public static Object newPacket(int legacyId) {
return newPacket(PacketType.findLegacy(legacyId));
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 () -> {
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();
}
return result;
}
/**
@@ -60,32 +92,7 @@ public class StructureCache {
* @return Created packet.
*/
public static Object newPacket(PacketType type) {
Class<?> clazz = PacketRegistry.getPacketClassFromType(type, true);
// Check the return value
if (clazz != null) {
// TODO: Optimize DefaultInstances
Object result = DefaultInstances.DEFAULT.create(clazz);
if (result == null) {
throw new IllegalArgumentException("Failed to create packet for type: " + type);
}
return result;
}
throw new IllegalArgumentException("Cannot find associated packet class: " + type);
}
/**
* Retrieve a cached structure modifier for the given packet id.
* <p>
* Deprecated: Use {@link #getStructure(PacketType)} instead.
* @param legacyId - the legacy (1.6.4) packet ID.
* @return A structure modifier.
*/
@Deprecated
public static StructureModifier<Object> getStructure(int legacyId) {
return getStructure(PacketType.findLegacy(legacyId));
return newPacket(PacketRegistry.getPacketClassFromType(type));
}
/**
@@ -116,20 +123,9 @@ public class StructureCache {
*/
public static StructureModifier<Object> getStructure(Class<?> packetType, boolean compile) {
// Get the ID from the class
return getStructure(PacketRegistry.getPacketType(packetType), compile);
}
/**
* Retrieve a cached structure modifier for the given packet ID.
* <p>
* Deprecated: Use {@link #getStructure(PacketType, boolean)} instead.
* @param legacyId - the legacy (1.6.4) packet ID.
* @param compile - whether or not to asynchronously compile the structure modifier.
* @return A structure modifier.
*/
@Deprecated
public static StructureModifier<Object> getStructure(final int legacyId, boolean compile) {
return getStructure(PacketType.findLegacy(legacyId), compile);
PacketType type = PacketRegistry.getPacketType(packetType);
Preconditions.checkNotNull(type, "No packet type associated with " + packetType);
return getStructure(type, compile);
}
/**
@@ -139,13 +135,14 @@ public class StructureCache {
* @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);
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>(
PacketRegistry.getPacketClassFromType(type, true), MinecraftReflection.getPacketClass(), true);
final StructureModifier<Object> value = new StructureModifier<>(
PacketRegistry.getPacketClassFromType(type), MinecraftReflection.getPacketClass(), true);
result = structureModifiers.putIfAbsent(type, value);
@@ -162,16 +159,42 @@ public class StructureCache {
final BackgroundCompiler compiler = BackgroundCompiler.getInstance();
if (!compiling.contains(type) && compiler != null) {
compiler.scheduleCompilation(result, new CompileListener<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
structureModifiers.put(type, compiledModifier);
}
});
compiler.scheduleCompilation(result,
compiledModifier -> structureModifiers.put(type, compiledModifier));
compiling.add(type);
}
}
}
return result;
}
/**
* 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.
*/
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
}
}
return TRICKED_DATA_SERIALIZER;
}
}
@@ -1,4 +1,4 @@
package com.comphenix.protocol.injector.spigot;
package com.comphenix.protocol.injector.netty;
import java.util.Set;
@@ -1,4 +1,4 @@
package com.comphenix.protocol.injector.spigot;
package com.comphenix.protocol.injector.netty;
import java.io.DataInputStream;
import java.util.Set;
@@ -30,24 +30,30 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
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.ObjectReconstructor;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.AttributeKey;
import io.netty.util.internal.TypeParameterMatcher;
import net.sf.cglib.proxy.Factory;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
@@ -55,8 +61,8 @@ import java.net.SocketAddress;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a channel injector.
* @author Kristian
@@ -83,19 +89,38 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
private static AttributeKey<Integer> PROTOCOL_KEY;
static {
PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL-" + keyId.getAndIncrement());
try {
PROTOCOL_KEY = AttributeKey.valueOf("PROTOCOL-" + keyId.getAndIncrement());
} catch (Exception ex) {
throw new RuntimeException("Encountered an error caused by a reload! Please properly restart your server!", ex);
}
Method hiddenClassMethod = null;
try {
if (Float.parseFloat(System.getProperty("java.class.version")) >= 59) {
hiddenClassMethod = Class.class.getMethod("isHidden");
}
} catch (NoSuchMethodException ignored) {
}
IS_HIDDEN_CLASS = hiddenClassMethod;
}
// Starting in Java 15 (59), the lambdas are hidden classes and we cannot use reflection to update
// the values anymore. Instead, the object will have to be reconstructed.
private static final Map<Class<?>, ObjectReconstructor<?>> RECONSTRUCTORS = new ConcurrentHashMap<>();
private static final Method IS_HIDDEN_CLASS;
// Saved accessors
private static Method DECODE_BUFFER;
private static Method ENCODE_BUFFER;
private Method decodeBuffer;
private Method encodeBuffer;
private static FieldAccessor ENCODER_TYPE_MATCHER;
// For retrieving the protocol
private static FieldAccessor PROTOCOL_ACCESSOR;
// The factory that created this injector
private InjectionFactory factory;
private final InjectionFactory factory;
// The player, or temporary player
private Player player;
@@ -108,10 +133,10 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// The current network manager and channel
private final Object networkManager;
private final Channel originalChannel;
private VolatileField channelField;
private final VolatileField channelField;
// Known network markers
private Map<Object, NetworkMarker> packetMarker = new WeakHashMap<>();
private final Map<Object, NetworkMarker> packetMarker = new WeakHashMap<>();
/**
* Indicate that this packet has been processed by event listeners.
@@ -126,21 +151,21 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
private PacketEvent finalEvent;
/**
* A flag set by the main thread to indiciate that a packet should not be processed.
* A queue of packets that were sent with filtered=false
*/
private final ThreadLocal<Boolean> scheduleProcessPackets = ThreadLocal.withInitial(() -> true);
private final PacketFilterQueue unfilteredProcessedPackets = new PacketFilterQueue();
// Other handlers
private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder;
private Deque<PacketEvent> finishQueue = new ArrayDeque<>();
private final Deque<PacketEvent> finishQueue = new ArrayDeque<>();
// The channel listener
private ChannelListener channelListener;
private final ChannelListener channelListener;
// Processing network markers
private NetworkProcessor processor;
private final NetworkProcessor processor;
// Closed
private boolean injected;
@@ -178,13 +203,31 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
return value != null ? value : MinecraftProtocolVersion.getCurrentVersion();
}
private void updateBufferMethods() {
try {
decodeBuffer = vanillaDecoder.getClass().getDeclaredMethod("decode",
ChannelHandlerContext.class, ByteBuf.class, List.class);
decodeBuffer.setAccessible(true);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Unable to find decode method in " + vanillaDecoder.getClass());
}
try {
encodeBuffer = vanillaEncoder.getClass().getDeclaredMethod("encode",
ChannelHandlerContext.class, Object.class, ByteBuf.class);
encodeBuffer.setAccessible(true);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Unable to find encode method in " + vanillaEncoder.getClass());
}
}
@Override
@SuppressWarnings("unchecked")
public boolean inject() {
synchronized (networkManager) {
if (closed)
return false;
if (originalChannel instanceof Factory)
if (originalChannel instanceof ByteBuddyFactory)
return false;
if (!originalChannel.isActive())
return false;
@@ -212,25 +255,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline());
patchEncoder(vanillaEncoder);
if (DECODE_BUFFER == null) {
try {
DECODE_BUFFER = vanillaDecoder.getClass().getDeclaredMethod("decode",
ChannelHandlerContext.class, ByteBuf.class, List.class);
DECODE_BUFFER.setAccessible(true);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Unable to find decode method in " + vanillaDecoder.getClass());
}
}
if (ENCODE_BUFFER == null) {
try {
ENCODE_BUFFER = vanillaEncoder.getClass().getDeclaredMethod("encode",
ChannelHandlerContext.class, Object.class, ByteBuf.class);
ENCODE_BUFFER.setAccessible(true);
} catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("Unable to find encode method in " + vanillaEncoder.getClass());
}
}
updateBufferMethods();
// Intercept sent packets
MessageToByteEncoder<Object> protocolEncoder = new MessageToByteEncoder<Object>() {
@@ -249,6 +274,11 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
super.write(ctx, packet, promise);
ChannelInjector.this.finalWrite();
}
@Override
public boolean acceptOutboundMessage(Object msg) {
return msg instanceof WirePacket || MinecraftReflection.getPacketClass().isAssignableFrom(msg.getClass());
}
};
// Intercept recieved packets
@@ -291,18 +321,18 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
@Override
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(callable, packetAccessor);
Pair<Callable<T>, PacketEvent> handled = handleScheduled(callable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
if (handled.getSecond() != null && handled.getSecond().isCancelled())
return null;
return () -> {
T result;
// This field must only be updated in the pipeline thread
currentEvent = event;
result = callable.call();
currentEvent = handled.getSecond();
result = handled.getFirst().call();
currentEvent = null;
return result;
};
@@ -310,33 +340,33 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
@Override
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
final PacketEvent event = handleScheduled(runnable, packetAccessor);
Pair<Runnable, PacketEvent> handled = handleScheduled(runnable, packetAccessor);
// Handle cancelled events
if (event != null && event.isCancelled())
if (handled.getSecond() != null && handled.getSecond().isCancelled())
return null;
return () -> {
currentEvent = event;
runnable.run();
currentEvent = handled.getSecond();
handled.getFirst().run();
currentEvent = null;
};
}
PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
<T> Pair<T, PacketEvent> handleScheduled(T instance, FieldAccessor accessor) {
// Let the filters handle this packet
Object original = accessor.get(instance);
// See if we've been instructed not to process packets
if (!scheduleProcessPackets.get()) {
if (unfilteredProcessedPackets.contains(original)) {
NetworkMarker marker = getMarker(original);
if (marker != null) {
PacketEvent result = new PacketEvent(ChannelInjector.class);
result.setNetworkMarker(marker);
return result;
return new Pair<>(instance, result);
} else {
return BYPASSED_PACKET;
return new Pair<>(instance, BYPASSED_PACKET);
}
}
@@ -346,11 +376,12 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Change packet to be scheduled
if (original != changed) {
accessor.set(instance, changed);
instance = (T) (isHiddenClass(instance.getClass()) ?
updatePacketMessageReconstruct(instance, changed, accessor) :
updatePacketMessageSetReflection(instance, changed, accessor));
}
}
return event != null ? event : BYPASSED_PACKET;
return new Pair<>(instance, event != null ? event : BYPASSED_PACKET);
}
});
@@ -359,6 +390,30 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
}
}
/**
* Changes the packet in a packet message using a {@link FieldAccessor}.
*/
private static Object updatePacketMessageSetReflection(Object instance, Object newPacket, FieldAccessor accessor) {
accessor.set(instance, newPacket);
return instance;
}
/**
* Changes the packet in a packet message using a {@link ObjectReconstructor}.
*/
private static Object updatePacketMessageReconstruct(Object instance, Object newPacket, FieldAccessor accessor) {
final ObjectReconstructor<?> objectReconstructor =
RECONSTRUCTORS.computeIfAbsent(instance.getClass(), ObjectReconstructor::new);
final Object[] values = objectReconstructor.getValues(instance);
final Field[] fields = objectReconstructor.getFields();
for (int idx = 0; idx < fields.length; ++idx)
if (fields[idx].equals(accessor.getField()))
values[idx] = newPacket;
return objectReconstructor.reconstruct(values);
}
/**
* Determine if the given object is a compressor or decompressor.
* @param handler - object to test.
@@ -416,7 +471,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
try {
// Skip every kind of non-filtered packet
if (!scheduleProcessPackets.get()) {
if (unfilteredProcessedPackets.remove(packet)) {
return;
}
@@ -447,7 +502,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Process output handler
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer();
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
encodeBuffer.invoke(vanillaEncoder, ctx, packet, packetBuffer);
// Let each handler prepare the actual output
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
@@ -470,7 +525,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Attempt to handle the packet nevertheless
if (packet != null) {
try {
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
encodeBuffer.invoke(vanillaEncoder, ctx, packet, output);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof Exception) {
//noinspection ThrowFromFinallyBlock
@@ -510,7 +565,12 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
try {
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
try {
decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets);
} catch (IllegalArgumentException ex) {
updateBufferMethods();
decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets);
}
// Reset queue
finishQueue.clear();
@@ -646,7 +706,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
*/
private void disconnect(String message) {
// If we're logging in, we can only close the channel
if (playerConnection == null || player instanceof Factory) {
if (playerConnection == null || player instanceof ByteBuddyGenerated) {
originalChannel.disconnect();
} else {
// Call the disconnect method
@@ -663,12 +723,11 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
saveMarker(packet, marker);
try {
scheduleProcessPackets.set(filtered);
invokeSendPacket(packet);
} finally {
scheduleProcessPackets.set(true);
if (!filtered) {
unfilteredProcessedPackets.add(packet);
}
invokeSendPacket(packet);
}
/**
@@ -680,7 +739,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
try {
if (player instanceof Factory) {
if (player instanceof ByteBuddyGenerated) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet);
} else {
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);
@@ -844,6 +903,8 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
}
}
/**
* Execute a specific command in the channel thread.
* <p>
@@ -889,7 +950,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
@Override
public Socket getSocket() {
return SocketAdapter.adapt((SocketChannel) injector.originalChannel);
return SocketAdapter.adapt(injector.originalChannel);
}
@Override
@@ -927,6 +988,11 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
injector.setPlayer(updatedPlayer);
}
@Override
public boolean isConnected() {
return injector.originalChannel.isActive();
}
ChannelInjector getChannelInjector() {
return injector;
}
@@ -935,4 +1001,15 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
public Channel getChannel() {
return originalChannel;
}
private static boolean isHiddenClass(Class<?> clz) {
if (IS_HIDDEN_CLASS == null) {
return false;
}
try {
return (Boolean) IS_HIDDEN_CLASS.invoke(clz);
} catch (Exception e) {
throw new RuntimeException("Failed to determine whether class '" + clz.getName() + "' is hidden or not", e);
}
}
}
@@ -1,222 +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.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Maps;
/**
* @author dmulloy2
*/
public class NettyProtocolRegistry extends ProtocolRegistry {
public NettyProtocolRegistry() {
super();
}
@Override
protected synchronized void initialize() {
if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BEE_UPDATE)) {
initializeNew();
return;
}
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null) {
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
}
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occasionally check have changed
for (Object map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object protocol : protocols) {
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
@SuppressWarnings("unchecked")
private synchronized void initializeNew() {
Object[] protocols = enumProtocol.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Class<?>, Integer>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Class<?>, Integer>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
Field mainMapField = null;
Field packetMapField = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (mainMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true);
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
mainMapField.setAccessible(true);
}
Map<Object, Object> directionMap;
try {
directionMap = (Map<Object, Object>) mainMapField.get(protocol);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
for (Entry<Object, Object> entry : directionMap.entrySet()) {
Object holder = entry.getValue();
if (packetMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(holder.getClass(), true);
packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
packetMapField.setAccessible(true);
}
Map<Class<?>, Integer> packetMap;
try {
packetMap = (Map<Class<?>, Integer>) packetMapField.get(holder);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, packetMap);
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, packetMap);
}
}
}
// Maps we have to occasionally check have changed
// TODO: Find equivalent in Object2IntMap
/* for (Object map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
} */
for (Object protocol : protocols) {
Enum<?> enumProtocol = (Enum<?>) protocol;
Protocol equivalent = Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol)) {
associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER);
}
if (clientMaps.containsKey(protocol)) {
associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT);
}
}
// Exchange (thread safe, as we have only one writer)
this.register = result;
}
/**
* Reverses a key->value map to value->key
* Non-deterministic behavior when multiple keys are mapped to the same value
*/
private <K, V> Map<V, K> reverse(Map<K, V> map) {
Map<V, K> newMap = new HashMap<>(map.size());
for (Entry<K, V> entry : map.entrySet()) {
newMap.put(entry.getValue(), entry.getKey());
}
return newMap;
}
@Override
protected void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
// System.out.println(Arrays.toString(type.getClassNames()) + " -> " + entry.getValue());
try {
register.typeToClass.put(type, entry.getValue());
if (sender == Sender.SERVER)
register.serverPackets.add(type);
if (sender == Sender.CLIENT)
register.clientPackets.add(type);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
}
}
}
@@ -0,0 +1,83 @@
/**
* 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;
}
}
}
@@ -22,12 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.*;
import io.netty.util.concurrent.EventExecutorGroup;
/**
@@ -44,10 +44,9 @@ 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.injector.spigot.AbstractPacketInjector;
import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
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;
@@ -148,10 +147,15 @@ public class ProtocolInjector implements ChannelListener {
protected void initChannel(final Channel channel) throws Exception {
try {
synchronized (networkManagers) {
// For some reason it needs to be delayed on 1.12, but the delay breaks 1.11 and below
// 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
if (MinecraftVersion.getCurrentVersion().getMinor() >= 12) {
channel.eventLoop().submit(() ->
// 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();
@@ -182,6 +186,12 @@ public class ProtocolInjector implements ChannelListener {
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);
@@ -297,9 +307,7 @@ public class ProtocolInjector implements ChannelListener {
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(clazz), packet);
return packetQueued(container, injector.getPlayer(), marker);
} catch (LinkageError e) {
// So far this has been seen when the jar is shared
System.err.println("[ProtocolLib] Encountered a LinkageError. Make sure you\'re not using this jar for multiple server instances!");
System.err.println("[ProtocolLib] If you\'re getting this error for other reasons, please report it!");
System.err.println("[ProtocolLib] Encountered a LinkageError (likely a misbehaving wrapper), please report this!");
e.printStackTrace();
}
}
@@ -1,125 +0,0 @@
package com.comphenix.protocol.injector.netty;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.injector.packet.MapContainer;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Represents a way of accessing the new netty Protocol enum.
* @author Kristian
*/
public abstract class ProtocolRegistry {
/**
* Represents a register we are currently building.
* @author Kristian
*/
protected static class Register {
// The main lookup table
public BiMap<PacketType, Class<?>> typeToClass = HashBiMap.create();
public volatile Set<PacketType> serverPackets = Sets.newHashSet();
public volatile Set<PacketType> clientPackets = Sets.newHashSet();
public List<MapContainer> containers = Lists.newArrayList();
public Register() {
}
/**
* Determine if the current register is outdated.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isOutdated() {
for (MapContainer container : containers) {
if (container.hasChanged()) {
return true;
}
}
return false;
}
}
protected Class<?> enumProtocol;
// Current register
protected volatile Register register;
public ProtocolRegistry() {
enumProtocol = MinecraftReflection.getEnumProtocolClass();
initialize();
}
/**
* Retrieve an immutable view of the packet type lookup.
* @return The packet type lookup.
*/
public Map<PacketType, Class<?>> getPacketTypeLookup() {
return Collections.unmodifiableMap(register.typeToClass);
}
/**
* Retrieve an immutable view of the class to packet type lookup.
* @return The packet type lookup.
*/
public Map<Class<?>, PacketType> getPacketClassLookup() {
return Collections.unmodifiableMap(register.typeToClass.inverse());
}
/**
* Retrieve every known client packet, from every protocol.
* @return Every client packet.
*/
public Set<PacketType> getClientPackets() {
return Collections.unmodifiableSet(register.clientPackets);
}
/**
* Retrieve every known server packet, from every protocol.
* @return Every server packet.
*/
public Set<PacketType> getServerPackets() {
return Collections.unmodifiableSet(register.serverPackets);
}
/**
* Ensure that our local register is up-to-date with Minecraft.
* <p>
* This operation may block the calling thread.
*/
public synchronized void synchronize() {
// Check if the packet registry has changed
if (register.isOutdated()) {
initialize();
}
}
/**
* Load the packet lookup tables in each protocol.
*/
protected abstract void initialize();
protected abstract void associatePackets(Register register, Map<Integer, Class<?>> lookup, Protocol protocol, Sender sender);
/**
* Retrieve the number of mapping in all the maps.
* @param maps - iterable of maps.
* @return The sum of all the entries.
*/
protected final int sum(Iterable<? extends Map<Integer, Class<?>>> maps) {
int count = 0;
for (Map<Integer, Class<?>> map : maps)
count += map.size();
return count;
}
}
@@ -43,8 +43,11 @@ public class SocketAdapter extends Socket {
this.ch = ch;
}
public static SocketAdapter adapt(SocketChannel ch) {
return new SocketAdapter(ch);
public static SocketAdapter adapt(Channel ch) {
if (!(ch instanceof SocketChannel)) {
return null;
}
return new SocketAdapter((SocketChannel) ch);
}
@Override
@@ -1,64 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Represents an input stream that stores every read block of bytes in another output stream.
*
* @author Kristian
*/
class CaptureInputStream extends FilterInputStream {
protected OutputStream out;
public CaptureInputStream(InputStream in, OutputStream out) {
super(in);
this.out = out;
}
@Override
public int read() throws IOException {
int value = super.read();
// Write the byte
if (value >= 0)
out.write(value);
return value;
}
@Override
public void close() throws IOException {
super.close();
out.close();
}
@Override
public int read(byte[] b) throws IOException {
int count = super.read(b);
if (count > 0 ) {
out.write(b, 0, count);
}
return count;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int count = super.read(b, off, len);
if (count > 0 ) {
out.write(b, off, count);
}
return count;
}
/**
* Retrieve the output stream that receives all the read information.
* @return The output stream everything is copied to.
*/
public OutputStream getOutputStream() {
return out;
}
}
@@ -1,154 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.DataOutput;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
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.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Retrieve a packet instance that has its write method intercepted.
* @author Kristian
*/
public class InterceptWritePacket {
public static final ReportType REPORT_CANNOT_FIND_WRITE_PACKET_METHOD = new ReportType("Cannot find write packet method in %s.");
public static final ReportType REPORT_CANNOT_CONSTRUCT_WRITE_PROXY = new ReportType("Cannot construct write proxy packet %s.");
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract WRITE_PACKET = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterDerivedOf(DataOutput.class).
parameterCount(1).
build();
private CallbackFilter filter;
private boolean writePacketIntercepted;
private ConcurrentMap<Integer, Class<?>> proxyClasses = Maps.newConcurrentMap();
private ErrorReporter reporter;
private WritePacketModifier modifierWrite;
private WritePacketModifier modifierRest;
public InterceptWritePacket(ErrorReporter reporter) {
this.reporter = reporter;
// Initialize modifiers
this.modifierWrite = new WritePacketModifier(reporter, true);
this.modifierRest = new WritePacketModifier(reporter, false);
}
// TODO: PacketId should probably do something...
private Class<?> createProxyClass() {
// Construct the proxy object
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
// Attempt to share callback filter
if (filter == null) {
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (WRITE_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
writePacketIntercepted = true;
return 0;
} else {
return 1;
}
}
};
}
// Subclass the generic packet class
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setCallbackFilter(filter);
ex.setUseCache(false);
ex.setCallbackTypes( new Class[] { WritePacketModifier.class, WritePacketModifier.class });
Class<?> proxyClass = ex.createClass();
// Register write modifiers too
Enhancer.registerStaticCallbacks(proxyClass, new Callback[] { modifierWrite, modifierRest });
if (proxyClass != null) {
// Check that we found the read method
if (!writePacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_WRITE_PACKET_METHOD).
messageParam(MinecraftReflection.getPacketClass()));
}
}
return proxyClass;
}
@SuppressWarnings("deprecation")
private Class<?> getProxyClass(int packetId) {
Class<?> stored = proxyClasses.get(packetId);
// Concurrent pattern
if (stored == null) {
final Class<?> created = createProxyClass();
stored = proxyClasses.putIfAbsent(packetId, created);
// We won!
if (stored == null) {
stored = created;
PacketRegistry.getPacketToID().put(stored, packetId);
}
}
return stored;
}
/**
* Construct a new instance of the proxy object.
* @param proxyObject - Object to construct proxy of
* @param event - Packet event
* @param marker - Network marker
* @return New instance of the proxy, or null if we failed.
*/
@SuppressWarnings("deprecation")
public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) {
Class<?> proxyClass = null;
try {
proxyClass = getProxyClass(event.getPacketID());
Object generated = proxyClass.newInstance();
modifierWrite.register(generated, proxyObject, event, marker);
modifierRest.register(generated, proxyObject, event, marker);
return generated;
} catch (Exception e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_CONSTRUCT_WRITE_PROXY).
messageParam(proxyClass));
return null;
}
}
/**
* Invoked when the write packet proxy class should be removed.
*/
@SuppressWarnings("deprecation")
public void cleanup() {
// Remove all proxy classes from the registry
for (Class<?> stored : proxyClasses.values()) {
PacketRegistry.getPacketToID().remove(stored);
}
}
}
@@ -1,66 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
public class InverseMaps {
private InverseMaps() {
// Not constructable
}
public static <K, V> Multimap<K, V> inverseMultimap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMultimap<K, V>() {
// The cached multimap
private Multimap<K, V> inverseMultimap;
@Override
protected Multimap<K, V> delegate() {
if (container.hasChanged()) {
inverseMultimap = HashMultimap.create();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMultimap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMultimap;
}
};
}
public static <K, V> Map<K, V> inverseMap(final Map<V, K> map, final Predicate<Map.Entry<V, K>> filter) {
final MapContainer container = new MapContainer(map);
return new ForwardingMap<K, V>() {
// The cached map
private Map<K, V> inverseMap;
@Override
protected Map<K, V> delegate() {
if (container.hasChanged()) {
inverseMap = Maps.newHashMap();
// Construct the inverse map
for (Map.Entry<V, K> entry : map.entrySet()) {
if (filter.apply(entry)) {
inverseMap.put(entry.getValue(), entry.getKey());
}
}
container.setChanged(false);
}
return inverseMap;
}
};
}
}
@@ -1,65 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
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;
import com.google.common.io.ByteSource;
import com.google.common.primitives.Bytes;
/**
* Represents a network marker for 1.6.4 and earlier.
* @author Kristian
*/
public class LegacyNetworkMarker extends NetworkMarker {
public LegacyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
public LegacyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// This has already been done
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
return ByteBuffer.wrap(Bytes.concat(new byte[] { (byte) type.getLegacyId() }, buffer.array()));
}
@Override
protected DataInputStream addHeader(final DataInputStream input, final PacketType type) {
ByteSource header = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
byte[] data = new byte[] { (byte) type.getLegacyId() };
return new ByteArrayInputStream(data);
}
};
ByteSource data = new ByteSource() {
@Override
public InputStream openStream() throws IOException {
return input;
}
};
// Combine them into a single stream
try {
return new DataInputStream(ByteSource.concat(header, data).openStream());
} catch (IOException e) {
throw new RuntimeException("Cannot add header.", e);
}
}
}
@@ -1,334 +0,0 @@
package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import net.sf.cglib.proxy.Factory;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.TroveWrapper;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
@SuppressWarnings("rawtypes")
class LegacyPacketRegistry {
private static final int MIN_SERVER_PACKETS = 5;
private static final int MIN_CLIENT_PACKETS = 5;
// Fuzzy reflection
private FuzzyReflection packetRegistry;
// The packet class to packet ID translator
private Map<Class, Integer> packetToID;
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
private Multimap<Integer, Class> customIdToPacket;
private Map<Integer, Class> vanillaIdToPacket;
// Whether or not certain packets are sent by the client or the server
private ImmutableSet<Integer> serverPackets;
private ImmutableSet<Integer> clientPackets;
// The underlying sets
private Set<Integer> serverPacketsRef;
private Set<Integer> clientPacketsRef;
// New proxy values
private Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
/**
* Initialize the registry.
*/
@SuppressWarnings({ "unchecked" })
public void initialize() {
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
} catch (IllegalArgumentException e) {
// Spigot 1.2.5 MCPC workaround
try {
packetToID = getSpigotWrapper();
} catch (Exception e2) {
// Very bad indeed
throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
}
// Create the inverse maps
customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return !MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
// And the vanilla pack - here we assume a unique ID to class mapping
vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
}
initializeSets();
}
@SuppressWarnings("unchecked")
private void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
// Impossible
if (serverPacketsRef == null || clientPacketsRef == null)
throw new FieldAccessException("Packet sets are in an illegal state.");
// NEVER allow callers to modify the underlying sets
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
// Check sizes
if (serverPackets.size() < MIN_SERVER_PACKETS)
throw new InsufficientPacketsException("Insufficient server packets.", false, serverPackets.size());
if (clientPackets.size() < MIN_CLIENT_PACKETS)
throw new InsufficientPacketsException("Insufficient client packets.", true, clientPackets.size());
} else {
throw new FieldAccessException("Cannot retrieve packet client/server sets.");
}
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access field.", e);
}
} else {
// Copy over again if it has changed
if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size())
serverPackets = ImmutableSet.copyOf(serverPacketsRef);
if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size())
clientPackets = ImmutableSet.copyOf(clientPacketsRef);
}
}
/**
* Retrieve the packet mapping.
* @return The packet map.
*/
public Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
initialize();
}
return packetToID;
}
private Map<Class, Integer> getSpigotWrapper() throws IllegalAccessException {
// If it talks like a duck, etc.
// Perhaps it would be nice to have a proper duck typing library as well
FuzzyClassContract mapLike = FuzzyClassContract.newBuilder().
method(FuzzyMethodContract.newBuilder().
nameExact("size").returnTypeExact(int.class)).
method(FuzzyMethodContract.newBuilder().
nameExact("put").parameterCount(2)).
method(FuzzyMethodContract.newBuilder().
nameExact("get").parameterCount(1)).
build();
Field packetsField = getPacketRegistry().getField(
FuzzyFieldContract.newBuilder().typeMatches(mapLike).build());
Object troveMap = FieldUtils.readStaticField(packetsField, true);
// Fix incorrect no entry values
TroveWrapper.transformNoEntryValue(troveMap, new Function<Integer, Integer>() {
public Integer apply(Integer value) {
if (value >= 0 && value < 256) {
// Someone forgot to set the no entry value. Let's help them.
return -1;
}
return value;
}
});
// We'll assume this a Trove map
return TroveWrapper.getDecoratedMap(troveMap);
}
/**
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
* @return Reflected packet registry.
*/
private FuzzyReflection getPacketRegistry() {
if (packetRegistry == null)
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
return packetRegistry;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* @return Injected classes.
*/
public Map<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public Map<Integer, Class> getPreviousPackets() {
return previousValues;
}
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
public Set<Integer> getServerPackets() throws FieldAccessException {
initializeSets();
// Sanity check. This is impossible!
if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS)
throw new FieldAccessException("Server packet list is empty. Seems to be unsupported");
return serverPackets;
}
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
public Set<Integer> getClientPackets() throws FieldAccessException {
initializeSets();
// As above
if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS)
throw new FieldAccessException("Client packet list is empty. Seems to be unsupported");
return clientPackets;
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @return The associated class.
*/
public Class getPacketClassFromID(int packetID) {
return getPacketClassFromID(packetID, false);
}
/**
* Retrieves the correct packet class from a given packet ID.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
*/
public Class getPacketClassFromID(int packetID, boolean forceVanilla) {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
Class<?> result = null;
// Optimized lookup
if (lookup.containsKey(packetID)) {
return removeEnhancer(lookup.get(packetID), forceVanilla);
}
// Refresh lookup tables
getPacketToID();
// See if we can look for non-vanilla classes
if (!forceVanilla) {
result = Iterables.getFirst(customIdToPacket.get(packetID), null);
}
if (result == null) {
result = vanillaIdToPacket.get(packetID);
}
// See if we got it
if (result != null)
return result;
else
throw new IllegalArgumentException("The packet ID " + packetID + " is not registered.");
}
/**
* Retrieve the packet ID of a given packet.
* @param packet - the type of packet to check.
* @return The ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
public int getPacketID(Class<?> packet) {
if (packet == null)
throw new IllegalArgumentException("Packet type class cannot be NULL.");
if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet))
throw new IllegalArgumentException("Type must be a packet.");
// The registry contains both the overridden and original packets
return getPacketToID().get(packet);
}
/**
* Find the first superclass that is not a CBLib proxy object.
* @param clazz - the class whose hierachy we're going to search through.
* @param remove - whether or not to skip enhanced (proxy) classes.
* @return If remove is TRUE, the first superclass that is not a proxy.
*/
private static Class removeEnhancer(Class clazz, boolean remove) {
if (remove) {
// Get the underlying vanilla class
while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) {
clazz = clazz.getSuperclass();
}
}
return clazz;
}
/**
* Occurs when we were unable to retrieve all the packets in the registry.
* @author Kristian
*/
public static class InsufficientPacketsException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final boolean client;
private final int packetCount;
private InsufficientPacketsException(String message, boolean client, int packetCount) {
super(message);
this.client = client;
this.packetCount = packetCount;
}
public boolean isClient() {
return client;
}
public int getPacketCount() {
return packetCount;
}
}
}
@@ -1,96 +0,0 @@
package com.comphenix.protocol.injector.packet;
import javax.annotation.Nonnull;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.base.Preconditions;
/**
* A builder responsible for creating incoming packet injectors.
*
* @author Kristian
*/
public class PacketInjectorBuilder {
protected PacketInjectorBuilder() {
// No need to construct this
}
/**
* Retrieve a new packet injector builder.
* @return Injector builder.
*/
public static PacketInjectorBuilder newBuilder() {
return new PacketInjectorBuilder();
}
protected ListenerInvoker invoker;
protected ErrorReporter reporter;
protected PlayerInjectionHandler playerInjection;
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PacketInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the player injection.
* @param playerInjection - the injection.
* @return This builder, for chaining.
*/
@Nonnull
public PacketInjectorBuilder playerInjection(@Nonnull PlayerInjectionHandler playerInjection) {
Preconditions.checkNotNull(playerInjection, "playerInjection cannot be NULL");
this.playerInjection = playerInjection;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (playerInjection == null)
throw new IllegalStateException("Player injection parameter must be initialized.");
}
/**
* Create a packet injector using the provided fields or the default values.
* <p>
* Note that any non-null builder parameters must be set.
* @return The created injector.
* @throws FieldAccessException If anything goes wrong in terms of reflection.
*/
public PacketInjector buildInjector() throws FieldAccessException {
initializeDefaults();
return new ProxyPacketInjector(invoker, playerInjection, reporter);
}
}
@@ -17,20 +17,20 @@
package com.comphenix.protocol.injector.packet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.netty.NettyProtocolRegistry;
import com.comphenix.protocol.injector.netty.ProtocolRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Function;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -38,37 +38,254 @@ import com.google.common.collect.Sets;
* Static packet registry in Minecraft.
* @author Kristian
*/
@SuppressWarnings("rawtypes")
public class PacketRegistry {
public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value.");
public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s");
public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s");
// The Netty packet registry
private static volatile ProtocolRegistry NETTY;
// Cached for Netty
private static volatile Set<Integer> LEGACY_SERVER_PACKETS;
private static volatile Set<Integer> LEGACY_CLIENT_PACKETS;
private static volatile Map<Integer, Class> LEGACY_PREVIOUS_PACKETS;
// Whether or not the registry has been initialized
private static volatile boolean INITIALIZED = false;
/**
* Represents a register we are currently building.
* @author Kristian
*/
protected static class Register {
// The main lookup table
private final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
private final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
private volatile Set<PacketType> serverPackets = Sets.newHashSet();
private volatile Set<PacketType> clientPackets = Sets.newHashSet();
private final List<MapContainer> containers = Lists.newArrayList();
public Register() {}
public void registerPacket(PacketType type, Class<?> clazz, Sender sender) {
typeToClass.put(type, Optional.of(clazz));
classToType.put(clazz, type);
if (sender == Sender.CLIENT) {
clientPackets.add(type);
} else {
serverPackets.add(type);
}
}
public void addContainer(MapContainer container) {
containers.add(container);
}
/**
* Determine if the current register is outdated.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isOutdated() {
for (MapContainer container : containers) {
if (container.hasChanged()) {
return true;
}
}
return false;
}
}
protected static final Class<?> ENUM_PROTOCOL = MinecraftReflection.getEnumProtocolClass();
// Current register
protected static volatile Register REGISTER;
/**
* Ensure that our local register is up-to-date with Minecraft.
* <p>
* This operation may block the calling thread.
*/
public static synchronized void synchronize() {
// Check if the packet registry has changed
if (REGISTER.isOutdated()) {
initialize();
}
}
protected static synchronized Register createOldRegister() {
Object[] protocols = ENUM_PROTOCOL.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Integer, Class<?>>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Integer, Class<?>>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
StructureModifier<Object> modifier = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (modifier == null) {
modifier = new StructureModifier<>(protocol.getClass().getSuperclass(), false);
}
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
for (Map.Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, entry.getValue());
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, entry.getValue());
}
}
}
// Maps we have to occasionally check have changed
for (Object map : serverMaps.values()) {
result.addContainer(new MapContainer(map));
}
for (Object map : clientMaps.values()) {
result.addContainer(new MapContainer(map));
}
for (Object protocol : protocols) {
Enum<?> enumProtocol = (Enum<?>) protocol;
PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol))
associatePackets(result, serverMaps.get(protocol), equivalent, Sender.SERVER);
if (clientMaps.containsKey(protocol))
associatePackets(result, clientMaps.get(protocol), equivalent, Sender.CLIENT);
}
return result;
}
@SuppressWarnings("unchecked")
private static synchronized Register createNewRegister() {
Object[] protocols = ENUM_PROTOCOL.getEnumConstants();
// ID to Packet class maps
Map<Object, Map<Class<?>, Integer>> serverMaps = Maps.newLinkedHashMap();
Map<Object, Map<Class<?>, Integer>> clientMaps = Maps.newLinkedHashMap();
Register result = new Register();
Field mainMapField = null;
Field packetMapField = null;
// Iterate through the protocols
for (Object protocol : protocols) {
if (mainMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true);
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
mainMapField.setAccessible(true);
}
Map<Object, Object> directionMap;
try {
directionMap = (Map<Object, Object>) mainMapField.get(protocol);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
for (Map.Entry<Object, Object> entry : directionMap.entrySet()) {
Object holder = entry.getValue();
if (packetMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(holder.getClass(), true);
packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
packetMapField.setAccessible(true);
}
Map<Class<?>, Integer> packetMap;
try {
packetMap = (Map<Class<?>, Integer>) packetMapField.get(holder);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
String direction = entry.getKey().toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, packetMap);
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, packetMap);
}
}
}
// Maps we have to occasionally check have changed
// TODO: Find equivalent in Object2IntMap
/* for (Object map : serverMaps.values()) {
result.containers.add(new MapContainer(map));
}
for (Object map : clientMaps.values()) {
result.containers.add(new MapContainer(map));
} */
for (Object protocol : protocols) {
Enum<?> enumProtocol = (Enum<?>) protocol;
PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol)) {
associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER);
}
if (clientMaps.containsKey(protocol)) {
associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT);
}
}
return result;
}
/**
* Reverses a key->value map to value->key
* Non-deterministic behavior when multiple keys are mapped to the same value
*/
private static <K, V> Map<V, K> reverse(Map<K, V> map) {
Map<V, K> newMap = new HashMap<>(map.size());
for (Map.Entry<K, V> entry : map.entrySet()) {
newMap.put(entry.getValue(), entry.getKey());
}
return newMap;
}
protected static void associatePackets(Register register, Map<Integer, Class<?>> lookup, PacketType.Protocol protocol, Sender sender) {
for (Map.Entry<Integer, Class<?>> entry : lookup.entrySet()) {
PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), entry.getValue());
try {
register.registerPacket(type, entry.getValue(), sender);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
}
}
private static void associate(PacketType type, Class<?> clazz) {
if (clazz != null) {
REGISTER.typeToClass.put(type, Optional.of(clazz));
REGISTER.classToType.put(clazz, type);
} else {
REGISTER.typeToClass.put(type, Optional.empty());
}
}
/**
* Initializes the packet registry.
*/
private static void initialize() {
if (INITIALIZED) {
if (NETTY == null) {
throw new IllegalStateException("Failed to initialize packet registry.");
}
return;
}
if (!INITIALIZED) {
INITIALIZED = true;
NETTY = new NettyProtocolRegistry();
INITIALIZED = true;
if (MinecraftVersion.BEE_UPDATE.atOrAbove()) {
REGISTER = createNewRegister();
} else {
REGISTER = createOldRegister();
}
}
}
/**
@@ -78,116 +295,18 @@ public class PacketRegistry {
*/
public static boolean isSupported(PacketType type) {
initialize();
return NETTY.getPacketTypeLookup().containsKey(type);
return tryGetPacketClass(type).isPresent();
}
/**
* Retrieve a map of every packet class to every ID.
* <p>
* Deprecated: Use {@link #getPacketToType()} instead.
* @return A map of packet classes and their corresponding ID.
*/
@Deprecated
public static Map<Class, Integer> getPacketToID() {
initialize();
@SuppressWarnings("unchecked")
Map<Class, Integer> result = (Map) Maps.transformValues(
NETTY.getPacketClassLookup(),
new Function<PacketType, Integer>() {
@Override
public Integer apply(PacketType type) {
return type.getLegacyId();
};
});
return result;
}
/**
* Retrieve a map of every packet class to the respective packet type.
* @return A map of packet classes and their corresponding packet type.
*/
public static Map<Class, PacketType> getPacketToType() {
initialize();
@SuppressWarnings("unchecked")
Map<Class, PacketType> result = (Map) NETTY.getPacketClassLookup();
return result;
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* <p>
* This is not supported in 1.7.2 and later.
* @return Injected classes.
*/
@Deprecated
public static Map<Integer, Class> getOverwrittenPackets() {
initialize();
throw new IllegalStateException("Not supported on Netty.");
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
@Deprecated
public static Map<Integer, Class> getPreviousPackets() {
initialize();
// Construct it first
if (LEGACY_PREVIOUS_PACKETS == null) {
Map<Integer, Class> map = Maps.newHashMap();
for (Entry<PacketType, Class<?>> entry : NETTY.getPacketTypeLookup().entrySet()) {
map.put(entry.getKey().getLegacyId(), entry.getValue());
}
LEGACY_PREVIOUS_PACKETS = Collections.unmodifiableMap(map);
}
return LEGACY_PREVIOUS_PACKETS;
}
/**
* Retrieve every known and supported server packet.
* <p>
* Deprecated: Use {@link #getServerPacketTypes()} instead.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getServerPackets() throws FieldAccessException {
if (LEGACY_SERVER_PACKETS == null) {
LEGACY_SERVER_PACKETS = toLegacy(getServerPacketTypes());
}
return LEGACY_SERVER_PACKETS;
}
/**
* Retrieve every known and supported server packet type.
* @return Every server packet type.
*/
public static Set<PacketType> getServerPacketTypes() {
initialize();
NETTY.synchronize();
synchronize();
return NETTY.getServerPackets();
}
/**
* Retrieve every known and supported client packet.
* <p>
* Deprecated: Use {@link #getClientPacketTypes()} instead.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
@Deprecated
public static Set<Integer> getClientPackets() throws FieldAccessException {
if (LEGACY_CLIENT_PACKETS == null) {
LEGACY_CLIENT_PACKETS = toLegacy(getClientPacketTypes());
}
return LEGACY_CLIENT_PACKETS;
return Collections.unmodifiableSet(REGISTER.serverPackets);
}
/**
@@ -196,125 +315,68 @@ public class PacketRegistry {
*/
public static Set<PacketType> getClientPacketTypes() {
initialize();
NETTY.synchronize();
synchronize();
return NETTY.getClientPackets();
return Collections.unmodifiableSet(REGISTER.clientPackets);
}
/**
* Convert a set of packet types to a set of integers based on the legacy packet ID.
* @param types - packet type.
* @return Set of integers.
*/
public static Set<Integer> toLegacy(Set<PacketType> types) {
Set<Integer> result = Sets.newHashSet();
for (PacketType type : types)
result.add(type.getLegacyId());
return Collections.unmodifiableSet(result);
private static Class<?> searchForPacket(List<String> classNames) {
for (String name : classNames) {
try {
Class<?> clazz = MinecraftReflection.getMinecraftClass(name);
if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers())) {
return clazz;
}
} catch (Exception ignored) {}
}
return null;
}
/**
* Convert a set of legacy packet IDs to packet types.
* @param ids - legacy packet IDs.
* @return Set of packet types.
*/
public static Set<PacketType> toPacketTypes(Set<Integer> ids) {
return toPacketTypes(ids, null);
}
/**
* Convert a set of legacy packet IDs to packet types.
* @param ids - legacy packet IDs.
* @param preference - the sender preference, if any.
* @return Set of packet types.
*/
public static Set<PacketType> toPacketTypes(Set<Integer> ids, Sender preference) {
Set<PacketType> result = Sets.newHashSet();
for (int id : ids)
result.add(PacketType.fromLegacy(id, preference));
return Collections.unmodifiableSet(result);
}
/**
* Retrieves the correct packet class from a given packet ID.
* <p>
* Deprecated: Use {@link #getPacketClassFromType(PacketType)} instead.
* @param packetID - the packet ID.
* @return The associated class.
*/
@Deprecated
public static Class getPacketClassFromID(int packetID) {
initialize();
return NETTY.getPacketTypeLookup().get(PacketType.findLegacy(packetID));
}
/**
* Retrieves the correct packet class from a given type.
* @param type - the packet type.
* @return The associated class.
*/
public static Class getPacketClassFromType(PacketType type) {
return getPacketClassFromType(type, false);
}
/**
* Retrieves the correct packet class from a given type.
* <p>
* Note that forceVanillla will be ignored on MC 1.7.2 and later.
*
* @param type - the packet type.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
* @deprecated forceVanilla no longer has any effect
*/
public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) {
@Deprecated
public static Class<?> getPacketClassFromType(PacketType type, boolean forceVanilla) {
return getPacketClassFromType(type);
}
public static Optional<Class<?>> tryGetPacketClass(PacketType type) {
initialize();
// Try the lookup first
Class<?> clazz = NETTY.getPacketTypeLookup().get(type);
if (clazz != null) {
return clazz;
// Try the lookup first (may be null, so check contains)
Optional<Class<?>> res = REGISTER.typeToClass.get(type);
if (res != null) {
return res;
}
// Then try looking up the class names
for (String name : type.getClassNames()) {
try {
clazz = MinecraftReflection.getMinecraftClass(name);
break;
} catch (Exception ex) {
}
Class<?> clazz = searchForPacket(type.getClassNames());
if (clazz != null) {
// we'd like for it to be associated correctly from the get-go; this is OK on older versions though
ProtocolLogger.warnAbove(type.getCurrentVersion(), "Updating associated class for {0} to {1}", type.name(), clazz);
}
// TODO Cache the result?
return clazz;
// cache it for next time
associate(type, clazz);
return Optional.ofNullable(clazz);
}
/**
* Retrieves the correct packet class from a given packet ID.
* <p>
* This method has been deprecated.
* @param packetID - the packet ID.
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
* @return The associated class.
* Get the packet class associated with a given type. First attempts to read from the
* type-to-class mapping, and tries
* @param type the packet type
* @return The associated class
*/
@Deprecated
public static Class getPacketClassFromID(int packetID, boolean forceVanilla) {
initialize();
return getPacketClassFromID(packetID);
}
/**
* Retrieve the packet ID of a given packet.
* <p>
* Deprecated: Use {@link #getPacketType(Class)}.
* @param packet - the type of packet to check.
* @return The legacy ID of the given packet.
* @throws IllegalArgumentException If this is not a valid packet.
*/
@Deprecated
public static int getPacketID(Class<?> packet) {
initialize();
return NETTY.getPacketClassLookup().get(packet).getLegacyId();
public static Class<?> getPacketClassFromType(PacketType type) {
return tryGetPacketClass(type)
.orElseThrow(() -> new IllegalArgumentException("Could not find packet for type " + type.name()));
}
/**
@@ -323,7 +385,8 @@ public class PacketRegistry {
* @return The packet type, or NULL if not found.
*/
public static PacketType getPacketType(Class<?> packet) {
return getPacketType(packet, null);
initialize();
return REGISTER.classToType.get(packet);
}
/**
@@ -331,9 +394,10 @@ public class PacketRegistry {
* @param packet - the class of the packet.
* @param sender - the sender of the packet, or NULL.
* @return The packet type, or NULL if not found.
* @deprecated sender no longer has any effect
*/
@Deprecated
public static PacketType getPacketType(Class<?> packet, Sender sender) {
initialize();
return NETTY.getPacketClassLookup().get(packet);
return getPacketType(packet);
}
}
@@ -1,372 +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.packet;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
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.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
/**
* This class is responsible for adding or removing proxy objects that intercepts received packets.
*
* @author Kristian
*/
class ProxyPacketInjector implements PacketInjector {
public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s.");
public static final ReportType REPORT_UNKNOWN_ORIGIN_FOR_PACKET = new ReportType("Timeout: Unknown origin %s for packet %s. Are you using GamePhase.LOGIN?");
/**
* Represents a way to update the packet ID to class lookup table.
* @author Kristian
*/
private static interface PacketClassLookup {
public void setLookup(int packetID, Class<?> clazz);
}
private static class IntHashMapLookup implements PacketClassLookup {
private WrappedIntHashMap intHashMap;
public IntHashMapLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
intHashMap.put(packetID, clazz);
}
private void initialize() throws IllegalAccessException {
if (intHashMap == null) {
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
Field intHashMapField = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true).
getFieldByType("packetIdMap", MinecraftReflection.getIntHashMapClass());
try {
intHashMap = WrappedIntHashMap.fromHandle(
FieldUtils.readField(intHashMapField, (Object) null, true));
} catch (IllegalArgumentException e) {
throw new RuntimeException("Minecraft is incompatible.", e);
}
}
}
}
private static class ArrayLookup implements PacketClassLookup {
private Class<?>[] array;
public ArrayLookup() throws IllegalAccessException {
initialize();
}
@Override
public void setLookup(int packetID, Class<?> clazz) {
array[packetID] = clazz;
}
private void initialize() throws IllegalAccessException {
FuzzyReflection reflection = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass());
// Is there a Class array with 256 elements instead?
for (Field field : reflection.getFieldListByType(Class[].class)) {
Class<?>[] test = (Class<?>[]) FieldUtils.readField(field, (Object)null);
if (test.length == 256) {
array = test;
return;
}
}
throw new IllegalArgumentException(
"Unable to find an array with the type " + Class[].class +
" in " + MinecraftReflection.getPacketClass());
}
}
/**
* Matches the readPacketData(DataInputStream) method in Packet.
*/
private static FuzzyMethodContract READ_PACKET = FuzzyMethodContract.newBuilder().
returnTypeVoid().
parameterDerivedOf(DataInput.class).
parameterCount(1).
build();
private static PacketClassLookup lookup;
// The packet filter manager
private ListenerInvoker manager;
// Error reporter
private ErrorReporter reporter;
// Allows us to determine the sender
private PlayerInjectionHandler playerInjection;
// Share callback filter
private CallbackFilter filter;
// Determine if the read packet method was found
private boolean readPacketIntercepted = false;
public ProxyPacketInjector(ListenerInvoker manager, PlayerInjectionHandler playerInjection,
ErrorReporter reporter) throws FieldAccessException {
this.manager = manager;
this.playerInjection = playerInjection;
this.reporter = reporter;
initialize();
}
@Override
public boolean isCancelled(Object packet) {
return ReadPacketModifier.isCancelled(packet);
}
@Override
public void setCancelled(Object packet, boolean cancelled) {
if (cancelled) {
ReadPacketModifier.setOverride(packet, null);
} else {
ReadPacketModifier.removeOverride(packet);
}
}
private void initialize() throws FieldAccessException {
if (lookup == null) {
try {
lookup = new IntHashMapLookup();
} catch (Exception e1) {
try {
lookup = new ArrayLookup();
} catch (Exception e2) {
// Wow
throw new FieldAccessException(e1.getMessage() + ". Workaround failed too.", e2);
}
}
// Should work fine now
}
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
// No need to do anything
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
final int packetID = type.getLegacyId();
if (hasPacketHandler(type))
return false;
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
// Unfortunately, we can't easily distinguish between these two functions:
// * Object lookup(int par1)
// * Object removeObject(int par1)
// So, we'll use the classMapToInt registry instead.
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Class old = PacketRegistry.getPacketClassFromType(type);
// If this packet is not known
if (old == null) {
throw new IllegalStateException("Packet ID " + type + " is not a valid packet type in this version.");
}
// Check for previous injections
if (Factory.class.isAssignableFrom(old)) {
throw new IllegalStateException("Packet " + type + " has already been injected.");
}
if (filter == null) {
readPacketIntercepted = false;
filter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Skip methods defined in Object
if (method.getDeclaringClass().equals(Object.class)) {
return 0;
} else if (READ_PACKET.isMatch(MethodInfo.fromMethod(method), null)) {
readPacketIntercepted = true;
return 1;
} else {
return 2;
}
}
};
}
// Subclass the specific packet class
ex.setSuperclass(old);
ex.setCallbackFilter(filter);
ex.setCallbackTypes(new Class<?>[] { NoOp.class, ReadPacketModifier.class, ReadPacketModifier.class });
Class proxy = ex.createClass();
// Create the proxy handlers
ReadPacketModifier modifierReadPacket = new ReadPacketModifier(packetID, this, reporter, true);
ReadPacketModifier modifierRest = new ReadPacketModifier(packetID, this, reporter, false);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest });
// Check that we found the read method
if (!readPacketIntercepted) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID));
}
// Override values
previous.put(packetID, old);
registry.put(proxy, packetID);
overwritten.put(packetID, proxy);
lookup.setLookup(packetID, proxy);
return true;
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public boolean removePacketHandler(PacketType type) {
final int packetID = type.getLegacyId();
if (!hasPacketHandler(type))
return false;
Map<Class, Integer> registry = PacketRegistry.getPacketToID();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Class old = previous.get(packetID);
Class proxy = PacketRegistry.getPacketClassFromType(type);
lookup.setLookup(packetID, old);
previous.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;
}
/**
* Determine if the data a packet read must be buffered.
* @param packetId - the packet to check.
* @return TRUE if it does, FALSE otherwise.
*/
@Deprecated
public boolean requireInputBuffers(int packetId) {
return manager.requireInputBuffer(packetId);
}
@SuppressWarnings("deprecation")
@Override
public boolean hasPacketHandler(PacketType type) {
return PacketRegistry.getPreviousPackets().containsKey(type.getLegacyId());
}
@SuppressWarnings("deprecation")
@Override
public Set<PacketType> getPacketHandlers() {
return PacketRegistry.toPacketTypes(PacketRegistry.getPreviousPackets().keySet(), Sender.CLIENT);
}
// Called from the ReadPacketModified monitor
public PacketEvent packetRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
if (playerInjection.canRecievePackets()) {
return playerInjection.handlePacketRecieved(packet, input, buffered);
}
try {
Player client = playerInjection.getPlayerByConnection((DataInputStream) input);
// Never invoke a event if we don't know where it's from
if (client != null) {
return packetRecieved(packet, client, buffered);
} else {
// The timeout elapsed!
reporter.reportDetailed(this, Report.newBuilder(REPORT_UNKNOWN_ORIGIN_FOR_PACKET).messageParam(input, packet.getType()));
return null;
}
} catch (InterruptedException e) {
// We will ignore this - it occurs when a player disconnects
//reporter.reportDetailed(this, "Thread was interrupted.", e, packet, input);
return null;
}
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(manager, packet, marker, client);
manager.invokePacketRecieving(event);
return event;
}
@Override
@SuppressWarnings({"rawtypes", "deprecation"})
public synchronized void cleanupAll() {
Map<Integer, Class> overwritten = PacketRegistry.getOverwrittenPackets();
Map<Integer, Class> previous = PacketRegistry.getPreviousPackets();
// Remove every packet handler
for (Integer id : previous.keySet().toArray(new Integer[0])) {
removePacketHandler(PacketType.findLegacy(id, Sender.CLIENT));
removePacketHandler(PacketType.findLegacy(id, Sender.SERVER));
}
overwritten.clear();
previous.clear();
}
}
@@ -1,186 +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.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
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.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.google.common.collect.MapMaker;
class ReadPacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet.");
// A cancel marker
private static final Object CANCEL_MARKER = new Object();
// Common for all packets of the same type
private ProxyPacketInjector packetInjector;
private int packetID;
// Report errors
private ErrorReporter reporter;
private NetworkProcessor processor;
// If this is a read packet data method
private boolean isReadPacketDataMethod;
// Whether or not a packet has been cancelled
private static Map<Object, Object> override = new MapMaker().weakKeys().makeMap();
public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) {
this.packetID = packetID;
this.packetInjector = packetInjector;
this.reporter = reporter;
this.processor = new NetworkProcessor(reporter);
this.isReadPacketDataMethod = isReadPacketDataMethod;
}
/**
* Remove any packet overrides.
* @param packet - the packet to rever
*/
public static void removeOverride(Object packet) {
override.remove(packet);
}
/**
* Retrieve the packet that overrides the methods of the given packet.
* @param packet - the given packet.
* @return Overriden object.
*/
public static Object getOverride(Object packet) {
return override.get(packet);
}
/**
* Set the packet instance to delegate to instead, or mark the packet as cancelled.
* <p>
* To undo a override, use {@link #removeOverride(Object)}.
* @param packet - the packet.
* @param override - the override method. NULL to cancel this packet.
*/
public static void setOverride(Object packet, Object overridePacket) {
override.put(packet, overridePacket != null ? overridePacket : CANCEL_MARKER);
}
/**
* Determine if the given packet has been cancelled before.
* @param packet - the packet to check.
* @return TRUE if it has been cancelled, FALSE otherwise.
*/
public static boolean isCancelled(Object packet) {
return getOverride(packet) == CANCEL_MARKER;
}
@SuppressWarnings("deprecation")
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// Atomic retrieval
Object overridenObject = override.get(thisObj);
Object returnValue = null;
// We need this in order to get the correct player
InputStream input = isReadPacketDataMethod ? (InputStream) args[0] : null;
ByteArrayOutputStream bufferStream = null;
// See if we need to buffer the read data
if (isReadPacketDataMethod && packetInjector.requireInputBuffers(packetID)) {
CaptureInputStream captured = new CaptureInputStream(
input, bufferStream = new ByteArrayOutputStream());
// Swap it with our custom stream
args[0] = new DataInputStream(captured);
}
if (overridenObject != null) {
// This packet has been cancelled
if (overridenObject == CANCEL_MARKER) {
// So, cancel all void methods
if (method.getReturnType().equals(Void.TYPE))
return null;
else // Revert to normal for everything else
overridenObject = thisObj;
}
returnValue = proxy.invokeSuper(overridenObject, args);
} else {
returnValue = proxy.invokeSuper(thisObj, args);
}
// Is this a readPacketData method?
if (isReadPacketDataMethod) {
// Swap back custom stream
args[0] = input;
try {
byte[] buffer = bufferStream != null ? bufferStream.toByteArray() : null;
// Let the people know
PacketType type = PacketType.findLegacy(packetID, Sender.CLIENT);
PacketContainer container = new PacketContainer(type, thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input, buffer);
// Handle override
if (event != null) {
Object result = event.getPacket().getHandle();
if (event.isCancelled()) {
override.put(thisObj, CANCEL_MARKER);
return returnValue;
} else if (!objectEquals(thisObj, result)) {
override.put(thisObj, result);
}
// This is fine - received packets are enqueued in any case
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
processor.invokePostEvent(event, marker);
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e)
);
}
}
return returnValue;
}
private boolean objectEquals(Object a, Object b) {
return System.identityHashCode(a) != System.identityHashCode(b);
}
}
@@ -1,130 +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.packet;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.util.Map;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
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.google.common.collect.MapMaker;
public class WritePacketModifier implements MethodInterceptor {
public static final ReportType REPORT_CANNOT_WRITE_SERVER_PACKET = new ReportType("Cannot write server packet.");
private static class ProxyInformation {
// Marker that contains custom writers
public final Object proxyObject;
public final PacketEvent event;
public final NetworkMarker marker;
public ProxyInformation(Object proxyObject, PacketEvent event, NetworkMarker marker) {
this.proxyObject = proxyObject;
this.event = event;
this.marker = marker;
}
}
private Map<Object, ProxyInformation> proxyLookup = new MapMaker().weakKeys().makeMap();
// Report errors
private final ErrorReporter reporter;
private final NetworkProcessor processor;
// Whether or not this represents the write method
private boolean isWriteMethod;
public WritePacketModifier(ErrorReporter reporter, boolean isWriteMethod) {
this.reporter = reporter;
this.processor = new NetworkProcessor(reporter);
this.isWriteMethod = isWriteMethod;
}
/**
* Associate the given generated instance of a class and the given parameteters.
* @param generatedClass - the generated class.
* @param proxyObject - the object to call from the generated class.
* @param event - the packet event.
* @param marker - the network marker.
*/
public void register(Object generatedClass, Object proxyObject, PacketEvent event, NetworkMarker marker) {
proxyLookup.put(generatedClass, new ProxyInformation(proxyObject, event, marker));
}
@Override
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ProxyInformation information = proxyLookup.get(thisObj);
if (information == null) {
// This is really bad - someone forgot to register the proxy
throw new RuntimeException("Cannot find proxy information for " + thisObj);
}
if (isWriteMethod) {
// If every output handler has been removed - ignore everything
if (!information.marker.getOutputHandlers().isEmpty()) {
try {
DataOutput output = (DataOutput) args[0];
// First - we need the initial buffer
ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream();
proxy.invoke(information.proxyObject, new Object[] { new DataOutputStream(outputBufferStream) });
// Let each handler prepare the actual output
byte[] outputBuffer = processor.processOutput(information.event, information.marker,
outputBufferStream.toByteArray());
// Write that output to the network stream
output.write(outputBuffer);
// We're done
processor.invokePostEvent(information.event, information.marker);
return null;
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft cannot handle this error
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e)
);
}
}
// Invoke this write method first
proxy.invoke(information.proxyObject, args);
processor.invokePostEvent(information.event, information.marker);
return null;
}
// Default to the super method
return proxy.invoke(information.proxyObject, args);
}
}
@@ -1,193 +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.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
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.injector.ListenerInvoker;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
/**
* The array list that notifies when packets are sent by the server.
*
* @author Kristian
*/
class InjectedArrayList extends ArrayList<Object> {
public static final ReportType REPORT_CANNOT_REVERT_CANCELLED_PACKET = new ReportType("Reverting cancelled packet failed.");
/**
* Silly Eclipse.
*/
private static final long serialVersionUID = -1173865905404280990L;
// Fake inverted proxy objects
private static ConcurrentMap<Object, Object> delegateLookup = new MapMaker().weakKeys().makeMap();
private transient PlayerInjector injector;
private transient Set<Object> ignoredPackets;
private transient InvertedIntegerCallback callback;
public InjectedArrayList(PlayerInjector injector, Set<Object> ignoredPackets) {
this.injector = injector;
this.ignoredPackets = ignoredPackets;
this.callback = new InvertedIntegerCallback();
}
@Override
public boolean add(Object packet) {
Object result = null;
// Check for fake packets and ignored packets
if (packet instanceof FakePacket) {
return true;
} else if (ignoredPackets.contains(packet)) {
// Don't send it to the filters
result = ignoredPackets.remove(packet);
} else {
result = injector.handlePacketSending(packet);
}
// A NULL packet indicate cancelling
try {
if (result != null) {
super.add(result);
} else {
// We'll use the FakePacket marker instead of preventing the filters
injector.sendServerPacket(createNegativePacket(packet), null, true);
}
// Collection.add contract
return true;
} catch (InvocationTargetException e) {
// Prefer to report this to the user, instead of risking sending it to Minecraft
ProtocolLibrary.getErrorReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_REVERT_CANCELLED_PACKET).error(e).callerParam(packet)
);
// Failure
return false;
}
}
/**
* Used by a hack that reverses the effect of a cancelled packet. Returns a packet
* whereby every int method's return value is inverted (a => -a).
*
* @param source - packet to invert.
* @return The inverted packet.
*/
@SuppressWarnings("deprecation")
Object createNegativePacket(Object source) {
ListenerInvoker invoker = injector.getInvoker();
PacketType type = invoker.getPacketType(source);
// We want to subtract the byte amount that were added to the running
// total of outstanding packets. Otherwise, cancelling too many packets
// might cause a "disconnect.overflow" error.
//
// We do that by constructing a special packet of the same type that returns
// a negative integer for all zero-parameter integer methods. This includes the
// size() method, which is used by the queue method to count the number of
// bytes to add.
//
// Essentially, we have:
//
// public class NegativePacket extends [a packet] {
// @Override
// public int size() {
// return -super.size();
// }
// ect.
// }
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(MinecraftReflection.getPacketClass());
ex.setInterfaces(new Class[] { FakePacket.class } );
ex.setUseCache(true);
ex.setCallbackType(InvertedIntegerCallback.class);
Class<?> proxyClass = ex.createClass();
Enhancer.registerCallbacks(proxyClass, new Callback[] { callback });
try {
// Temporarily associate the fake packet class
invoker.registerPacketClass(proxyClass, type.getLegacyId());
Object proxy = proxyClass.newInstance();
InjectedArrayList.registerDelegate(proxy, source);
return proxy;
} catch (Exception e) {
// Don't pollute the throws tree
throw new RuntimeException("Cannot create fake class.", e);
} finally {
// Remove this association
invoker.unregisterPacketClass(proxyClass);
}
}
/**
* Ensure that the inverted integer proxy uses the given object as source.
* @param proxy - inverted integer proxy.
* @param source - source object.
*/
private static void registerDelegate(Object proxy, Object source) {
delegateLookup.put(proxy, source);
}
/**
* Inverts the integer result of every integer method.
* @author Kristian
*/
private class InvertedIntegerCallback implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
final Object delegate = delegateLookup.get(obj);
if (delegate == null) {
throw new IllegalStateException("Unable to find delegate source for " + obj);
}
if (method.getReturnType().equals(int.class) && args.length == 0) {
Integer result = (Integer) proxy.invoke(delegate, args);
return -result;
} else {
return proxy.invoke(delegate, args);
}
}
}
}
@@ -1,442 +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.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Used to ensure that the 1.3 server is referencing the correct server handler.
*
* @author Kristian
*/
public class InjectedServerConnection {
// A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
private static Field listenerThreadField;
private static Field minecraftServerField;
private static Field listField;
private static Field dedicatedThreadField;
private static Method serverConnectionMethod;
private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists;
// The current detected server socket
public enum ServerSocketType {
SERVER_CONNECTION,
LISTENER_THREAD,
}
// Used to inject net handlers
private NetLoginInjector netLoginInjector;
// Inject server connections
private AbstractInputStreamLookup socketInjector;
// Detected by the initializer
private ServerSocketType socketType;
private Server server;
private ErrorReporter reporter;
private boolean hasAttempted;
private boolean hasSuccess;
private Object minecraftServer = null;
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
this.listFields = new ArrayList<VolatileField>();
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
this.reporter = reporter;
this.server = server;
this.socketInjector = socketInjector;
this.netLoginInjector = netLoginInjector;
}
/**
* Retrieve the current server connection.
* @param reporter - error reproter.
* @param server - the current server.
* @return The current server connection, or NULL if it hasn't been initialized yet.
* @throws FieldAccessException Reflection error.
*/
public static Object getServerConnection(ErrorReporter reporter, Server server) {
try {
// Now we are probably able to check for Netty
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
return inspector.getServerConnection();
} catch (IllegalAccessException e) {
throw new FieldAccessException("Reflection error.", e);
} catch (IllegalArgumentException e) {
throw new FieldAccessException("Corrupt data.", e);
} catch (InvocationTargetException e) {
throw new FieldAccessException("Minecraft error.", e);
}
}
/**
* Initial reflective detective work. Will be automatically called by most methods in this class.
*/
public void initialize() {
// Only execute this method once
if (!hasAttempted)
hasAttempted = true;
else
return;
if (minecraftServerField == null)
minecraftServerField = FuzzyReflection.fromObject(server, true).
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
try {
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
} catch (IllegalAccessException e1) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
return;
}
try {
if (serverConnectionMethod == null)
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {});
// We're using Minecraft 1.3.1
socketType = ServerSocketType.SERVER_CONNECTION;
} catch (IllegalArgumentException e) {
// Minecraft 1.2.5 or lower
socketType = ServerSocketType.LISTENER_THREAD;
} catch (Exception e) {
// Oh damn - inform the player
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
}
}
/**
* Retrieve the known server socket type.
* <p>
* This depends on the version of CraftBukkit we are using.
* @return The server socket type.
*/
public ServerSocketType getServerSocketType() {
return socketType;
}
/**
* Inject the connection interceptor into the correct server socket implementation.
*/
public void injectList() {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION) {
injectServerConnection();
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
injectListenerThread();
} else {
// Damn it
throw new IllegalStateException("Unable to detected server connection.");
}
}
/**
* Retrieve the listener thread field.
*/
private void initializeListenerField() {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
}
/**
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
* @return The listener thread, or NULL.
* @throws IllegalAccessException Cannot access field.
* @throws RuntimeException Unexpected class structure - the field doesn't exist.
*/
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
initialize();
if (socketType == ServerSocketType.LISTENER_THREAD) {
initializeListenerField();
return listenerThreadField.get(minecraftServer);
} else {
return null;
}
}
/**
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
* @return The socket connection, or NULL.
* @throws IllegalAccessException If the reflective operation failed.
* @throws IllegalArgumentException If the reflective operation failed.
* @throws InvocationTargetException If the reflective operation failed.
*/
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION)
return serverConnectionMethod.invoke(minecraftServer);
else
return null;
}
private void injectListenerThread() {
try {
initializeListenerField();
} catch (RuntimeException e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
);
return;
}
Object listenerThread = null;
// Attempt to get the thread
try {
listenerThread = getListenerThread();
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return;
}
// Inject the server socket too
injectServerSocket(listenerThread);
// Just inject every list field we can get
injectEveryListField(listenerThread, 1);
hasSuccess = true;
}
private void injectServerConnection() {
Object serverConnection = null;
// Careful - we might fail
try {
serverConnection = getServerConnection();
} catch (Exception e) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
);
return;
}
if (listField == null)
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
getFieldByType("netServerHandlerList", List.class);
if (dedicatedThreadField == null) {
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
getFieldListByType(Thread.class);
// Verify the field count
if (matches.size() != 1)
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
);
else
dedicatedThreadField = matches.get(0);
}
// Next, try to get the dedicated thread
try {
if (dedicatedThreadField != null) {
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
// Inject server socket and NetServerHandlers.
injectServerSocket(dedicatedThread);
injectEveryListField(dedicatedThread, 1);
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
}
injectIntoList(serverConnection, listField);
hasSuccess = true;
}
private void injectServerSocket(Object container) {
socketInjector.inject(container);
}
/**
* Automatically inject into every List-compatible public or private field of the given object.
* @param container - container object with the fields to inject.
* @param minimum - the minimum number of fields we expect exists.
*/
private void injectEveryListField(Object container, int minimum) {
// Ok, great. Get every list field
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
for (Field list : lists) {
injectIntoList(container, list);
}
// Warn about unexpected errors
if (lists.size() < minimum) {
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
}
}
@SuppressWarnings("unchecked")
private void injectIntoList(Object instance, Field field) {
VolatileField listFieldRef = new VolatileField(field, instance, true);
List<Object> list = (List<Object>) listFieldRef.getValue();
// Careful not to inject twice
if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list);
} else {
ReplacedArrayList<Object> injectedList = createReplacement(list);
replacedLists.add(injectedList);
listFieldRef.setValue(injectedList);
listFields.add(listFieldRef);
}
}
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
/**
* Shut up Eclipse!
*/
private static final long serialVersionUID = 2070481080950500367L;
// Object writer we'll use
private final ObjectWriter writer = new ObjectWriter();
@Override
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object?
if (!(inserting instanceof Factory)) {
// If so, copy the content of the old element to the new
try {
writer.copyTo(inserting, replacement, inserting.getClass());
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(InjectedServerConnection.this,
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
);
}
}
}
@Override
protected void onInserting(Object inserting) {
// Ready for some login handler injection?
if (MinecraftReflection.isLoginHandler(inserting)) {
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
// Only replace if it has changed
if (inserting != replaced)
addMapping(inserting, replaced, true);
}
}
@Override
protected void onRemoved(Object removing) {
// Clean up?
if (MinecraftReflection.isLoginHandler(removing)) {
netLoginInjector.cleanup(removing);
}
}
};
}
/**
* Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler.
*/
public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) {
injectList();
}
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.addMapping(oldHandler, newHandler);
}
}
}
/**
* Revert to the old vanilla server handler, if it has been replaced.
* @param oldHandler - old vanilla server handler.
*/
public void revertServerHandler(Object oldHandler) {
if (hasSuccess) {
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.removeMapping(oldHandler);
}
}
}
/**
* Undoes everything.
*/
public void cleanupAll() {
if (replacedLists.size() > 0) {
// Repair the underlying lists
for (ReplacedArrayList<Object> replacedList : replacedLists) {
replacedList.revertAll();
}
for (VolatileField field : listFields) {
field.revertValue();
}
listFields.clear();
replacedLists.clear();
}
}
}
@@ -1,162 +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.player;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
/**
* Injects every NetLoginHandler created by the server.
*
* @author Kristian
*/
class NetLoginInjector {
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
// Handles every hook
private ProxyPlayerInjectionHandler injectionHandler;
// Create temporary players
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
// The current error reporter
private ErrorReporter reporter;
private Server server;
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
this.reporter = reporter;
this.server = server;
this.injectionHandler = injectionHandler;
}
/**
* Invoked when a NetLoginHandler has been created.
* @param inserting - the new NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public Object onNetLoginCreated(Object inserting) {
try {
// Make sure we actually need to inject during this phase
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
return inserting;
Player temporary = playerFactory.createTemporaryPlayer(server);
// Note that we bail out if there's an existing player injector
PlayerInjector injector = injectionHandler.injectPlayer(
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
if (injector != null) {
// Update injector as well
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
injector.updateOnLogin = true;
// Save the login
injectedLogins.putIfAbsent(inserting, injector);
}
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
return inserting;
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft can't handle this, so we'll deal with it here
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(inserting, injectionHandler).
error(e)
);
return inserting;
}
}
/**
* Invoked when a NetLoginHandler should be reverted.
* @param inserting - the original NetLoginHandler.
* @return An injected NetLoginHandler, or the original object.
*/
public synchronized void cleanup(Object removing) {
PlayerInjector injected = injectedLogins.get(removing);
if (injected != null) {
try {
PlayerInjector newInjector = null;
Player player = injected.getPlayer();
// Clean up list
injectedLogins.remove(removing);
// No need to clean up twice
if (injected.isClean())
return;
// Hack to clean up other references
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
injectionHandler.uninjectPlayer(player);
// Update NetworkManager
if (newInjector != null) {
if (injected instanceof NetworkObjectInjector) {
newInjector.setNetworkManager(injected.getNetworkManager(), true);
}
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Don't leak this to Minecraft
reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
messageParam(MinecraftReflection.getNetLoginHandlerName()).
callerParam(removing).
error(e)
);
}
}
}
/**
* Remove all injected hooks.
*/
public void cleanupAll() {
for (PlayerInjector injector : injectedLogins.values()) {
injector.cleanupAll();
}
injectedLogins.clear();
}
}
@@ -1,221 +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.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Sets;
/**
* Injection hook that overrides the packet queue lists in NetworkHandler.
*
* @author Kristian
*/
class NetworkFieldInjector extends PlayerInjector {
/**
* Marker interface that indicates a packet is fake and should not be processed.
* @author Kristian
*/
public interface FakePacket {
// Nothing
}
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Packets to ignore
private Set<Object> ignoredPackets = Sets.newSetFromMap(new ConcurrentHashMap<Object, Boolean>());
// Overridden fields
private List<VolatileField> overridenLists = new ArrayList<VolatileField>();
// Sync field
private static Field syncField;
private Object syncObject;
// Determine if we're listening
private IntegerSet sendingFilters;
public NetworkFieldInjector(ErrorReporter reporter, Player player, ListenerInvoker manager, IntegerSet sendingFilters) {
super(reporter, player, manager);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public synchronized void initialize(Object injectionSource) throws IllegalAccessException {
super.initialize(injectionSource);
// Get the sync field as well
if (hasInitialized) {
if (syncField == null)
syncField = FuzzyReflection.fromObject(networkManager, true).getFieldByType("java\\.lang\\.Object");
syncObject = FieldUtils.readField(syncField, networkManager, true);
}
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
if (networkManager != null) {
try {
if (!filtered) {
ignoredPackets.add(packet);
}
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkManager, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
@SuppressWarnings("deprecation")
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}
@Override
public void injectManager() {
if (networkManager != null) {
@SuppressWarnings("rawtypes")
StructureModifier<List> list = networkModifier.withType(List.class);
// Subclass both send queues
for (Field field : list.getFields()) {
VolatileField overwriter = new VolatileField(field, networkManager, true);
@SuppressWarnings("unchecked")
List<Object> minecraftList = (List<Object>) overwriter.getOldValue();
synchronized(syncObject) {
// The list we'll be inserting
List<Object> hackedList = new InjectedArrayList(this, ignoredPackets);
// Add every previously stored packet
for (Object packet : minecraftList) {
hackedList.add(packet);
}
// Don' keep stale packets around
minecraftList.clear();
overwriter.setValue(Collections.synchronizedList(hackedList));
}
overridenLists.add(overwriter);
}
}
}
@Override
@SuppressWarnings("unchecked")
protected void cleanHook() {
// Clean up
for (VolatileField overriden : overridenLists) {
List<Object> minecraftList = (List<Object>) overriden.getOldValue();
List<Object> hacketList = (List<Object>) overriden.getValue();
if (minecraftList == hacketList) {
return;
}
// Get a lock before we modify the list
synchronized(syncObject) {
try {
// Copy over current packets
for (Object packet : (List<Object>) overriden.getValue()) {
minecraftList.add(packet);
}
} finally {
overriden.revertValue();
}
}
}
overridenLists.clear();
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// All phases should work
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
}
}
@@ -1,230 +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.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.LazyLoader;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Injection method that overrides the NetworkHandler itself, and its queue-method.
*
* @author Kristian
*/
public class NetworkObjectInjector extends PlayerInjector {
// Determine if we're listening
private IntegerSet sendingFilters;
// After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.),
// CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue.
// Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets
// will be slightly processed).
private MinecraftVersion safeVersion = new MinecraftVersion("1.4.4");
// Shared callback filter - avoid creating a new class every time
private volatile static CallbackFilter callbackFilter;
// Temporary player factory
private static volatile TemporaryPlayerFactory tempPlayerFactory;
/**
* Create a new network object injector.
* <p>
* Note: This class is intended to be internal. Do not use.
* @param reporter - the error reporter.
* @param player - the player Bukkit entity.
* @param invoker - the packet invoker.
* @param sendingFilters - list of permitted packet IDs.
* @throws IllegalAccessException If reflection failed.
*/
public NetworkObjectInjector(ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException {
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
/**
* Create a temporary player for use during login.
* @param server - Bukkit server.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server) {
if (tempPlayerFactory == null)
tempPlayerFactory = new TemporaryPlayerFactory();
// Create and associate the fake player with this network injector
return tempPlayerFactory.createTemporaryPlayer(server, this);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue();
if (networkDelegate != null) {
try {
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
queueMethod.invoke(networkDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access queue method.", e);
}
} else {
throw new IllegalStateException("Unable to load network mananager. Cannot send packet.");
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
if (version != null && version.compareTo(safeVersion) > 0) {
return null;
} else {
@SuppressWarnings("deprecation")
int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK };
// Unfortunately, we don't support chunk packets
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) {
return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported);
} else {
return null;
}
}
}
@Override
public void injectManager() {
if (networkManager != null) {
final Class<?> networkInterface = networkManagerRef.getField().getType();
final Object networkDelegate = networkManagerRef.getOldValue();
if (!networkInterface.isInterface()) {
throw new UnsupportedOperationException(
"Must use CraftBukkit 1.3.0 or later to inject into into NetworkMananger.");
}
Callback queueFilter = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Delegate to our underlying class
return proxy.invokeSuper(networkDelegate, args);
}
};
Callback dispatch = new LazyLoader() {
@Override
public Object loadObject() throws Exception {
return networkDelegate;
}
};
// Share callback filter - that way, we avoid generating a new class every time.
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(queueMethod))
return 0;
else
return 1;
}
};
}
// Create our proxy object
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(networkInterface);
ex.setCallbacks(new Callback[] { queueFilter, dispatch });
ex.setCallbackFilter(callbackFilter);
// Inject it, if we can.
networkManagerRef.setValue(ex.create());
}
}
@Override
protected void cleanHook() {
// Clean up
if (networkManagerRef != null && networkManagerRef.isCurrentSet()) {
networkManagerRef.revertValue();
}
}
@Override
public void handleDisconnect() {
// No need to do anything
}
@Override
public boolean canInject(GamePhase phase) {
// Works for all phases
return true;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_MANAGER_OBJECT;
}
}
@@ -1,367 +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.player;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.concurrency.IntegerSet;
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.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectWriter;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
/**
* Represents a player hook into the NetServerHandler class.
*
* @author Kristian
*/
class NetworkServerInjector extends PlayerInjector {
// Disconnected field
public static final ReportType REPORT_ASSUMING_DISCONNECT_FIELD = new ReportType("Unable to find 'disconnected' field. Assuming %s.");
public static final ReportType REPORT_DISCONNECT_FIELD_MISSING = new ReportType("Cannot find disconnected field. Is ProtocolLib up to date?");
public static final ReportType REPORT_DISCONNECT_FIELD_FAILURE = new ReportType("Unable to update disconnected field. Player quit event may be sent twice.");
private volatile static CallbackFilter callbackFilter;
private volatile static boolean foundSendPacket;
private volatile static Field disconnectField;
private InjectedServerConnection serverInjection;
// Determine if we're listening
private IntegerSet sendingFilters;
// Whether or not the player has disconnected
private boolean hasDisconnected;
// Used to copy fields
private final ObjectWriter writer = new ObjectWriter();
public NetworkServerInjector(
ErrorReporter reporter, Player player,
ListenerInvoker invoker, IntegerSet sendingFilters,
InjectedServerConnection serverInjection) {
super(reporter, player, invoker);
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException {
Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
if (serverDelegate != null) {
try {
if (marker != null) {
queuedMarkers.put(packet, marker);
}
// Note that invocation target exception is a wrapper for a checked exception
MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet);
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unable to access send packet method.", e);
}
} else {
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
}
}
@Override
public void injectManager() {
if (serverHandlerRef == null)
throw new IllegalStateException("Cannot find server handler.");
// Don't inject twice
if (serverHandlerRef.getValue() instanceof Factory)
return;
if (!tryInjectManager()) {
Class<?> serverHandlerClass = MinecraftReflection.getPlayerConnectionClass();
// Try to override the proxied object
if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
serverHandler = serverHandlerRef.getValue();
if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
else
serverHandlerClass = serverHandler.getClass();
// Try again
if (tryInjectManager()) {
// It worked - probably
return;
}
}
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the "
+ serverHandlerClass.getName() + " object.");
}
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
Callback sendPacketCallback = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object packet = args[0];
if (packet != null) {
packet = handlePacketSending(packet);
// A NULL packet indicate cancelling
if (packet != null)
args[0] = packet;
else
return null;
}
// Call the method directly
return proxy.invokeSuper(obj, args);
};
};
Callback noOpCallback = NoOp.INSTANCE;
// Share callback filter - that way, we avoid generating a new class for
// every logged in player.
if (callbackFilter == null) {
callbackFilter = new SendMethodFilter();
}
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(callbackFilter);
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
// Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
// Use the existing server proxy when we create one
if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else {
serverInstances = DefaultInstances.fromArray(generator);
}
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Inject it now
if (proxyObject != null) {
// Did we override a sendPacket method?
if (!foundSendPacket) {
throw new IllegalArgumentException("Unable to find a sendPacket method in " + serverClass);
}
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
} else {
return false;
}
}
private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try {
return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Oh well
}
}
return null;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (MinecraftReflection.isMinecraftClass(clazz))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
@Override
protected void cleanHook() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
writer.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
try {
if (getNetHandler() != null) {
// Restore packet listener
try {
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
} catch (IllegalAccessException e) {
// Oh well
e.printStackTrace();
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// Prevent the PlayerQuitEvent from being sent twice
if (hasDisconnected) {
setDisconnect(serverHandlerRef.getValue(), true);
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void handleDisconnect() {
hasDisconnected = true;
}
/**
* Set the disconnected field in a NetServerHandler.
* @param handler - the NetServerHandler.
* @param value - the new value.
*/
private void setDisconnect(Object handler, boolean value) {
// Set it
try {
// Load the field
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*");
}
FieldUtils.writeField(disconnectField, handler, value);
} catch (IllegalArgumentException e) {
// Assume it's the first ...
if (disconnectField == null) {
disconnectField = FuzzyReflection.fromObject(handler).getFieldByType("disconnected", boolean.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUMING_DISCONNECT_FIELD).messageParam(disconnectField));
// Try again
if (disconnectField != null) {
setDisconnect(handler, value);
return;
}
}
// This is really bad
reporter.reportDetailed(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_MISSING).error(e));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_DISCONNECT_FIELD_FAILURE).error(e));
}
}
@Override
public UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener) {
// We support everything
return null;
}
@Override
public boolean canInject(GamePhase phase) {
// Doesn't work when logging in
return phase == GamePhase.PLAYING;
}
@Override
public PlayerInjectHooks getHookType() {
return PlayerInjectHooks.NETWORK_SERVER_OBJECT;
}
/**
* Represents a CallbackFilter that only matches the SendPacket method.
* @author Kristian
*/
private static class SendMethodFilter implements CallbackFilter {
private Method sendPacket = MinecraftMethods.getSendPacketMethod();
@Override
public int accept(Method method) {
if (isCallableEqual(sendPacket, method)) {
NetworkServerInjector.foundSendPacket = true;
return 0;
} else {
return 1;
}
}
/**
* Determine if the two methods are equal in terms of call semantics.
* <p>
* Two methods are equal if they have the same name, parameter types and return type.
* @param first - first method.
* @param second - second method.
* @return TRUE if they are, FALSE otherwise.
*/
private boolean isCallableEqual(Method first, Method second) {
return first.getName().equals(second.getName()) &&
first.getReturnType().equals(second.getReturnType()) &&
Arrays.equals(first.getParameterTypes(), second.getParameterTypes());
}
}
}
@@ -1,760 +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.player;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import net.sf.cglib.proxy.Factory;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
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.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.MapMaker;
public abstract class PlayerInjector implements SocketInjector {
// Disconnect method related reports
public static final ReportType REPORT_ASSUME_DISCONNECT_METHOD = new ReportType("Cannot find disconnect method by name. Assuming %s.");
public static final ReportType REPORT_INVALID_ARGUMENT_DISCONNECT = new ReportType("Invalid argument passed to disconnect method: %s");
public static final ReportType REPORT_CANNOT_ACCESS_DISCONNECT = new ReportType("Unable to access disconnect method.");
public static final ReportType REPORT_CANNOT_CLOSE_SOCKET = new ReportType("Unable to close socket.");
public static final ReportType REPORT_ACCESS_DENIED_CLOSE_SOCKET = new ReportType("Insufficient permissions. Cannot close socket.");
public static final ReportType REPORT_DETECTED_CUSTOM_SERVER_HANDLER =
new ReportType("Detected server handler proxy type by another plugin. Conflict may occur!");
public static final ReportType REPORT_CANNOT_PROXY_SERVER_HANDLER = new ReportType("Unable to load server handler from proxy type.");
public static final ReportType REPORT_CANNOT_UPDATE_PLAYER = new ReportType("Cannot update player in PlayerEvent.");
public static final ReportType REPORT_CANNOT_HANDLE_PACKET = new ReportType("Cannot handle server packet.");
public static final ReportType REPORT_INVALID_NETWORK_MANAGER = new ReportType("NetworkManager doesn't appear to be valid.");
// Net login handler stuff
private static Field netLoginNetworkField;
// Different disconnect methods
private static Method loginDisconnect;
private static Method serverDisconnect;
// Cache previously retrieved fields
protected static Field serverHandlerField;
protected static Field proxyServerField;
protected static Field networkManagerField;
protected static Field netHandlerField;
protected static Field socketField;
protected static Field socketAddressField;
private static Field inputField;
private static Field entityPlayerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists
protected static StructureModifier<Object> networkModifier;
// And methods
protected static Method queueMethod;
protected static Method processMethod;
protected volatile Player player;
protected boolean hasInitialized;
// Reference to the player's network manager
protected VolatileField networkManagerRef;
protected VolatileField serverHandlerRef;
protected Object networkManager;
// Current net handler
protected Object loginHandler;
protected Object serverHandler;
protected Object netHandler;
// Current socket and address
protected Socket socket;
protected SocketAddress socketAddress;
// The packet manager and filters
protected ListenerInvoker invoker;
// Previous data input
protected DataInputStream cachedInput;
// Handle errors
protected ErrorReporter reporter;
// Previous markers
protected Map<Object, NetworkMarker> queuedMarkers = new MapMaker().weakKeys().makeMap();
protected InterceptWritePacket writePacketInterceptor;
// Whether or not the injector has been cleaned
private boolean clean;
// Whether or not to update the current player on the first Packet1Login
boolean updateOnLogin;
volatile Player updatedPlayer;
public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) {
this.reporter = reporter;
this.player = player;
this.invoker = invoker;
this.writePacketInterceptor = invoker.getInterceptWritePacket();
}
/**
* Retrieve the notch (NMS) entity player object.
* @param player - the player to retrieve.
* @return Notch player object.
*/
protected Object getEntityPlayer(Player player) {
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
return unwrapper.unwrapItem(player);
}
/**
* Initialize all fields for this player injector, if it hasn't already.
* @param injectionSource - Injection source
* @throws IllegalAccessException An error has occured.
*/
public void initialize(Object injectionSource) throws IllegalAccessException {
if (injectionSource == null)
throw new IllegalArgumentException("injectionSource cannot be NULL");
//Dispatch to the correct injection method
if (injectionSource instanceof Player)
initializePlayer((Player) injectionSource);
else if (MinecraftReflection.isLoginHandler(injectionSource))
initializeLogin(injectionSource);
else
throw new IllegalArgumentException("Cannot initialize a player hook using a " + injectionSource.getClass().getName());
}
/**
* Initialize the player injector using an actual player instance.
* @param player - the player to hook.
*/
public void initializePlayer(Player player) {
Object notchEntity = getEntityPlayer(player);
// Save the player too
this.player = player;
if (!hasInitialized) {
// Do this first, in case we encounter an exception
hasInitialized = true;
// Retrieve the server handler
if (serverHandlerField == null) {
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(
"NetServerHandler", MinecraftReflection.getPlayerConnectionClass());
proxyServerField = getProxyField(notchEntity, serverHandlerField);
}
// Yo dawg
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue();
// Next, get the network manager
if (networkManagerField == null)
networkManagerField = FuzzyReflection.fromObject(serverHandler).getFieldByType(
"networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(networkManagerField, serverHandler);
}
}
/**
* Initialize the player injector from a NetLoginHandler.
* @param netLoginHandler - the net login handler to inject.
*/
public void initializeLogin(Object netLoginHandler) {
if (!hasInitialized) {
// Just in case
if (!MinecraftReflection.isLoginHandler(netLoginHandler))
throw new IllegalArgumentException("netLoginHandler (" + netLoginHandler + ") is not a " +
MinecraftReflection.getNetLoginHandlerName());
hasInitialized = true;
loginHandler = netLoginHandler;
if (netLoginNetworkField == null)
netLoginNetworkField = FuzzyReflection.fromObject(netLoginHandler).
getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
initializeNetworkManager(netLoginNetworkField, netLoginHandler);
}
}
private void initializeNetworkManager(Field reference, Object container) {
networkManagerRef = new VolatileField(reference, container);
networkManager = networkManagerRef.getValue();
// No, don't do it
if (networkManager instanceof Factory) {
return;
}
// Create the network manager modifier from the actual object type
if (networkManager != null && networkModifier == null)
networkModifier = new StructureModifier<Object>(networkManager.getClass(), null, false);
// And the queue method
if (queueMethod == null)
queueMethod = FuzzyReflection.fromClass(reference.getType()).
getMethodByParameters("queue", MinecraftReflection.getPacketClass());
}
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
/**
* Retrieve the current network manager.
* @return Current network manager.
*/
public Object getNetworkManager() {
return networkManagerRef.getValue();
}
/**
* Retrieve the current server handler (PlayerConnection).
* @return Current server handler.
*/
public Object getServerHandler() {
return serverHandlerRef.getValue();
}
/**
* Set the current network manager.
* @param value - new network manager.
* @param force - whether or not to save this value.
*/
public void setNetworkManager(Object value, boolean force) {
networkManagerRef.setValue(value);
if (force)
networkManagerRef.saveValue();
initializeNetworkManager(networkManagerField, serverHandler);
}
/**
* Retrieve the associated socket of this player.
* @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
@Override
public Socket getSocket() throws IllegalAccessException {
try {
if (socketField == null)
socketField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(Socket.class).get(0);
if (socket == null)
socket = (Socket) FieldUtils.readField(socketField, networkManager, true);
return socket;
} catch (IndexOutOfBoundsException e) {
throw new IllegalAccessException("Unable to read the socket field.");
}
}
/**
* Retrieve the associated remote address of a player.
* @return The associated remote address.
* @throws IllegalAccessException If we're unable to read the socket address field.
*/
@Override
public SocketAddress getAddress() throws IllegalAccessException {
try {
if (socketAddressField == null)
socketAddressField = FuzzyReflection.fromObject(networkManager, true).
getFieldListByType(SocketAddress.class).get(0);
if (socketAddress == null)
socketAddress = (SocketAddress) FieldUtils.readField(socketAddressField, networkManager, true);
return socketAddress;
} catch (IndexOutOfBoundsException e) {
// Inform about the state of the network manager too
reporter.reportWarning(
this, Report.newBuilder(REPORT_INVALID_NETWORK_MANAGER).callerParam(networkManager).build());
throw new IllegalAccessException("Unable to read the socket address field.");
}
}
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
* @throws InvocationTargetException If disconnection failed.
*/
@Override
public void disconnect(String message) throws InvocationTargetException {
// Get a non-null handler
boolean usingNetServer = serverHandler != null;
Object handler = usingNetServer ? serverHandler : loginHandler;
Method disconnect = usingNetServer ? serverDisconnect : loginDisconnect;
// Execute disconnect on it
if (handler != null) {
if (disconnect == null) {
try {
disconnect = FuzzyReflection.fromObject(handler).getMethodByName("disconnect.*");
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
disconnect = FuzzyReflection.fromObject(handler).getMethodByParameters("disconnect", String.class);
reporter.reportWarning(this, Report.newBuilder(REPORT_ASSUME_DISCONNECT_METHOD).messageParam(disconnect));
}
// Save the method for later
if (usingNetServer)
serverDisconnect = disconnect;
else
loginDisconnect = disconnect;
}
try {
disconnect.invoke(handler, message);
return;
} catch (IllegalArgumentException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_INVALID_ARGUMENT_DISCONNECT).error(e).messageParam(message).callerParam(handler));
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_ACCESS_DISCONNECT).error(e));
}
}
// Fuck it
try {
Socket socket = getSocket();
try {
socket.close();
} catch (IOException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_CLOSE_SOCKET).error(e).callerParam(socket));
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_ACCESS_DENIED_CLOSE_SOCKET).error(e));
}
}
private Field getProxyField(Object notchEntity, Field serverField) {
try {
Object currentHandler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// This is bad
if (currentHandler == null)
throw new ServerHandlerNull();
// See if this isn't a standard net handler class
if (!isStandardMinecraftNetHandler(currentHandler)) {
// This is our proxy object
if (currentHandler instanceof Factory)
return null;
hasProxyType = true;
reporter.reportWarning(this, Report.newBuilder(REPORT_DETECTED_CUSTOM_SERVER_HANDLER).callerParam(serverField));
// No? Is it a Proxy type?
try {
FuzzyReflection reflection = FuzzyReflection.fromObject(currentHandler, true);
// It might be
return reflection.getFieldByType("NetServerHandler", MinecraftReflection.getPlayerConnectionClass());
} catch (RuntimeException e) {
// Damn
}
}
} catch (IllegalAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_PROXY_SERVER_HANDLER).error(e).callerParam(notchEntity, serverField));
}
// Nope, just go with it
return null;
}
/**
* Determine if a given object is a standard Minecraft net handler.
* @param obj the object to test.
* @return TRUE if it is, FALSE otherwise.
*/
private boolean isStandardMinecraftNetHandler(Object obj) {
if (obj == null)
return false;
Class<?> clazz = obj.getClass();
return MinecraftReflection.getNetLoginHandlerClass().equals(clazz) ||
MinecraftReflection.getPlayerConnectionClass().equals(clazz);
}
/**
* Retrieves the current net handler for this player.
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
protected Object getNetHandler() throws IllegalAccessException {
return getNetHandler(false);
}
/**
* Retrieves the current net handler for this player.
* @param refresh - Whether or not to refresh
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
* @return The current net handler for this player
*/
protected Object getNetHandler(boolean refresh) throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType("NetHandler", MinecraftReflection.getNetHandlerClass());
} catch (RuntimeException e1) {
// Swallow it
}
// Second attempt
if (netHandlerField == null) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(MinecraftReflection.getMinecraftObjectRegex());
} catch (RuntimeException e2) {
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler == null || refresh)
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
/**
* Retrieve the stored entity player from a given NetHandler.
* @param netHandler - the nethandler to retrieve it from.
* @return The stored entity player.
* @throws IllegalAccessException If the reflection failed.
*/
private Object getEntityPlayer(Object netHandler) throws IllegalAccessException {
if (entityPlayerField == null)
entityPlayerField = FuzzyReflection.fromObject(netHandler).getFieldByType(
"EntityPlayer", MinecraftReflection.getEntityPlayerClass());
return FieldUtils.readField(entityPlayerField, netHandler);
}
/**
* Processes the given packet as if it was transmitted by the current player.
* @param packet - packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Object packet) throws IllegalAccessException, InvocationTargetException {
Object netHandler = getNetHandler();
// Get the process method
if (processMethod == null) {
try {
processMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
getMethodByParameters("processPacket", netHandlerField.getType());
} catch (RuntimeException e) {
throw new IllegalArgumentException("Cannot locate process packet method: " + e.getMessage());
}
}
// We're ready
try {
processMethod.invoke(packet, netHandler);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Method " + processMethod.getName() + " is not compatible.");
} catch (InvocationTargetException e) {
throw e;
}
}
/**
* Send a packet to the client.
* @param packet - server packet to send.
* @param marker - the network marker.
* @param filtered - whether or not the packet will be filtered by our listeners.
* @throws InvocationTargetException If an error occured when sending the packet.
*/
@Override
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException;
/**
* Inject a hook to catch packets sent to the current player.
*/
public abstract void injectManager();
/**
* Remove all hooks and modifications.
*/
public final void cleanupAll() {
if (!clean) {
cleanHook();
writePacketInterceptor.cleanup();
}
clean = true;
}
/**
* Clean up after the player has disconnected.
*/
public abstract void handleDisconnect();
/**
* Override to add custom cleanup behavior.
*/
protected abstract void cleanHook();
/**
* Determine whether or not this hook has already been cleaned.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClean() {
return clean;
}
/**
* Determine if this inject method can even be attempted.
* @param state - Game phase
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
*/
public abstract boolean canInject(GamePhase state);
/**
* Retrieve the hook type this class represents.
* @return Hook type this class represents.
*/
public abstract PlayerInjectHooks getHookType();
/**
* Invoked before a new listener is registered.
* <p>
* The player injector should only return a non-null value if some or all of the packet IDs are unsupported.
* @param version - the current Minecraft version, or NULL if unknown.
* @param listener - the listener that is about to be registered.
* @return A error message with the unsupported packet IDs, or NULL if this listener is valid.
*/
public abstract UnsupportedListener checkListener(MinecraftVersion version, PacketListener listener);
/**
* Allows a packet to be sent by the listeners.
* @param packet - packet to sent.
* @return The given packet, or the packet replaced by the listeners.
*/
@SuppressWarnings({ "deprecation", "null" })
public Object handlePacketSending(Object packet) {
try {
// Get the packet ID too
Integer id = invoker.getPacketID(packet);
Player currentPlayer = player;
// Hack #1
if (updateOnLogin) {
if (updatedPlayer == null) {
try {
final Object handler = getNetHandler(true);
// Is this a net server class?
if (MinecraftReflection.getPlayerConnectionClass().isAssignableFrom(handler.getClass())) {
setUpdatedPlayer(
(Player) MinecraftReflection.getBukkitEntity(getEntityPlayer(handler))
);
}
} catch (IllegalAccessException e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLAYER).error(e).callerParam(packet));
}
}
// This will only occur in the NetLoginHandler injection
if (updatedPlayer != null) {
currentPlayer = updatedPlayer;
updateOnLogin = false;
}
}
// Make sure we're listening
if (id != null && hasListener(id)) {
NetworkMarker marker = queuedMarkers.remove(packet);
// A packet has been sent guys!
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = PacketEvent.fromServer(invoker, container, marker, currentPlayer);
invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled())
return null;
// Right, remember to replace the packet again
Object result = event.getPacket().getHandle();
marker = NetworkMarker.getNetworkMarker(event);
// See if we need to proxy the write method
if (result != null && (NetworkMarker.hasOutputHandlers(marker) || NetworkMarker.hasPostListeners(marker))) {
result = writePacketInterceptor.constructProxy(result, event, marker);
}
return result;
}
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_HANDLE_PACKET).error(e).callerParam(packet));
}
return packet;
}
/**
* Determine if the given injector is listening for this packet ID.
* @param packetID - packet ID to check.
* @return TRUE if it is, FALSE oterhwise.
*/
protected abstract boolean hasListener(int packetID);
/**
* Retrieve the current player's input stream.
* @param cache - whether or not to cache the result of this method.
* @return The player's input stream.
*/
public DataInputStream getInputStream(boolean cache) {
// And the data input stream that we'll use to identify a player
if (networkManager == null)
throw new IllegalStateException("Network manager is NULL.");
if (inputField == null)
inputField = FuzzyReflection.fromObject(networkManager, true).
getFieldByType("java\\.io\\.DataInputStream");
// Get the associated input stream
try {
if (cache && cachedInput != null)
return cachedInput;
// Save to cache
cachedInput = (DataInputStream) FieldUtils.readField(inputField, networkManager, true);
return cachedInput;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read input stream.", e);
}
}
/**
* Retrieve the hooked player.
*/
@Override
public Player getPlayer() {
return player;
}
/**
* Set the hooked player.
* <p>
* Should only be called during the creation of the injector.
* @param player - the new hooked player.
*/
public void setPlayer(Player player) {
this.player = player;
}
/**
* Object that can invoke the packet events.
* @return Packet event invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
*/
@Override
public Player getUpdatedPlayer() {
if (updatedPlayer != null)
return updatedPlayer;
else
return player;
}
@Override
public void transferState(SocketInjector delegate) {
// Do nothing
}
@Override
public void setUpdatedPlayer(Player updatedPlayer) {
this.updatedPlayer = updatedPlayer;
}
/**
* Indicates that a player's NetServerHandler or PlayerConnection was NULL.
* <p>
* This is usually because the player has just logged out, or due to it being a "fake" player in MCPC+/Cauldron.
* @author Kristian
*/
public static class ServerHandlerNull extends IllegalAccessError {
private static final long serialVersionUID = 1L;
public ServerHandlerNull() {
super("Unable to fetch server handler: was NUll.");
}
public ServerHandlerNull(String s) {
super(s);
}
}
}
@@ -1,143 +0,0 @@
package com.comphenix.protocol.injector.player;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
/**
* Constructor for different player injectors.
*
* @author Kristian
*/
public class PlayerInjectorBuilder {
public static PlayerInjectorBuilder newBuilder() {
return new PlayerInjectorBuilder();
}
protected PlayerInjectorBuilder() {
// Use the static method.
}
protected ErrorReporter reporter;
protected Predicate<GamePhase> injectionFilter;
protected ListenerInvoker invoker;
protected Set<PacketListener> packetListeners;
protected Server server;
protected MinecraftVersion version;
/**
* The error reporter used by the created injector.
* @param reporter - new error reporter.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
this.reporter = reporter;
return this;
}
/**
* The injection filter that is used to determine if it is necessary to perform
* injection during a certain phase.
* @param injectionFilter - filter predicate.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder injectionFilter(@Nonnull Predicate<GamePhase> injectionFilter) {
Preconditions.checkNotNull(injectionFilter, "injectionFilter cannot be NULL");
this.injectionFilter = injectionFilter;
return this;
}
/**
* The packet stream invoker.
* @param invoker - the invoker.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
this.invoker = invoker;
return this;
}
/**
* Set the set of packet listeners.
* @param packetListeners - packet listeners.
* @return This builder, for chaining.
*/
@Nonnull
public PlayerInjectorBuilder packetListeners(@Nonnull Set<PacketListener> packetListeners) {
Preconditions.checkNotNull(packetListeners, "packetListeners cannot be NULL");
this.packetListeners = packetListeners;
return this;
}
/**
* Set the Bukkit server used for scheduling.
* @param server - the Bukkit server.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder server(@Nonnull Server server) {
Preconditions.checkNotNull(server, "server cannot be NULL");
this.server = server;
return this;
}
/**
* Set the current Minecraft version.
* @param version - the current Minecraft version, or NULL if unknown.
* @return This builder, for chaining.
*/
public PlayerInjectorBuilder version(MinecraftVersion version) {
this.version = version;
return this;
}
/**
* Called before an object is created with this builder.
*/
private void initializeDefaults() {
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
// Initialize with default values if we can
if (reporter == null)
reporter = ProtocolLibrary.getErrorReporter();
if (invoker == null)
invoker = (PacketFilterManager) manager;
if (server == null)
server = Bukkit.getServer();
if (injectionFilter == null)
throw new IllegalStateException("injectionFilter must be initialized.");
if (packetListeners == null)
throw new IllegalStateException("packetListeners must be initialized.");
}
/**
* Construct the injection handler.
* <p>
* Any builder parameter marked as NON-NULL is essential and must be initialized.
* @return The constructed injection handler using the current parameters.
*/
public PlayerInjectionHandler buildHandler() {
// Fill any default fields
initializeDefaults();
return new ProxyPlayerInjectionHandler(
reporter, injectionFilter,
invoker, packetListeners, server, version);
}
}
@@ -1,763 +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.player;
import io.netty.channel.Channel;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.BlockingHashMap;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListenerOptions;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerInjectHooks;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
import com.comphenix.protocol.injector.server.BukkitSocketInjector;
import com.comphenix.protocol.injector.server.InputStreamLookupBuilder;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
// Warnings and errors
public static final ReportType REPORT_UNSUPPPORTED_LISTENER = new ReportType("Cannot fully register listener for %s: %s");
// Fallback to older player hook types
public static final ReportType REPORT_PLAYER_HOOK_FAILED = new ReportType("Player hook %s failed.");
public static final ReportType REPORT_SWITCHED_PLAYER_HOOK = new ReportType("Switching to %s instead.");
public static final ReportType REPORT_HOOK_CLEANUP_FAILED = new ReportType("Cleaing up after player hook failed.");
public static final ReportType REPORT_CANNOT_REVERT_HOOK = new ReportType("Unable to fully revert old injector. May cause conflicts.");
// Server connection injection
private InjectedServerConnection serverInjection;
// Server socket injection
private AbstractInputStreamLookup inputStreamLookup;
// NetLogin injector
private NetLoginInjector netLoginInjector;
// The last successful player hook
private WeakReference<PlayerInjector> lastSuccessfulHook;
// Dummy injection
private ConcurrentMap<Player, PlayerInjector> dummyInjectors =
SafeCacheBuilder.newBuilder().
expireAfterWrite(30, TimeUnit.SECONDS).
build(BlockingHashMap.<Player, PlayerInjector>newInvalidCacheLoader());
// Player injection
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
// Player injection types
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error reporter
private ErrorReporter reporter;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Current Minecraft version
private MinecraftVersion version;
// Enabled packet filters
private IntegerSet sendingFilters = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
// List of packet listeners
private Set<PacketListener> packetListeners;
// Used to filter injection attempts
private Predicate<GamePhase> injectionFilter;
public ProxyPlayerInjectionHandler(
ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server, MinecraftVersion version) {
this.reporter = reporter;
this.invoker = invoker;
this.injectionFilter = injectionFilter;
this.packetListeners = packetListeners;
this.version = version;
this.inputStreamLookup = InputStreamLookupBuilder.newBuilder().
server(server).
reporter(reporter).
build();
// Create net login injectors and the server connection injector
this.netLoginInjector = new NetLoginInjector(reporter, server, this);
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
serverInjection.injectList();
}
@Override
public int getProtocolVersion(Player player) {
// Just use the server version
return MinecraftProtocolVersion.getCurrentVersion();
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook() {
return getPlayerHook(GamePhase.PLAYING);
}
/**
* Retrieves how the server packets are read.
* @param phase - the current game phase.
* @return Injection method for reading server packets.
*/
@Override
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
switch (phase) {
case LOGIN:
return loginPlayerHook;
case PLAYING:
return playingPlayerHook;
default:
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
}
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return sendingFilters.contains(type.getLegacyId());
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(PlayerInjectHooks playerHook) {
setPlayerHook(GamePhase.PLAYING, playerHook);
}
/**
* Sets how the server packets are read.
* @param phase - the current game phase.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
if (phase.hasLogin())
loginPlayerHook = playerHook;
if (phase.hasPlaying())
playingPlayerHook = playerHook;
// Make sure the current listeners are compatible
checkListener(packetListeners);
}
@Override
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
sendingFilters.add(type.getLegacyId());
}
@Override
public void removePacketHandler(PacketType type) {
sendingFilters.remove(type.getLegacyId());
}
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @throws IllegalAccessException Unable to do our reflection magic.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(reporter, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(reporter, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(reporter, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
/**
* Retrieve a player by its DataInput connection.
* @param inputStream - the associated DataInput connection.
* @return The player we found.
*/
@Override
public Player getPlayerByConnection(DataInputStream inputStream) {
// Wait until the connection owner has been established
SocketInjector injector = inputStreamLookup.waitSocketInjector(inputStream);
if (injector != null) {
return injector.getPlayer();
} else {
return null;
}
}
/**
* Helper function that retrieves the injector type of a given player injector.
* @param injector - injector type.
* @return The injector type.
*/
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
* @param player - player to hook.
* @param strategy - how to handle previous player injections.
*/
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
// Inject using the player instance itself
if (isInjectionNecessary(GamePhase.PLAYING)) {
injectPlayer(player, player, strategy, GamePhase.PLAYING);
}
}
/**
* Determine if it's truly necessary to perform the given player injection.
* @param phase - current game phase.
* @return TRUE if we should perform the injection, FALSE otherwise.
*/
public boolean isInjectionNecessary(GamePhase phase) {
return injectionFilter.apply(phase);
}
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This method will always perform the instructed injection.
*
* @param player - player to hook.
* @param injectionPoint - the object to use during the injection process.
* @param phase - the current game phase.
* @return The resulting player injector, or NULL if the injection failed.
*/
PlayerInjector injectPlayer(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
if (player == null)
throw new IllegalArgumentException("Player cannot be NULL.");
if (injectionPoint == null)
throw new IllegalArgumentException("injectionPoint cannot be NULL.");
if (phase == null)
throw new IllegalArgumentException("phase cannot be NULL.");
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
synchronized (player) {
return injectPlayerInternal(player, injectionPoint, stategy, phase);
}
}
// Unsafe variant of the above
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, ConflictStrategy stategy, GamePhase phase) {
PlayerInjector injector = playerInjection.get(player);
PlayerInjectHooks tempHook = getPlayerHook(phase);
PlayerInjectHooks permanentHook = tempHook;
// The given player object may be fake, so be careful!
// See if we need to inject something else
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
// Don't inject if the class has closed
if (!hasClosed && (tempHook != getInjectorType(injector) || invalidInjector)) {
while (tempHook != PlayerInjectHooks.NONE) {
// Whether or not the current hook method failed completely
boolean hookFailed = false;
// Remove the previous hook, if any
cleanupHook(injector);
try {
injector = getHookInstance(player, tempHook);
// Make sure this injection method supports the current game phase
if (injector.canInject(phase)) {
injector.initialize(injectionPoint);
// Get socket and socket injector
SocketAddress address = injector.getAddress();
// Ignore logged out players
if (address == null)
return null;
SocketInjector previous = inputStreamLookup.peekSocketInjector(address);
Socket socket = injector.getSocket();
// Close any previously associated hooks before we proceed
if (previous != null && !(player instanceof Factory)) {
switch (stategy) {
case OVERRIDE:
uninjectPlayer(previous.getPlayer(), true);
break;
case BAIL_OUT:
return null;
}
}
injector.injectManager();
saveAddressLookup(address, socket, injector);
break;
}
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception e) {
// Mark this injection attempt as a failure
reporter.reportDetailed(this,
Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e)
);
hookFailed = true;
}
// Choose the previous player hook type
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
if (hookFailed)
reporter.reportWarning(this, Report.newBuilder(REPORT_SWITCHED_PLAYER_HOOK).messageParam(tempHook));
// Check for UTTER FAILURE
if (tempHook == PlayerInjectHooks.NONE) {
cleanupHook(injector);
injector = null;
hookFailed = true;
}
// Should we set the default hook method too?
if (hookFailed) {
permanentHook = tempHook;
}
}
// Update values
if (injector != null)
lastSuccessfulHook = new WeakReference<PlayerInjector>(injector);
if (permanentHook != getPlayerHook(phase))
setPlayerHook(phase, tempHook);
// Save injector
if (injector != null) {
playerInjection.put(player, injector);
}
}
return injector;
}
private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) {
SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null;
if (socketAddress != null && !Objects.equal(socketAddress, address)) {
// Save this version as well
inputStreamLookup.setSocketInjector(socketAddress, injector);
}
// Save injector
inputStreamLookup.setSocketInjector(address, injector);
}
private void cleanupHook(PlayerInjector injector) {
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} catch (Exception ex) {
reporter.reportDetailed(this, Report.newBuilder(REPORT_HOOK_CLEANUP_FAILED).callerParam(injector).error(ex));
}
}
/**
* Invoke special routines for handling disconnect before a player is uninjected.
* @param player - player to process.
*/
@Override
public void handleDisconnect(Player player) {
PlayerInjector injector = getInjector(player);
if (injector != null) {
injector.handleDisconnect();
}
}
@Override
public void updatePlayer(Player player) {
SocketAddress address = player.getAddress();
// Ignore logged out players
if (address != null) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
if (injector != null) {
injector.setUpdatedPlayer(player);
} else {
inputStreamLookup.setSocketInjector(player.getAddress(),
new BukkitSocketInjector(player));
}
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(Player player) {
return uninjectPlayer(player, false);
}
/**
* Unregisters the given player.
* @param player - player to unregister.
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
private boolean uninjectPlayer(Player player, boolean prepareNextHook) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.remove(player);
if (injector != null) {
injector.cleanupAll();
// Remove the "hooked" network manager in our instance as well
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
} catch (IllegalAccessException e) {
// Let the user know
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REVERT_HOOK).error(e));
}
}
return true;
}
}
return false;
}
/**
* Unregisters a player by the given address.
* <p>
* If the server handler has been created before we've gotten a chance to unject the player,
* the method will try a workaround to remove the injected hook in the NetServerHandler.
*
* @param address - address of the player to unregister.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
if (!hasClosed && address != null) {
SocketInjector injector = inputStreamLookup.peekSocketInjector(address);
// Clean up
if (injector != null)
uninjectPlayer(injector.getPlayer(), true);
return true;
}
return false;
}
/**
* Send the given packet to the given receiver.
* @param receiver - the player receiver.
* @param packet - the packet to send.
* @param filters - whether or not to invoke the packet filters.
* @throws InvocationTargetException If an error occured during sending.
*/
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
SocketInjector injector = getInjector(receiver);
// Send the packet, or drop it completely
if (injector != null) {
injector.sendServerPacket(packet.getHandle(), marker, filters);
} else {
throw new PlayerLoggedOutException(String.format(
"Unable to send packet %s (%s): Player %s has logged out.",
packet.getType(), packet, receiver
));
}
}
/**
* Recieve a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
// Process the given packet, or simply give up
if (injector != null)
injector.processPacket(mcPacket);
else
throw new PlayerLoggedOutException(String.format(
"Unable to receieve packet %s. Player %s has logged out.",
mcPacket, player
));
}
/**
* Retrieve the injector associated with this player.
* @param player - the player to find.
* @return The injector, or NULL if not found.
*/
private PlayerInjector getInjector(Player player) {
PlayerInjector injector = playerInjection.get(player);
if (injector == null) {
// Try getting it from the player itself
SocketAddress address = player.getAddress();
// Must have logged out - there's nothing we can do
if (address == null)
return null;
// Look that up without blocking
SocketInjector result = inputStreamLookup.peekSocketInjector(address);
// Ensure that it is non-null and a player injector
if (result instanceof PlayerInjector)
return (PlayerInjector) result;
else
// Make a dummy injector them
return createDummyInjector(player);
} else {
return injector;
}
}
/**
* Construct a simple dummy injector incase none has been constructed.
* @param player - the CraftPlayer to construct for.
* @return A dummy injector, or NULL if the given player is not a CraftPlayer.
*/
private PlayerInjector createDummyInjector(Player player) {
if (!MinecraftReflection.getCraftPlayerClass().isAssignableFrom(player.getClass())) {
// No - this is not safe
return null;
}
try {
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
dummyInjector.initializePlayer(player);
// This probably means the player has disconnected
if (dummyInjector.getSocket() == null) {
return null;
}
inputStreamLookup.setSocketInjector(dummyInjector.getAddress(), dummyInjector);
dummyInjectors.put(player, dummyInjector);
return dummyInjector;
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access fields.", e);
}
}
/**
* Retrieve a player injector by looking for its NetworkManager.
* @param networkManager - current network manager.
* @return Related player injector.
*/
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
// That's not legal
if (networkManager == null)
return null;
// O(n) is okay in this instance. This is only a backup solution.
for (PlayerInjector injector : playerInjection.values()) {
if (injector.getNetworkManager() == networkManager)
return injector;
}
// None found
return null;
}
@Override
public boolean canRecievePackets() {
return false;
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
throw new UnsupportedOperationException("Proxy injection cannot handle received packets.");
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
@Override
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (getLastSuccessfulHook() != null) {
for (PacketListener listener : listeners) {
checkListener(listener);
}
}
}
/**
* Retrieve the last successful hook.
* <p>
* May be NULL if the hook has been uninjected.
* @return Last successful hook.
*/
private PlayerInjector getLastSuccessfulHook() {
return lastSuccessfulHook != null ? lastSuccessfulHook.get() : null;
}
/**
* Determine if a listener is valid or not.
* <p>
* If not, a warning will be printed to the console.
* @param listener - listener to check.
*/
@Override
public void checkListener(PacketListener listener) {
PlayerInjector last = getLastSuccessfulHook();
if (last != null) {
UnsupportedListener result = last.checkListener(version, listener);
// We won't prevent the listener, as it may still have valid packets
if (result != null) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result)
);
// These are illegal
for (int packetID : result.getPackets()) {
removePacketHandler(PacketType.findLegacy(packetID, Sender.CLIENT));
removePacketHandler(PacketType.findLegacy(packetID, Sender.SERVER));
}
}
}
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
@Override
public Set<PacketType> getSendingFilters() {
return PacketRegistry.toPacketTypes(sendingFilters.toSet(), Sender.SERVER);
}
@Override
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
if (inputStreamLookup != null)
inputStreamLookup.cleanupAll();
if (serverInjection != null)
serverInjection.cleanupAll();
if (netLoginInjector != null)
netLoginInjector.cleanupAll();
inputStreamLookup = null;
serverInjection = null;
netLoginInjector = null;
hasClosed = true;
playerInjection.clear();
invoker = null;
}
@Override
public Channel getChannel(Player player) {
throw new UnsupportedOperationException();
}
}
@@ -1,370 +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.player;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.google.common.base.Objects;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
/**
* Represents an array list that wraps another list, while automatically replacing one element with another.
* <p>
* The replaced elements can be recovered.
*
* @author Kristian
* @param <TKey> - type of the elements we're replacing.
*/
class ReplacedArrayList<TKey> extends ArrayList<TKey> {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 1008492765999744804L;
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
private List<TKey> underlyingList;
public ReplacedArrayList(List<TKey> underlyingList) {
this.underlyingList = underlyingList;
}
/**
* Invoked when a element inserted is replaced.
* @param inserting - the element inserted.
* @param replacement - the element that it should replace.
*/
protected void onReplacing(TKey inserting, TKey replacement) {
// Default is to do nothing.
}
/**
* Invoked when an element is being inserted.
* <p>
* This should be used to add a "replace" map.
* @param inserting - the element to insert.
*/
protected void onInserting(TKey inserting) {
// Default is again nothing
}
/**
* Invoked when an element is being removed.
* @param removing - the element being removed.
*/
protected void onRemoved(TKey removing) {
// Do nothing
}
@Override
public boolean add(TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().add(replacement);
} else {
return delegate().add(element);
}
}
@Override
public void add(int index, TKey element) {
onInserting(element);
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
delegate().add(index, replacement);
} else {
delegate().add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends TKey> collection) {
int oldSize = size();
for (TKey element : collection)
add(element);
return size() != oldSize;
}
@Override
public boolean addAll(int index, Collection<? extends TKey> elements) {
int oldSize = size();
for (TKey element : elements)
add(index++, element);
return size() != oldSize;
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object object) {
boolean success = delegate().remove(object);
if (success)
onRemoved((TKey) object);
return success;
}
@Override
public TKey remove(int index) {
TKey removed = delegate().remove(index);
if (removed != null)
onRemoved(removed);
return removed;
}
@Override
public boolean removeAll(Collection<?> collection) {
int oldSize = size();
// Use the remove method
for (Object element : collection)
remove(element);
return size() != oldSize;
}
protected List<TKey> delegate() {
return underlyingList;
}
@Override
public void clear() {
for (TKey element : delegate())
onRemoved(element);
delegate().clear();
}
@Override
public boolean contains(Object o) {
return delegate().contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return delegate().containsAll(c);
}
@Override
public TKey get(int index) {
return delegate().get(index);
}
@Override
public int indexOf(Object o) {
return delegate().indexOf(o);
}
@Override
public boolean isEmpty() {
return delegate().isEmpty();
}
@Override
public Iterator<TKey> iterator() {
return delegate().iterator();
}
@Override
public int lastIndexOf(Object o) {
return delegate().lastIndexOf(o);
}
@Override
public ListIterator<TKey> listIterator() {
return delegate().listIterator();
}
@Override
public ListIterator<TKey> listIterator(int index) {
return delegate().listIterator(index);
}
@Override
public boolean retainAll(Collection<?> c) {
int oldSize = size();
for (Iterator<TKey> it = delegate().iterator(); it.hasNext(); ) {
TKey current = it.next();
// Remove elements that are not in the list
if (!c.contains(current)) {
it.remove();
onRemoved(current);
}
}
return size() != oldSize;
}
@Override
public TKey set(int index, TKey element) {
// Make sure to replace the element
if (replaceMap.containsKey(element)) {
TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return delegate().set(index, replacement);
} else {
return delegate().set(index, element);
}
}
@Override
public int size() {
return delegate().size();
}
@Override
public List<TKey> subList(int fromIndex, int toIndex) {
return delegate().subList(fromIndex, toIndex);
}
@Override
public Object[] toArray() {
return delegate().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return delegate().toArray(a);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
*/
public synchronized void addMapping(TKey target, TKey replacement) {
addMapping(target, replacement, false);
}
/**
* Retrieve the old value, if it exists.
* @param target - the key.
* @return The value that was replaced, or NULL.
*/
public TKey getMapping(TKey target) {
return replaceMap.get(target);
}
/**
* Add a replace rule.
* <p>
* This automatically replaces every existing element.
* @param target - instance to find.
* @param replacement - instance to replace with.
* @param ignoreExisting - whether or not to ignore the existing elements.
*/
public synchronized void addMapping(TKey target, TKey replacement, boolean ignoreExisting) {
replaceMap.put(target, replacement);
// Replace existing elements
if (!ignoreExisting) {
replaceAll(target, replacement);
}
}
/**
* Revert the given mapping.
* @param target - the instance we replaced.
* @return The old mapped value, or NULL if nothing was replaced.
*/
public synchronized TKey removeMapping(TKey target) {
// Make sure the mapping exist
if (replaceMap.containsKey(target)) {
TKey replacement = replaceMap.get(target);
replaceMap.remove(target);
// Revert existing elements
replaceAll(replacement, target);
return replacement;
}
return null;
}
/**
* Swap the new replaced value with its old value.
* @param target - the instance we replaced.
* @param The old mapped value, or NULL if nothing was swapped.
*/
public synchronized TKey swapMapping(TKey target) {
// Make sure the mapping exist
TKey replacement = removeMapping(target);
// Add the reverse
if (replacement != null) {
replaceMap.put(replacement, target);
}
return replacement;
}
/**
* Replace all instances of the given object.
* @param find - object to find.
* @param replace - object to replace it with.
*/
public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find)) {
onReplacing(find, replace);
underlyingList.set(i, replace);
}
}
}
/**
* Undo all replacements.
*/
public synchronized void revertAll() {
// No need to do anything else
if (replaceMap.size() < 1)
return;
BiMap<TKey, TKey> inverse = replaceMap.inverse();
for (int i = 0; i < underlyingList.size(); i++) {
TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) {
TKey original = inverse.get(replaced);
onReplacing(replaced, original);
underlyingList.set(i, original);
}
}
replaceMap.clear();
}
@Override
protected void finalize() throws Throwable {
revertAll();
super.finalize();
}
}
@@ -1,64 +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.player;
import java.util.Arrays;
import com.google.common.base.Joiner;
/**
* Represents an error message from a player injector.
*
* @author Kristian
*/
class UnsupportedListener {
private String message;
private int[] packets;
/**
* Create a new error message.
* @param message - the message.
* @param packets - unsupported packets.
*/
public UnsupportedListener(String message, int[] packets) {
super();
this.message = message;
this.packets = packets;
}
/**
* Retrieve the error message.
* @return Error message.
*/
public String getMessage() {
return message;
}
/**
* Retrieve all unsupported packets.
* @return Unsupported packets.
*/
public int[] getPackets() {
return packets;
}
@Override
public String toString() {
return String.format("%s (%s)", message, Joiner.on(", ").join(Arrays.asList(packets)));
}
}
@@ -80,4 +80,9 @@ public class BukkitSocketInjector implements SocketInjector {
public void setUpdatedPlayer(Player updatedPlayer) {
this.player = updatedPlayer;
}
@Override
public boolean isConnected() {
return player.isOnline();
}
}
@@ -18,22 +18,24 @@ public interface SocketInjector {
* Retrieve the associated socket of this player.
* @return The associated socket.
* @throws IllegalAccessException If we're unable to read the socket field.
* @deprecated May be null on certain server implementations. Also don't use raw sockets.
*/
public abstract Socket getSocket() throws IllegalAccessException;
@Deprecated
Socket getSocket() throws IllegalAccessException;
/**
* Retrieve the associated address of this player.
* @return The associated address.
* @throws IllegalAccessException If we're unable to read the socket field.
*/
public abstract SocketAddress getAddress() throws IllegalAccessException;
SocketAddress getAddress() throws IllegalAccessException;
/**
* Attempt to disconnect the current client.
* @param message - the message to display.
* @throws InvocationTargetException If disconnection failed.
*/
public abstract void disconnect(String message) throws InvocationTargetException;
void disconnect(String message) throws InvocationTargetException;
/**
* Send a packet to the client.
@@ -42,30 +44,36 @@ public interface SocketInjector {
* @param filtered - whether or not the packet will be filtered by our listeners.
* @throws InvocationTargetException If an error occured when sending the packet.
*/
public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered)
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered)
throws InvocationTargetException;
/**
* Retrieve the hooked player.
* @return The hooked player.
*/
public abstract Player getPlayer();
Player getPlayer();
/**
* Retrieve the hooked player object OR the more up-to-date player instance.
* @return The hooked player, or a more up-to-date instance.
*/
public abstract Player getUpdatedPlayer();
Player getUpdatedPlayer();
/**
* Invoked when a delegated socket injector transfers the state of one injector to the next.
* @param delegate - the new injector.
*/
public abstract void transferState(SocketInjector delegate);
void transferState(SocketInjector delegate);
/**
* Set the real Bukkit player that we will use.
* @param updatedPlayer - the real Bukkit player.
*/
public abstract void setUpdatedPlayer(Player updatedPlayer);
void setUpdatedPlayer(Player updatedPlayer);
/**
* Determines if the player is currently connected.
* @return true if the player is connected.
*/
boolean isConnected();
}
@@ -17,15 +17,25 @@
package com.comphenix.protocol.injector.server;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.Server;
import org.bukkit.entity.Player;
@@ -38,8 +48,7 @@ import com.comphenix.protocol.utility.ChatExtensions;
* Create fake player instances that represents pre-authenticated clients.
*/
public class TemporaryPlayerFactory {
// Prevent too many class creations
private static CallbackFilter callbackFilter;
private static final Constructor<? extends Player> temporaryPlayerConstructor = setupProxyPlayerConstructor();
/**
* Retrieve the injector from a given player if it contains one.
@@ -82,21 +91,34 @@ public class TemporaryPlayerFactory {
* @return A temporary player instance.
*/
public Player createTemporaryPlayer(final Server server) {
// Default implementation
Callback implementation = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
try {
return temporaryPlayerConstructor.newInstance(server);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
}
private static Constructor<? extends Player> setupProxyPlayerConstructor()
{
final MethodDelegation implementation = MethodDelegation.to(new Object() {
@RuntimeType
public Object delegate(@This Object obj, @Origin Method method, @FieldValue("server") Server server,
@AllArguments Object... args) throws Throwable {
String methodName = method.getName();
SocketInjector injector = ((TemporaryPlayer) obj).getInjector();
if (injector == null)
throw new IllegalStateException("Unable to find injector.");
// Use the socket to get the address
else if (methodName.equals("getPlayer"))
return injector.getUpdatedPlayer();
else if (methodName.equals("getAddress"))
else if (methodName.equals("getAddress"))
return injector.getAddress();
else if (methodName.equals("getServer"))
return server;
@@ -104,70 +126,71 @@ public class TemporaryPlayerFactory {
// Handle send message methods
if (methodName.equals("chat") || methodName.equals("sendMessage")) {
try {
Object argument = args[0];
// Dynamic overloading
if (argument instanceof String) {
return sendMessage(injector, (String) argument);
} else if (argument instanceof String[]) {
for (String message : (String[]) argument) {
sendMessage(injector, message);
Object argument = args[0];
// Dynamic overloading
if (argument instanceof String) {
return sendMessage(injector, (String) argument);
} else if (argument instanceof String[]) {
for (String message : (String[]) argument) {
sendMessage(injector, message);
}
return null;
}
return null;
}
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
// Also, handle kicking
if (methodName.equals("kickPlayer")) {
injector.disconnect((String) args[0]);
return null;
}
// The fallback instance
Player updated = injector.getUpdatedPlayer();
if (updated != obj && updated != null) {
return proxy.invoke(updated, args);
return method.invoke(updated, args);
}
// Methods that are supported in the fallback instance
if (methodName.equals("isOnline"))
return injector.getSocket() != null && injector.getSocket().isConnected();
return injector.isConnected();
else if (methodName.equals("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
return "UNKNOWN[" + injector.getAddress() + "]";
// Ignore all other methods
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for temporary players.");
}
};
// Shared callback filter
if (callbackFilter == null) {
callbackFilter = new CallbackFilter() {
@Override
public int accept(Method method) {
// Do not override the object method or the superclass methods
if (method.getDeclaringClass().equals(Object.class) ||
method.getDeclaringClass().equals(TemporaryPlayer.class))
return 0;
else
return 1;
}
};
});
final ElementMatcher.Junction<ByteCodeElement> callbackFilter = ElementMatchers.not(
ElementMatchers.isDeclaredBy(Object.class).or(ElementMatchers.isDeclaredBy(TemporaryPlayer.class)));
try {
final Constructor<?> constructor = ByteBuddyFactory.getInstance()
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(TemporaryPlayerFactory.class.getPackage().getName() + ".TemporaryPlayerInvocationHandler")
.implement(Player.class)
.defineField("server", Server.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameters(Server.class)
.intercept(MethodCall.invoke(TemporaryPlayer.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("server").setsArgumentAt(0)))
.method(callbackFilter)
.intercept(implementation)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(Server.class);
return (Constructor<? extends Player>) constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Temporary Player constructor!", e);
}
// CGLib is amazing
Enhancer ex = new Enhancer();
ex.setSuperclass(TemporaryPlayer.class);
ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(callbackFilter);
return (Player) ex.create();
}
/**
@@ -191,7 +214,7 @@ public class TemporaryPlayerFactory {
* @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet.
*/
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
private static Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendServerPacket(packet.getHandle(), null, false);
}
@@ -1,45 +0,0 @@
package com.comphenix.protocol.injector.spigot;
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.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
/**
* Dummy packet injector that simply delegates to its parent Spigot packet injector or receiving filters.
*
* @author Kristian
*/
class DummyPacketInjector extends AbstractPacketInjector {
private SpigotPacketInjector injector;
private PacketTypeSet lastBufferedPackets = new PacketTypeSet();
public DummyPacketInjector(SpigotPacketInjector injector, PacketTypeSet reveivedFilters) {
super(reveivedFilters);
this.injector = injector;
}
@Override
public void inputBuffersChanged(Set<PacketType> set) {
Set<PacketType> removed = Sets.difference(lastBufferedPackets.values(), set);
Set<PacketType> added = Sets.difference(set, lastBufferedPackets.values());
// Update the proxy packet injector
for (PacketType packet : removed) {
injector.getProxyPacketInjector().removePacketHandler(packet);
}
for (PacketType packet : added) {
injector.getProxyPacketInjector().addPacketHandler(packet, null);
}
}
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
return injector.packetReceived(packet, client, buffered);
}
}
@@ -1,92 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import io.netty.channel.Channel;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
/**
* Dummy player handler that simply delegates to its parent Spigot packet injector.
*
* @author Kristian
*/
class DummyPlayerHandler extends AbstractPlayerHandler {
private SpigotPacketInjector injector;
@Override
public int getProtocolVersion(Player player) {
// Just use the server version
return MinecraftProtocolVersion.getCurrentVersion();
}
public DummyPlayerHandler(SpigotPacketInjector injector, PacketTypeSet sendingFilters) {
super(sendingFilters);
this.injector = injector;
}
@Override
public boolean uninjectPlayer(InetSocketAddress address) {
return true;
}
@Override
public boolean uninjectPlayer(Player player) {
injector.uninjectPlayer(player);
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
injector.sendServerPacket(receiver, packet, marker, filters);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
injector.processPacket(player, mcPacket);
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
// We don't care about strategy
injector.injectPlayer(player);
}
@Override
public boolean hasMainThreadListener(PacketType type) {
return sendingFilters.contains(type);
}
@Override
public void handleDisconnect(Player player) {
// Just ignore
}
@Override
public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) {
// Associate this buffered data
if (buffered != null) {
injector.saveBuffered(packet.getHandle(), buffered);
}
return null;
}
@Override
public void updatePlayer(Player player) {
// Do nothing
}
@Override
public Channel getChannel(Player player) {
throw new UnsupportedOperationException();
}
}
@@ -1,672 +0,0 @@
package com.comphenix.protocol.injector.spigot;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.DelegatedErrorReporter;
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.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.LegacyNetworkMarker;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.NetworkObjectInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.MethodInfo;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
/**
* Offload all the work to Spigot, if possible.
*
* @author Kristian
*/
public class SpigotPacketInjector implements SpigotPacketListener {
public static final ReportType REPORT_CANNOT_CLEANUP_SPIGOT = new ReportType("Cannot cleanup Spigot listener.");
// Lazily retrieve the spigot listener class
private static volatile Class<?> spigotListenerClass;
private static volatile boolean classChecked;
// Retrieve the entity player from a PlayerConnection
private static volatile Field playerConnectionPlayer;
// Packets that are not to be processed by the filters
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
/**
* The amount of ticks to wait before removing all traces of a player.
*/
private static final int CLEANUP_DELAY = 100;
// The listener we will register on Spigot.
// Unfortunately, due to the use of PlayerConnection, INetworkManager and Packet, we're
// unable to reference it directly. But with CGLib, it shouldn't cost us much.
private Object dynamicListener;
// Reference to ProtocolLib
private Plugin plugin;
// Different sending filters
private PacketTypeSet queuedFilters;
private PacketTypeSet reveivedFilters;
// NetworkManager to injector and player
private ConcurrentMap<Object, NetworkObjectInjector> networkManagerInjector = Maps.newConcurrentMap();
// Player to injector
private ConcurrentMap<Player, NetworkObjectInjector> playerInjector = Maps.newConcurrentMap();
// For handling read buffered packet data
private Map<Object, byte[]> readBufferedPackets = new MapMaker().weakKeys().makeMap();
// Responsible for informing the PL packet listeners
private ListenerInvoker invoker;
private ErrorReporter reporter;
private Server server;
// The proxy packet injector
private PacketInjector proxyPacketInjector;
// Background task
private static final int BACKGROUND_DELAY = 30 * PacketFilterManager.TICKS_PER_SECOND;
private int backgroundId;
/**
* Create a new spigot injector.
* @param reporter - error reporter
* @param invoker - listener invoker
* @param server - server
*/
public SpigotPacketInjector(ErrorReporter reporter, ListenerInvoker invoker, Server server) {
this.reporter = reporter;
this.invoker = invoker;
this.server = server;
this.queuedFilters = new PacketTypeSet();
this.reveivedFilters = new PacketTypeSet();
}
/**
* Retrieve the underlying listener invoker.
* @return The invoker.
*/
public ListenerInvoker getInvoker() {
return invoker;
}
/**
* Set the real proxy packet injector.
* @param proxyPacketInjector - the real injector.
*/
public void setProxyPacketInjector(PacketInjector proxyPacketInjector) {
this.proxyPacketInjector = proxyPacketInjector;
}
/**
* Retrieve the real proxy packet injector.
* @return The real injector.
*/
public PacketInjector getProxyPacketInjector() {
return proxyPacketInjector;
}
/**
* Retrieve the spigot packet listener class.
* @return The listener class.
*/
private static Class<?> getSpigotListenerClass() {
if (!classChecked) {
try {
spigotListenerClass = SpigotPacketInjector.class.getClassLoader().loadClass("org.spigotmc.netty.PacketListener");
} catch (ClassNotFoundException e) {
return null;
} finally {
// We've given it a try now
classChecked = true;
}
}
return spigotListenerClass;
}
/**
* Retrieve the register packet listener method.
* @return The method used to register a packet listener.
*/
private static Method getRegisterMethod() {
Class<?> clazz = getSpigotListenerClass();
if (clazz != null) {
try {
return clazz.getMethod("register", clazz, Plugin.class);
} catch (SecurityException e) {
// If this happens, then ... we're doomed
throw new RuntimeException("Reflection is not allowed.", e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find register() method in " + clazz, e);
}
}
// Also bad
throw new IllegalStateException("Spigot could not be found!");
}
/**
* Determine if there is a Spigot packet listener.
* @return Spigot packet listener.
*/
public static boolean canUseSpigotListener() {
return getSpigotListenerClass() != null;
}
/**
* Register the Spigot packet injector.
* @param plugin - the parent plugin.
* @return TRUE if we registered the plugin, FALSE otherwise.
*/
public boolean register(Plugin plugin) {
if (hasRegistered())
return false;
// Save the plugin too
this.plugin = plugin;
final Callback[] callbacks = new Callback[3];
final boolean[] found = new boolean[3];
// Packets received from the clients
callbacks[0] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetReceived(args[0], args[1], args[2]);
}
};
// Packet sent/queued
callbacks[1] = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return SpigotPacketInjector.this.packetQueued(args[0], args[1], args[2]);
}
};
// Don't care for everything else
callbacks[2] = NoOp.INSTANCE;
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer();
enhancer.setSuperclass(getSpigotListenerClass());
enhancer.setCallbacks(callbacks);
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
// We'll be pretty stringent
if (matchMethod("packetReceived", method)) {
found[0] = true;
return 0;
} else if (matchMethod("packetQueued", method)) {
found[1] = true;
return 1;
} else {
found[2] = true;
return 2;
}
}
});
dynamicListener = enhancer.create();
// Verify methods
if (!found[0])
throw new IllegalStateException("Unable to find a valid packet receiver in Spigot.");
if (!found[1])
throw new IllegalStateException("Unable to find a valid packet queue in Spigot.");
// Lets register it too
try {
getRegisterMethod().invoke(null, dynamicListener, plugin);
} catch (Exception e) {
throw new RuntimeException("Cannot register Spigot packet listener.", e);
}
// Remember to register background task
backgroundId = createBackgroundTask();
// If we succeed
return true;
}
/**
* Create and register a background task.
* @return The background task ID.
*/
private int createBackgroundTask() {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
@Override
public void run() {
cleanupInjectors();
}
}, BACKGROUND_DELAY, BACKGROUND_DELAY);
}
/**
* Ensure that all disconnected injectors are removed from memory.
*/
private void cleanupInjectors() {
for (NetworkObjectInjector injector : networkManagerInjector.values()) {
try {
if (injector.getSocket() != null && injector.getSocket().isClosed()) {
cleanupInjector(injector);
}
} catch (Exception e) {
reporter.reportMinimal(plugin, "cleanupInjectors", e);
// What?
cleanupInjector(injector);
}
}
}
/**
* Remove a given network object injector.
* @param injector - the injector.
*/
private void cleanupInjector(final NetworkObjectInjector injector) {
// Clean up
playerInjector.remove(injector.getPlayer());
playerInjector.remove(injector.getUpdatedPlayer());
networkManagerInjector.remove(injector.getNetworkManager());
}
/**
* Determine if the given method is a valid packet receiver or queued method.
* @param methodName - the expected name of the method.
* @param method - the method we're testing.
* @return TRUE if this is a correct method, FALSE otherwise.
*/
private boolean matchMethod(String methodName, Method method) {
return FuzzyMethodContract.newBuilder().
nameExact(methodName).
parameterCount(3).
parameterSuperOf(MinecraftReflection.getNetHandlerClass(), 1).
parameterSuperOf(MinecraftReflection.getPacketClass(), 2).
returnTypeExact(MinecraftReflection.getPacketClass()).
build().
isMatch(MethodInfo.fromMethod(method), null);
}
/**
* Determine if the Spigot packet listener has been registered.
* @return TRUE if it has, FALSE otherwise.
*/
public boolean hasRegistered() {
return dynamicListener != null;
}
/**
* Retrieve the dummy player injection handler.
* @return Dummy player injection handler.
*/
public PlayerInjectionHandler getPlayerHandler() {
return new DummyPlayerHandler(this, queuedFilters);
}
/**
* Retrieve the dummy packet injection handler.
* @return Dummy packet injection handler.
*/
public PacketInjector getPacketInjector() {
return new DummyPacketInjector(this, reveivedFilters);
}
/**
* Retrieve the currently registered injector for the given player.
* @param player - injected player.
* @param createNew - whether or not to create a new injector if the current is missing.
* @return The injector.
*/
NetworkObjectInjector getInjector(Player player, boolean createNew) {
NetworkObjectInjector injector = playerInjector.get(player);
if (injector == null && createNew) {
// Check for temporary players ..
if ((player instanceof Factory))
throw new IllegalArgumentException("Cannot inject tempoary player " + player);
try {
NetworkObjectInjector created = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), null, invoker, null);
created.initializePlayer(player);
if (created.getNetworkManager() == null)
throw new PlayerLoggedOutException("Player " + player + " has logged out.");
injector = saveInjector(created.getNetworkManager(), created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return injector;
}
/**
* Retrieve or create a registered injector for the given network manager and connection.
* @param networkManager - a INetworkManager object.
* @param connection - a Connection (PlayerConnection, PendingConnection) object.
* @return The created NetworkObjectInjector with a temporary player.
*/
NetworkObjectInjector getInjector(Object networkManager, Object connection) {
NetworkObjectInjector dummyInjector = networkManagerInjector.get(networkManager);
if (dummyInjector == null) {
// Inject the network manager
try {
NetworkObjectInjector created = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), null, invoker, null);
if (MinecraftReflection.isLoginHandler(connection)) {
created.initialize(connection);
created.setPlayer(created.createTemporaryPlayer(server));
} else if (MinecraftReflection.isServerHandler(connection)) {
// Get the player instead
if (playerConnectionPlayer == null)
playerConnectionPlayer = FuzzyReflection.fromObject(connection).
getFieldByType("player", MinecraftReflection.getEntityPlayerClass());
Object entityPlayer = playerConnectionPlayer.get(connection);
created.initialize(MinecraftReflection.getBukkitEntity(entityPlayer));
} else {
throw new IllegalArgumentException("Unregonized connection in NetworkManager.");
}
dummyInjector = saveInjector(networkManager, created);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create dummy injector.", e);
}
}
return dummyInjector;
}
/**
* Return a delegated error reporter that ignores certain warnings that are irrelevant on Spigot.
* @param reporter - error reporter to delegate.
* @return The filtered error reporter.
*/
private ErrorReporter filterImpossibleWarnings(ErrorReporter reporter) {
return new DelegatedErrorReporter(reporter) {
@Override
protected Report filterReport(Object sender, Report report, boolean detailed) {
// This doesn't matter - ignore it
if (report.getType() == NetworkObjectInjector.REPORT_DETECTED_CUSTOM_SERVER_HANDLER)
return null;
return report;
}
};
}
/**
* Save a given player injector for later.
* @param networkManager - the associated network manager.
* @param created - the created network object creator.
* @return Any other network injector that came before us.
*/
private NetworkObjectInjector saveInjector(Object networkManager, NetworkObjectInjector created) {
// Concurrency - use the same injector!
NetworkObjectInjector result = networkManagerInjector.putIfAbsent(networkManager, created);
if (result == null) {
result = created;
}
// Save the player as well
playerInjector.put(created.getPlayer(), created);
return result;
}
/**
* Save the buffered serialized input packet.
* @param handle - the associated packet.
* @param buffered - the buffere data to save.
*/
public void saveBuffered(Object handle, byte[] buffered) {
readBufferedPackets.put(handle, buffered);
}
@Override
public Object packetReceived(Object networkManager, Object connection, Object packet) {
if (reveivedFilters.contains(packet.getClass())) {
@SuppressWarnings("deprecation")
Integer id = invoker.getPacketID(packet);
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player sender = getInjector(networkManager, connection).getUpdatedPlayer();
PacketType type = PacketType.findLegacy(id, Sender.CLIENT);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = packetReceived(container, sender, readBufferedPackets.get(packet));
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
@Override
public Object packetQueued(Object networkManager, Object connection, Object packet) {
if (queuedFilters.contains(packet.getClass())) {
@SuppressWarnings("deprecation")
Integer id = invoker.getPacketID(packet);
// Check for ignored packets
if (ignoredPackets.remove(packet)) {
return packet;
}
Player reciever = getInjector(networkManager, connection).getUpdatedPlayer();
PacketType type = PacketType.findLegacy(id, Sender.SERVER);
PacketContainer container = new PacketContainer(type, packet);
PacketEvent event = packetQueued(container, reciever);
if (!event.isCancelled())
return event.getPacket().getHandle();
else
return null; // Cancel
}
// Don't change anything
return packet;
}
/**
* 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.
*/
PacketEvent packetQueued(PacketContainer packet, Player receiver) {
PacketEvent event = PacketEvent.fromServer(this, packet, 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.
* @return The packet event that was used.
*/
PacketEvent packetReceived(PacketContainer packet, Player sender, byte[] buffered) {
NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event);
return event;
}
/**
* Called when a player has logged in properly.
* @param player - the player that has logged in.
*/
void injectPlayer(Player player) {
try {
NetworkObjectInjector dummy = new NetworkObjectInjector(
filterImpossibleWarnings(reporter), player, invoker, null);
dummy.initializePlayer(player);
// Save this player for the network manager
NetworkObjectInjector realInjector = networkManagerInjector.get(dummy.getNetworkManager());
if (realInjector != null) {
// Update all future references
realInjector.setUpdatedPlayer(player);
playerInjector.put(player, realInjector);
} else {
// Ah - in that case, save this injector
saveInjector(dummy.getNetworkManager(), dummy);
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot inject " + player);
}
}
/**
* Uninject the given player.
* @param player - the player to uninject.
*/
void uninjectPlayer(Player player) {
final NetworkObjectInjector injector = getInjector(player, false);
if (player != null && injector != null) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
@Override
public void run() {
cleanupInjector(injector);
}
}, CLEANUP_DELAY);
}
}
/**
* Invoked when a plugin wants to sent a packet.
* @param receiver - the packet receiver.
* @param packet - the packet to transmit.
* @param marker - the network marker object.
* @param filters - whether or not to invoke the packet listeners.
* @throws InvocationTargetException If anything went wrong.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(receiver, true);
// If TRUE, process this packet like any other
if (filters)
ignoredPackets.remove(packet.getHandle());
else
ignoredPackets.add(packet.getHandle());
networkObject.sendServerPacket(packet.getHandle(), marker, filters);
}
/**
* Invoked when a plugin wants to simulate receiving a packet.
* @param player - the supposed sender.
* @param mcPacket - the packet to receieve.
* @throws IllegalAccessException Reflection is not permitted.
* @throws InvocationTargetException Minecraft threw an exception.
*/
void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
NetworkObjectInjector networkObject = getInjector(player, true);
// We will always ignore this packet
ignoredPackets.add(mcPacket);
networkObject.processPacket(mcPacket);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void cleanupListener() {
Class<?> listenerClass = getSpigotListenerClass();
// Yeah ... there's no easy way to remove the listener
synchronized (listenerClass) {
try {
Field listenersField = FieldUtils.getField(listenerClass, "listeners", true);
Field bakedField = FieldUtils.getField(listenerClass, "baked", true);
Map<Object, Plugin> listenerMap = (Map<Object, Plugin>) listenersField.get(null);
List<Object> listenerArray = Lists.newArrayList((Object[]) bakedField.get(null));
listenerMap.remove(dynamicListener);
listenerArray.remove(dynamicListener);
// Save the array back
bakedField.set(null, Iterables.toArray(listenerArray, (Class)listenerClass));
// Success
dynamicListener = null;
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_CLEANUP_SPIGOT).
callerParam(dynamicListener).error(e));
}
}
}
/**
* Invoked when the server is cleaning up.
*/
public void cleanupAll() {
// Cleanup the Spigot listener
if (dynamicListener != null) {
cleanupListener();
}
if (backgroundId >= 0) {
Bukkit.getScheduler().cancelTask(backgroundId);
backgroundId = -1;
}
// Cleanup network marker
if (proxyPacketInjector != null) {
proxyPacketInjector.cleanupAll();
}
}
}
@@ -1,34 +0,0 @@
package com.comphenix.protocol.injector.spigot;
/**
* Represents a proxy for a Spigot packet listener.
*
* @author Kristian
*/
interface SpigotPacketListener {
/**
* Called when a packet has been received and is about to be handled by the
* current Connection.
* <p>
* The returned packet will be the packet passed on for handling, or in the case of
* null being returned, not handled at all.
*
* @param networkManager - the NetworkManager receiving the packet
* @param connection - the connection which will handle the packet
* @param packet - the received packet
* @return the packet to be handled, or null to cancel
*/
public Object packetReceived(Object networkManager, Object connection, Object packet);
/**
* Called when a packet is queued to be sent.
* <p>
* The returned packet will be the packet sent. In the case of null being returned,
* the packet will not be sent.
* @param networkManager - the NetworkManager which will send the packet
* @param connection - the connection which queued the packet
* @param packet - the queue packet
* @return the packet to be sent, or null if the packet will not be sent.
*/
public Object packetQueued(Object networkManager, Object connection, Object packet);
}
@@ -2,7 +2,6 @@ package com.comphenix.protocol.metrics;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.craftbukkit.libs.org.apache.commons.lang3.tuple.Pair;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
@@ -174,10 +173,10 @@ public class Metrics {
JSONObject data = new JSONObject();
String pluginName = plugin.getDescription().getName();
Pair<String, String> pluginVersion = Statistics.splitVersion(); // ProtocolLib - remove build number
String pluginVersion = Statistics.getVersion(); // ProtocolLib - remove build number
data.put("pluginName", pluginName); // Append the name of the plugin
data.put("pluginVersion", pluginVersion.getLeft()); // Append the version of the plugin
data.put("pluginVersion", pluginVersion); // Append the version of the plugin
JSONArray customCharts = new JSONArray();
for (CustomChart customChart : charts) {
// Add the data of the custom charts
@@ -253,9 +252,9 @@ public class Metrics {
for (RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {
try {
pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider()));
} catch (Exception ignored) { }
} catch (Throwable ignored) { }
}
} catch (Exception ignored) { }
} catch (Throwable ignored) { }
}
data.put("plugins", pluginData);
@@ -21,7 +21,9 @@ import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketListener;
import org.bukkit.craftbukkit.libs.org.apache.commons.lang3.tuple.Pair;
import com.comphenix.protocol.utility.Util;
import org.bukkit.Bukkit;
import java.io.IOException;
import java.util.HashMap;
@@ -36,22 +38,45 @@ public class Statistics {
metrics = new Metrics(plugin);
metrics.logFailedRequests(plugin.getProtocolConfig().isDebug());
// Determine who is using this library
addPluginUserGraph(metrics);
addCustomGraphs(metrics);
}
private void addPluginUserGraph(Metrics metrics) {
metrics.addCustomChart(new Metrics.AdvancedPie("Plugin Users", this::getPluginUsers));
metrics.addCustomChart(new Metrics.SimplePie("buildVersion", () -> splitVersion().getRight()));
private void addCustomGraphs(Metrics metrics) {
metrics.addCustomChart(new Metrics.AdvancedPie("pluginUsers", this::getPluginUsers));
metrics.addCustomChart(new Metrics.SimplePie("buildVersion", this::getBuildNumber));
metrics.addCustomChart(new Metrics.SimplePie("serverBrand", this::getServerBrand));
}
public static Pair<String, String> splitVersion() {
private String getServerBrand() {
// spigot doesn't overwrite the serverName, but paper does
if (!Bukkit.getServer().getName().equals("CraftBukkit")) {
return Bukkit.getServer().getName();
} else if (Util.isUsingSpigot()) {
return "Spigot";
} else {
return "CraftBukkit";
}
}
private String getBuildNumber() {
String version = ProtocolLibrary.getPlugin().getDescription().getVersion();
if (version.contains("-b")) {
String[] split = version.split("-b");
return Pair.of(split[0], split[1]);
return split[1];
} else if (!version.contains("SNAPSHOT")) {
return "Release";
} else {
return Pair.of(version, "Unknown");
return "Unknown";
}
}
public static String getVersion() {
String version = ProtocolLibrary.getPlugin().getDescription().getVersion();
if (version.contains("-b")) {
String[] split = version.split("-b");
return split[0];
} else {
return version;
}
}
@@ -61,13 +86,7 @@ public class Statistics {
for (PacketListener listener : ProtocolLibrary.getProtocolManager().getPacketListeners()) {
String name = PacketAdapter.getPluginName(listener);
// Increment occurence
if (!users.containsKey(name)) {
users.put(name, 1);
} else {
users.put(name, users.get(name) + 1);
}
users.put(name, 1);
}
return users;
@@ -6,8 +6,11 @@ import java.util.List;
import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes;
import com.google.common.collect.Lists;
import net.sf.cglib.asm.*;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
public class ClassAnalyser {
/**
@@ -26,11 +29,11 @@ public class ClassAnalyser {
public static AsmOpcodes fromIntOpcode(int opcode) {
switch (opcode) {
case $Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL;
case $Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL;
case $Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC;
case $Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE;
case $Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC;
case Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL;
case Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL;
case Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC;
case Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE;
case Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC;
default: throw new IllegalArgumentException("Unknown opcode: " + opcode);
}
}
@@ -105,18 +108,18 @@ public class ClassAnalyser {
* @throws IOException Cannot access the parent class.
*/
private List<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException {
final $ClassReader reader = new $ClassReader(clazz.getCanonicalName());
final ClassReader reader = new ClassReader(clazz.getCanonicalName());
final List<AsmMethod> output = Lists.newArrayList();
// The method we are looking for
final String methodName = method.getName();
final String methodDescription = $Type.getMethodDescriptor(method);
final String methodDescription = Type.getMethodDescriptor(method);
reader.accept(new $ClassVisitor($Opcodes.ASM5) {
reader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public $MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (methodName.equals(name) && methodDescription.equals(desc)) {
return new $MethodVisitor($Opcodes.ASM5) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean flag) {
output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc));
@@ -126,7 +129,7 @@ public class ClassAnalyser {
return null;
}
}, $ClassReader.EXPAND_FRAMES);
}, ClassReader.EXPAND_FRAMES);
return output;
}
}
@@ -117,12 +117,11 @@ public class ObjectWriter {
}
// Copy private fields underneath
Class<?> superclass = commonType.getSuperclass();
if (superclass != null && !superclass.equals(Object.class)) {
copyToInternal(source, destination, superclass, false);
}
// Class<?> superclass = commonType.getSuperclass();
//
// if (superclass != null && !superclass.equals(Object.class)) {
// copyToInternal(source, destination, superclass, false);
// }
} catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
}
@@ -19,6 +19,8 @@ package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
@@ -81,7 +83,7 @@ public class StructureModifier<TField> {
providers.addAll(DefaultInstances.DEFAULT.getRegistered());
return DefaultInstances.fromCollection(providers);
}
/**
* Creates a structure modifier.
* @param targetType - the structure to modify.
@@ -458,7 +460,16 @@ public class StructureModifier<TField> {
return this;
}
private static Type[] getParamTypes(Field field) {
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
return ((ParameterizedType) genericType).getActualTypeArguments();
}
return new Class<?>[0];
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param <T> Type
@@ -466,8 +477,20 @@ public class StructureModifier<TField> {
* @param converter - converts objects into the given type.
* @return A structure modifier for fields of this type.
*/
@SuppressWarnings("unchecked")
public <T> StructureModifier<T> withType(Class fieldType, EquivalentConverter<T> converter) {
return withParamType(fieldType, converter);
}
/**
* Retrieves a structure modifier that only reads and writes fields of a given type.
* @param <T> Type
* @param fieldType - the type, or supertype, of every field to modify.
* @param converter - converts objects into the given type.
* @param paramTypes - field type parameters
* @return A structure modifier for fields of this type.
*/
@SuppressWarnings("unchecked")
public <T> StructureModifier<T> withParamType(Class fieldType, EquivalentConverter<T> converter, Class... paramTypes) {
if (fieldType == null) {
// It's not supported in this version, so return an empty modifier
return new StructureModifier<T>() {
@@ -489,11 +512,13 @@ public class StructureModifier<TField> {
for (Field field : data) {
if (fieldType.isAssignableFrom(field.getType())) {
filtered.add(field);
// Don't use the original index
if (defaultFields.containsKey(field))
defaults.put(field, index);
if (paramTypes.length == 0 || Arrays.equals(getParamTypes(field), paramTypes)) {
filtered.add(field);
// Don't use the original index
if (defaultFields.containsKey(field))
defaults.put(field, index);
}
}
// Keep track of the field index
@@ -289,6 +289,7 @@ public final class Accessors {
* @return The method accessor.
*/
public static ConstructorAccessor getConstructorAccessor(final Constructor<?> constructor) {
constructor.setAccessible(true); // let us in even if we are not allowed to
return new DefaultConstrutorAccessor(constructor);
}
@@ -256,11 +256,11 @@ public class AggregateCloner implements Cloner {
if (index < cloners.size()) {
Cloner cloner = cloners.get(index);
try {
return cloner.clone(source);
} catch (Exception ex) {
throw new RuntimeException("Failed to clone " + source + " (" + source.getClass() + ") with " + cloner, ex);
}
// try {
return cloner.clone(source);
// } catch (Exception ex) {
// throw new RuntimeException("Failed to clone " + source + " (" + source.getClass() + ") with " + cloner, ex);
// }
}
// Damn - failure
@@ -47,16 +47,17 @@ public class BukkitCloner implements Cloner {
if (nmsClass != null) {
CLONERS.put(nmsClass, nmsObject -> fromHandle.apply(nmsObject).deepClone().getHandle());
}
} catch (RuntimeException ignored) { }
} catch (Throwable ignored) { }
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static void fromConverter(Supplier<Class<?>> getClass, EquivalentConverter converter) {
try {
Class<?> nmsClass = getClass.get();
if (nmsClass != null) {
CLONERS.put(nmsClass, nmsObject -> converter.getGeneric(converter.getSpecific(nmsObject)));
}
} catch (RuntimeException ignored) { }
} catch (Throwable ignored) { }
}
private static void fromManual(Supplier<Class<?>> getClass, Function<Object, Object> cloner) {
@@ -65,7 +66,7 @@ public class BukkitCloner implements Cloner {
if (nmsClass != null) {
CLONERS.put(nmsClass, cloner);
}
} catch (RuntimeException ignored) { }
} catch (Throwable ignored) { }
}
static {
@@ -80,9 +81,18 @@ public class BukkitCloner implements Cloner {
fromManual(MinecraftReflection::getNonNullListClass, source -> nonNullListCloner().clone(source));
fromWrapper(MinecraftReflection::getNBTBaseClass, NbtFactory::fromNMS);
fromWrapper(MinecraftReflection::getIChatBaseComponentClass, WrappedChatComponent::fromHandle);
fromManual(ComponentConverter::getBaseComponentArrayClass, source ->
ComponentConverter.clone((BaseComponent[]) source));
fromWrapper(WrappedVillagerData::getNmsClass, WrappedVillagerData::fromHandle);
fromConverter(MinecraftReflection::getSectionPositionClass, BukkitConverters.getSectionPositionConverter());
try {
fromManual(ComponentConverter::getBaseComponentArrayClass, source ->
ComponentConverter.clone((BaseComponent[]) source));
} catch (Throwable ignored) { }
try {
fromManual(AdventureComponentConverter::getComponentClass, source ->
AdventureComponentConverter.clone(source));
} catch (Throwable ignored) { }
}
private Function<Object, Object> findCloner(Class<?> type) {
@@ -62,22 +62,30 @@ public class ImmutableDetector implements Cloner {
add(MinecraftReflection::getDataWatcherSerializerClass);
add(MinecraftReflection::getBlockClass);
add(MinecraftReflection::getItemClass);
add("SoundEffect");
add("sounds.SoundEffect", "SoundEffect");
if (MinecraftVersion.atOrAbove(MinecraftVersion.AQUATIC_UPDATE)) {
if (MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
add(MinecraftReflection::getFluidTypeClass);
add(MinecraftReflection::getParticleTypeClass);
add("Particle");
add("core.particles.Particle", "Particle");
}
if (MinecraftVersion.atOrAbove(MinecraftVersion.VILLAGE_UPDATE)) {
add("EntityTypes");
add("VillagerType");
add("VillagerProfession");
if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) {
add(MinecraftReflection::getEntityTypes);
add("world.entity.npc.VillagerType", "VillagerType");
add("world.entity.npc.VillagerProfession", "VillagerProfession");
}
// TODO automatically detect the technically-not-an-enum enums that Mojang is so fond of
// Would also probably go in tandem with having the FieldCloner use this
if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) {
add("core.IRegistry", "IRegistry");
}
if (MinecraftVersion.NETHER_UPDATE_2.atOrAbove()) {
add(MinecraftReflection::getResourceKey);
}
}
private static void add(Supplier<Class<?>> getClass) {
@@ -89,13 +97,11 @@ public class ImmutableDetector implements Cloner {
} catch (RuntimeException ignored) { }
}
private static void add(String className) {
try {
Class<?> clazz = MinecraftReflection.getMinecraftClass(className);
if (clazz != null) {
immutableNMS.add(clazz);
}
} catch (RuntimeException ignored) { }
private static void add(String className, String... aliases) {
Class<?> clazz = MinecraftReflection.getNullableNMS(className, aliases);
if (clazz != null) {
immutableNMS.add(clazz);
}
}
@Override
@@ -76,6 +76,8 @@ public class BackgroundCompiler {
private ExecutorService executor;
private ErrorReporter reporter;
private final Object unknownPermGenBean = new Object();
private Object permGenBean = unknownPermGenBean;
private double disablePermGenFraction = DEFAULT_DISABLE_AT_PERM_GEN;
/**
@@ -295,11 +297,21 @@ public class BackgroundCompiler {
* @return Usage of the perm gen space.
*/
private double getPermGenUsage() {
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
if (item.getName().contains("Perm Gen")) {
MemoryUsage usage = item.getUsage();
return usage.getUsed() / (double) usage.getCommitted();
Object permGenBean = this.permGenBean;
if (permGenBean == unknownPermGenBean) {
for (MemoryPoolMXBean item : ManagementFactory.getMemoryPoolMXBeans()) {
if (item.getName().contains("Perm Gen")) {
permGenBean = this.permGenBean = item;
break;
}
}
if (permGenBean == unknownPermGenBean) {
permGenBean = this.permGenBean = null;
}
}
if (permGenBean != null) {
MemoryUsage usage = ((MemoryPoolMXBean) permGenBean).getUsage();
return usage.getUsed() / (double) usage.getCommitted();
}
// Unknown
@@ -17,25 +17,25 @@
package com.comphenix.protocol.reflect.compiler;
import net.sf.cglib.asm.$MethodVisitor;
import net.sf.cglib.asm.$Opcodes;
import net.sf.cglib.asm.$Type;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
/**
* Used by the compiler to automatically box and unbox values.
*/
class BoxingHelper {
private final static $Type BYTE_$Type = $Type.getObjectType("java/lang/Byte");
private final static $Type BOOLEAN_$Type = $Type.getObjectType("java/lang/Boolean");
private final static $Type SHORT_$Type = $Type.getObjectType("java/lang/Short");
private final static $Type CHARACTER_$Type = $Type.getObjectType("java/lang/Character");
private final static $Type INTEGER_$Type = $Type.getObjectType("java/lang/Integer");
private final static $Type FLOAT_$Type = $Type.getObjectType("java/lang/Float");
private final static $Type LONG_$Type = $Type.getObjectType("java/lang/Long");
private final static $Type DOUBLE_$Type = $Type.getObjectType("java/lang/Double");
private final static $Type NUMBER_$Type = $Type.getObjectType("java/lang/Number");
private final static $Type OBJECT_$Type = $Type.getObjectType("java/lang/Object");
private final static Type BYTE_Type = Type.getObjectType("java/lang/Byte");
private final static Type BOOLEAN_Type = Type.getObjectType("java/lang/Boolean");
private final static Type SHORT_Type = Type.getObjectType("java/lang/Short");
private final static Type CHARACTER_Type = Type.getObjectType("java/lang/Character");
private final static Type INTEGER_Type = Type.getObjectType("java/lang/Integer");
private final static Type FLOAT_Type = Type.getObjectType("java/lang/Float");
private final static Type LONG_Type = Type.getObjectType("java/lang/Long");
private final static Type DOUBLE_Type = Type.getObjectType("java/lang/Double");
private final static Type NUMBER_Type = Type.getObjectType("java/lang/Number");
private final static Type OBJECT_Type = Type.getObjectType("java/lang/Object");
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
@@ -44,9 +44,9 @@ class BoxingHelper {
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
private $MethodVisitor mv;
private MethodVisitor mv;
public BoxingHelper($MethodVisitor mv) {
public BoxingHelper(MethodVisitor mv) {
this.mv = mv;
}
@@ -54,42 +54,42 @@ class BoxingHelper {
* Generates the instructions to box the top stack value. This value is
* replaced by its boxed equivalent on top of the stack.
*
* @param type the $Type of the top stack value.
* @param type the Type of the top stack value.
*/
public void box(final $Type type){
if(type.getSort() == $Type.OBJECT || type.getSort() == $Type.ARRAY) {
public void box(final Type type){
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return;
}
if(type == $Type.VOID_TYPE) {
if(type == Type.VOID_TYPE) {
push((String) null);
} else {
$Type boxed = type;
Type boxed = type;
switch(type.getSort()) {
case $Type.BYTE:
boxed = BYTE_$Type;
case Type.BYTE:
boxed = BYTE_Type;
break;
case $Type.BOOLEAN:
boxed = BOOLEAN_$Type;
case Type.BOOLEAN:
boxed = BOOLEAN_Type;
break;
case $Type.SHORT:
boxed = SHORT_$Type;
case Type.SHORT:
boxed = SHORT_Type;
break;
case $Type.CHAR:
boxed = CHARACTER_$Type;
case Type.CHAR:
boxed = CHARACTER_Type;
break;
case $Type.INT:
boxed = INTEGER_$Type;
case Type.INT:
boxed = INTEGER_Type;
break;
case $Type.FLOAT:
boxed = FLOAT_$Type;
case Type.FLOAT:
boxed = FLOAT_Type;
break;
case $Type.LONG:
boxed = LONG_$Type;
case Type.LONG:
boxed = LONG_Type;
break;
case $Type.DOUBLE:
boxed = DOUBLE_$Type;
case Type.DOUBLE:
boxed = DOUBLE_Type;
break;
}
@@ -105,46 +105,46 @@ class BoxingHelper {
swap();
}
invokeConstructor(boxed, new MethodDescriptor("<init>", $Type.VOID_TYPE, new $Type[] {type}));
invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
}
}
/**
* Generates the instruction to invoke a constructor.
*
* @param $Type the class in which the constructor is defined.
* @param Type the class in which the constructor is defined.
* @param method the constructor to be invoked.
*/
public void invokeConstructor(final $Type $Type, final MethodDescriptor method){
invokeInsn($Opcodes.INVOKESPECIAL, $Type, method);
public void invokeConstructor(final Type Type, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKESPECIAL, Type, method);
}
/**
* Generates a DUP_X1 instruction.
*/
public void dupX1(){
mv.visitInsn($Opcodes.DUP_X1);
mv.visitInsn(Opcodes.DUP_X1);
}
/**
* Generates a DUP_X2 instruction.
*/
public void dupX2(){
mv.visitInsn($Opcodes.DUP_X2);
mv.visitInsn(Opcodes.DUP_X2);
}
/**
* Generates a POP instruction.
*/
public void pop(){
mv.visitInsn($Opcodes.POP);
mv.visitInsn(Opcodes.POP);
}
/**
* Generates a SWAP instruction.
*/
public void swap(){
mv.visitInsn($Opcodes.SWAP);
mv.visitInsn(Opcodes.SWAP);
}
/**
@@ -163,11 +163,11 @@ class BoxingHelper {
*/
public void push(final int value) {
if (value >= -1 && value <= 5) {
mv.visitInsn($Opcodes.ICONST_0 + value);
mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn($Opcodes.BIPUSH, value);
mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn($Opcodes.SIPUSH, value);
mv.visitIntInsn(Opcodes.SIPUSH, value);
} else {
mv.visitLdcInsn(new Integer(value));
}
@@ -176,10 +176,10 @@ class BoxingHelper {
/**
* Generates the instruction to create a new object.
*
* @param $Type the class of the object to be created.
* @param Type the class of the object to be created.
*/
public void newInstance(final $Type $Type){
$TypeInsn($Opcodes.NEW, $Type);
public void newInstance(final Type Type){
TypeInsn(Opcodes.NEW, Type);
}
/**
@@ -189,7 +189,7 @@ class BoxingHelper {
*/
public void push(final String value) {
if (value == null) {
mv.visitInsn($Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.ACONST_NULL);
} else {
mv.visitLdcInsn(value);
}
@@ -200,35 +200,35 @@ class BoxingHelper {
* replaced by its unboxed equivalent on top of the stack.
*
* @param type
* the $Type of the top stack value.
* the Type of the top stack value.
*/
public void unbox(final $Type type){
$Type t = NUMBER_$Type;
public void unbox(final Type type){
Type t = NUMBER_Type;
MethodDescriptor sig = null;
switch(type.getSort()) {
case $Type.VOID:
case Type.VOID:
return;
case $Type.CHAR:
t = CHARACTER_$Type;
case Type.CHAR:
t = CHARACTER_Type;
sig = CHAR_VALUE;
break;
case $Type.BOOLEAN:
t = BOOLEAN_$Type;
case Type.BOOLEAN:
t = BOOLEAN_Type;
sig = BOOLEAN_VALUE;
break;
case $Type.DOUBLE:
case Type.DOUBLE:
sig = DOUBLE_VALUE;
break;
case $Type.FLOAT:
case Type.FLOAT:
sig = FLOAT_VALUE;
break;
case $Type.LONG:
case Type.LONG:
sig = LONG_VALUE;
break;
case $Type.INT:
case $Type.SHORT:
case $Type.BYTE:
case Type.INT:
case Type.SHORT:
case Type.BYTE:
sig = INT_VALUE;
}
@@ -242,13 +242,13 @@ class BoxingHelper {
/**
* Generates the instruction to check that the top stack value is of the
* given $Type.
* given Type.
*
* @param $Type a class or interface $Type.
* @param Type a class or interface Type.
*/
public void checkCast(final $Type $Type){
if(!$Type.equals(OBJECT_$Type)) {
$TypeInsn($Opcodes.CHECKCAST, $Type);
public void checkCast(final Type Type){
if(!Type.equals(OBJECT_Type)) {
TypeInsn(Opcodes.CHECKCAST, Type);
}
}
@@ -258,35 +258,35 @@ class BoxingHelper {
* @param owner the class in which the method is defined.
* @param method the method to be invoked.
*/
public void invokeVirtual(final $Type owner, final MethodDescriptor method){
invokeInsn($Opcodes.INVOKEVIRTUAL, owner, method);
public void invokeVirtual(final Type owner, final MethodDescriptor method){
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
}
/**
* Generates an invoke method instruction.
*
* @param opcode the instruction's opcode.
* @param $Type the class in which the method is defined.
* @param type the class in which the method is defined.
* @param method the method to be invoked.
*/
private void invokeInsn(final int opcode, final $Type $Type, final MethodDescriptor method){
String owner = $Type.getSort() == $Type.ARRAY ? $Type.getDescriptor() : $Type.getInternalName();
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
}
/**
* Generates a $Type dependent instruction.
* Generates a Type dependent instruction.
*
* @param opcode the instruction's opcode.
* @param $Type the instruction's operand.
* @param type the instruction's operand.
*/
private void $TypeInsn(final int opcode, final $Type $Type){
private void TypeInsn(final int opcode, final Type type){
String desc;
if($Type.getSort() == $Type.ARRAY) {
desc = $Type.getDescriptor();
if(type.getSort() == Type.ARRAY) {
desc = type.getDescriptor();
} else {
desc = $Type.getInternalName();
desc = type.getInternalName();
}
mv.visitTypeInsn(opcode, desc);
@@ -20,7 +20,7 @@ package com.comphenix.protocol.reflect.compiler;
import java.util.HashMap;
import java.util.Map;
import net.sf.cglib.asm.$Type;
import net.bytebuddy.jar.asm.Type;
/**
* Represents a method.
@@ -75,10 +75,10 @@ class MethodDescriptor {
*/
public MethodDescriptor(
final String name,
final $Type returnType,
final $Type[] argumentTypes)
final Type returnType,
final Type[] argumentTypes)
{
this(name, $Type.getMethodDescriptor(returnType, argumentTypes));
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
}
/**
@@ -206,8 +206,8 @@ class MethodDescriptor {
*
* @return the return type of the method described by this object.
*/
public $Type getReturnType() {
return $Type.getReturnType(desc);
public Type getReturnType() {
return Type.getReturnType(desc);
}
/**
@@ -215,8 +215,8 @@ class MethodDescriptor {
*
* @return the argument types of the method described by this object.
*/
public $Type[] getArgumentTypes() {
return $Type.getArgumentTypes(desc);
public Type[] getArgumentTypes() {
return Type.getArgumentTypes(desc);
}
public String toString() {
@@ -25,19 +25,13 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.asm.$ClassWriter;
import net.sf.cglib.asm.$FieldVisitor;
import net.sf.cglib.asm.$Label;
import net.sf.cglib.asm.$MethodVisitor;
import net.sf.cglib.asm.$Opcodes;
import net.sf.cglib.asm.$Type;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects;
import com.google.common.primitives.Primitives;
import net.bytebuddy.jar.asm.*;
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
//
@@ -147,6 +141,18 @@ public final class StructureCompiler {
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
// On java 9+ (53.0+) CLassLoader#defineClass(String, byte[], int, int) should not be used anymore.
// It will throw warnings and on Java 16+ (60.0+), it does not work at all anymore.
private static final boolean LEGACY_CLASS_DEFINITION =
Float.parseFloat(System.getProperty("java.class.version")) < 53;
/**
* The MethodHandles.Lookup object for this compiler. Only used when using the modern defineClass strategy.
*/
private Object lookup = null;
// Used to get the MethodHandles.Lookup object on newer versions of Java.
private volatile static Method lookupMethod;
public static boolean attemptClassLoad = false;
/**
@@ -276,15 +282,15 @@ public final class StructureCompiler {
*/
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
$ClassWriter cw = new $ClassWriter(0);
ClassWriter cw = new ClassWriter(0);
Class<?> targetType = source.getTargetType();
String className = getCompiledName(source);
String targetSignature = $Type.getDescriptor(targetType);
String targetSignature = Type.getDescriptor(targetType);
String targetName = targetType.getName().replace('.', '/');
// Define class
cw.visit($Opcodes.V1_6, $Opcodes.ACC_PUBLIC + $Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
null, COMPILED_CLASS, null);
createFields(cw, targetSignature);
@@ -295,36 +301,54 @@ public final class StructureCompiler {
byte[] data = cw.toByteArray();
// Call the define method
Class<?> clazz = defineClass(data);
// DEBUG CODE: Print the content of the generated class.
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
return clazz;
}
private Class<?> defineClassLegacy(byte[] data) throws InvocationTargetException, IllegalAccessException,
NoSuchMethodException {
if (defineMethod == null) {
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
new Class<?>[]{String.class, byte[].class, int.class, int.class});
// Awesome. Now, create and return it.
defined.setAccessible(true);
defineMethod = defined;
}
return (Class<?>) defineMethod.invoke(loader, null, data, 0, data.length);
}
private Class<?> defineClassModern(byte[] data) throws InvocationTargetException, IllegalAccessException,
ClassNotFoundException, NoSuchMethodException {
if (defineMethod == null) {
defineMethod = Class.forName("java.lang.invoke.MethodHandles$Lookup")
.getDeclaredMethod("defineClass", byte[].class);
}
if (lookupMethod == null) {
lookupMethod = Class.forName("java.lang.invoke.MethodHandles").getDeclaredMethod("lookup");
}
if (lookup == null)
lookup = lookupMethod.invoke(null);
return (Class<?>) defineMethod.invoke(lookup, data);
}
private Class<?> defineClass(byte[] data) {
try {
if (defineMethod == null) {
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
new Class<?>[] { String.class, byte[].class, int.class, int.class });
// Awesome. Now, create and return it.
defined.setAccessible(true);
defineMethod = defined;
}
@SuppressWarnings("rawtypes")
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
// DEBUG CODE: Print the content of the generated class.
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
return clazz;
return LEGACY_CLASS_DEFINITION ? defineClassLegacy(data) : defineClassModern(data);
} catch (SecurityException e) {
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
} catch (NoSuchMethodException e) {
} catch (NoSuchMethodException | ClassNotFoundException e) {
throw new IllegalStateException("Incompatible JVM.", e);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Security limitation! Cannot dynamically load class.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in code generator.", e);
throw new RuntimeException("Error occurred in code generator.", e);
}
}
@@ -352,35 +376,35 @@ public final class StructureCompiler {
return !Modifier.isFinal(field.getModifiers());
}
private void createFields($ClassWriter cw, String targetSignature) {
$FieldVisitor typedField = cw.visitField($Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
private void createFields(ClassWriter cw, String targetSignature) {
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
typedField.visitEnd();
}
private void createWriteMethod($ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + "<Ljava/lang/Object;>;";
$MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
new String[] { FIELD_EXCEPTION_CLASS });
BoxingHelper boxingHelper = new BoxingHelper(mv);
String generatedClassName = PACKAGE_NAME + "/" + className;
mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitFieldInsn($Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
mv.visitVarInsn($Opcodes.ASTORE, 3);
mv.visitVarInsn($Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ASTORE, 3);
mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last $Label is for the default switch
$Label[] $Labels = new $Label[fields.size()];
$Label error$Label = new $Label();
$Label return$Label = new $Label();
Label[] $Labels = new Label[fields.size()];
Label error$Label = new Label();
Label return$Label = new Label();
// Generate $Labels
for (int i = 0; i < fields.size(); i++) {
$Labels[i] = new $Label();
$Labels[i] = new Label();
}
mv.visitTableSwitchInsn(0, $Labels.length - 1, error$Label, $Labels);
@@ -390,82 +414,82 @@ public final class StructureCompiler {
Field field = fields.get(i);
Class<?> outputType = field.getType();
Class<?> inputType = Primitives.wrap(outputType);
String typeDescriptor = $Type.getDescriptor(outputType);
String typeDescriptor = Type.getDescriptor(outputType);
String inputPath = inputType.getName().replace('.', '/');
mv.visitLabel($Labels[i]);
// Push the compare object
if (i == 0)
mv.visitFrame($Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Only write to public non-final fields
if (isPublic(field) && isNonFinal(field)) {
mv.visitVarInsn($Opcodes.ALOAD, 3);
mv.visitVarInsn($Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ALOAD, 3);
mv.visitVarInsn(Opcodes.ALOAD, 2);
if (!outputType.isPrimitive())
mv.visitTypeInsn($Opcodes.CHECKCAST, inputPath);
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
else
boxingHelper.unbox($Type.getType(outputType));
boxingHelper.unbox(Type.getType(outputType));
mv.visitFieldInsn($Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor);
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor);
} else {
// Use reflection. We don't have a choice, unfortunately.
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ILOAD, 1);
mv.visitVarInsn($Opcodes.ALOAD, 2);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V");
}
mv.visitJumpInsn($Opcodes.GOTO, return$Label);
mv.visitJumpInsn(Opcodes.GOTO, return$Label);
}
mv.visitLabel(error$Label);
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn($Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn($Opcodes.DUP);
mv.visitTypeInsn($Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn($Opcodes.DUP);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn($Opcodes.ILOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn($Opcodes.ATHROW);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(return$Label);
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitInsn($Opcodes.ARETURN);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(5, 4);
mv.visitEnd();
}
private void createReadMethod($ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
$MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
BoxingHelper boxingHelper = new BoxingHelper(mv);
String generatedClassName = PACKAGE_NAME + "/" + className;
mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitFieldInsn($Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
mv.visitVarInsn($Opcodes.ASTORE, 2);
mv.visitVarInsn($Opcodes.ILOAD, 1);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ASTORE, 2);
mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last $Label is for the default switch
$Label[] $Labels = new $Label[fields.size()];
$Label error$Label = new $Label();
Label[] $Labels = new Label[fields.size()];
Label error$Label = new Label();
// Generate $Labels
for (int i = 0; i < fields.size(); i++) {
$Labels[i] = new $Label();
$Labels[i] = new Label();
}
mv.visitTableSwitchInsn(0, fields.size() - 1, error$Label, $Labels);
@@ -474,74 +498,74 @@ public final class StructureCompiler {
Field field = fields.get(i);
Class<?> outputType = field.getType();
String typeDescriptor = $Type.getDescriptor(outputType);
String typeDescriptor = Type.getDescriptor(outputType);
mv.visitLabel($Labels[i]);
// Push the compare object
if (i == 0)
mv.visitFrame($Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Note that byte code cannot access non-public fields
if (isPublic(field)) {
mv.visitVarInsn($Opcodes.ALOAD, 2);
mv.visitFieldInsn($Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor);
boxingHelper.box($Type.getType(outputType));
boxingHelper.box(Type.getType(outputType));
} else {
// We have to use reflection for private and protected fields.
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ILOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;");
}
mv.visitInsn($Opcodes.ARETURN);
mv.visitInsn(Opcodes.ARETURN);
}
mv.visitLabel(error$Label);
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn($Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn($Opcodes.DUP);
mv.visitTypeInsn($Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn($Opcodes.DUP);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn($Opcodes.ILOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn($Opcodes.ATHROW);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(5, 3);
mv.visitEnd();
}
private void createConstructor($ClassWriter cw, String className, String targetSignature, String targetName) {
$MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PUBLIC, "<init>",
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
"(L" + SUPER_CLASS + "<Ljava/lang/Object;>;L" + PACKAGE_NAME + "/StructureCompiler;)V", null);
String fullClassName = PACKAGE_NAME + "/" + className;
mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V");
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;");
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitFieldInsn($Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;");
mv.visitTypeInsn($Opcodes.CHECKCAST, targetName);
mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature);
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 2);
mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
mv.visitInsn($Opcodes.RETURN);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;");
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 3);
mv.visitEnd();
}
@@ -17,23 +17,20 @@
package com.comphenix.protocol.reflect.instances;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import javax.annotation.Nullable;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.ProtocolLogger;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
/**
* Used to construct default instances of any type.
* @author Kristian
*
*/
public class DefaultInstances implements InstanceProvider {
@@ -41,10 +38,13 @@ public class DefaultInstances implements InstanceProvider {
* Standard default instance provider.
*/
public static final DefaultInstances DEFAULT = DefaultInstances.fromArray(
PrimitiveGenerator.INSTANCE, CollectionGenerator.INSTANCE);
PrimitiveGenerator.INSTANCE,
CollectionGenerator.INSTANCE,
MinecraftGenerator.INSTANCE
);
/**
* The maximum height of the hierachy of creates types. Used to prevent cycles.
* The maximum height of the heirarchy of creates types. Used to prevent cycles.
*/
private int maximumRecursion = 20;
@@ -187,7 +187,7 @@ public class DefaultInstances implements InstanceProvider {
// Note that we don't allow recursive types - that is, types that
// require itself in the constructor.
if (types.length < lastCount) {
if (!contains(types, type)) {
if (!contains(types, type) && !contains(types, MinecraftReflection.getPacketDataSerializerClass())) {
if (nonNull) {
// Make sure all of these types are non-null
if (isAnyNull(types, providers, recursionLevel)) {
@@ -272,6 +272,7 @@ public class DefaultInstances implements InstanceProvider {
Constructor<T> minimum = getMinimumConstructor(type, providers, recursionLevel + 1);
// Create the type with this constructor using default values. This might fail, though.
try {
if (minimum != null) {
int parameterCount = minimum.getParameterTypes().length;
@@ -299,24 +300,6 @@ public class DefaultInstances implements InstanceProvider {
return null;
}
/**
* Construct default instances using the CGLIB enhancer object instead.
* @param enhancer - a CGLIB enhancer to use.
* @return A default instance generator that uses the CGLIB enhancer.
*/
public DefaultInstances forEnhancer(Enhancer enhancer) {
final Enhancer ex = enhancer;
return new DefaultInstances(this) {
@SuppressWarnings("unchecked")
@Override
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
// Use the enhancer instead
return (T) ex.create(types, params);
}
};
}
/**
* Used by the default instance provider to create a class from a given constructor.
* The default method uses reflection.
@@ -0,0 +1,77 @@
package com.comphenix.protocol.reflect.instances;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MinecraftGenerator {
// system unique id representation
public static final UUID SYS_UUID;
// minecraft default types
public static final Object AIR_ITEM_STACK;
private static Object DEFAULT_ENTITY_TYPES; // modern servers only (older servers will use an entity type id)
// minecraft method accessors
private static final MethodAccessor NON_NULL_LIST_CREATE;
// fast util mappings for paper relocation
private static final Map<Class<?>, Constructor<?>> FAST_MAP_CONSTRUCTORS;
static {
try {
SYS_UUID = new UUID(0L, 0L);
AIR_ITEM_STACK = BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.AIR));
FAST_MAP_CONSTRUCTORS = new ConcurrentHashMap<>();
NON_NULL_LIST_CREATE = MinecraftReflection.getNonNullListCreateAccessor();
} catch (Throwable ex) {
throw new RuntimeException("Failed to create static fields in MinecraftGenerator", ex);
}
}
public static final InstanceProvider INSTANCE = type -> {
if (type != null) {
if (type == UUID.class) {
return SYS_UUID;
} else if (type.isEnum()) {
return type.getEnumConstants()[0];
} else if (type == MinecraftReflection.getItemStackClass()) {
return AIR_ITEM_STACK;
} else if (type == MinecraftReflection.getEntityTypes()) {
if (DEFAULT_ENTITY_TYPES == null) {
// try to initialize now
try {
DEFAULT_ENTITY_TYPES = BukkitConverters.getEntityTypeConverter().getGeneric(EntityType.AREA_EFFECT_CLOUD);
} catch (Exception ignored) {
// not available in this version of minecraft
}
}
return DEFAULT_ENTITY_TYPES;
} else if (type.isAssignableFrom(Map.class)) {
Constructor<?> ctor = FAST_MAP_CONSTRUCTORS.computeIfAbsent(type, __ -> {
try {
String name = type.getCanonicalName();
if (name != null && name.contains("it.unimi.fastutils")) {
return Class.forName(name.substring(name.length() - 3) + "OpenHashMap").getConstructor();
}
} catch (Exception ignored) {}
return null;
});
if (ctor != null) {
try {
return ctor.newInstance();
} catch (ReflectiveOperationException ignored) {}
}
} else if (NON_NULL_LIST_CREATE != null && type == MinecraftReflection.getNonNullListClass()) {
return NON_NULL_LIST_CREATE.invoke(null);
}
}
return null;
};
}
@@ -113,7 +113,7 @@ public class TimingReportGenerator {
}
private String getPacketId(PacketType type) {
return Strings.padStart(Integer.toString(type.getCurrentId()), 2, '0') + " (Legacy: " + type.getLegacyId() + ")";
return Strings.padStart(Integer.toString(type.getCurrentId()), 2, '0');
}
/**
@@ -88,20 +88,15 @@ public final class SpigotUpdater extends Updater {
}
}
private static final String PROTOCOL = "https://";
private static final String RESOURCE_URL = PROTOCOL + "www.spigotmc.org/resources/protocollib.1997/";
private static final String API_URL = PROTOCOL + "www.spigotmc.org/api/general.php";
private static final String ACTION = "POST";
private static final int ID = 1997;
private static final byte[] API_KEY = ("key=98BE0FE67F88AB82B4C197FAF1DC3B69206EFDCC4D3B80FC83A00037510B99B4&resource=" + ID).getBytes(Charsets.UTF_8);
private static final String RESOURCE_URL = "https://www.spigotmc.org/resources/protocollib.1997/";
private static final String UPDATE_URL = "https://api.spigotmc.org/legacy/update.php?resource=1997";
private static final String ACTION = "GET";
public String getSpigotVersion() throws IOException {
try (Closer closer = Closer.create()) {
HttpURLConnection con = (HttpURLConnection) new URL(API_URL).openConnection();
HttpURLConnection con = (HttpURLConnection) new URL(UPDATE_URL).openConnection();
con.setDoOutput(true);
con.setRequestMethod(ACTION);
con.getOutputStream().write(API_KEY);
InputStreamReader isr = closer.register(new InputStreamReader(con.getInputStream()));
BufferedReader br = closer.register(new BufferedReader(isr));
@@ -271,21 +271,21 @@ public abstract class Updater {
private final String description;
private UpdateResult(String description) {
UpdateResult(String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
}
public static Updater create(ProtocolLib protocolLib, int id, File file, UpdateType type, boolean announce) {
public static Updater create(Plugin plugin, int id, File file, UpdateType type, boolean announce) {
if (Util.isUsingSpigot()) {
return new SpigotUpdater(protocolLib, type, announce);
return new SpigotUpdater(plugin, type, announce);
} else {
return new BukkitUpdater(protocolLib, id, file, type, announce);
return new BukkitUpdater(plugin, id, file, type, announce);
}
}
@@ -0,0 +1,65 @@
package com.comphenix.protocol.utility;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
/**
* Represents a shared ByteBuddy factory.
* @author Kristian
*/
public class ByteBuddyFactory {
private static ByteBuddyFactory INSTANCE = new ByteBuddyFactory();
// The current class loader
private ClassLoader loader = ByteBuddyFactory.class.getClassLoader();
public static ByteBuddyFactory getInstance() {
return INSTANCE;
}
/**
* Set the current class loader to use when constructing enhancers.
* @param loader - the class loader
*/
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
/**
* Get the current class loader we are using.
* @return The current class loader.
*/
public ClassLoader getClassLoader() {
return loader;
}
/**
* Creates a type builder for a subclass of a given {@link Class}.
* @param clz The class for which to create a subclass.
* @return A type builder for creating a new class extending the provided clz and implementing
* {@link ByteBuddyGenerated}.
*/
public <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz)
{
return new ByteBuddy()
.subclass(clz)
.implement(ByteBuddyGenerated.class);
}
/**
* Creates a type builder for a subclass of a given {@link Class}.
* @param clz The class for which to create a subclass.
* @param constructorStrategy The constructor strategy to use.
* @return A type builder for creating a new class extending the provided clz and implementing
* {@link ByteBuddyGenerated}.
*/
public <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz,
ConstructorStrategy.Default constructorStrategy)
{
return new ByteBuddy()
.subclass(clz, constructorStrategy)
.implement(ByteBuddyGenerated.class);
}
}
@@ -0,0 +1,9 @@
package com.comphenix.protocol.utility;
/**
* Represents an object that has been generated using ByteBuddy.
*
* @author Pim
*/
public interface ByteBuddyGenerated {
}
@@ -17,27 +17,21 @@
package com.comphenix.protocol.utility;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
/**
* Utility methods for sending chat messages.
@@ -46,15 +40,10 @@ import com.google.common.collect.Iterables;
*/
public class ChatExtensions {
// Used to sent chat messages
private ProtocolManager manager;
// The chat packet constructor
private static volatile PacketConstructor chatConstructor;
// Whether or not we have to use the post-1.6.1 chat format
private static volatile Constructor<?> jsonConstructor = getJsonFormatConstructor();
private static volatile MethodAccessor messageFactory;
private final ProtocolManager manager;
private static final UUID SERVER_UUID = new UUID(0L, 0L);
public ChatExtensions(ProtocolManager manager) {
this.manager = manager;
}
@@ -100,53 +89,26 @@ public class ChatExtensions {
* @param message - the message to send.
* @return The packets.
*/
public static PacketContainer[] createChatPackets(String message) {
if (jsonConstructor != null) {
if (chatConstructor == null) {
Class<?> messageClass = jsonConstructor.getParameterTypes()[0];
chatConstructor = PacketConstructor.DEFAULT.withPacket(PacketType.Play.Server.CHAT, new Object[] { messageClass });
// Try one of the string constructors
if (MinecraftReflection.isUsingNetty()) {
messageFactory = Accessors.getMethodAccessor(
MinecraftReflection.getCraftMessageClass(), "fromString", String.class);
} else {
messageFactory = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(messageClass).getMethod(
FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
parameterCount(1).
parameterExactType(String.class).
returnTypeMatches(FuzzyMatchers.matchParent()).
build())
);
}
}
// Minecraft 1.7.2 and later
if (MinecraftReflection.isUsingNetty()) {
Object[] components = (Object[]) messageFactory.invoke(null, message);
PacketContainer[] packets = new PacketContainer[components.length];
for (int i = 0; i < components.length; i++) {
packets[i] = chatConstructor.createPacket(components[i]);
}
return packets;
// Minecraft 1.6.1 - 1.6.4
} else {
return new PacketContainer[] { chatConstructor.createPacket(messageFactory.invoke(null, message)) };
}
} else {
if (chatConstructor == null) {
chatConstructor = PacketConstructor.DEFAULT.withPacket(PacketType.Play.Server.CHAT, new Object[] { message });
}
// Minecraft 1.6.0 and earlier
return new PacketContainer[] { chatConstructor.createPacket(message) };
public static List<PacketContainer> createChatPackets(String message) {
List<PacketContainer> packets = new ArrayList<>();
WrappedChatComponent[] components = WrappedChatComponent.fromChatMessage(message);
for (WrappedChatComponent component : components) {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.CHAT);
packet.getChatComponents().write(0, component);
// 1.12+
packet.getChatTypes().writeSafely(0, EnumWrappers.ChatType.SYSTEM);
// 1.8-1.12
packet.getBytes().writeSafely(0, (byte) 1);
// 1.16+
packet.getUUIDs().writeSafely(0, SERVER_UUID);
packets.add(packet);
}
return packets;
}
/**
* Broadcast a message without invoking any packet listeners.
* @param message - message to send.
@@ -203,27 +165,11 @@ public class ChatExtensions {
int current = 0;
// Find the longest line
for (int i = 0; i < lines.length; i++) {
if (current < lines[i].length())
current = lines[i].length();
for (String line : lines) {
if (current < line.length())
current = line.length();
}
return current;
}
/**
* Retrieve a constructor for post-1.6.1 chat packets.
* @return A constructor for JSON-based packets.
*/
static Constructor<?> getJsonFormatConstructor() {
Class<?> chatPacket = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.CHAT, true);
List<Constructor<?>> list = FuzzyReflection.fromClass(chatPacket).getConstructorList(
FuzzyMethodContract.newBuilder().
parameterCount(1).
parameterMatches(MinecraftReflection.getMinecraftObjectMatcher()).
build()
);
// First element or NULL
return Iterables.getFirst(list, null);
}
}
@@ -21,10 +21,10 @@ package com.comphenix.protocol.utility;
*/
public final class Constants {
public static final String PACKAGE_VERSION = "v1_15_R1";
public static final String NMS = "net.minecraft.server." + PACKAGE_VERSION;
public static final String PACKAGE_VERSION = "v1_17_R1";
public static final String NMS = "net.minecraft";
public static final String OBC = "org.bukkit.craftbukkit." + PACKAGE_VERSION;
public static final MinecraftVersion CURRENT_VERSION = MinecraftVersion.BEE_UPDATE;
public static final MinecraftVersion CURRENT_VERSION = MinecraftVersion.CAVES_CLIFFS_1;
public static void init() {
MinecraftReflection.setMinecraftPackage(NMS, OBC);
@@ -1,44 +0,0 @@
package com.comphenix.protocol.utility;
import net.sf.cglib.proxy.Enhancer;
/**
* Represents a shared enchancer factory.
* @author Kristian
*/
public class EnhancerFactory {
private static EnhancerFactory INSTANCE = new EnhancerFactory();
// The current class loader
private ClassLoader loader = EnhancerFactory.class.getClassLoader();
public static EnhancerFactory getInstance() {
return INSTANCE;
}
/**
* Create a new CGLib enhancer.
* @return The new enhancer.
*/
public Enhancer createEnhancer() {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(loader);
return enhancer;
}
/**
* Set the current class loader to use when constructing enhancers.
* @param loader - the class loader
*/
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
/**
* Get the current class loader we are using.
* @return The current class loader.
*/
public ClassLoader getClassLoader() {
return loader;
}
}
@@ -1,7 +1,12 @@
package com.comphenix.protocol.utility;
import java.io.IOException;
import java.util.Map;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.google.common.base.Preconditions;
/**
@@ -12,6 +17,11 @@ import com.google.common.base.Preconditions;
public class HexDumper {
private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
/**
* Number of bytes before we do a hex dump.
*/
private static final int HEX_DUMP_THRESHOLD = 256;
// Default values
private int positionLength = 6;
private char[] positionSuffix = ": ".toCharArray();
@@ -228,4 +238,64 @@ public class HexDumper {
// Total expected length of each line
return constant + delimiter.length * (groups - 1) + groupLength * groups;
}
/**
* Retrieve the closest equivalent converter to a specific class.
* @param clazz - the class.
* @return The closest converter, or NULL if not found,
*/
public static EquivalentConverter<Object> findConverter(Class<?> clazz) {
Map<Class<?>, EquivalentConverter<Object>> converters = BukkitConverters.getConvertersForGeneric();
while (clazz != null) {
EquivalentConverter<Object> result = converters.get(clazz);
if (result != null)
return result;
else
clazz = clazz.getSuperclass();
}
return null;
}
/**
* Retrieve a detailed string representation of the given packet.
* @param packetContainer - the packet to describe.
* @return The detailed description.
* @throws IllegalAccessException An error occured.
*/
public static String getPacketDescription(PacketContainer packetContainer) throws IllegalAccessException {
Object packet = packetContainer.getHandle();
Class<?> clazz = packet.getClass();
// Get the first Minecraft super class
while (clazz != null && clazz != Object.class &&
(!MinecraftReflection.isMinecraftClass(clazz) ||
ByteBuddyGenerated.class.isAssignableFrom(clazz))) {
clazz = clazz.getSuperclass();
}
return PrettyPrinter.printObject(packet, clazz, MinecraftReflection.getPacketClass(),
PrettyPrinter.RECURSE_DEPTH, (output, value) -> {
// Special case
if (value instanceof byte[]) {
byte[] data = (byte[]) value;
if (data.length > HEX_DUMP_THRESHOLD) {
output.append("[");
HexDumper.defaultDumper().appendTo(output, data);
output.append("]");
return true;
}
} else if (value != null) {
EquivalentConverter<Object> converter = findConverter(value.getClass());
if (converter != null) {
output.append(converter.getSpecific(value));
return true;
}
}
return false;
});
}
}
@@ -1,9 +1,12 @@
package com.comphenix.protocol.utility;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
@@ -12,10 +15,14 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.GenericFutureListener;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;
/**
* Static methods for accessing Minecraft methods.
@@ -33,6 +40,8 @@ public class MinecraftMethods {
// For packet
private volatile static Method packetReadByteBuf;
private volatile static Method packetWriteByteBuf;
private static Constructor<?> proxyConstructor;
/**
* Retrieve the send packet method in PlayerConnection/NetServerHandler.
@@ -85,7 +94,7 @@ public class MinecraftMethods {
* @param playerConnection - the player connection.
* @return The
*/
public static Method getDisconnectMethod(Class<? extends Object> playerConnection) {
public static Method getDisconnectMethod(Class<?> playerConnection) {
try {
return FuzzyReflection.fromClass(playerConnection).getMethodByName("disconnect.*");
} catch (IllegalArgumentException e) {
@@ -95,7 +104,7 @@ public class MinecraftMethods {
}
/**
* Retrieve the handle(Packet, GenericFutureListener[]) method of network manager.
* Retrieve the handle/send packet method of network manager.
* <p>
* This only exists in version 1.7.2 and above.
* @return The handle method.
@@ -104,7 +113,12 @@ public class MinecraftMethods {
if (networkManagerHandle == null) {
networkManagerHandle = FuzzyReflection
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
.getMethodByParameters("handle", MinecraftReflection.getPacketClass());
.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getPacketClass())
.build());
networkManagerHandle.setAccessible(true);
}
@@ -112,7 +126,7 @@ public class MinecraftMethods {
}
/**
* Retrieve the packetRead(ChannelHandlerContext, Packet) method of NetworkMananger.
* Retrieve the packetRead(ChannelHandlerContext, Packet) method of NetworkManager.
* <p>
* This only exists in version 1.7.2 and above.
* @return The packetRead method.
@@ -161,36 +175,59 @@ public class MinecraftMethods {
initializePacket();
return packetWriteByteBuf;
}
private static Constructor<?> setupProxyConstructor()
{
try {
return ByteBuddyFactory.getInstance()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.name(MinecraftMethods.class.getPackage().getName() + ".PacketDecorator")
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
.intercept(MethodDelegation.to(new Object() {
@RuntimeType
public Object delegate(@SuperCall Callable<?> zuper, @Origin Method method) throws Exception {
if (method.getName().contains("read")) {
throw new ReadMethodException();
}
if (method.getName().contains("write")) {
throw new WriteMethodException();
}
return zuper.call();
}
}))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(MinecraftReflection.getByteBufClass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find constructor!", e);
}
}
/**
* Initialize the two read() and write() methods.
*/
private static void initializePacket() {
// Initialize the methods
if (packetReadByteBuf == null || packetWriteByteBuf == null) {
// This object will allow us to detect which methods were called
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer();
enhancer.setSuperclass(MinecraftReflection.getPacketDataSerializerClass());
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
if (method.getName().contains("read")) {
throw new ReadMethodException();
}
if (proxyConstructor == null)
proxyConstructor = setupProxyConstructor();
if (method.getName().contains("write")) {
throw new WriteMethodException();
}
final Object javaProxy;
try {
javaProxy = proxyConstructor.newInstance(Unpooled.buffer());
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
return proxy.invokeSuper(obj, args);
});
// Create our proxy object
Object javaProxy = enhancer.create(
new Class<?>[] { MinecraftReflection.getByteBufClass() },
new Object[] { Unpooled.buffer() }
);
Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle();
List<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
final Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle();
final List<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
.getMethodListByParameters(Void.TYPE, new Class<?>[] { MinecraftReflection.getPacketDataSerializerClass() });
// Look through all the methods
@@ -211,8 +248,8 @@ public class MinecraftMethods {
}
}
if (packetReadByteBuf == null)
throw new IllegalStateException("Unable to find Packet.read(PacketDataSerializer)");
// if (packetReadByteBuf == null)
// throw new IllegalStateException("Unable to find Packet.read(PacketDataSerializer)");
if (packetWriteByteBuf == null)
throw new IllegalStateException("Unable to find Packet.write(PacketDataSerializer)");
}
@@ -37,20 +37,45 @@ public class MinecraftProtocolVersion {
// After Netty
map.put(new MinecraftVersion(1, 7, 1), 4);
map.put(new MinecraftVersion(1, 7, 6), 5);
map.put(new MinecraftVersion(1, 8, 0), 47);
map.put(new MinecraftVersion(1, 9, 0), 107);
map.put(new MinecraftVersion(1, 9, 2), 109);
map.put(new MinecraftVersion(1, 9, 4), 110);
map.put(new MinecraftVersion(1, 10, 0), 210);
map.put(new MinecraftVersion(1, 11, 0), 315);
map.put(new MinecraftVersion(1, 11, 1), 316);
map.put(new MinecraftVersion(1, 12, 0), 335);
map.put(new MinecraftVersion(1, 12, 1), 338);
map.put(new MinecraftVersion(1, 12, 2), 340);
map.put(new MinecraftVersion(1, 13, 0), 393);
map.put(new MinecraftVersion(1, 13, 1), 401);
map.put(new MinecraftVersion(1, 13, 2), 404);
map.put(new MinecraftVersion(1, 14, 0), 477);
map.put(new MinecraftVersion(1, 14, 1), 480);
map.put(new MinecraftVersion(1, 14, 2), 485);
map.put(new MinecraftVersion(1, 14, 3), 490);
map.put(new MinecraftVersion(1, 14, 4), 498);
map.put(new MinecraftVersion(1, 15, 0), 573);
map.put(new MinecraftVersion(1, 15, 1), 575);
map.put(new MinecraftVersion(1, 15, 2), 578);
map.put(new MinecraftVersion(1, 16, 0), 735);
map.put(new MinecraftVersion(1, 16, 1), 736);
map.put(new MinecraftVersion(1, 16, 2), 751);
map.put(new MinecraftVersion(1, 16, 3), 753);
map.put(new MinecraftVersion(1, 16, 4), 754);
map.put(new MinecraftVersion(1, 16, 5), 754);
map.put(new MinecraftVersion(1, 17, 0), 755);
map.put(new MinecraftVersion(1, 17, 1), 756);
return map;
}
@@ -27,10 +27,8 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
@@ -57,6 +55,7 @@ import com.comphenix.protocol.reflect.ClassAnalyser;
import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod;
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.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
@@ -65,6 +64,7 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException;
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.comphenix.protocol.wrappers.nbt.NbtType;
import com.google.common.base.Joiner;
@@ -222,30 +222,35 @@ public class MinecraftReflection {
Class<?> craftEntity = getCraftEntityClass();
Method getHandle = craftEntity.getMethod("getHandle");
MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName());
// Pretty important invariantt
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
// See if we got the Forge entity package
if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) {
// USe the standard NMS versioned package
MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion);
} else {
// Assume they're the same instead
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
}
// The package is usualy flat, so go with that assumption
String matcher =
(MINECRAFT_PREFIX_PACKAGE.length() > 0 ?
Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX;
// We'll still accept the default location, however
setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")");
} else {
// Use the standard matcher
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
// total rework of the NMS structure in 1.17 (at least there's no versioning)
MINECRAFT_FULL_PACKAGE = MINECRAFT_PREFIX_PACKAGE = "net.minecraft";
setDynamicPackageMatcher(MINECRAFT_OBJECT);
} else {
MINECRAFT_FULL_PACKAGE = getPackage(getHandle.getReturnType().getCanonicalName());
// Pretty important invariantt
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
// See if we got the Forge entity package
if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) {
// USe the standard NMS versioned package
MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion);
} else {
// Assume they're the same instead
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
}
// The package is usualy flat, so go with that assumption
String matcher =
(MINECRAFT_PREFIX_PACKAGE.length() > 0 ? Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX;
// We'll still accept the default location, however
setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_OBJECT + ")");
} else {
// Use the standard matcher
setDynamicPackageMatcher(MINECRAFT_OBJECT);
}
}
return MINECRAFT_FULL_PACKAGE;
@@ -580,7 +585,7 @@ public class MinecraftReflection {
*/
public static Class<?> getEntityPlayerClass() {
try {
return getMinecraftClass("EntityPlayer");
return getMinecraftClass("server.level.EntityPlayer","EntityPlayer");
} catch (RuntimeException e) {
try {
// Grab CraftPlayer's handle
@@ -637,7 +642,7 @@ public class MinecraftReflection {
*/
public static Class<?> getEntityClass() {
try {
return getMinecraftClass("Entity");
return getMinecraftClass("server.world.Entity", "Entity");
} catch (RuntimeException e) {
return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle");
}
@@ -657,7 +662,7 @@ public class MinecraftReflection {
*/
public static Class<?> getWorldServerClass() {
try {
return getMinecraftClass("WorldServer");
return getMinecraftClass("server.level.WorldServer", "WorldServer");
} catch (RuntimeException e) {
return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle");
}
@@ -669,7 +674,7 @@ public class MinecraftReflection {
*/
public static Class<?> getNmsWorldClass() {
try {
return getMinecraftClass("World");
return getMinecraftClass("world.level.World", "World");
} catch (RuntimeException e) {
return setMinecraftClass("World", getWorldServerClass().getSuperclass());
}
@@ -696,7 +701,7 @@ public class MinecraftReflection {
*/
public static Class<?> getPacketClass() {
try {
return getMinecraftClass("Packet");
return getMinecraftClass("network.protocol.Packet", "Packet");
} catch (RuntimeException e) {
FuzzyClassContract paketContract = null;
@@ -752,8 +757,10 @@ public class MinecraftReflection {
* @return The Enum protocol class.
*/
public static Class<?> getEnumProtocolClass() {
try {
return getMinecraftClass("EnumProtocol");
return getMinecraftClass("network.EnumProtocol", "EnumProtocol");
/* try {
return getMinecraftClass("network.EnumProtocol", "EnumProtocol");
} catch (RuntimeException e) {
Method protocolMethod = FuzzyReflection.fromClass(getNetworkManagerClass()).getMethod(
FuzzyMethodContract.newBuilder().
@@ -762,7 +769,7 @@ public class MinecraftReflection {
build()
);
return setMinecraftClass("EnumProtocol", protocolMethod.getParameterTypes()[0]);
}
} */
}
/**
@@ -771,7 +778,7 @@ public class MinecraftReflection {
*/
public static Class<?> getIChatBaseComponentClass() {
try {
return getMinecraftClass("IChatBaseComponent");
return getMinecraftClass("network.chat.IChatbaseComponent", "IChatBaseComponent");
} catch (RuntimeException e) {
return setMinecraftClass("IChatBaseComponent",
Accessors.getMethodAccessor(getCraftChatMessage(), "fromString", String.class).
@@ -790,7 +797,7 @@ public class MinecraftReflection {
*/
public static Class<?> getChatComponentTextClass() {
try {
return getMinecraftClass("ChatComponentText");
return getMinecraftClass("network.chat.ChatComponentText", "ChatComponentText");
} catch (RuntimeException e) {
try {
Method getScoreboardDisplayName = FuzzyReflection.fromClass(getEntityClass()).
@@ -818,7 +825,8 @@ public class MinecraftReflection {
*/
public static Class<?> getChatSerializerClass() {
try {
return getMinecraftClass("ChatSerializer", "IChatBaseComponent$ChatSerializer");
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer",
"ChatSerializer", "IChatBaseComponent$ChatSerializer");
} catch (RuntimeException e) {
// TODO: Figure out a functional fallback
throw new IllegalStateException("Could not find ChatSerializer class.", e);
@@ -830,11 +838,8 @@ public class MinecraftReflection {
* @return The ServerPing class.
*/
public static Class<?> getServerPingClass() {
if (!isUsingNetty())
throw new IllegalStateException("ServerPing is only supported in 1.7.2.");
try {
return getMinecraftClass("ServerPing");
return getMinecraftClass("network.protocol.status.ServerPing", "ServerPing");
} catch (RuntimeException e) {
Class<?> statusServerInfo = PacketType.Status.Server.SERVER_INFO.getPacketClass();
@@ -856,11 +861,9 @@ public class MinecraftReflection {
* @return The ServerPingServerData class.
*/
public static Class<?> getServerPingServerDataClass() {
if (!isUsingNetty())
throw new IllegalStateException("ServerPingServerData is only supported in 1.7.2.");
try {
return getMinecraftClass("ServerPingServerData", "ServerPing$ServerData");
return getMinecraftClass("network.protocol.status.ServerPing$ServerData",
"ServerPingServerData", "ServerPing$ServerData");
} catch (RuntimeException e) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(getServerPingClass(), true);
return setMinecraftClass("ServerPingServerData", fuzzy.getFieldByType("(.*)(ServerData)(.*)").getType());
@@ -872,11 +875,9 @@ public class MinecraftReflection {
* @return The ServerPingPlayerSample class.
*/
public static Class<?> getServerPingPlayerSampleClass() {
if (!isUsingNetty())
throw new IllegalStateException("ServerPingPlayerSample is only supported in 1.7.2.");
try {
return getMinecraftClass("ServerPingPlayerSample", "ServerPing$ServerPingPlayerSample");
return getMinecraftClass("network.protocol.status.ServerPing$ServerPingPlayerSample",
"ServerPingPlayerSample", "ServerPing$ServerPingPlayerSample");
} catch (RuntimeException e) {
Class<?> serverPing = getServerPingClass();
@@ -942,7 +943,7 @@ public class MinecraftReflection {
*/
public static Class<?> getMinecraftServerClass() {
try {
return getMinecraftClass("MinecraftServer");
return getMinecraftClass("server.MinecraftServer","MinecraftServer");
} catch (RuntimeException e) {
useFallbackServer();
@@ -958,7 +959,7 @@ public class MinecraftReflection {
*/
public static Class<?> getStatisticClass() {
// TODO: Implement fallback
return getMinecraftClass("Statistic");
return getMinecraftClass("stats.Statistic", "Statistic");
}
/**
@@ -967,7 +968,7 @@ public class MinecraftReflection {
*/
public static Class<?> getStatisticListClass() {
// TODO: Implement fallback
return getMinecraftClass("StatisticList");
return getMinecraftClass("stats.StatisticList", "StatisticList");
}
/**
@@ -994,7 +995,8 @@ public class MinecraftReflection {
*/
public static Class<?> getPlayerListClass() {
try {
return getMinecraftClass("ServerConfigurationManager", "PlayerList");
return getMinecraftClass("server.players.PlayerList",
"ServerConfigurationManager", "PlayerList");
} catch (RuntimeException e) {
useFallbackServer();
@@ -1033,7 +1035,7 @@ public class MinecraftReflection {
*/
public static Class<?> getPlayerConnectionClass() {
try {
return getMinecraftClass("PlayerConnection", "NetServerHandler");
return getMinecraftClass("server.network.PlayerConnection","PlayerConnection", "NetServerHandler");
} catch (RuntimeException e) {
try {
// Use the player connection field
@@ -1077,7 +1079,7 @@ public class MinecraftReflection {
*/
public static Class<?> getNetworkManagerClass() {
try {
return getMinecraftClass("INetworkManager", "NetworkManager");
return getMinecraftClass("network.NetworkManager", "INetworkManager", "NetworkManager");
} catch (RuntimeException e) {
Constructor<?> selected = FuzzyReflection.fromClass(getPlayerConnectionClass()).
getConstructor(FuzzyMethodContract.newBuilder().
@@ -1110,7 +1112,7 @@ public class MinecraftReflection {
*/
public static Class<?> getItemStackClass() {
try {
return getMinecraftClass("ItemStack");
return getMinecraftClass("world.item.ItemStack", "ItemStack");
} catch (RuntimeException e) {
// Use the handle reference
return setMinecraftClass("ItemStack",
@@ -1124,7 +1126,7 @@ public class MinecraftReflection {
*/
public static Class<?> getBlockClass() {
try {
return getMinecraftClass("Block");
return getMinecraftClass("world.level.block.Block", "Block");
} catch (RuntimeException e) {
FuzzyReflection reflect = FuzzyReflection.fromClass(getItemStackClass());
Set<Class<?>> candidates = new HashSet<Class<?>>();
@@ -1149,15 +1151,15 @@ public class MinecraftReflection {
}
public static Class<?> getItemClass() {
return getNullableNMS("Item");
return getNullableNMS("world.item.Item", "Item");
}
public static Class<?> getFluidTypeClass() {
return getNullableNMS("FluidType");
return getNullableNMS("world.level.material.FluidType", "FluidType");
}
public static Class<?> getParticleTypeClass() {
return getNullableNMS("ParticleType");
return getNullableNMS("core.particles.ParticleType", "ParticleType");
}
/**
@@ -1188,7 +1190,7 @@ public class MinecraftReflection {
*/
public static Class<?> getDataWatcherClass() {
try {
return getMinecraftClass("DataWatcher");
return getMinecraftClass("network.syncher.DataWatcher", "DataWatcher");
} catch (RuntimeException e) {
// Describe the DataWatcher
FuzzyClassContract dataWatcherContract = FuzzyClassContract.newBuilder().
@@ -1236,7 +1238,7 @@ public class MinecraftReflection {
*/
public static Class<?> getBlockPositionClass() {
try {
return getMinecraftClass("BlockPosition");
return getMinecraftClass("core.BlockPosition", "BlockPosition");
} catch (RuntimeException e) {
try {
Class<?> normalChunkGenerator = getCraftBukkitClass("generator.NormalChunkGenerator");
@@ -1263,7 +1265,7 @@ public class MinecraftReflection {
*/
public static Class<?> getVec3DClass() {
try {
return getMinecraftClass("Vec3D");
return getMinecraftClass("world.phys.Vec3D", "Vec3D");
} catch (RuntimeException e) {
// TODO: Figure out a fuzzy field contract
return null;
@@ -1288,13 +1290,10 @@ public class MinecraftReflection {
* @return The ChunkCoordIntPair class.
*/
public static Class<?> getChunkCoordIntPair() {
if (!isUsingNetty())
throw new IllegalArgumentException("Not supported on 1.6.4 and older.");
try {
return getMinecraftClass("ChunkCoordIntPair");
return getMinecraftClass("world.level.ChunkCoordIntPair", "ChunkCoordIntPair");
} catch (RuntimeException e) {
Class<?> packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.MULTI_BLOCK_CHANGE);
Class<?> packet = PacketType.Play.Server.MULTI_BLOCK_CHANGE.getPacketClass();
AbstractFuzzyMatcher<Class<?>> chunkCoordIntContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
@@ -1313,11 +1312,9 @@ public class MinecraftReflection {
}
/**
* Retrieve the WatchableObject class.
* Retrieve the WatchableObject class. Replaced by {@link #getDataWatcherItemClass()}
* @return The WatchableObject class.
* @deprecated Replaced by {@link #getDataWatcherItemClass()}
*/
@Deprecated
public static Class<?> getWatchableObjectClass() {
return getDataWatcherItemClass();
}
@@ -1328,7 +1325,8 @@ public class MinecraftReflection {
*/
public static Class<?> getDataWatcherItemClass() {
try {
return getMinecraftClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
return getMinecraftClass("network.syncher.DataWatcher$Item",
"DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
} catch (RuntimeException e) {
Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true).
getMethod(FuzzyMethodContract.newBuilder().
@@ -1344,7 +1342,7 @@ public class MinecraftReflection {
public static Class<?> getDataWatcherObjectClass() {
try {
return getMinecraftClass("DataWatcherObject");
return getMinecraftClass("network.syncher.DataWatcherObject", "DataWatcherObject");
} catch (RuntimeException ex) {
return null;
}
@@ -1364,27 +1362,27 @@ public class MinecraftReflection {
public static Class<?> getDataWatcherSerializerClass() {
// TODO Implement a fallback
return getNullableNMS("DataWatcherSerializer");
return getNullableNMS("network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
}
public static Class<?> getDataWatcherRegistryClass() {
// TODO Implement a fallback
return getMinecraftClass("DataWatcherRegistry");
return getMinecraftClass("network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
}
public static Class<?> getMinecraftKeyClass() {
// TODO Implement a fallback
return getMinecraftClass("MinecraftKey");
return getMinecraftClass("resources.MinecraftKey", "MinecraftKey");
}
public static Class<?> getMobEffectListClass() {
// TODO Implement a fallback
return getMinecraftClass("MobEffectList");
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList");
}
public static Class<?> getSoundEffectClass() {
try {
return getMinecraftClass("SoundEffect");
return getMinecraftClass("sounds.SoundEffect", "SoundEffect");
} catch (RuntimeException ex) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), true);
Field field = fuzzy.getFieldByType("(.*)(Sound)(.*)");
@@ -1398,7 +1396,7 @@ public class MinecraftReflection {
*/
public static Class<?> getServerConnectionClass() {
try {
return getMinecraftClass("ServerConnection");
return getMinecraftClass("server.network.ServerConnection", "ServerConnection");
} catch (RuntimeException e) {
Method selected = null;
FuzzyClassContract.Builder serverConnectionContract = FuzzyClassContract.newBuilder().
@@ -1443,7 +1441,7 @@ public class MinecraftReflection {
*/
public static Class<?> getNBTBaseClass() {
try {
return getMinecraftClass("NBTBase");
return getMinecraftClass("nbt.NBTBase", "NBTBase");
} catch (RuntimeException e) {
Class<?> nbtBase = null;
@@ -1503,7 +1501,7 @@ public class MinecraftReflection {
* @return The NBT read limiter.
*/
public static Class<?> getNBTReadLimiterClass() {
return getMinecraftClass("NBTReadLimiter");
return getMinecraftClass("nbt.NBTReadLimiter", "NBTReadLimiter");
}
/**
@@ -1512,7 +1510,7 @@ public class MinecraftReflection {
*/
public static Class<?> getNBTCompoundClass() {
try {
return getMinecraftClass("NBTTagCompound");
return getMinecraftClass("nbt.NBTTagCompound","NBTTagCompound");
} catch (RuntimeException e) {
return setMinecraftClass(
"NBTTagCompound",
@@ -1527,7 +1525,8 @@ public class MinecraftReflection {
*/
public static Class<?> getEntityTrackerClass() {
try {
return getMinecraftClass("EntityTracker", "PlayerChunkMap$EntityTracker");
return getMinecraftClass("server.level.PlayerChunkMap$EntityTracker",
"EntityTracker", "PlayerChunkMap$EntityTracker");
} catch (RuntimeException e) {
FuzzyClassContract entityTrackerContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
@@ -1555,38 +1554,6 @@ public class MinecraftReflection {
}
}
/**
* Retrieve the NetworkListenThread class (NMS).
* <p>
* Note that this class was removed after Minecraft 1.3.1.
* @return NetworkListenThread class.
*/
public static Class<?> getNetworkListenThreadClass() {
try {
return getMinecraftClass("NetworkListenThread");
} catch (RuntimeException e) {
FuzzyClassContract networkListenContract = FuzzyClassContract.newBuilder().
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(ServerSocket.class)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(Thread.class)).
field(FuzzyFieldContract.newBuilder().
typeDerivedOf(List.class)).
method(FuzzyMethodContract.newBuilder().
parameterExactType(getPlayerConnectionClass())).
build();
Field selected = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass(), true).
getField(FuzzyFieldContract.newBuilder().
typeMatches(networkListenContract).
build()
);
// Go by the defined type of this field
return setMinecraftClass("NetworkListenThread", selected.getType());
}
}
/**
* Retrieve the attribute snapshot class.
* <p>
@@ -1595,7 +1562,8 @@ public class MinecraftReflection {
*/
public static Class<?> getAttributeSnapshotClass() {
try {
return getMinecraftClass("AttributeSnapshot", "PacketPlayOutUpdateAttributes$AttributeSnapshot");
return getMinecraftClass("network.protocol.game.PacketPlayOutUpdateAttributes$AttributeSnapshot",
"AttributeSnapshot", "PacketPlayOutUpdateAttributes$AttributeSnapshot");
} catch (RuntimeException ex) {
try {
// It should be the parameter of a list in the update attributes packet
@@ -1654,7 +1622,7 @@ public class MinecraftReflection {
*/
public static Class<?> getAttributeModifierClass() {
try {
return getMinecraftClass("AttributeModifier");
return getMinecraftClass("world.entity.ai.attributes.AttributeModifier", "AttributeModifier");
} catch (RuntimeException e) {
getAttributeSnapshotClass();
@@ -1670,10 +1638,10 @@ public class MinecraftReflection {
*/
public static Class<?> getMobEffectClass() {
try {
return getMinecraftClass("MobEffect");
return getMinecraftClass("world.effect.MobEffect", "MobEffect");
} catch (RuntimeException e) {
// It is the second parameter in Packet41MobEffect
Class<?> packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.ENTITY_EFFECT);
Class<?> packet = PacketType.Play.Server.ENTITY_EFFECT.getPacketClass();
Constructor<?> constructor = FuzzyReflection.fromClass(packet).getConstructor(
FuzzyMethodContract.newBuilder().
parameterCount(2).
@@ -1691,7 +1659,7 @@ public class MinecraftReflection {
*/
public static Class<?> getPacketDataSerializerClass() {
try {
return getMinecraftClass("PacketDataSerializer");
return getMinecraftClass("network.PacketDataSerializer", "PacketDataSerializer");
} catch (RuntimeException e) {
Class<?> packet = getPacketClass();
Method method = FuzzyReflection.fromClass(packet).getMethod(
@@ -1711,7 +1679,7 @@ public class MinecraftReflection {
*/
public static Class<?> getNbtCompressedStreamToolsClass() {
try {
return getMinecraftClass("NBTCompressedStreamTools");
return getMinecraftClass("nbt.NBTCompressedStreamTools", "NBTCompressedStreamTools");
} catch (RuntimeException e) {
Class<?> packetSerializer = getPacketDataSerializerClass();
@@ -1740,7 +1708,7 @@ public class MinecraftReflection {
* @return The tile entity class.
*/
public static Class<?> getTileEntityClass() {
return getMinecraftClass("TileEntity");
return getMinecraftClass("world.level.block.entity.TileEntity", "TileEntity");
}
/**
@@ -1822,7 +1790,54 @@ public class MinecraftReflection {
* @return The PlayerInfoData class
*/
public static Class<?> getPlayerInfoDataClass() {
return getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
return getMinecraftClass("network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData",
"PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
}
/**
* Retrieves the entity use action class in 1.17.
* @return The EntityUseAction class
*/
public static Class<?> getEnumEntityUseActionClass() {
Class<?> packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass();
return FuzzyReflection.fromClass(packetClass, true).getFieldByType("^.*(EnumEntityUseAction)").getType();
}
/**
* Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17.
* @return a method accessor to get the actual use action
*/
public static MethodAccessor getEntityUseActionEnumMethodAccessor() {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(EnumWrappers.getEntityUseActionClass())
.build()));
}
/**
* Get a field accessor for the hand in the wrapping EnumEntityUseAction in 1.17.
*
* @param enumEntityUseAction the object instance of the action, the field is not present in attack.
* @return a field accessor for the hand in the wrapping EnumEntityUseAction
*/
public static FieldAccessor getHandEntityUseActionEnumFieldAccessor(Object enumEntityUseAction) {
FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true);
return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
.typeExact(EnumWrappers.getHandClass())
.build()));
}
/**
* Get a field accessor for the vec3d in the wrapping EnumEntityUseAction in 1.17.
*
* @param enumEntityUseAction the object instance of the action, the field is not present in attack.
* @return a field accessor for the hand in the wrapping EnumEntityUseAction
*/
public static FieldAccessor getVec3EntityUseActionEnumFieldAccessor(Object enumEntityUseAction) {
FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true);
return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getVec3DClass())
.build()));
}
/**
@@ -1839,7 +1854,7 @@ public class MinecraftReflection {
* @return The IBlockData class
*/
public static Class<?> getIBlockDataClass() {
return getMinecraftClass("IBlockData");
return getMinecraftClass("world.level.block.state.IBlockData", "IBlockData");
}
/**
@@ -1858,22 +1873,45 @@ public class MinecraftReflection {
return getArrayClass(getMultiBlockChangeInfoClass());
}
/**
* Retrieve the PacketPlayOutGameStateChange.a class, aka GameState in 1.16
* @return The GameState class
*/
public static Class<?> getGameStateClass() {
// it's called "a" so there's not a whole lot we can do to identify it
Class<?> packetClass = PacketType.Play.Server.GAME_STATE_CHANGE.getPacketClass();
return packetClass.getClasses()[0];
}
public static boolean signUpdateExists() {
try {
return getMinecraftClass("PacketPlayOutUpdateSign") != null;
} catch (RuntimeException ex) {
return false;
}
return getNullableNMS("PacketPlayOutUpdateSign") != null;
}
public static Class<?> getNonNullListClass() {
return getMinecraftClass("NonNullList");
return getMinecraftClass("core.NonNullList", "NonNullList");
}
public static MethodAccessor getNonNullListCreateAccessor() {
try {
Class<?> nonNullListType = MinecraftReflection.getNonNullListClass();
return Accessors.getMethodAccessor(FuzzyReflection.fromClass(nonNullListType).getMethod(
FuzzyMethodContract.newBuilder()
.returnTypeExact(nonNullListType)
.requireModifier(Modifier.STATIC)
.build()));
} catch (Exception ex) {
return null;
}
}
public static Class<?> getCraftSoundClass() {
return getCraftBukkitClass("CraftSound");
}
public static Class<?> getSectionPositionClass() {
return getMinecraftClass("core.SectionPosition", "SectionPosition");
}
// ---- ItemStack conversions
private static Object itemStackAir = null;
private static Method asNMSCopy = null;
@@ -2025,10 +2063,12 @@ public class MinecraftReflection {
.orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className));
}
public static Class<?> getNullableNMS(String className) {
if (minecraftPackage == null)
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
return minecraftPackage.getPackageClass(className).orElse(null);
public static Class<?> getNullableNMS(String className, String... aliases) {
try {
return getMinecraftClass(className, aliases);
} catch (RuntimeException ex) {
return null;
}
}
/**
@@ -2161,4 +2201,70 @@ public class MinecraftReflection {
throw new RuntimeException("Cannot construct packet serializer.", e);
}
}
public static Class<?> getNbtTagTypes() {
return getMinecraftClass("nbt.NBTTagTypes", "NBTTagTypes");
}
public static Class<?> getChatDeserializer() {
return getMinecraftClass("util.ChatDeserializer", "ChatDeserializer");
}
public static Class<?> getDimensionManager() {
return getMinecraftClass("world.level.dimension.DimensionManager", "DimensionManager");
}
public static Class<?> getMerchantRecipeList() {
return getMinecraftClass("world.item.trading.MerchantRecipeList", "MerchantRecipeList");
}
public static Class<?> getResourceKey() {
return getMinecraftClass("resources.ResourceKey", "ResourceKey");
}
public static Class<?> getEntityTypes() {
return getMinecraftClass("world.entity.EntityTypes", "EntityTypes");
}
public static Class<?> getParticleParam() {
return getMinecraftClass("core.particles.ParticleParam", "ParticleParam");
}
public static Class<?> getSectionPosition() {
return getMinecraftClass("core.SectionPosition", "SectionPosition");
}
public static Class<?> getChunkProviderServer() {
return getMinecraftClass("server.level.ChunkProviderServer", "ChunkProviderServer");
}
public static Class<?> getPlayerChunkMap() {
return getMinecraftClass("server.level.PlayerChunkMap", "PlayerChunkMap");
}
public static Class<?> getIRegistry() {
return getNullableNMS("core.IRegistry", "IRegistry");
}
public static Class<?> getAttributeBase() {
return getMinecraftClass("world.entity.ai.attributes.AttributeBase", "AttributeBase");
}
public static Class<?> getFastUtilClass(String className) {
try {
return getMinecraftLibraryClass("it.unimi.dsi.fastutil." + className);
} catch (RuntimeException ex) {
Class<?> clazz = getMinecraftLibraryClass("org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil." + className);
setMinecraftLibraryClass("it.unimi.dsi.fastutil." + className, clazz);
return clazz;
}
}
public static Class<?> getInt2ObjectMapClass() {
return getFastUtilClass("ints.Int2ObjectMap");
}
public static Class<?> getIntArrayListClass() {
return getFastUtilClass("ints.IntArrayList");
}
}
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff. Show more