Compare commits

..
96 Commits
Author SHA1 Message Date
Tim Langner 9cb125fb1f Update to Minecraft 26.3 (#3681)
* Update to Minecraft 26.3

- Bump the server dependency, latest version and plugin api-version to 26.3
- Add the configuration POST_EFFECTS packet and shift the following ids
- Add the CHANGE_DESTROY_DIRECTION player dig type
- Stop resolving the legacy particle enum on 1.13+, the 26.3 particle packet has an unrelated enum
- Read optional message signatures in getMessageSignatures
- Cache parameterized structure modifiers separately from the raw field type
- Add WrappedVecDelta and WrappedPositionPath for the new entity movement packets
- Update tests for the 26.3 server classes

* Change default class type from null to void.class
2026-11-03 21:36:13 -06:00
Mark Cockram 0f7670e13d Fix recursive update in StructureCache on legacy versions (#3682)
ConcurrentHashMap.computeIfAbsent rejects re-entrant updates on Java 9+,
and determineBestCreator can re-enter the cache. On 1.8 it also recursed
through PacketRegistry.getStreamCodec into createOldRegister, so only ask
for stream codecs on 1.20.5+, where they exist.
2026-11-03 21:25:51 -06:00
Rishabh Venu 47f465d3f5 Run sync listeners of bundled packets on the main thread (#3674)
* Run sync listeners of bundled packets on the main thread

processOutbound only checked the BUNDLE packet type for main-thread listeners before the bundle was unpacked, so a synchronous listener registered for a packet inside the bundle ran on the thread the bundle was sent from.

Fixes #3665

* Keep the network marker when rescheduling a bundled packet
2026-11-03 21:24:38 -06:00
Dan Mulloy 501e187342 Update links 2026-10-26 08:41:41 -05:00
Rishabh Venu 6720a70dbd Use the stream codec in WirePacket on 1.20.5 and newer (#3676) 2026-10-24 02:42:11 +00:00
Rishabh Venu 3d8e597244 Replace the inbound chat example in the README (#3675) 2026-10-23 22:34:30 -04:00
LOOHP 58d45e79b1 Fix Early Channel Injection Ordering on Paper (#3679) 2026-10-23 21:21:29 -05:00
1wairesd d89a5deb3e Fix NoClassDefFoundError: CommandProtocol$1 on Paper plugin classloader (#3673) 2026-10-06 22:02:46 +00:00
Diogo Correia 1ea47d63ba Fix WrappedLevelChunkData in MC 26.3 (#3671) 2026-10-03 18:18:42 +00:00
Diogo Correia 3c616caf74 Fix asCraftMirror method accessor in MC 26.3 (#3672) 2026-10-03 11:18:45 -05:00
Richard Strunk 583353e9fa Fix Minecraft 26.3 play packet mappings (#3670)
Fix 26.3 play packet mappings
2026-09-24 23:02:31 -05:00
Dan Mulloy f0e959a1ad Fix WrappedDataWatcher in 1.8
Fixes #3557
2026-08-23 20:06:32 -05:00
Dan Mulloy cbe7fd87e4 Fix WrappedGameProfile in 1.21.10
Fixes #3573
2026-08-23 19:55:06 -05:00
Dan Mulloy 3fab29d3c6 Fix WrappedStatistic in modern versions
Fixes #3591
2026-08-23 19:35:28 -05:00
Dan Mulloy fec45cf8fc Restore Java 17 compat for Spigot too 2026-08-23 14:34:29 -05:00
Dan Mulloyandjustcarlux 5c6c773935 Prioritize searching for singletons when determining best creators
Fixes #3663

Co-authored-by: justcarlux <manuelitojuan691@gmail.com>
2026-08-23 14:30:03 -05:00
kik 78b10165ff Fix: Java 17 bytecode target on paper module (#3662)
Fixes #3661
2026-08-19 12:51:56 -05:00
Dan Mulloyandrkfsociety dda284cd7a Fix custom payload handling on modern servers
Co-authored-by: rkfsociety <romankuzminvital@gmail.com>
2026-08-17 21:50:35 -05:00
Dan MulloyandSpagles debdd6d9a2 Fix async packet handling and utility edge cases
Co-authored-by: Spagles <106791090+Spagles@users.noreply.github.com>
2026-08-17 21:23:40 -05:00
Dan Mulloy 2c53c29cf7 Only copy bundle packets when something changes
Fixes #3596
2026-08-17 20:58:33 -05:00
Dan Mulloy 1ef8431764 Fix some issues with bundle packets
Addresses #3596
2026-08-17 20:43:23 -05:00
Dan Mulloy 90eb2a6558 Fix item particles in 26.1+
Fixes #3658
2026-08-17 20:29:16 -05:00
Dan Mulloy 67ce937109 Create modern Paper plugin artifact (#3656) 2026-08-02 20:34:39 -05:00
Dan Mulloy 98856a8b9f Restore compatibility with Java 17
fixes #3643
2026-08-02 19:35:14 -05:00
Dan Mulloy 5ec23ef33b Update guidelines for agents 2026-07-18 14:30:48 -05:00
Matvei Slotvinskiy 589ee12e12 perf: avoid per-call lambda allocation in CachedPackage#getPackageClass (#3651) 2026-07-18 14:04:58 +00:00
LoneDev 6cf7c7d8c3 Fixed typo (#2744) 2026-07-12 21:46:37 -05:00
gamerover98 3139e82aea Fixed TinyProtocol for 1.19+ (#2499) 2026-07-13 02:46:06 +00:00
yesidodo73 3385fe664b fix: select player info data list field (#3650) 2026-07-11 17:20:38 -05:00
Diogo Correia 0e267a5f1a fix: explicitly declare parameters of ServerPingRecord constructor (#3644)
Some Spigot forks (e.g., Leaf) add additional constructors with
different parameters, which causes the previous naive implementation of
getting the first available constructor to fail.
2026-06-24 10:14:39 -05:00
Dan Mulloy 2d3b3e3363 Update to Minecraft 26.2 (#3642) 2026-06-20 10:52:30 -05:00
Aldin 8eeb82f89d fix: only sign non snapshots and non jitpack versions (#3641) 2026-06-20 10:38:13 -05:00
Aldin b0b9b666a7 fix: only sign release versions to support jitpack snapshots builds again (#3639) 2026-06-19 23:05:20 -05:00
NekoEp1sode b723ff3351 fix: NettyChannelInjector.getPlayer() overwrites TemporaryPlayer with null (#3629) 2026-05-31 13:30:13 +00:00
Maurice Eisenblätter 72fd0709b6 Fix duplicate MONITOR listener invocation after async packet processing (#3630) 2026-05-31 09:26:34 -04:00
Richard Strunk 5a9afed462 Fix 26.1.2 problem with invalid constructor selection (#3626) 2026-05-12 14:17:40 -05:00
LeonTG 9404ce32db Fix Injector.getPlayer() using TemporaryPlayer when a Bukkit player is available (#3614) 2026-04-25 23:08:08 +00:00
Copilotanddmulloy2 6c6bde522e Add WrappedPositionMoveRotation for 1.21.2+ entity teleport packets (#3610)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dmulloy2 <3039119+dmulloy2@users.noreply.github.com>
2026-03-27 20:12:42 -05:00
Dan Mulloy f911ee904c Bump version to 5.5.0-SNAPSHOT 2026-03-26 20:42:11 -05:00
Dan Mulloy 22012d3dc9 Mark 26.1 as supported 2026-03-26 20:35:46 -05:00
Dan Mulloy c52b4bb10c Use Java's base64 encoder 2026-03-26 20:30:20 -05:00
Dan Mulloy b9f96469f5 Fix or ignore remaining tests 2026-03-26 20:22:18 -05:00
Dan Mulloy e5413ccc5b Update packet types and protocol versions 2026-03-26 19:52:09 -05:00
Dan Mulloy b37a3bcb13 Start update to 26.1 2026-03-24 22:21:54 -05:00
Dan Mulloy 19cb81b6ab Don't modify read-only bundle packets
Fixes #3603
2026-03-18 15:30:44 -04:00
coolomet f606cc954d Add support for ShreddedPaper's lazy packet execution (#3592) 2026-02-01 23:04:50 -06:00
Antti Koponen 774e679571 Mark 1.21.11 as supported (#3589) 2026-02-02 05:00:59 +00:00
icefear 1257506929 feat: add support for 1.21.11 (#3578) 2025-12-16 16:45:00 -06:00
Dan Mulloy 5773bcbc16 Fix WrappedServerPing#getPlayers in 1.21.10
Fixes #3570
2025-11-16 20:44:10 -06:00
Dan Mulloy 468082c9f9 Bump byte buddy version to fix Java 25
Fixes #3566
2025-10-12 19:28:46 -05:00
Dan Mulloy a11b403c4b Fix GameProfile properties in 1.21.9+
Fixes #3564
2025-10-12 19:26:19 -05:00
Dan Mulloy aa4fab1632 Update to 1.21.9 2025-10-05 18:20:39 -05:00
Dan Mulloy 771a530fe2 Fix CME on disable
Fixes #3544
2025-08-16 22:38:57 -05:00
Diogo Correia ca9b5bbb80 fix: use RegistryOps when (de)serializing chat component on 1.21.6+ (#3546)
This fixes an error during serialization when a hover event has an item
with `components` (e.g., enchantments).
2025-08-13 01:13:43 +00:00
NotSecureFly d790ded81f Use the correct reflection method in setTypeAndData method, and rename GET_BLOCK_DATA to DEFAULT_BLOCK_DATA for clarity (#3530) 2025-08-13 01:10:44 +00:00
Tim203 f357ac85d0 Preserve the pipeline order when async receiving packets (#3351) 2025-08-13 01:07:29 +00:00
Dan Mulloy 66cdb5b69d Move dev builds from Jenkins to GitHub (#3542) 2025-08-10 16:38:54 -05:00
Dan Mulloy 1aeb002afe Update version to 5.4.0 for release 2025-08-06 20:25:03 -05:00
Dan Mulloy 79d44f0d6e Minor build fixes 2025-08-06 20:06:30 -05:00
Dan Mulloy 9bde9b4231 Update group in Gradle example 2025-08-06 11:58:30 -05:00
Dan Mulloy 714252df6f Publish to Maven Central 2025-08-06 11:04:00 -05:00
Leviaria e56bcf624d Added support for Minecraft 1.21.8 (#3523) 2025-07-27 19:30:42 -05:00
Diogo Correia 596e5e4246 fix: incorrect version gating on WrappedTeamParamters (#3496) 2025-06-27 19:22:06 -05:00
Diogo Correia f3f895995a fix: initialization error of ChunkData wrapper on MC 1.21.5+ (#3495) 2025-06-27 12:23:21 -05:00
Diogo Correia 67e7375b2f fix: initialization error for WrappedChatComponent on 1.20.4-1.21.5 (#3493) 2025-06-27 10:17:03 -05:00
Pasqual Koschmieder 9417eee444 drop unnecessary minecraft import from EnumWrappers (#3476) 2025-06-19 12:57:42 -05:00
Dan Mulloy a432fae4dd 1.21.6 update (#3475) 2025-06-18 15:33:20 -05:00
JavaFruit 8a5e59d4f2 fix: re-add string overloads for nametagVisibility and collisionRule in WrappedTeamParameters.Builder (#3445) 2025-05-23 20:03:53 +02:00
Maurice Eisenblätter 90a386b743 Fix some 1.21.5 related issues (unit tests) (#3432) 2025-05-09 19:07:58 -05:00
Pasqual Koschmieder a80762feed update dependencies for java 24 support (#3425) 2025-05-06 14:07:29 -05:00
Brokkonaut a936183b76 Fix WrappedList - NBT lists may now be heterogeneous (#3394) 2025-04-22 19:21:43 -05:00
Pasqual Koschmieder 804aa5df11 fix: data watcher serializer initialization (#3384) 2025-03-31 09:46:08 -05:00
Maurice Eisenblätter 1e73df8600 Initial support for 1.21.5 (#3381) 2025-03-29 17:26:16 -05:00
Ryan 4314a55866 fix: Show helpful message when no supported updater loads (#3358) 2025-03-25 19:37:11 -05:00
Diogo Correia 2c0d632dc2 fix: listOrder not preserved when converting PlayerInfoData (#3347)
The field `listOrder` was always set to zero in the wrapper instead of
being read from the NMS object. This commit fixes that, while adding the
respective constructor and getter.
2025-02-04 01:11:03 +00:00
Pasqual Koschmieder fd5d8c576e fix/improve publishing (#3350) 2025-02-03 19:03:37 -06:00
Diogo Correia 0256953056 fix: unable to convert PlayerInfoData if gamemode is null (#3334) 2024-12-30 19:30:26 -06:00
Dan Mulloy 24262d3f7d Update link to wiki.vg 2024-12-09 14:41:23 -06:00
Brokkonaut 7a85663ded Update to 1.21.4 (#3319) 2024-12-09 14:17:17 -06:00
Dan Mulloy bcff6c6719 Update to 1.21.3 2024-11-08 11:03:51 -06:00
Dan Mulloy 3155e76d06 Mojang Mappings 2024-10-27 21:33:15 -05:00
Dan Mulloy 6845acd89d Update to 1.21.3 2024-10-26 15:21:23 -05:00
Dan Mulloy 3442d3fb8f Update version to 5.3.0 for release 2024-10-20 17:12:58 -05:00
Pasqual Koschmieder 1e3fa2d36e deps: update bytebuddy and shadow (#3247) 2024-09-24 21:53:32 +02:00
JustRed23 c9ee2f16e2 Fix serializing and deserializing of ItemStack on 1.21 (#3217) 2024-09-23 12:30:34 +00:00
Dan Mulloy 7f615378e4 Compile against 1.21.1 2024-09-22 21:41:32 -05:00
SzczurekYT d636be81e7 Fix ConcurrentModificationException when shutting down async listeners (#3169) 2024-09-23 00:11:17 +00:00
Bjorn Beishline 3cb261f466 Fix ConcurrentModificationException in PacketListenerSet (#3241) 2024-09-23 00:04:12 +00:00
Maurice Eisenblätter 7338183ed6 Fix Github actions & unit tests (#3244)
* chore: update github actions

* test: fix initialization

* test: add missing imports for test initialization
2024-09-22 19:00:57 -05:00
iiAhmedYT fb085676a9 Add AdventureComponentConverter#fromComponentObject (#3127) 2024-07-23 02:59:30 +00:00
Diogo Correia 9fc68d4b6c fix: race condition in EnumWrappers#initialize (#3121)
Adds synchronization primitives inside the initialization function to
ensure that when the function returns, everything is guaranteed to be
initialized.

Fixes #3120
2024-07-22 21:56:48 -05:00
Dan Mulloy f227b17f5f fix: test instantiation 2024-07-22 21:52:43 -05:00
Scott Hiett 4aa344bab2 Fix WrappedTeamParameters.Builder incorrectly using displayName for prefix and suffix (#3050) 2024-06-27 03:12:12 +00:00
Maurice Eisenblätter da65c9d3c1 Improve/cleanup injector (part 2) (#3042)
* fix(#3024): cache noop accessor on ctor lookup failure
* fix(#3023): add client intent enum wrapper
2024-06-25 21:07:16 -05:00
Maurice Eisenblätter 47bcd53fd0 Improve/cleanup injector (#2992) 2024-06-16 20:42:46 -05:00
Dan Mulloy 2cad996252 fix: gracefully fail for unknown protocols
Fixes #3012
Fixes #3001
Fixes #2994
2024-06-14 14:00:59 -05:00
192 changed files with 8090 additions and 3950 deletions

No files matched your search

+1
View File
@@ -1 +1,2 @@
* text=auto eol=lf
.github/workflows/*.lock.yml linguist-generated=true merge=ours
+73 -36
View File
@@ -1,38 +1,75 @@
Contributing to ProtocolLib
===========================
This page will detail specific things that must be done if you intend to contribute to this project.
# Contributing to ProtocolLib
## Updating ProtocolLib to a New Protocol Version
#### Before we get started:
1. **When do we need to update the protocol version?**
_A Minecraft protocol version is a number for each specification of the Minecraft protocol.
This means it is incremented when there is a change to the [Protocol Specification](http://wiki.vg/Protocol),
but not necessarily for each Minecraft version update. If the latest Minecraft version contains a protocol change,
that means ProtocolLib needs to be updated._
2. **When do we need to update other parts of ProtocolLib?**
_When the package version guard changes. The package version guard is the version string inserted
into the package identifier for CraftBukkit and net.minecraft.server (e.g. v1_9_R2). It is incremented
by Spigot if there is a major breaking change in the internal server code. This is to encourage plugins
that interact directly with CraftBukkit/nms code to update more specifically. However, since most of
ProtocolLib uses reflection, and the structure of packet classes is generally static, one will have
to go through the code and change imports from nms/CraftBukkit._
ProtocolLib supports a wide range of Minecraft versions through reflection and compatibility wrappers. Changes to packet
registration, reflection, or public wrappers can affect plugins on versions other than the one currently used for
development, so keep changes focused and preserve existing behavior unless a break is unavoidable.
#### 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 [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
(or had their ID changed), make sure to update this list. If a packet was removed
in favor of usage of another packet, instead of removing it, move it to the bottom of the list
in its section, add a deprecation warning to it, and redirect it to the packet that replaced it.
4. `mvn` in the root directory to build the project.
5. If tests fail in the maven build, go through the tests to make sure you removed references to any removed packets
and changed the NMS version guards.
6. Increment the package version in `com.comphenix.protocol.utility.Constants`.
7. `com.comphenix.protocol.ProtocolLibrary` contains several constants that must be updated, including the Minecraft version
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!
## Building ProtocolLib
Use the Gradle wrapper from the repository root. The root project builds the platform-neutral library published to
Maven. The `paper` module compiles and tests it against Paper's development bundle and produces the primary plugin,
`build/libs/ProtocolLib.jar`. The `spigot` compatibility module produces `build/libs/ProtocolLib-Spigot.jar`.
```shell
./gradlew test
./gradlew build shadowJar
```
`shadowJar` builds both plugin distributions with the required Byte Buddy classes included. Neither platform provider
nor plugin descriptor is included in the Maven core artifact.
ProtocolLib uses the Java 25 toolchain to compile against the current server, but production classes target Java 17 for
compatibility with older supported servers.
## Updating ProtocolLib for a Minecraft Release
Start by reviewing the [Java Edition protocol](https://minecraft.wiki/w/Java_Edition_protocol) and the corresponding
server classes. The server packet classes and codecs are authoritative when documentation and implementation differ.
### 1. Update version metadata
- Update `mcVersion` in `gradle.properties`.
- Add the release to `MinecraftVersion` and update `MinecraftVersion.LATEST`.
- Update `ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION` and `ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE`.
- Update `MinecraftProtocolVersion` if the release changed the protocol number.
### 2. Reconcile packet types
`PacketType` must contain a matching definition for every packet in the current server protocol registries. Compare the
serverbound and clientbound packet classes and codecs against every protocol phase, including configuration.
- Add new, renamed, or split packets and update their current IDs and aliases.
- Do not remove an existing public `PacketType` constant. Move removed types to the deprecated section and document their
replacement when one exists.
- Represent split packets as separate packet types instead of redirecting incompatible packet shapes.
- Run the packet registry tests after changing packet definitions. A missing definition can prevent later packets in the
same protocol registry from being registered.
### 3. Update reflection and wrappers
- Inspect changed fields, constructors, records, nested classes, and stream codecs instead of relying only on class names.
- Prefer structural or type-based reflection where names are unstable.
- Treat the current server API shape as primary and use older supported shapes only as narrow compatibility fallbacks.
- Put version checks at the narrowest compatibility boundary and leave the older path unchanged.
- If an NMS concept was removed, report it as unsupported or absent rather than treating a different class as equivalent.
- Preserve packet fields that are unrelated to the value being changed.
- Keep converters symmetric and preserve wrapper cloning and equality semantics.
- A custom `StructureModifier` must maintain the complete modifier contract, including safe reads and writes, field count,
metadata, introspection, defaults, and `withTarget()`.
### 4. Add regression coverage
- Initialize the test environment with `BukkitInitialization` when tests access CraftBukkit or NMS classes.
- Exercise the actual packet structure supplied by the current Paper dependency.
- Cover both sides of a version guard when feasible.
- Re-enable previously disabled tests when the underlying incompatibility is fixed.
- Run targeted tests while developing, followed by `./gradlew test`.
- Before declaring a Minecraft release supported, build the shaded plugin and test it on the corresponding server.
## Pull Request Expectations
- Keep the change limited to the reported problem or release update.
- Include regression tests for packet, reflection, converter, or wrapper behavior changes.
- Preserve public API signatures and binary compatibility where possible.
- Clearly document any unavoidable compatibility change.
- Do not hide reflection failures with broad exception handling or silent fallbacks.
+11 -7
View File
@@ -1,28 +1,32 @@
name: ProtocolLib full build lifecycle
on:
- push
- pull_request
push:
branches-ignore:
- 'gh-pages'
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
java-version: '25'
cache: 'gradle'
- name: Run gradle build lifecycle
run: ./gradlew build shadowJar --no-daemon
- name: Upload plugin file
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ProtocolLib
path: build/libs/ProtocolLib.jar
path: |
build/libs/ProtocolLib.jar
build/libs/ProtocolLib-Spigot.jar
+4 -4
View File
@@ -20,17 +20,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: 'java'
@@ -38,4 +38,4 @@ jobs:
run: ./gradlew build -x test --no-daemon
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
+46
View File
@@ -0,0 +1,46 @@
name: ProtocolLib development build
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '25'
cache: 'gradle'
- name: Run gradle build lifecycle
run: |
COMMIT_SHA=${{ github.sha }} ./gradlew build shadowJar --no-daemon
- name: Upload plugin file
uses: actions/upload-artifact@v4
with:
name: ProtocolLib
path: |
build/libs/ProtocolLib.jar
build/libs/ProtocolLib-Spigot.jar
- name: Create or update GitHub pre-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
COMMIT_MSG=$(git log -1 --pretty=%B)
SHORT_HASH=$(echo "${{ github.sha }}" | cut -c1-7)
RELEASE_NOTES=$(gh release view dev-build --repo ${{ github.repository }} --json body -q ".body" 2>/dev/null || echo "")
gh release view dev-build --repo ${{ github.repository }} || \
gh release create dev-build --prerelease --title "Development Build" --notes "## Changelog" --repo ${{ github.repository }}
NEW_NOTES="$RELEASE_NOTES"$'\n'"* [$SHORT_HASH] $COMMIT_MSG"
gh release edit dev-build --notes "$NEW_NOTES" --repo ${{ github.repository }}
gh release upload dev-build build/libs/ProtocolLib.jar#ProtocolLib.jar --clobber --repo ${{ github.repository }}
gh release upload dev-build build/libs/ProtocolLib-Spigot.jar#ProtocolLib-Spigot.jar --clobber --repo ${{ github.repository }}
-1
View File
@@ -181,4 +181,3 @@ logs/
build/
logs/
.java-version
gradle.properties
+124
View File
@@ -0,0 +1,124 @@
# ProtocolLib Agent Guide
This file applies to the entire repository. Read `.github/CONTRIBUTING.md` before changing Minecraft support, packet
definitions, reflection, or public wrappers.
## Repository Basics
- The main project uses the Gradle wrapper. Do not use `TinyProtocol/pom.xml` to build ProtocolLib.
- The Java toolchain and current Spigot dependency are defined in `build.gradle.kts`.
- Keep the Java 25 build toolchain separate from the Java 17 production bytecode target.
- Production code is under `src/main/java`; JUnit tests and NMS test initialization are under `src/test/java`.
- Keep changes focused. Do not combine protocol fixes with unrelated cleanup or formatting.
- Preserve public API and binary compatibility unless the task explicitly requires a breaking change.
## Build and Test Commands
```shell
./gradlew test
./gradlew build shadowJar
```
Run the smallest relevant test while developing:
```shell
./gradlew test --tests fully.qualified.TestClass
./gradlew test --tests fully.qualified.TestClass.testMethod
```
After changing production Java code, run the targeted tests and then the complete test suite. Use `build shadowJar` when
the task affects packaging, dependencies, release support, or the final plugin artifact.
Do not introduce another build system, test runner, formatter, or lint tool unless the repository adopts it separately.
## Important Code Areas
- `PacketType.java`: public packet constants, current IDs, aliases, and deprecated packet definitions.
- `injector/packet/internal/ProtocolRegistry_1_20_5.java`: current packet discovery and codec registration.
- `utility/MinecraftVersion.java`: named Minecraft versions and the latest supported release.
- `utility/MinecraftProtocolVersion.java`: Minecraft release to protocol-number mappings.
- `ProtocolLibrary.java`: supported version bounds and latest release date.
- `utility/MinecraftReflection.java`: centralized NMS and CraftBukkit class discovery.
- `reflect/StructureModifier.java`: indexed packet field access and conversion contract.
- `wrappers/`: stable public representations of unstable NMS structures.
- `events/PacketContainer.java` and `events/AbstractStructure.java`: packet construction and typed field access.
- `src/test/java/com/comphenix/protocol/BukkitInitialization.java`: test bootstrap for CraftBukkit and NMS access.
## Minecraft Release Updates
Follow the complete checklist in `.github/CONTRIBUTING.md`. In particular:
- Update all version metadata together; do not bump only the Gradle dependency.
- Reconcile every serverbound and clientbound packet in every protocol phase with `PacketType`.
- Add packet types for newly split packets instead of forcing incompatible shapes through an older packet constant.
- Preserve removed public `PacketType` constants in the deprecated section.
- Update `MinecraftProtocolVersion` only when the protocol number actually changed.
Every packet exposed by the current NMS protocol registries must have a matching `PacketType`. An undefined or incorrectly
renamed packet can abort registration of later packets in the same registry.
## Version Compatibility
- Put version checks at the narrowest affected boundary.
- Treat the current supported server API shape as the primary path. Use older supported shapes only as narrow fallbacks.
- Keep the pre-change code path intact whenever possible.
- Use named `MinecraftVersion` constants with `atOrAbove()` instead of scattered version strings.
- A public method should keep the same signature and broad contract across versions.
- If Minecraft removed a concept, return an unsupported or absent result consistent with existing APIs. Do not claim an
unrelated packet or class is the removed type.
- When Minecraft splits one packet into several packets, expose and use the new packet types explicitly.
- Preserve unrelated packet fields when adapting or rewriting one logical value.
The current test dependency exercises the latest supported server. Review older guarded paths carefully because they may
not execute in the local test run.
## Packet and Reflection Rules
- Treat the current server packet classes, record components, constructors, and codecs as authoritative.
- Prefer existing `MinecraftReflection`, `FuzzyReflection`, and accessor helpers over new direct reflection.
- Prefer structural and type-based matching where obfuscated names are unstable.
- Search for an existing class resolver, converter, or accessor before adding another one.
- Cache stable reflective lookups following nearby patterns; do not repeatedly scan classes on packet hot paths.
- Do not hide reflection failures with broad catches, silent defaults, or unrelated fallback types.
- Account for static fields, inherited fields, records, nested implementations, and duplicate field types when writing
fuzzy contracts.
## `StructureModifier` Contract
Typed accessors returned from packet APIs must behave like normal `StructureModifier` instances.
- `size()`, `getFields()`, `getField()`, target metadata, and field metadata must agree.
- `readSafely()` returns `null` for an absent index.
- `writeSafely()` ignores an absent index without throwing.
- `withTarget()` must preserve the modifier's behavior for the new target.
- `writeDefaults()` must remain safe.
- Converters must be applied symmetrically on reads and writes.
If a packet no longer contains a real backing field, prefer an empty modifier when compatible. If a virtual field is
required for API compatibility, initialize all modifier metadata and test its inherited methods.
## Wrapper and Converter Rules
- Wrapper factories, converters, getters, setters, and `deepClone()` must describe the same logical value.
- Preserve converter symmetry: converting specific to generic and back must not unexpectedly lose represented state.
- If a wrapper handle changes from a nested value to an entire packet, define logical `equals()` and `hashCode()` so
unrelated packet metadata does not affect equality.
- Clones that represent the same logical value should compare equal.
- Do not fabricate legacy states that the modern wire format cannot distinguish; document or surface the limitation.
- Record or immutable packet updates must retain entity IDs, flags, and other components not owned by the wrapper.
## Tests
- Call `BukkitInitialization.initializeAll()` before tests that resolve CraftBukkit or NMS classes.
- Prefer regression tests using the actual Spigot packet classes supplied by the test dependency.
- Test reads, writes, safe access, cloning, equality, and preservation of unrelated fields when changing wrappers.
- Test modifier metadata and inherited methods when introducing a custom `StructureModifier`.
- Re-enable disabled tests when fixing the incompatibility that caused them to be disabled.
- Do not weaken assertions or encode known incorrect behavior merely to make a new Minecraft version pass.
## Documentation and Review
- Update Javadocs when version support, nullability, or unsupported behavior changes.
- Update `.github/CONTRIBUTING.md` when the repository-wide build or release workflow changes.
- Keep this file operational and durable; do not add task-specific notes, temporary workarounds, or current PR details.
- Before finishing, inspect the complete diff for accidental API changes and unrelated edits.
+64 -23
View File
@@ -2,7 +2,7 @@
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modifying Minecraft directly. A common technique is to modify incoming
and outgoing [packets](https://wiki.vg/Protocol), or inject custom packets into the
and outgoing packets, or inject custom packets into the
stream. This is quite cumbersome to do, however, and most implementations will break
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
@@ -12,16 +12,21 @@ lead to more subtle bugs.
### Resources
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
* [Spigot Page](https://spigotmc.org/resources/protocollib.1997/)
* [Releases](https://github.com/dmulloy2/ProtocolLib/releases)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib/)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
* [Protocol Wiki](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol)
`ProtocolLib.jar` is the primary Paper plugin. Spigot servers must use the compatibility artifact,
`ProtocolLib-Spigot.jar`. The Maven artifact is the platform-neutral library shared by both distributions.
### Compilation
ProtocolLib is built with [Gradle](https://gradle.org/). If you have it installed, just run
`./gradlew build` in the root project folder. Other gradle targets you may be interested in
include `clean`, `test`, and `shadowJar`. `shadowJar` will create a jar with all dependencies
(ByteBuddy) included.
(ByteBuddy) included for both Paper and Spigot.
### A new API
@@ -42,18 +47,11 @@ depend: [ ProtocolLib ]
You can also add ProtocolLib as a Maven dependency:
````xml
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.1.0</version>
<version>5.4.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -63,14 +61,56 @@ Or use the maven dependency with gradle:
```gradle
repositories {
maven { url "https://repo.dmulloy2.net/repository/public/" }
mavenCentral()
}
dependencies {
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
compileOnly 'net.dmulloy2:ProtocolLib:5.4.0'
}
```
#### Snapshot builds
Snapshot builds are available for testing changes before the next release. To use `5.5.0-SNAPSHOT`,
add the [public repository](https://repo.dmulloy2.net/repository/public/) and update your dependency version.
For Maven:
```xml
<repositories>
<repository>
<id>dmulloy2-public</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.5.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
For Gradle:
```gradle
repositories {
mavenCentral()
maven {
url = uri('https://repo.dmulloy2.net/repository/public/')
}
}
dependencies {
compileOnly 'net.dmulloy2:ProtocolLib:5.5.0-SNAPSHOT'
}
```
#### Accessing the API
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
````java
@@ -97,25 +137,26 @@ protocolManager.addPacketListener(new PacketAdapter(
});
````
It's also possible to read and modify the content of these packets. For instance, you can create a global
censor by listening for Packet3Chat events:
It's also possible to read and modify the content of packets, including the ones sent by a client. For
instance, you can censor the text players write on signs by listening for `UPDATE_SIGN` packets:
````java
// Censor
// Censor signs
protocolManager.addPacketListener(new PacketAdapter(
this,
ListenerPriority.NORMAL,
PacketType.Play.Client.CHAT
PacketType.Play.Client.UPDATE_SIGN
) {
@Override
public void onPacketReceiving(PacketEvent event) {
PacketContainer packet = event.getPacket();
String message = packet.getStrings().read(0);
String[] lines = packet.getStringArrays().read(0);
if (message.contains("shit") || message.contains("damn")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
for (int i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace("shit", "****").replace("damn", "****");
}
packet.getStringArrays().write(0, lines);
}
});
````
@@ -64,7 +64,7 @@ public abstract class TinyProtocol {
// Packets we have to intercept
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getClass("{nms}.PacketLoginInStart", "net.minecraft.network.protocol.login.PacketLoginInStart");
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
private static final FieldAccessor<String> getPlayerName = new PlayerNameAccessor();
// Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
@@ -492,6 +492,59 @@ public abstract class TinyProtocol {
}
}
/**
* Get the player name from the login start packet.
* This fixes the issue from 1.19 where the GameProfile field has been removed from this login packet.
*
* @author gamerover98
*/
private static class PlayerNameAccessor implements FieldAccessor<String> {
private FieldAccessor<String> getPlayerName;
private FieldAccessor<GameProfile> getGameProfile;
PlayerNameAccessor() {
try {
this.getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
} catch (IllegalArgumentException illegalArgumentException) {
// nothing to do.
}
try {
//HOT-FIX for 1.19+
this.getPlayerName = Reflection.getField(PACKET_LOGIN_IN_START, String.class, 0);
} catch (IllegalArgumentException illegalArgumentException) {
// nothing to do.
}
if (getGameProfile == null && getPlayerName == null) {
throw new UnsupportedOperationException("The current server version is not supported by TinyProtocol");
}
}
@Override
public String get(Object target) {
if (getPlayerName != null) {
String playerName = getPlayerName.get(target);
return playerName.substring(0, Math.min(16, playerName.length()));
}
return getGameProfile.get(target).getName();
}
@Override
public void set(Object target, Object value) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public boolean hasField(Object target) {
return getPlayerName != null
? getPlayerName.hasField(target)
: getGameProfile.hasField(target);
}
}
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
@@ -533,8 +586,7 @@ public abstract class TinyProtocol {
private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
GameProfile profile = getGameProfile.get(packet);
channelLookup.put(profile.getName(), channel);
channelLookup.put(getPlayerName.get(packet), channel);
}
}
}
-130
View File
@@ -1,130 +0,0 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
group = 'com.comphenix.protocol'
version = '5.3.0-SNAPSHOT'
description = 'Provides access to the Minecraft protocol'
def isSnapshot = version.endsWith('-SNAPSHOT')
repositories {
mavenLocal() // can speed up build, but may fail in CI
mavenCentral()
maven {
url 'https://repo.dmulloy2.net/repository/public/'
}
maven {
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
}
maven {
url 'https://libraries.minecraft.net/'
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.14.14'
compileOnly 'org.spigotmc:spigot-api:1.20.6-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.21-R0.1-SNAPSHOT:remapped-mojang'
compileOnly 'io.netty:netty-all:4.0.23.Final'
compileOnly 'net.kyori:adventure-text-serializer-gson:4.14.0'
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0'
testImplementation 'org.mockito:mockito-core:5.6.0'
testImplementation 'io.netty:netty-common:4.1.97.Final'
testImplementation 'io.netty:netty-transport:4.1.97.Final'
testImplementation 'org.spigotmc:spigot:1.21-R0.1-SNAPSHOT:remapped-mojang'
testImplementation 'net.kyori:adventure-text-serializer-gson:4.14.0'
testImplementation 'net.kyori:adventure-text-serializer-plain:4.14.0'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withJavadocJar()
withSourcesJar()
}
jar {
manifest {
attributes(
'paperweight-mappings-namespace': 'mojang'
)
}
}
shadowJar {
dependencies {
include(dependency('net.bytebuddy:byte-buddy:.*'))
}
relocate 'net.bytebuddy', 'com.comphenix.net.bytebuddy'
archiveFileName = 'ProtocolLib.jar'
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = 'full'
}
}
compileTestJava {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
processResources {
def includeBuild = isSnapshot && System.getenv('BUILD_NUMBER')
def fullVersion = includeBuild
? version + '-' + System.getenv('BUILD_NUMBER')
: version
eachFile { expand(['version': fullVersion]) }
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
afterEvaluate {
artifactId = 'ProtocolLib'
}
}
}
repositories {
maven {
url isSnapshot
? 'https://repo.dmulloy2.net/repository/snapshots/'
: 'https://repo.dmulloy2.net/repository/releases/'
credentials {
username project.nexusUsername
password project.nexusPassword
}
}
}
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
tasks.withType(Javadoc) {
options.encoding = 'UTF-8'
}
+134
View File
@@ -0,0 +1,134 @@
plugins {
`java-library`
`maven-publish`
signing
id("io.github.patrick.remapper") version "1.4.3"
id("com.vanniktech.maven.publish") version "0.36.0"
}
group = "net.dmulloy2"
description = "Provides access to the Minecraft protocol"
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val isJitPack = System.getenv("JITPACK")?.equals("true", ignoreCase = true) ?: false
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
repositories {
if (!isCI) {
mavenLocal()
}
mavenCentral()
maven {
url = uri("https://repo.codemc.io/repository/nms/")
}
maven {
url = uri("https://hub.spigotmc.org/nexus/content/groups/public/")
}
maven {
url = uri("https://libraries.minecraft.net/")
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation("net.bytebuddy:byte-buddy:1.18.2")
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
compileOnly("commons-lang:commons-lang:2.6")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
sourceSets {
test {
java.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
}
tasks {
test {
dependsOn(":paper:test")
}
check {
dependsOn(":paper:check", ":spigot:check")
}
javadoc {
options.encoding = "UTF-8"
}
compileJava {
options.encoding = "UTF-8"
options.release.set(17)
}
}
tasks.register("shadowJar") {
group = "build"
description = "Builds the Paper and Spigot plugin distributions."
dependsOn(":paper:shadowJar", ":spigot:shadowJar")
}
mavenPublishing {
publishToMavenCentral()
if (!isSnapshot && !isJitPack) {
signAllPublications()
}
coordinates("$group", project.name, "$version")
pom {
name.set(project.name)
description.set(project.description)
inceptionYear.set("2012")
url.set("https://github.com/dmulloy2/ProtocolLib")
developers {
developer {
id.set("dmulloy2")
name.set("Dan Mulloy")
url.set("https://dmulloy2.net/")
email.set("dev@dmulloy2.net")
}
}
licenses {
license {
name.set("GNU GENERAL PUBLIC LICENSE - Version 2, June 1991")
url.set("https://www.gnu.org/licenses/gpl-2.0.txt")
distribution.set("repo")
}
}
scm {
tag.set("HEAD")
url.set("https://github.com/dmulloy2/ProtocolLib")
connection.set("scm:git:git://github.com/dmulloy2/ProtocolLib.git")
developerConnection.set("scm:git:git@github.com:dmulloy2/ProtocolLib.git")
}
issueManagement {
system.set("GitHub Issues")
url.set("https://github.com/dmulloy2/ProtocolLib/issues")
}
}
}
+7 -2
View File
@@ -1,2 +1,7 @@
nexusUsername=""
nexusPassword=""
version=5.5.0-SNAPSHOT
mcVersion=26.3
mavenCentralUsername=
mavenCentralPassword=
signing.keyId=
signing.password=
signing.secretKeyRingFile=
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+13 -12
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+104
View File
@@ -0,0 +1,104 @@
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
id("com.gradleup.shadow") version "9.4.0"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
group = "net.dmulloy2"
version = rootProject.version
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.codemc.io/repository/nms/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://libraries.minecraft.net/") {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation(project(":"))
paperweight.paperDevBundle("$mcVersion.build.+")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("commons-lang:commons-lang:2.6")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-common:4.2.8.Final")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("net.kyori:adventure-text-serializer-gson:4.25.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.25.0")
testImplementation("commons-lang:commons-lang:2.6")
}
paperweight.reobfArtifactConfiguration =
io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
configurations.matching { it.name == "compileClasspath" || it.name == "runtimeClasspath" || it.name == "testCompileClasspath" || it.name == "testRuntimeClasspath" }.configureEach {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
sourceSets {
test {
java.srcDir(rootProject.file("src/test/java"))
resources.srcDir(rootProject.file("src/test/resources"))
}
}
tasks {
processResources {
val fullVersion = if (isSnapshot && isCI) "${version}-${commitHash.take(7)}" else version
eachFile {
expand("version" to fullVersion)
}
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
compileJava {
options.release.set(17)
}
shadowJar {
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
archiveFileName.set("ProtocolLib.jar")
}
}
@@ -0,0 +1,105 @@
package com.comphenix.protocol.paper;
import java.util.List;
import java.util.function.Consumer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.internal.PlatformProvider;
import io.netty.channel.Channel;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.network.ChannelInitializeListenerHolder;
import net.kyori.adventure.key.Key;
public final class PaperPlatformProvider implements PlatformProvider {
public static final Key CHANNEL_INITIALIZER_KEY = Key.key("protocollib", "channel-initializer");
@Override
public boolean hasEarlyChannelInitialization() {
return true;
}
@Override
public Runnable registerChannelInitializer(Consumer<Channel> channelInitializer) {
ChannelInitializeListenerHolder.addListener(CHANNEL_INITIALIZER_KEY, channelInitializer::accept);
return () -> ChannelInitializeListenerHolder.removeListener(CHANNEL_INITIALIZER_KEY);
}
@Override
public void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor) {
CommandMetadata metadata = getMetadata(name);
Command command = new RegisteredCommand(name, metadata);
plugin.registerCommand(
name,
metadata.description(),
metadata.aliases(),
new RegisteredBasicCommand(name, metadata, command, executor));
}
private static CommandMetadata getMetadata(String name) {
return switch (name) {
case "protocol" -> new CommandMetadata(
"Performs administrative tasks regarding ProtocolLib.",
"/<command> config|check|update|timings|listeners|version|dump",
"protocol.admin",
List.of());
case "packet" -> new CommandMetadata(
"Add or remove a simple packet listener.",
"/<command> add|remove|names client|server [ID start]-[ID stop] [detailed]",
"protocol.admin",
List.of());
case "filter" -> new CommandMetadata(
"Add or remove programmable filters to the packet listeners.",
"/<command> add|remove name [ID start]-[ID stop]",
"protocol.admin",
List.of("packet_filter"));
case "packetlog" -> new CommandMetadata(
"Logs hex representations of packets to a file or console",
"/<command> <protocol> <sender> <packet> [location]",
"protocol.admin",
List.of());
default -> throw new IllegalArgumentException("Unknown ProtocolLib command " + name);
};
}
private record CommandMetadata(String description, String usage, String permission, List<String> aliases) {
}
private record RegisteredBasicCommand(
String name,
CommandMetadata metadata,
Command command,
CommandExecutor executor) implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] arguments) {
if (!this.executor.onCommand(source.getSender(), this.command, this.name, arguments)) {
source.getSender().sendMessage(this.metadata.usage().replace("<command>", this.name));
}
}
@Override
public String permission() {
return this.metadata.permission();
}
}
private static final class RegisteredCommand extends Command {
private RegisteredCommand(String name, CommandMetadata metadata) {
super(name, metadata.description(), metadata.usage(), metadata.aliases());
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
throw new UnsupportedOperationException();
}
}
}
@@ -0,0 +1 @@
com.comphenix.protocol.paper.PaperPlatformProvider
+22
View File
@@ -0,0 +1,22 @@
name: ProtocolLib
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2]
main: com.comphenix.protocol.ProtocolLib
load: STARTUP
api-version: "26.3"
folia-supported: true
permissions:
protocol.*:
description: Gives access to everything.
children:
protocol.admin: true
protocol.info: true
protocol.admin:
description: Able to initiate the update process and configure debug mode.
default: op
protocol.info:
description: Can read update notifications and error reports.
default: op
+2
View File
@@ -3,3 +3,5 @@
*/
rootProject.name = 'ProtocolLib'
include 'paper', 'spigot'
+79
View File
@@ -0,0 +1,79 @@
import org.gradle.api.attributes.java.TargetJvmVersion
plugins {
`java-library`
id("com.gradleup.shadow") version "9.4.0"
}
group = "net.dmulloy2"
version = rootProject.version
val mcVersion: String by project
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.codemc.io/repository/nms/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://libraries.minecraft.net/") {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation(project(":"))
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
}
configurations.matching { it.name == "compileClasspath" || it.name == "runtimeClasspath" || it.name == "testCompileClasspath" || it.name == "testRuntimeClasspath" }.configureEach {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
tasks {
processResources {
eachFile {
expand("version" to project.version)
}
}
test {
useJUnitPlatform()
}
compileJava {
options.release.set(17)
}
shadowJar {
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
archiveFileName.set("ProtocolLib-Spigot.jar")
}
}
@@ -0,0 +1,34 @@
package com.comphenix.protocol.spigot;
import java.util.function.Consumer;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.internal.PlatformProvider;
import io.netty.channel.Channel;
public final class SpigotPlatformProvider implements PlatformProvider {
@Override
public boolean hasEarlyChannelInitialization() {
return false;
}
@Override
public Runnable registerChannelInitializer(Consumer<Channel> channelInitializer) {
throw new UnsupportedOperationException("Early channel initialization is unavailable");
}
@Override
public void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor) {
PluginCommand command = plugin.getCommand(name);
if (command == null) {
throw new IllegalStateException("plugin.yml might be corrupt.");
}
command.setExecutor(executor);
}
}
@@ -0,0 +1 @@
com.comphenix.protocol.spigot.SpigotPlatformProvider
@@ -1,7 +1,7 @@
name: ProtocolLib
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2, comphenix]
authors: [dmulloy2]
main: com.comphenix.protocol.ProtocolLib
load: STARTUP
@@ -44,4 +44,4 @@ permissions:
default: op
protocol.info:
description: Can read update notifications and error reports.
default: op
default: op
@@ -0,0 +1,26 @@
package com.comphenix.protocol;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.spigot.SpigotPlatformProvider;
class PlatformCommandRegistrarTest {
@Test
void registersLegacyExecutorFromPluginDescriptor() {
ProtocolLib plugin = mock(ProtocolLib.class);
CommandExecutor executor = mock(CommandExecutor.class);
PluginCommand command = mock(PluginCommand.class);
when(plugin.getCommand("protocol")).thenReturn(command);
new SpigotPlatformProvider().registerCommand(plugin, "protocol", executor);
verify(command).setExecutor(executor);
}
}
@@ -0,0 +1,17 @@
package com.comphenix.protocol.injector.netty.manager;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.internal.PlatformProvider;
class PlatformChannelInitializerTest {
@Test
void reportsPaperInitializerUnavailable() {
PlatformProvider provider = PlatformProvider.get();
assertInstanceOf(com.comphenix.protocol.spigot.SpigotPlatformProvider.class, provider);
assertFalse(provider.hasEarlyChannelInitialization());
}
}
@@ -120,25 +120,9 @@ class CommandProtocol extends CommandBase {
return;
}
// Perform on an async thread
Runnable notify = new Runnable() {
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
updateFinished();
}
};
// Top-level class instead of anonymous inner class so that PaperPluginClassLoader
// can resolve the .class entry when BukkitScheduler dispatches it on the main thread.
Runnable notify = new UpdateNotifyRunnable(sender, command, updater, config, plugin, this);
updater.start(type);
updater.addListener(notify);
}
@@ -14,18 +14,18 @@ import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
import com.comphenix.protocol.PacketTypeLookup.ClassLookup;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.scheduler.UniversalRunnable;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
/**
* Represents the type of a packet in a specific protocol.
* <p>
@@ -109,130 +109,148 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType BUNDLE = new PacketType(PROTOCOL, SENDER, 0x00, "BundleDelimiter", "Delimiter", "BundleDelimiterPacket");
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x01, "AddEntity", "SpawnEntity", "SPacketSpawnObject");
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x02, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x03, "Animate", "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x04, "AwardStats", "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, "BlockDestruction", "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "BlockEntityData", "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "BlockEvent", "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x09, "BlockUpdate", "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0A, "BossEvent", "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0B, "ChangeDifficulty", "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHUNK_BATCH_FINISHED = new PacketType(PROTOCOL, SENDER, 0x0C, "ChunkBatchFinished");
public static final PacketType CHUNK_BATCH_START = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunkBatchStart");
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0E, "ChunksBiomes", "ClientboundChunksBiomesPacket");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0F, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x10, "CommandSuggestions", "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x11, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetData", "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x15, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x16, "CookieRequest");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x17, "Cooldown", "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x18, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x19, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x1A, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1C, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1D, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1E, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1F, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x20, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x22, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x23, "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x24, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x25, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x26, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x27, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x28, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x29, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x2A, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x2B, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x2C, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2D, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x30, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x31, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x32, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x33, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x34, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x35, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x36, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x37, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x38, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3D, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3E, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x3F, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerPosition", "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x41, "Recipe", "Recipes", "SPacketRecipeBook");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x42, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x43, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x44, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x45, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x46, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x47, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x48, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x49, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x4A, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x4B, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x4C, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x4D, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x4E, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x4F, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x50, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x51, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x52, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x53, "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x54, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x55, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x56, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x57, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x58, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x59, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x5A, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x5B, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x5C, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x5D, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5E, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x60, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x61, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x62, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x63, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x64, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x65, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x66, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x67, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x68, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x69, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x6A, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x6B, "StoreCookie");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x6C, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x6D, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x6E, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x6F, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x70, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x71, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x72, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x73, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x74, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x75, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x76, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x77, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x78, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x79, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x7A, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x7B, "ServerLinks");
// ---- Removed in 1.9
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x02, "Animate", "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x03, "AwardStats", "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x04, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x05, "BlockDestruction", "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x06, "BlockEntityData", "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x07, "BlockEvent", "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x08, "BlockUpdate", "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x09, "BossEvent", "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0A, "ChangeDifficulty", "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHUNK_BATCH_FINISHED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchFinished");
public static final PacketType CHUNK_BATCH_START = new PacketType(PROTOCOL, SENDER, 0x0C, "ChunkBatchStart");
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunksBiomes", "ClientboundChunksBiomesPacket");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0E, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestions", "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x10, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetData", "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x15, "CookieRequest");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x16, "Cooldown", "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x17, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x18, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x19, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DEBUG_BLOCK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugBlockValue");
public static final PacketType DEBUG_CHUNK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugChunkValue");
public static final PacketType DEBUG_ENTITY_VALUE = new PacketType(PROTOCOL, SENDER, 0x1C, "DebugEntityValue");
public static final PacketType DEBUG_EVENT = new PacketType(PROTOCOL, SENDER, 0x1D, "DebugEvent");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1E, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1F, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x20, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x21, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x22, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x23, "EntityPositionSync");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x24, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType ADD_TRANSIENT_BLOCK = new PacketType(PROTOCOL, SENDER, 0x25, MinecraftVersion.v26_3, "AddTransientBlock");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x26, MinecraftVersion.v26_3, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x27, MinecraftVersion.v26_3, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType GAME_RULE_VALUES = new PacketType(PROTOCOL, SENDER, 0x28, MinecraftVersion.v26_3, "GameRuleValues");
public static final PacketType GAME_TEST_HIGHLIGHT_POS = new PacketType(PROTOCOL, SENDER, 0x29, MinecraftVersion.v26_3, "GameTestHighlightPos");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x2A, MinecraftVersion.v26_3, "MountScreenOpen", "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2B, MinecraftVersion.v26_3, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x2C, MinecraftVersion.v26_3, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x2D, MinecraftVersion.v26_3, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x2E, MinecraftVersion.v26_3, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x2F, MinecraftVersion.v26_3, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x30, MinecraftVersion.v26_3, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x31, MinecraftVersion.v26_3, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x32, MinecraftVersion.v26_3, "Login", "SPacketJoinGame");
public static final PacketType LOW_DISK_SPACE_WARNING = new PacketType(PROTOCOL, SENDER, 0x33, MinecraftVersion.v26_3, "LowDiskSpaceWarning");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x34, MinecraftVersion.v26_3, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x35, MinecraftVersion.v26_3, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x36, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x37, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x38, MinecraftVersion.v26_3, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x39, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x3A, MinecraftVersion.v26_3, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x3B, MinecraftVersion.v26_3, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x3C, MinecraftVersion.v26_3, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x3D, MinecraftVersion.v26_3, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x3E, MinecraftVersion.v26_3, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x3F, MinecraftVersion.v26_3, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x40, MinecraftVersion.v26_3, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x41, MinecraftVersion.v26_3, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x42, MinecraftVersion.v26_3, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x43, MinecraftVersion.v26_3, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x44, MinecraftVersion.v26_3, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x45, MinecraftVersion.v26_3, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x46, MinecraftVersion.v26_3, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x47, MinecraftVersion.v26_3, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x48, MinecraftVersion.v26_3, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x49, MinecraftVersion.v26_3, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x4A, MinecraftVersion.v26_3, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x4B, MinecraftVersion.v26_3, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x4C, MinecraftVersion.v26_3, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x4D, MinecraftVersion.v26_3, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x4E, MinecraftVersion.v26_3, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x4F, MinecraftVersion.v26_3, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x50, MinecraftVersion.v26_3, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x51, MinecraftVersion.v26_3, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x52, MinecraftVersion.v26_3, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType POST_EFFECTS = new PacketType(PROTOCOL, SENDER, 0x53, MinecraftVersion.v26_3, "PostEffects");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x54, MinecraftVersion.v26_3, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x55, MinecraftVersion.v26_3, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x56, MinecraftVersion.v26_3, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x57, MinecraftVersion.v26_3, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x58, MinecraftVersion.v26_3, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x59, MinecraftVersion.v26_3, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x5A, MinecraftVersion.v26_3, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x5B, MinecraftVersion.v26_3, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x5C, MinecraftVersion.v26_3, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x5D, MinecraftVersion.v26_3, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5E, MinecraftVersion.v26_3, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x5F, MinecraftVersion.v26_3, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x60, MinecraftVersion.v26_3, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x61, MinecraftVersion.v26_3, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x62, MinecraftVersion.v26_3, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x63, MinecraftVersion.v26_3, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x64, MinecraftVersion.v26_3, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x65, MinecraftVersion.v26_3, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x66, MinecraftVersion.v26_3, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x67, MinecraftVersion.v26_3, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x68, MinecraftVersion.v26_3, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x69, MinecraftVersion.v26_3, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x6A, MinecraftVersion.v26_3, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x6B, MinecraftVersion.v26_3, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x6C, MinecraftVersion.v26_3, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x6D, MinecraftVersion.v26_3, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x6E, MinecraftVersion.v26_3, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x6F, MinecraftVersion.v26_3, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x70, MinecraftVersion.v26_3, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x71, MinecraftVersion.v26_3, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x72, MinecraftVersion.v26_3, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x73, MinecraftVersion.v26_3, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x74, MinecraftVersion.v26_3, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x75, MinecraftVersion.v26_3, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x76, MinecraftVersion.v26_3, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x77, MinecraftVersion.v26_3, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x78, MinecraftVersion.v26_3, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x79, MinecraftVersion.v26_3, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x7A, MinecraftVersion.v26_3, "StoreCookie");
public static final PacketType SWING_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x7B, MinecraftVersion.v26_3, "SwingAnimation");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x7C, MinecraftVersion.v26_3, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x7D, MinecraftVersion.v26_3, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x7E, MinecraftVersion.v26_3, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x7F, MinecraftVersion.v26_3, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x80, MinecraftVersion.v26_3, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x81, MinecraftVersion.v26_3, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x82, MinecraftVersion.v26_3, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x83, MinecraftVersion.v26_3, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x84, MinecraftVersion.v26_3, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x85, MinecraftVersion.v26_3, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x86, MinecraftVersion.v26_3, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x87, MinecraftVersion.v26_3, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x88, MinecraftVersion.v26_3, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x89, MinecraftVersion.v26_3, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x8A, MinecraftVersion.v26_3, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x8B, MinecraftVersion.v26_3, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x8C, MinecraftVersion.v26_3, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x8D, MinecraftVersion.v26_3, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8E, MinecraftVersion.v26_3, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8F, MinecraftVersion.v26_3, "ShowDialog");
/**
* @deprecated Removed in 1.9
@@ -252,8 +270,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType UPDATE_ENTITY_NBT = new PacketType(PROTOCOL, SENDER, 253, "UpdateEntityNBT");
// ----- Renamed packets
/**
* @deprecated Renamed to {@link #WINDOW_DATA}
*/
@@ -278,8 +294,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType OPEN_SIGN_ENTITY = OPEN_SIGN_EDITOR.clone();
// ----- Replaced in 1.9.4
/**
* @deprecated Replaced by {@link #TILE_ENTITY_DATA}
*/
@@ -287,8 +301,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType UPDATE_SIGN = MinecraftReflection.signUpdateExists() ? new PacketType(PROTOCOL, SENDER, 252, "UpdateSign") :
TILE_ENTITY_DATA.clone();
// ---- Removed in 1.14
/**
* @deprecated Removed in 1.14
*/
@@ -403,6 +415,18 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 234, "ResourcePackSend", "SPacketResourcePackSend");
/**
* @deprecated Removed in 1.21.2: replaced with individual packets for adding/removing recipes
*/
@Deprecated
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 233, "Recipe", "Recipes", "SPacketRecipeBook");
/**
* @deprecated Removed in 1.21.5; replace by SPAWN_ENTITY
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 232, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
private static final Server INSTANCE = new Server();
// Prevent accidental construction
@@ -424,67 +448,74 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "AcceptTeleportation", "TeleportAccept", "CPacketConfirmTeleport");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x02, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x03, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x05, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x06, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x07, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x08, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x09, "ClientCommand", "CPacketClientStatus");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0A, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0B, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0C, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x0D, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0E, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0F, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x10, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x11, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x12, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x13, "DebugSampleSubscription");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x14, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x15, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x16, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x17, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x18, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x19, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1A, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1B, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x1C, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x1D, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x1E, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x1F, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x20, "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x21, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x22, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x23, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x24, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x25, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x26, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x27, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x28, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x29, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x2A, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x2B, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x2C, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x2D, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x2E, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x30, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x31, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x32, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x33, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x34, "SetStructureBlock", "Struct");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x35, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x36, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x37, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem");
// public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
// public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType ATTACK = new PacketType(PROTOCOL, SENDER, 0x01, "Attack");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x02, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x03, "SelectBundleItem");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x05, "ChangeGameMode");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x08, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x09, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x0A, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientCommand", "CPacketClientStatus");
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientTickEnd");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x10, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x15, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x16, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SUBSCRIPTION_REQUEST = new PacketType(PROTOCOL, SENDER, 0x17, "DebugSubscriptionRequest");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x18, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x19, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x1A, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1B, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1C, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1D, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x21, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x23, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromBlock");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x25, "PickItemFromEntity", "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x26, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x27, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2C, "PlayerLoaded");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2D, "Pong");
public static final PacketType PUNCH = new PacketType(PROTOCOL, SENDER, 0x2E, MinecraftVersion.v26_3, "Punch");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2F, MinecraftVersion.v26_3, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x30, MinecraftVersion.v26_3, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x31, MinecraftVersion.v26_3, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x32, MinecraftVersion.v26_3, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x33, MinecraftVersion.v26_3, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x34, MinecraftVersion.v26_3, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x35, MinecraftVersion.v26_3, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x36, MinecraftVersion.v26_3, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x37, MinecraftVersion.v26_3, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x38, MinecraftVersion.v26_3, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x39, MinecraftVersion.v26_3, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_GAME_RULE = new PacketType(PROTOCOL, SENDER, 0x3A, MinecraftVersion.v26_3, "SetGameRule");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x3B, MinecraftVersion.v26_3, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x3C, MinecraftVersion.v26_3, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3D, MinecraftVersion.v26_3, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3E, MinecraftVersion.v26_3, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType SPECTATE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x3F, MinecraftVersion.v26_3, "SpectatorAction", "SpectateEntity");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x40, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType TEST_INSTANCE_BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x41, "TestInstanceBlockAction");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x42, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x43, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x44, "CustomClickAction");
/**
* @deprecated Removed in 1.17
@@ -504,6 +535,18 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 253, "ChatPreview");
/**
* deprecated Removed in 1.21.9
*/
@Deprecated
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
/**
* @deprecated Removed in 26.3
*/
@Deprecated
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 252, "Swing", "ArmAnimation", "CPacketAnimation");
/**
* @deprecated Renamed to USE_ITEM_ON
*/
@@ -613,7 +656,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@ForceAsync
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, "LoginDisconnect", "Disconnect", "SPacketDisconnect");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "Hello", "EncryptionBegin", "SPacketEncryptionRequest");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "GameProfile", "Success", "SPacketLoginSuccess");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "LoginFinished", "GameProfile", "Success", "SPacketLoginSuccess");
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, "LoginCompression", "SetCompression", "SPacketEnableCompression");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, "CustomQuery", "CustomPayload", "SPacketCustomPayload");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x05, "CookieRequest");
@@ -685,13 +728,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType REGISTRY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "RegistryData");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x08, "ResourcePackPop");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x09, "ResourcePackPush");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0A, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0B, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0C, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x0F, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x10, "ServerLinks");
public static final PacketType POST_EFFECTS = new PacketType(PROTOCOL, SENDER, 0x0A, "PostEffects");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0B, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0C, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0E, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0F, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x10, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x11, "ServerLinks");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x12, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x13, "ShowDialog");
public static final PacketType CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x14, "CodeOfConduct");
/**
* @deprecated Removed in 1.20.4: replaced with new packets for removing and sending resource packs
@@ -723,6 +770,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x05, "Pong");
public static final PacketType RESOURCE_PACK_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ResourcePack");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x07, "ServerboundSelectKnownPacks");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "CustomClickAction");
public static final PacketType ACCEPT_CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x09, "AcceptCodeOfConduct");
private static final Client INSTANCE = new Client();
@@ -49,10 +49,11 @@ import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.internal.PlatformProvider;
/**
* The main entry point for ProtocolLib.
*
@@ -444,14 +445,7 @@ public class ProtocolLib extends JavaPlugin {
return;
}
PluginCommand command = this.getCommand(name);
// Try to load the command
if (command != null) {
command.setExecutor(executor);
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
PlatformProvider.get().registerCommand(this, name, executor);
} catch (RuntimeException e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
@@ -504,6 +498,17 @@ public class ProtocolLib extends JavaPlugin {
}
private void checkUpdates() {
// Do not run updater if no supported updater is loaded
if (this.updater == null) {
highlyVisibleError(
" WARNING ",
" ProtocolLib is unable to check for updates. ",
" You are likely not using a supported version (Such as CraftBukkit). "
);
ProtocolLibrary.disableUpdates();
return;
}
// Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
@@ -17,9 +17,7 @@ package com.comphenix.protocol;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.comphenix.protocol.utility.MinecraftVersion;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
@@ -37,17 +35,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.20.4";
public static final String MAXIMUM_MINECRAFT_VERSION = "26.3";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (26.3) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-12-07";
/**
* Plugins that are currently incompatible with ProtocolLib.
*/
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
public static final String MINECRAFT_LAST_RELEASE_DATE = "2026-09-15";
private static Plugin plugin;
private static ProtocolConfig config;
@@ -0,0 +1,69 @@
/**
* 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 org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.updater.Updater;
/**
* Callback invoked on the main thread after an asynchronous update check completes.
* <p>
* Extracted as a top-level class (instead of an anonymous inner class) so that
* {@code PaperPluginClassLoader} can always resolve its {@code .class} entry when
* {@link org.bukkit.scheduler.BukkitScheduler#runTask} dispatches it back onto the
* main server thread — the same pattern used by {@code SpigotUpdateRunnable}.
*/
final class UpdateNotifyRunnable implements Runnable {
private final CommandSender sender;
private final boolean command;
private final Updater updater;
private final ProtocolConfig config;
private final Plugin plugin;
private final CommandProtocol owner;
UpdateNotifyRunnable(CommandSender sender, boolean command,
Updater updater, ProtocolConfig config,
Plugin plugin, CommandProtocol owner) {
this.sender = sender;
this.command = command;
this.updater = updater;
this.config = config;
this.plugin = plugin;
this.owner = owner;
}
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
owner.updateFinished();
}
}
@@ -17,14 +17,6 @@
package com.comphenix.protocol.async;
import java.util.List;
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 com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
@@ -35,10 +27,19 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a filter manager for asynchronous packets.
@@ -274,7 +275,7 @@ public class AsyncFilterManager implements AsynchronousManager {
* @return TRUE if we are, FALSE otherwise.
*/
private boolean onMainThread() {
return Thread.currentThread().getId() == mainThread.getId();
return Thread.currentThread() == mainThread;
}
@Override
@@ -284,9 +285,9 @@ public class AsyncFilterManager implements AsynchronousManager {
}
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (AsyncListenerHandler listener : processingQueue.values()) {
for (AsyncListenerHandler listener : ImmutableList.copyOf(processingQueue.values())) {
// Remove the listener
if (Objects.equal(listener.getPlugin(), plugin)) {
unregisterAsyncHandler(listener);
@@ -308,12 +309,10 @@ public class AsyncFilterManager implements AsynchronousManager {
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
// The player is only be null when they're logged out,
// so this should be a pretty safe check
Player player = syncPacket.getPlayer();
if (player != null) {
PacketSendingQueue sendingQueue = getSendingQueue(syncPacket);
if (sendingQueue != null) {
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
sendingQueue.enqueue(newEvent);
// We know this is occurring on the main thread, so pass TRUE
getProcessingQueue(syncPacket).enqueue(newEvent, true);
@@ -322,12 +321,12 @@ public class AsyncFilterManager implements AsynchronousManager {
@Override
public Set<PacketType> getReceivingTypes() {
return serverProcessingQueue.keySet();
return clientProcessingQueue.keySet();
}
@Override
public Set<PacketType> getSendingTypes() {
return clientProcessingQueue.keySet();
return serverProcessingQueue.keySet();
}
/**
@@ -347,8 +346,8 @@ public class AsyncFilterManager implements AsynchronousManager {
* Construct a asynchronous marker with all the default values.
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
public AsyncMarker createAsyncMarker(Injector injector) {
return createAsyncMarker(injector, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
}
/**
@@ -356,13 +355,13 @@ public class AsyncFilterManager implements AsynchronousManager {
* @param timeoutDelta - how long (in ms) until the packet expire.
* @return An async marker.
*/
public AsyncMarker createAsyncMarker(long timeoutDelta) {
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
public AsyncMarker createAsyncMarker(Injector injector, long timeoutDelta) {
return createAsyncMarker(injector, timeoutDelta, currentSendingIndex.incrementAndGet());
}
// Helper method
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
private AsyncMarker createAsyncMarker(Injector injector, long timeoutDelta, long sendingIndex) {
return new AsyncMarker(injector, sendingIndex, System.currentTimeMillis(), timeoutDelta);
}
@Override
@@ -538,7 +538,7 @@ public class AsyncListenerHandler {
*/
private void listenerLoop(int workerID) {
// Danger, danger!
if (Thread.currentThread().getId() == mainThread.getId())
if (Thread.currentThread() == mainThread)
throw new IllegalStateException("Do not call this method from the main thread.");
if (cancelled)
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
@@ -607,8 +607,6 @@ public class AsyncListenerHandler {
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
@@ -23,14 +23,18 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -64,7 +68,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
private final Injector injector;
/**
* Current list of async packet listeners.
@@ -86,7 +90,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
private final AtomicBoolean transmitted = new AtomicBoolean(false);
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
@@ -109,11 +113,8 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* Create a container for asyncronous packets.
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
*/
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
AsyncMarker(Injector injector, long sendingIndex, long initialTime, long timeoutDelta) {
this.injector = Objects.requireNonNull(injector, "injector is nul");
// Timeout
this.initialTime = initialTime;
@@ -180,16 +181,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* Retrieve the packet stream responsible for transmitting this packet.
* @return The packet stream.
*/
@Deprecated
public PacketStream getPacketStream() {
return packetStream;
return ProtocolLibrary.getProtocolManager();
}
/**
* Sets the output packet stream responsible for transmitting this packet.
* @param packetStream - new output packet stream.
*/
@Deprecated
public void setPacketStream(PacketStream packetStream) {
this.packetStream = packetStream;
// NOOP
}
/**
@@ -285,7 +288,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* @return TRUE if it has been sent before, FALSE otherwise.
*/
public boolean isTransmitted() {
return transmitted;
return transmitted.get();
}
/**
@@ -383,13 +386,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* @throws IOException If the packet couldn't be sent.
*/
void sendPacket(PacketEvent event) throws IOException {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
false);
if (!this.transmitted.compareAndSet(false, true)) {
return;
}
Object handle = event.getPacket().getHandle();
if (event.isServerPacket()) {
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
this.injector.sendClientboundPacket(handle, marker, false);
} else {
this.injector.readServerboundPacket(handle);
}
transmitted = true;
}
/**
@@ -30,6 +30,8 @@ import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MinMaxPriorityQueue;
@@ -209,7 +211,7 @@ class PacketProcessingQueue {
public void cleanupAll() {
// Cancel all the threads and every listener
for (AsyncListenerHandler handler : map.values()) {
for (AsyncListenerHandler handler : ImmutableList.copyOf(map.values())) {
handler.cancel();
}
@@ -134,14 +134,18 @@ class PlayerSendingHandler {
* @return The server or client sending queue the packet belongs to.
*/
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
Player player = packet.getPlayer();
if (player == null)
return null;
QueueContainer queues = playerSendingQueues.get(player);
// Safe concurrent initialization
if (queues == null && createNew) {
final QueueContainer newContainer = new QueueContainer();
// Attempt to map the queue
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
queues = playerSendingQueues.putIfAbsent(player, newContainer);
if (queues == null) {
queues = newContainer;
@@ -58,6 +58,8 @@ public class IntegerMap<T> {
* @return The old associated value, or NULL.
*/
public T remove(int key) {
if (key < 0 || key >= array.length) return null;
T old = array[key];
array[key] = null;
@@ -1,494 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import com.google.common.base.Objects;
import com.google.common.collect.Range;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
/**
* Represents a generic store of intervals and associated values. No two intervals
* can overlap in this representation.
* <p>
* Note that this implementation is not thread safe.
*
* @author Kristian
*
* @param <TKey> - type of the key. Must implement Comparable.
* @param <TValue> - type of the value to associate.
*/
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
protected enum State {
OPEN,
CLOSE,
BOTH
}
/**
* Represents a range and a value in this interval tree.
*/
public class Entry implements Map.Entry<Range<TKey>, TValue> {
private final EndPoint left;
private final EndPoint right;
Entry(EndPoint left, EndPoint right) {
if (left == null)
throw new IllegalAccessError("left cannot be NUll");
if (right == null)
throw new IllegalAccessError("right cannot be NUll");
if (left.key.compareTo(right.key) > 0)
throw new IllegalArgumentException(
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
this.left = left;
this.right = right;
}
@Override
public Range<TKey> getKey() {
return Range.closed(left.key, right.key);
}
@Override
public TValue getValue() {
return left.value;
}
@Override
public TValue setValue(TValue value) {
TValue old = left.value;
// Set both end points
left.value = value;
right.value = value;
return old;
}
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object obj) {
// Quick equality check
if (obj == this) {
return true;
} else if (obj instanceof AbstractIntervalTree.Entry) {
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(left.key, right.key, left.value);
}
@Override
public String toString() {
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
}
}
/**
* Represents a single end point (open, close or both) of a range.
*/
protected class EndPoint {
// Whether or not the end-point is opening a range, closing a range or both.
public State state;
// The value this range contains
public TValue value;
// The key of this end point
public TKey key;
public EndPoint(State state, TKey key, TValue value) {
this.state = state;
this.key = key;
this.value = value;
}
}
// To quickly look up ranges we'll index them by endpoints
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<>();
/**
* Removes every interval that intersects with the given range.
* @param lowerBound - lowest value to remove.
* @param upperBound - highest value to remove.
* @return Intervals that were removed
*/
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
return remove(lowerBound, upperBound, false);
}
/**
* Removes every interval that intersects with the given range.
* @param lowerBound - lowest value to remove.
* @param upperBound - highest value to remove.
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
* @return Intervals that were removed
*/
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
checkBounds(lowerBound, upperBound);
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
EndPoint first = getNextEndPoint(lowerBound, true);
EndPoint last = getPreviousEndPoint(upperBound, true);
// Used while resizing intervals
EndPoint previous = null;
EndPoint next = null;
Set<Entry> resized = new HashSet<>();
Set<Entry> removed = new HashSet<>();
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
if (first != null && first.state == State.CLOSE) {
previous = getPreviousEndPoint(first.key, false);
// Add the interval back
if (previous != null) {
removed.add(getEntry(previous, first));
}
}
// Get the closing element too.
if (last != null && last.state == State.OPEN) {
next = getNextEndPoint(last.key, false);
if (next != null) {
removed.add(getEntry(last, next));
}
}
// Now remove both ranges
removeEntrySafely(previous, first);
removeEntrySafely(last, next);
// Add new resized intervals
if (preserveDifference) {
if (previous != null) {
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
}
if (next != null) {
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
}
}
// Get the removed entries too
getEntries(removed, range);
invokeEntryRemoved(removed);
if (preserveDifference) {
invokeEntryAdded(resized);
}
// Remove the range as well
range.clear();
return removed;
}
/**
* Retrieve the entry from a given set of end points.
* @param left - leftmost end point.
* @param right - rightmost end point.
* @return The associated entry.
*/
protected Entry getEntry(EndPoint left, EndPoint right) {
if (left == null)
throw new IllegalArgumentException("left endpoint cannot be NULL.");
if (right == null)
throw new IllegalArgumentException("right endpoint cannot be NULL.");
// Make sure the order is correct
if (right.key.compareTo(left.key) < 0) {
return getEntry(right, left);
} else {
return new Entry(left, right);
}
}
private void removeEntrySafely(EndPoint left, EndPoint right) {
if (left != null && right != null) {
bounds.remove(left.key);
bounds.remove(right.key);
}
}
// Adds a given end point
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
EndPoint endPoint = bounds.get(key);
if (endPoint != null) {
endPoint.state = State.BOTH;
} else {
endPoint = new EndPoint(state, key, value);
bounds.put(key, endPoint);
}
return endPoint;
}
/**
* Associates a given interval of keys with a certain value. Any previous
* association will be overwritten in the given interval.
* <p>
* Overlapping intervals are not permitted. A key can only be associated with a single value.
*
* @param lowerBound - the minimum key (inclusive).
* @param upperBound - the maximum key (inclusive).
* @param value - the value, or NULL to reset this range.
*/
public void put(TKey lowerBound, TKey upperBound, TValue value) {
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
remove(lowerBound, upperBound, true);
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
}
/**
* Associates a given interval without performing any interval checks.
* @param lowerBound - the minimum key (inclusive).
* @param upperBound - the maximum key (inclusive).
* @param value - the value, or NULL to reset the range.
*/
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
// OK. Add the end points now
if (value != null) {
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
return new Entry(left, right);
} else {
return null;
}
}
/**
* Used to verify the validity of the given interval.
* @param lowerBound - lower bound (inclusive).
* @param upperBound - upper bound (inclusive).
*/
private void checkBounds(TKey lowerBound, TKey upperBound) {
if (lowerBound == null)
throw new IllegalAccessError("lowerbound cannot be NULL.");
if (upperBound == null)
throw new IllegalAccessError("upperBound cannot be NULL.");
if (upperBound.compareTo(lowerBound) < 0)
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
}
/**
* Determines if the given key is within an interval.
* @param key - key to check.
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
*/
public boolean containsKey(TKey key) {
return getEndPoint(key) != null;
}
/**
* Enumerates over every range in this interval tree.
* @return Number of ranges.
*/
public Set<Entry> entrySet() {
// Don't mind the Java noise
Set<Entry> result = new HashSet<>();
getEntries(result, bounds);
return result;
}
/**
* Remove every interval.
*/
public void clear() {
if (!bounds.isEmpty()) {
remove(bounds.firstKey(), bounds.lastKey());
}
}
/**
* Converts a map of end points into a set of entries.
* @param destination - set of entries.
* @param map - a map of end points.
*/
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
Map.Entry<TKey, EndPoint> last = null;
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
switch (entry.getValue().state) {
case BOTH:
EndPoint point = entry.getValue();
destination.add(new Entry(point, point));
break;
case CLOSE:
if (last != null) {
destination.add(new Entry(last.getValue(), entry.getValue()));
}
break;
case OPEN:
// We don't know the full range yet
last = entry;
break;
default:
throw new IllegalStateException("Illegal open/close state detected.");
}
}
}
/**
* Inserts every range from the given tree into the current tree.
* @param other - the other tree to read from.
*/
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
// Naively copy every range.
for (Entry entry : other.entrySet()) {
put(entry.left.key, entry.right.key, entry.getValue());
}
}
/**
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
* @param key - the level to read for.
* @return The correct amount of experience, or NULL if nothing was recorded.
*/
public TValue get(TKey key) {
EndPoint point = getEndPoint(key);
if (point != null)
return point.value;
else
return null;
}
/**
* Get the left-most end-point associated with this key.
* @param key - key to search for.
* @return The end point found, or NULL.
*/
protected EndPoint getEndPoint(TKey key) {
EndPoint ends = bounds.get(key);
if (ends != null) {
// Always return the end point to the left
if (ends.state == State.CLOSE) {
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
return left != null ? left.getValue() : null;
} else {
return ends;
}
} else {
// We need to determine if the point intersects with a range
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
// We only need to check to the left
if (left != null && left.getValue().state == State.OPEN) {
return left.getValue();
} else {
return null;
}
}
}
/**
* Get the previous end point of a given key.
* @param point - the point to search with.
* @param inclusive - whether or not to include the current point in the search.
* @return The previous end point of a given given key, or NULL if not found.
*/
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
if (point != null) {
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
if (previous != null)
return previous.getValue();
}
return null;
}
/**
* Get the next end point of a given key.
* @param point - the point to search with.
* @param inclusive - whether or not to include the current point in the search.
* @return The next end point of a given given key, or NULL if not found.
*/
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
if (point != null) {
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
if (next != null)
return next.getValue();
}
return null;
}
private void invokeEntryAdded(Entry added) {
if (added != null) {
onEntryAdded(added);
}
}
private void invokeEntryAdded(Set<Entry> added) {
for (Entry entry : added) {
onEntryAdded(entry);
}
}
private void invokeEntryRemoved(Set<Entry> removed) {
for (Entry entry : removed) {
onEntryRemoved(entry);
}
}
// Listeners for added or removed entries
/**
* Invoked when an entry is added.
* @param added - the entry that was added.
*/
protected void onEntryAdded(Entry added) { }
/**
* Invoked when an entry is removed.
* @param removed - the removed entry.
*/
protected void onEntryRemoved(Entry removed) { }
// Helpers for decrementing or incrementing key values
/**
* Decrement the given key by one unit.
* @param key - the key that should be decremented.
* @return The new decremented key.
*/
protected abstract TKey decrementKey(TKey key);
/**
* Increment the given key by one unit.
* @param key - the key that should be incremented.
* @return The new incremented key.
*/
protected abstract TKey incrementKey(TKey key);
}
@@ -1,266 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* A map that supports blocking on read operations. Null keys are not supported.
* <p>
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
* <p>
* @author Kristian
*
* @param <TKey> - type of the key.
* @param <TValue> - type of the value.
*/
public class BlockingHashMap<TKey, TValue> {
// Map of values
private final ConcurrentMap<TKey, TValue> backingMap;
// Map of locked objects
private final ConcurrentMap<TKey, Object> locks;
/**
* Retrieve a cache loader that will always throw an exception.
* @param <TKey> Type of the key
* @param <TValue> Type of the value
* @return An invalid cache loader.
*/
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
return new CacheLoader<TKey, TValue>() {
@Override
public TValue load(TKey key) throws Exception {
throw new IllegalStateException("Illegal use. Access the map directly instead.");
}
};
}
/**
* Initialize a new map.
*/
public BlockingHashMap() {
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
weakValues().
removalListener(
new RemovalListener<TKey, TValue>() {
@Override
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
// Clean up locks too
if (entry.getCause() != RemovalCause.REPLACED) {
locks.remove(entry.getKey());
}
}
}).
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
// Normal concurrent hash map
locks = new ConcurrentHashMap<>();
}
/**
* Initialize a new map.
* @param <TKey> Type of the key
* @param <TValue> Type of the value
* @return The created map.
*/
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
return new BlockingHashMap<>();
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* @param key - the key whose associated value is to be returned
* @return The value to which the specified key is mapped.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key) throws InterruptedException {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
TValue value = backingMap.get(key);
// Only lock if no value is available
if (value == null) {
final Object lock = getLock(key);
synchronized (lock) {
while (value == null) {
lock.wait();
value = backingMap.get(key);
}
}
}
return value;
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* @param key - the key whose associated value is to be returned
* @param timeout - the amount of time to wait until an association has been made.
* @param unit - unit of timeout.
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
return get(key, timeout, unit, false);
}
/**
* Waits until a value has been associated with the given key, and then retrieves that value.
* <p>
* If timeout is zero, this method will return immediately if it can't find an socket injector.
*
* @param key - the key whose associated value is to be returned
* @param timeout - the amount of time to wait until an association has been made.
* @param unit - unit of timeout.
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
* @throws InterruptedException If the current thread got interrupted while waiting.
*/
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
if (key == null)
throw new IllegalArgumentException("key cannot be NULL.");
if (unit == null)
throw new IllegalArgumentException("Unit cannot be NULL.");
if (timeout < 0)
throw new IllegalArgumentException("Timeout cannot be less than zero.");
TValue value = backingMap.get(key);
// Only lock if no value is available
if (value == null && timeout > 0) {
final Object lock = getLock(key);
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
// Don't exceed the timeout
synchronized (lock) {
while (value == null) {
try {
long remainingTime = stopTimeNS - System.nanoTime();
if (remainingTime > 0) {
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
value = backingMap.get(key);
} else {
// Timeout elapsed
break;
}
} catch (InterruptedException e) {
// This is fairly dangerous - but we might HAVE to block the thread
if (!ignoreInterrupted)
throw e;
}
}
}
}
return value;
}
/**
* Associate a given key with the given value.
* <p>
* Wakes up any blocking getters on this specific key.
*
* @param key - the key to associate.
* @param value - the value.
* @return The previously associated value.
*/
public TValue put(TKey key, TValue value) {
if (value == null)
throw new IllegalArgumentException("This map doesn't support NULL values.");
final TValue previous = backingMap.put(key, value);
final Object lock = getLock(key);
// Inform our readers about this change
synchronized (lock) {
lock.notifyAll();
return previous;
}
}
/**
* If and only if a key is not present in the map will it be associated with the given value.
* @param key - the key to associate.
* @param value - the value to associate.
* @return The previous value this key has been associated with.
*/
public TValue putIfAbsent(TKey key, TValue value) {
if (value == null)
throw new IllegalArgumentException("This map doesn't support NULL values.");
final TValue previous = backingMap.putIfAbsent(key, value);
// No need to unlock readers if we haven't changed anything
if (previous == null) {
final Object lock = getLock(key);
synchronized (lock) {
lock.notifyAll();
}
}
return previous;
}
public int size() {
return backingMap.size();
}
public Collection<TValue> values() {
return backingMap.values();
}
public Set<TKey> keys() {
return backingMap.keySet();
}
/**
* Atomically retrieve the lock associated with a given key.
* @param key - the current key.
* @return An asssociated lock.
*/
private Object getLock(TKey key) {
Object lock = locks.get(key);
if (lock == null) {
Object created = new Object();
// Do this atomically
lock = locks.putIfAbsent(key, created);
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
if (lock == null) {
lock = created;
}
}
return lock;
}
}
@@ -190,7 +190,8 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
Preconditions.checkNotNull(player, "player cannot be null");
Object key = keyMethod.apply(player);
Preconditions.checkState(key != null, "Unable to derive a key for the player.");
keyLookup.put(key, player);
return key;
}
@@ -30,8 +30,12 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolConfig;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.collections.ExpireHashMap;
@@ -41,11 +45,6 @@ import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
/**
* Internal class used to handle exceptions.
*
@@ -479,7 +478,7 @@ public class DetailedErrorReporter implements ErrorReporter {
} catch (LinkageError ex) {
// Apache is probably missing
apacheCommonsMissing = true;
} catch (ThreadDeath | OutOfMemoryError e) {
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable ex) {
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
@@ -1,17 +1,18 @@
package com.comphenix.protocol.events;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Preconditions;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.Sound;
@@ -25,10 +26,52 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import com.comphenix.protocol.wrappers.Either;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.MovingObjectPositionBlock;
import com.comphenix.protocol.wrappers.MultiBlockChangeInfo;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedAttribute;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataValue;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedLevelChunkData;
import com.comphenix.protocol.wrappers.WrappedMessageSignature;
import com.comphenix.protocol.wrappers.WrappedNumberFormat;
import com.comphenix.protocol.wrappers.WrappedParticle;
import com.comphenix.protocol.wrappers.WrappedPositionMoveRotation;
import com.comphenix.protocol.wrappers.WrappedPositionPath;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.WrappedRegistrable;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.WrappedRemoteChatSessionData;
import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedStatistic;
import com.comphenix.protocol.wrappers.WrappedTeamParameters;
import com.comphenix.protocol.wrappers.WrappedVecDelta;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Preconditions;
public abstract class AbstractStructure {
protected transient Object handle;
@@ -557,9 +600,7 @@ public abstract class AbstractStructure {
*/
public StructureModifier<List<PlayerInfoData>> getPlayerInfoDataLists() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(PlayerInfoData.getConverter()));
return getLists(PlayerInfoData.getConverter());
}
/**
@@ -1125,15 +1166,52 @@ public abstract class AbstractStructure {
}
/**
* Since 26.3 the signature fields are optional, an absent signature is read and written as {@code null}.
*
* @return read/writer structure direct access to unsalted signature data for example in chat message (since 1.19.3)
*/
public StructureModifier<WrappedMessageSignature> getMessageSignatures() {
if (MinecraftVersion.v26_3.atOrAbove()) {
EquivalentConverter<WrappedMessageSignature> converter = BukkitConverters.getWrappedMessageSignatureConverter();
return structureModifier.withParamType(
Optional.class,
new EquivalentConverter<WrappedMessageSignature>() {
@Override
public Object getGeneric(WrappedMessageSignature specific) {
return Optional.ofNullable(converter.getGeneric(specific));
}
@Override
public WrappedMessageSignature getSpecific(Object generic) {
return generic == null ? null : ((Optional<?>) generic).map(converter::getSpecific).orElse(null);
}
@Override
public Class<WrappedMessageSignature> getSpecificType() {
return WrappedMessageSignature.class;
}
},
MinecraftReflection.getMessageSignatureClass()
);
}
return structureModifier.withType(
MinecraftReflection.getMessageSignatureClass(),
BukkitConverters.getWrappedMessageSignatureConverter()
);
}
/**
* Retrieve a read/write structure for the ClientIntent enum used in Handshake/SetProtocol packet
* @return A modifier for Protocol enum fields.
*/
public StructureModifier<EnumWrappers.ClientIntent> getClientIntents() {
// Convert to and from the wrapper
return structureModifier.withType(
EnumWrappers.getClientIntentClass(),
EnumWrappers.getClientIntentConverter());
}
/**
* @param leftConverter converter for left values
* @param rightConverter converter for right values
@@ -1246,6 +1324,48 @@ public abstract class AbstractStructure {
));
}
/**
* Retrieve a read/write structure for {@link WrappedPositionMoveRotation} (available since Minecraft 1.21.2).
* <p>
* This is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC} (before 26.3).
*
* @return A modifier for PositionMoveRotation fields.
*/
public StructureModifier<WrappedPositionMoveRotation> getPositionMoveRotation() {
return structureModifier.withType(
MinecraftReflection.getPositionMoveRotationClass(),
WrappedPositionMoveRotation.getConverter()
);
}
/**
* Retrieve a read/write structure for {@link WrappedVecDelta} (available since Minecraft 26.3).
* <p>
* This is used in {@code REL_ENTITY_MOVE} and {@code REL_ENTITY_MOVE_LOOK}.
*
* @return A modifier for VecDelta fields.
*/
public StructureModifier<WrappedVecDelta> getVecDeltas() {
return structureModifier.withType(
MinecraftReflection.getVecDeltaClass().orElse(void.class),
WrappedVecDelta.getConverter()
);
}
/**
* Retrieve a read/write structure for {@link WrappedPositionPath} (available since Minecraft 26.3).
* <p>
* This is used in {@code ENTITY_POSITION_SYNC}.
*
* @return A modifier for PositionPath fields.
*/
public StructureModifier<WrappedPositionPath> getPositionPaths() {
return structureModifier.withType(
MinecraftReflection.getPositionPathClass().orElse(void.class),
WrappedPositionPath.getConverter()
);
}
/**
* Represents an equivalent converter for ItemStack arrays.
* @author Kristian
@@ -13,6 +13,7 @@ public enum ListenerOptions {
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
* known to be transmitted during login.
*/
@Deprecated
DISABLE_GAMEPHASE_DETECTION,
/**
@@ -17,7 +17,12 @@
package com.comphenix.protocol.events;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
@@ -90,20 +95,6 @@ public class ListeningWhitelist {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
*
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
}
}
/**
* Construct a copy of a given set.
*
@@ -311,6 +302,7 @@ public class ListeningWhitelist {
* @param gamePhase - the gamephase.
* @return This builder, for chaining.
*/
@Deprecated
public Builder gamePhase(GamePhase gamePhase) {
this.gamePhase = gamePhase;
return this;
@@ -321,6 +313,7 @@ public class ListeningWhitelist {
*
* @return This builder, for chaining.
*/
@Deprecated
public Builder gamePhaseBoth() {
return gamePhase(GamePhase.BOTH);
}
@@ -232,13 +232,13 @@ public abstract class PacketAdapter implements PacketListener {
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
throw new IllegalStateException("Override onPacketReceiving to get notifications of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
throw new IllegalStateException("Override onPacketSending to get notifications of sent packets!");
}
@Override
@@ -391,6 +391,8 @@ public class PacketEvent extends EventObject implements Cancellable {
* <li>getPlayer</li>
* <li>getAddress</li>
* <li>getServer</li>
* <li>getName, after the login start packet</li>
* <li>getUniqueId, when supplied by the login protocol</li>
* <li>chat</li>
* <li>sendMessage</li>
* <li>kickPlayer</li>
@@ -22,8 +22,10 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@@ -68,7 +70,7 @@ import org.jetbrains.annotations.Nullable;
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
class SerializedOfflinePlayer implements Serializable {
/**
* Generated by Eclipse.
@@ -122,22 +124,18 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
@Override
public boolean isOp() {
return operator;
}
@Override
public void setOp(boolean operator) {
this.operator = operator;
}
@Override
public Map<String, Object> serialize() {
throw new UnsupportedOperationException();
}
@Override
public Location getBedSpawnLocation() {
return bedSpawnLocation;
}
@@ -156,7 +154,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return null;
}
@Override
public Location getLocation() {
return null;
}
@@ -220,52 +217,34 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public void setStatistic(Statistic statistic, EntityType entityType, int i) {
}
@Override
public Location getLastDeathLocation() {
return null;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
}
@Override
public long getLastPlayed() {
return lastPlayed;
}
@Override
public UUID getUniqueId() {
return uuid;
}
/**
* @return
*/
@NotNull
@Override
public PlayerProfile getPlayerProfile() {
public @NotNull PlayerProfile getPlayerProfile() {
return null;
}
/*
public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() {
return null;
}
*/
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@@ -292,22 +271,14 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
// @Override
public boolean isConnected() {
return false;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
@@ -344,7 +315,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
@@ -363,7 +333,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
*/
public Player getProxyPlayer() {
try {
return (Player) proxyPlayerConstructor.newInstance(this);
return (Player) proxyPlayerConstructor.newInstance(this.createOfflinePlayerProxy());
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
@@ -373,6 +343,38 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
private OfflinePlayer createOfflinePlayerProxy() {
return (OfflinePlayer) Proxy.newProxyInstance(
OfflinePlayer.class.getClassLoader(),
new Class<?>[]{OfflinePlayer.class},
(proxy, method, arguments) -> {
if (method.getDeclaringClass() == Object.class) {
return switch (method.getName()) {
case "equals" -> proxy == arguments[0];
case "hashCode" -> System.identityHashCode(proxy);
case "toString" -> "SerializedOfflinePlayer[" + this.name + "]";
default -> throw new UnsupportedOperationException("Unsupported Object method " + method);
};
}
try {
Method target = SerializedOfflinePlayer.class.getMethod(
method.getName(),
method.getParameterTypes());
return target.invoke(this, arguments);
} catch (NoSuchMethodException exception) {
if (method.isDefault()) {
return InvocationHandler.invokeDefault(proxy, method, arguments);
}
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.",
exception);
} catch (InvocationTargetException exception) {
throw exception.getCause();
}
});
}
private static Constructor<? extends Player> setupProxyPlayerConstructor() {
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final String[] methodNames = new String[offlinePlayerMethods.length];
@@ -22,6 +22,7 @@ package com.comphenix.protocol.injector;
*
* @author Kristian
*/
@Deprecated
public enum GamePhase {
/**
* Only listen for packets sent or received before a player has logged in.
@@ -25,7 +25,7 @@ import com.comphenix.protocol.events.PacketEvent;
*
* @author Kristian
*/
public interface ListenerInvoker {
public interface ListenerManager {
boolean hasInboundListener(PacketType packetType);
@@ -38,24 +38,12 @@ public interface ListenerInvoker {
*
* @param event - the packet event to invoke.
*/
void invokePacketReceiving(PacketEvent event);
void invokeInboundPacketListeners(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated type of a packet.
*
* @param packet - the packet.
* @return The packet type.
* @deprecated use
* {@link com.comphenix.protocol.injector.packet.PacketRegistry#getPacketType(PacketType.Protocol, Class)}
* instead.
*/
@Deprecated
PacketType getPacketType(Object packet);
void invokeOutboundPacketListeners(PacketEvent event);
}
@@ -1,5 +1,7 @@
package com.comphenix.protocol.injector;
import java.util.Deque;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
@@ -7,7 +9,6 @@ import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketPostListener;
import com.comphenix.protocol.events.ScheduledPacket;
import java.util.Deque;
/**
* Represents a processor for network markers.
@@ -43,7 +44,7 @@ public class NetworkProcessor {
for (PacketPostListener listener : marker.getPostListeners()) {
try {
listener.onPostEvent(event);
} catch (OutOfMemoryError | ThreadDeath e) {
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
@@ -172,8 +172,6 @@ public class PacketConstructor {
result = unwrapper.unwrapItem(values[i]);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
lastException = e;
}
@@ -43,18 +43,14 @@ import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.PacketListenerSet;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector;
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.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableSet;
import io.netty.channel.Channel;
public class PacketFilterManager implements ListenerInvoker, InternalManager {
public class PacketFilterManager implements ListenerManager, InternalManager {
// plugin verifier reports
private static final ReportType PLUGIN_VERIFIER_ERROR = new ReportType("Plugin verifier error: %s");
@@ -80,7 +76,6 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
private final Set<PacketListener> registeredListeners;
// injectors
private final PlayerInjectionHandler playerInjectionHandler;
private final NetworkManagerInjector networkManagerInjector;
// status of this manager
@@ -111,10 +106,8 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
// injectors
this.networkManagerInjector = new NetworkManagerInjector(
builder.getLibrary(),
builder.getServer(),
this,
builder.getReporter());
this.playerInjectionHandler = this.networkManagerInjector.getPlayerInjectionHandler();
// ensure that all packet types are loaded and synced
PacketRegistry.getClientPacketTypes();
@@ -122,7 +115,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
// hook into all connected players
for (Player player : this.server.getOnlinePlayers()) {
this.playerInjectionHandler.injectPlayer(player, ConflictStrategy.BAIL_OUT);
this.networkManagerInjector.getInjector(player).inject();
}
}
@@ -159,29 +152,20 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
if (!this.closed) {
// if we skip the packet events later when actually writing into the pipeline we at least notify all
// monitor listeners before doing so - they will not be able to change the event tho
if (!filters) {
// ensure we are on the main thread if any listener requires that
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
NetworkMarker copy = marker; // okay fine
ProtocolLibrary.getScheduler().scheduleSyncDelayedTask(
() -> this.sendServerPacket(receiver, packet, copy, false), 1L);
return;
}
// construct the event and post to all monitor listeners
if (this.closed) {
return;
}
// A monitor listener should never modify a packet/event so we can simply invoke monitor listeners
// independently of our injector pipeline
if (!filters) {
this.runMonitorListeners(packet, () -> {
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false);
this.outboundListeners.invoke(event, ListenerPriority.MONITOR);
// update the marker of the event without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event);
}
// process outbound
this.playerInjectionHandler.sendServerPacket(receiver, packet, marker, filters);
});
}
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
}
@Override
@@ -192,13 +176,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
@Override
public void sendWirePacket(Player receiver, WirePacket packet) {
if (!this.closed) {
// special case - we just throw the wire packet down the pipeline without processing it
Channel outboundChannel = this.playerInjectionHandler.getChannel(receiver);
if (outboundChannel == null) {
throw new IllegalArgumentException("Unable to obtain connection of player " + receiver);
}
outboundChannel.writeAndFlush(packet);
this.networkManagerInjector.getInjector(receiver).sendWirePacket(packet);
}
}
@@ -214,39 +192,45 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
@Override
public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) {
if (!this.closed) {
// make sure we are on the main thread if any listener of the packet needs it
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
ProtocolLibrary.getScheduler().runTask(
() -> this.receiveClientPacket(sender, packet, marker, filters));
if (this.closed) {
return;
}
// make sure we are on the main thread if any listener of the packet needs it
if (filters && this.requiresMainThread(packet)) {
ProtocolLibrary.getScheduler().runTask(
() -> this.receiveClientPacket(sender, packet, marker, filters));
return;
}
Object nmsPacket = packet.getHandle();
if (filters) {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
this.invokeInboundPacketListeners(event);
if (event.isCancelled()) {
return;
}
Object nmsPacket = packet.getHandle();
// check to which listeners we need to post the packet
if (filters) {
// post to all listeners
PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender);
this.invokePacketReceiving(event);
if (event.isCancelled()) {
return;
}
// prevent possible de-sync
nmsPacket = event.getPacket().getHandle();
} else {
// Prevent possible de-sync if the packet was replaced by a listener.
nmsPacket = event.getPacket().getHandle();
} else {
// A monitor listener should never modify a packet/event so we can simply invoke monitor listeners
// independently of our injector pipeline
this.runMonitorListeners(packet, () -> {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
this.inboundListeners.invoke(event, ListenerPriority.MONITOR);
}
// post to the player inject, reset our cancel state change
this.playerInjectionHandler.receiveClientPacket(sender, nmsPacket);
});
}
// post to the player inject, reset our cancel state change
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
}
@Override
public int getProtocolVersion(Player player) {
return this.playerInjectionHandler.getProtocolVersion(player);
return this.networkManagerInjector.getInjector(player).getProtocolVersion();
}
@Override
@@ -442,17 +426,23 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
this.injected = true;
this.networkManagerInjector.inject();
// Paper disables reconfiguration when any PlayerLoginEvent listener is registered.
if (!this.networkManagerInjector.isUsingPaperChannelInitializer()) {
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
}, plugin);
}
// all listeners we need, this is a bit messy, but it makes the job correctly
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -518,38 +508,37 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
}
@Override
public void invokePacketReceiving(PacketEvent event) {
public void invokeInboundPacketListeners(PacketEvent event) {
if (!this.closed) {
this.postPacketToListeners(this.inboundListeners, event, false);
}
}
@Override
public void invokePacketSending(PacketEvent event) {
public void invokeOutboundPacketListeners(PacketEvent event) {
if (!this.closed) {
this.postPacketToListeners(this.outboundListeners, event, true);
}
}
private boolean requiresMainThread(PacketContainer packet) {
return this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread();
}
@Override
public PacketType getPacketType(Object packet) {
if (!MinecraftReflection.isPacketClass(packet)) {
throw new IllegalArgumentException("Given packet is not a minecraft packet instance");
private void runMonitorListeners(PacketContainer packet, Runnable notifyMonitor) {
if (this.requiresMainThread(packet)) {
ProtocolLibrary.getScheduler().runTask(notifyMonitor);
} else {
notifyMonitor.run();
}
PacketType type = PacketRegistry.getPacketType(packet.getClass());
if (type != null) {
return type;
}
throw new IllegalArgumentException("Unable to associate given packet " + packet + " with a registered packet!");
}
private void postPacketToListeners(PacketListenerSet listeners, PacketEvent event, boolean outbound) {
try {
// append async marker if any async listener for the packet was registered
if (this.asyncFilterManager.hasAsynchronousListeners(event)) {
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker());
Injector injector = this.networkManagerInjector.getInjector(event.getPlayer());
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker(injector));
}
// post to sync listeners
@@ -35,6 +35,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
@@ -82,15 +83,26 @@ public class StructureCache {
return newInstance(packetClass);
}
// determineBestCreator can re-enter this cache (legacy 1.8 packet registry), which
// ConcurrentHashMap.computeIfAbsent rejects on Java 9+, so use get/put instead.
private static Optional<Supplier<Object>> creatorFor(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.get(clazz);
if (creator == null) {
creator = Optional.ofNullable(determineBestCreator(clazz));
Optional<Supplier<Object>> existing = CACHED_INSTANCE_CREATORS.putIfAbsent(clazz, creator);
if (existing != null) {
creator = existing;
}
}
return creator;
}
public static boolean canCreateInstance(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, x ->
Optional.ofNullable(determineBestCreator(clazz)));
return creator.isPresent();
return creatorFor(clazz).isPresent();
}
public static Object newInstance(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, x ->
Optional.ofNullable(determineBestCreator(clazz)));
Optional<Supplier<Object>> creator = creatorFor(clazz);
if (!creator.isPresent()) {
throw new IllegalArgumentException("Cannot create instance of " + clazz);
}
@@ -98,15 +110,21 @@ public class StructureCache {
}
static Supplier<Object> determineBestCreator(Class<?> clazz) {
// certain packets are singletons which can't really be created
if (MinecraftReflection.isPacketClass(clazz)) {
boolean packetClass = MinecraftReflection.isPacketClass(clazz);
boolean immutableClass = ImmutableDetector.isImmutable(clazz);
// Certain packets are singletons which can't really be created. Immutable classes may also expose multiple
// constant instances as an enum replacement, which should be reused instead of invoking their constructors.
if (packetClass || immutableClass) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(clazz, false);
List<Field> singletons = fuzzy.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(clazz)
.requireModifier(Modifier.STATIC)
.requireModifier(Modifier.PUBLIC)
.build());
if (singletons.size() == 1) {
// Preserve the existing packet singleton behavior.
if (packetClass && singletons.size() == 1) {
FieldAccessor accessor = Accessors.getFieldAccessor(singletons.get(0));
try {
accessor.get(null);
@@ -114,6 +132,23 @@ public class StructureCache {
} catch (Exception ignored) {
}
}
if (immutableClass) {
for (Field singleton : singletons) {
if (!Modifier.isFinal(singleton.getModifiers())) {
continue;
}
FieldAccessor accessor = Accessors.getFieldAccessor(singleton);
try {
Object instance = accessor.get(null);
if (instance != null) {
return () -> instance;
}
} catch (Exception ignored) {
}
}
}
}
try {
@@ -124,7 +159,10 @@ public class StructureCache {
} catch (Exception ignored) {
}
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(clazz);
// stream codecs only exist on 1.20.5+; on legacy versions asking the registry re-enters its own
// initialization (createOldRegister builds StructureModifiers, which land back here)
WrappedStreamCodec streamCodec = MinecraftVersion.v1_20_5.atOrAbove()
? PacketRegistry.getStreamCodec(clazz) : null;
// use the new stream codec for versions above 1.20.5 if possible
if (streamCodec != null && tryInitTrickDataSerializer()) {
@@ -287,8 +325,8 @@ public class StructureCache {
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(serializerBase, true).getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
.parameterExactType(ByteBuf.class)
.parameterExactType(MinecraftReflection.getRegistryAccessClass())
.build()));
TRICKED_DATA_SERIALIZER_BASE = (buf) -> accessor.invoke(buf, MinecraftRegistryAccess.get());
} else {
@@ -13,6 +13,7 @@ import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.wrappers.Converters;
public class OutboundPacketListenerSet extends PacketListenerSet {
@@ -31,38 +32,46 @@ public class OutboundPacketListenerSet extends PacketListenerSet {
if (event.getPacketType() == PacketType.Play.Server.BUNDLE && !event.isCancelled()) {
// unpack the bundle and invoke for each packet in the bundle
Iterable<PacketContainer> packets = event.getPacket().getPacketBundles().read(0);
List<PacketContainer> outPackets = new ArrayList<>();
for (PacketContainer subPacket : packets) {
// ignore null packets as the will throw an error in the packet encoder
if (subPacket == null) {
continue;
List<PacketContainer> packets = Converters.toList(event.getPacket().getPacketBundles().read(0));
// lazily allocated when the first packet is removed or replaced
List<PacketContainer> outPackets = null;
for (int index = 0; index < packets.size(); index++) {
PacketContainer subPacket = packets.get(index);
PacketContainer packet = null;
if (subPacket != null) {
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(),
event.getPlayer(), event.isFiltered(), event);
super.invoke(subPacketEvent, priorityFilter);
if (!subPacketEvent.isCancelled()) {
PacketContainer result = subPacketEvent.getPacket();
if (result != null && result.getHandle() != null) {
packet = result;
}
}
}
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(),
event.getPlayer());
super.invoke(subPacketEvent, priorityFilter);
// if the packet has been cancelled, the packet will not be add to the bundle
if (subPacketEvent.isCancelled()) {
continue;
boolean changed = packet == null || packet.getHandle() != subPacket.getHandle();
if (outPackets == null && changed) {
outPackets = new ArrayList<>(packets.size());
outPackets.addAll(packets.subList(0, index));
}
PacketContainer packet = subPacketEvent.getPacket();
if (packet == null || packet.getHandle() == null) {
// super.invoke() should prevent us from getting new null packet so we just ignore it here
continue;
} else {
if (outPackets != null && packet != null) {
outPackets.add(packet);
}
}
if (packets.iterator().hasNext()) {
event.getPacket().getPacketBundles().write(0, outPackets);
} else {
// cancel entire packet if each individual packet has been cancelled
event.setCancelled(true);
if (!event.isReadOnly() && outPackets != null) {
if (!outPackets.isEmpty()) {
// the original bundle may be shared between multiple recipients
PacketContainer bundle = event.getPacket().shallowClone();
bundle.getPacketBundles().write(0, outPackets);
event.setPacket(bundle);
} else {
// cancel entire packet if each individual packet has been cancelled
event.setCancelled(true);
}
}
}
}
@@ -1,5 +1,6 @@
package com.comphenix.protocol.injector.collection;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nullable;
@@ -45,7 +46,9 @@ public abstract class PacketListenerSet {
public void addListener(PacketListener packetListener) {
ListeningWhitelist listeningWhitelist = getListeningWhitelist(packetListener);
for (PacketType packetType : listeningWhitelist.getTypes()) {
for (Iterator<PacketType> packet = listeningWhitelist.getTypes().iterator(); packet.hasNext();) {
PacketType packetType = packet.next();
Set<PacketType> supportedPacketTypes = (packetType.getSender() == Sender.SERVER)
? PacketRegistry.getServerPacketTypes()
: PacketRegistry.getClientPacketTypes();
@@ -56,7 +59,7 @@ public abstract class PacketListenerSet {
.build());
// remove unknown packet types
listeningWhitelist.getTypes().remove(packetType);
packet.remove();
}
}
@@ -1,59 +0,0 @@
package com.comphenix.protocol.injector.netty;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
/**
* Represents a listener for received or sent packets.
*
* @author Kristian
*/
public interface ChannelListener {
/**
* Invoked when a packet is being sent to the client.
* <p>
* This is invoked on the main thread.
*
* @param injector - the channel injector.
* @param packet - the packet.
* @param marker - the network marker.
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
*/
PacketEvent onPacketSending(Injector injector, PacketContainer packet, NetworkMarker marker);
/**
* Invoked when a packet is being received from a client.
* <p>
* This is invoked on an asynchronous worker thread.
*
* @param injector - the channel injector.
* @param packet - the packet.
* @param marker - the associated network marker, if any.
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
*/
PacketEvent onPacketReceiving(Injector injector, PacketContainer packet, NetworkMarker marker);
boolean hasInboundListener(PacketType packetType);
boolean hasOutboundListener(PacketType packetType);
boolean hasMainThreadListener(PacketType type);
/**
* Retrieve the current error reporter.
*
* @return The error reporter.
*/
ErrorReporter getReporter();
/**
* Determine if debug mode is enabled.
*
* @return TRUE if it is, FALSE otherwise.
*/
boolean isDebug();
}
@@ -1,9 +1,13 @@
package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import org.bukkit.entity.Player;
/**
* Represents an injected client connection.
@@ -12,6 +16,8 @@ import org.bukkit.entity.Player;
*/
public interface Injector {
SocketAddress getAddress();
/**
* Retrieve the current protocol version of the player.
*
@@ -23,12 +29,8 @@ public interface Injector {
* Inject the current channel.
* <p>
* Note that only active channels can be injected.
*
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
*/
boolean inject();
void uninject();
void inject();
/**
* Close the current injector.
@@ -42,20 +44,13 @@ public interface Injector {
* @param marker - the network marker.
* @param filtered - whether or not the packet is filtered.
*/
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered);
void receiveClientPacket(Object packet);
void readServerboundPacket(Object packet);
/**
* Retrieve the current protocol state.
*
* @return The current protocol.
* @deprecated use {@link #getCurrentProtocol(PacketType.Sender)} instead.
*/
@Deprecated
default Protocol getCurrentProtocol() {
return this.getCurrentProtocol(PacketType.Sender.SERVER);
}
void sendWirePacket(WirePacket packet);
void disconnect(String message);
/**
* Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different
@@ -66,22 +61,6 @@ public interface Injector {
*/
Protocol getCurrentProtocol(PacketType.Sender sender);
/**
* Retrieve the network marker associated with a given packet.
*
* @param packet - the packet.
* @return The network marker.
*/
NetworkMarker getMarker(Object packet);
/**
* Associate a given network marker with a specific packet.
*
* @param packet - the NMS packet.
* @param marker - the associated marker.
*/
void saveMarker(Object packet, NetworkMarker marker);
/**
* Retrieve the current player or temporary player associated with the injector.
*
@@ -89,6 +68,24 @@ public interface Injector {
*/
Player getPlayer();
/**
* Retrieve the player name captured during login.
*
* @return The player name, or NULL if it is not known yet.
*/
default String getPlayerName() {
return null;
}
/**
* Retrieve the player UUID captured during login.
*
* @return The player UUID, or NULL if it is not available.
*/
default UUID getPlayerUniqueId() {
return null;
}
/**
* Set the current player instance.
*
@@ -96,7 +93,7 @@ public interface Injector {
*/
void setPlayer(Player player);
void disconnect(String message);
boolean isConnected();
/**
* Determine if the channel has already been injected.
@@ -19,10 +19,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
@@ -81,6 +84,11 @@ public class WirePacket {
public static byte[] bytesFromPacket(PacketContainer packet) {
checkNotNull(packet, "packet cannot be null!");
// packets are written by their stream codec since 1.20.5
if (MinecraftVersion.v1_20_5.atOrAbove()) {
return bytesFromStreamCodec(packet);
}
ByteBuf buffer = PacketContainer.createPacketBuffer();
ByteBuf store = PacketContainer.createPacketBuffer();
@@ -107,6 +115,44 @@ public class WirePacket {
return bytes;
}
private static byte[] bytesFromStreamCodec(PacketContainer packet) {
// Spigot keeps the data of unknown custom payloads in a buffer and the codec reads from it,
// so the reader index is restored to keep the packet writable after this call
ByteBuf payloadData = getCustomPayloadData(packet);
int readerIndex = payloadData != null ? payloadData.readerIndex() : 0;
try {
ByteBuf buffer = (ByteBuf) packet.serializeToBuffer();
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
} finally {
if (payloadData != null) {
payloadData.readerIndex(readerIndex);
}
}
}
private static ByteBuf getCustomPayloadData(PacketContainer packet) {
PacketType type = packet.getType();
if (type != PacketType.Play.Server.CUSTOM_PAYLOAD
&& type != PacketType.Play.Client.CUSTOM_PAYLOAD
&& type != PacketType.Configuration.Server.CUSTOM_PAYLOAD
&& type != PacketType.Configuration.Client.CUSTOM_PAYLOAD) {
return null;
}
Object payload = packet.getModifier()
.withType(CustomPacketPayloadWrapper.getCustomPacketPayloadClass())
.readSafely(0);
if (payload == null) {
return null;
}
StructureModifier<ByteBuf> buffers = new StructureModifier<>(payload.getClass())
.withTarget(payload)
.withType(ByteBuf.class);
return buffers.readSafely(0);
}
/**
* Creates a WirePacket from an existing Minecraft packet
*
@@ -118,6 +164,10 @@ public class WirePacket {
checkNotNull(packet, "packet cannot be null!");
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
if (MinecraftVersion.v1_20_5.atOrAbove()) {
return fromPacket(PacketContainer.fromPacket(packet));
}
ByteBuf buffer = PacketContainer.createPacketBuffer();
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
@@ -1,10 +1,15 @@
package com.comphenix.protocol.injector.netty.channel;
import java.net.SocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.netty.Injector;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.netty.WirePacket;
final class EmptyInjector implements Injector {
@@ -16,18 +21,21 @@ final class EmptyInjector implements Injector {
this.player = player;
}
@Override
public SocketAddress getAddress() {
if (this.player != null) {
return this.player.getAddress();
}
return null;
}
@Override
public int getProtocolVersion() {
return Integer.MIN_VALUE;
}
@Override
public boolean inject() {
return false;
}
@Override
public void uninject() {
public void inject() {
}
@Override
@@ -35,11 +43,19 @@ final class EmptyInjector implements Injector {
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
}
@Override
public void receiveClientPacket(Object packet) {
public void readServerboundPacket(Object packet) {
}
@Override
public void sendWirePacket(WirePacket packet) {
}
@Override
public void disconnect(String message) {
}
@Override
@@ -47,27 +63,29 @@ final class EmptyInjector implements Injector {
return Protocol.HANDSHAKING;
}
@Override
public NetworkMarker getMarker(Object packet) {
return null;
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
}
@Override
public Player getPlayer() {
return this.player;
}
@Override
public String getPlayerName() {
return this.player != null ? this.player.getName() : null;
}
@Override
public UUID getPlayerUniqueId() {
return this.player != null ? this.player.getUniqueId() : null;
}
@Override
public void setPlayer(Player player) {
this.player = player;
}
@Override
public void disconnect(String message) {
public boolean isConnected() {
return false;
}
@Override
@@ -2,7 +2,8 @@ package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -12,39 +13,38 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter {
private final NettyChannelInjector injector;
private final ChannelListener channelListener;
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
public InboundPacketInterceptor(NettyChannelInjector injector) {
this.injector = injector;
this.channelListener = listener;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (MinecraftReflection.isPacketClass(msg)) {
// process the login if the packet is one before posting the packet to any
// handler to provide "real" data the method invocation will do nothing if the
// packet is not a login packet
this.injector.tryProcessLogin(msg);
// try get packet type
PacketType.Protocol protocol = this.injector.getInboundProtocol();
PacketType packetType = PacketRegistry.getPacketType(protocol, msg.getClass());
if (protocol == Protocol.UNKNOWN) {
ProtocolLogger.debug("skipping unknown inbound protocol for {0}", msg.getClass());
ctx.fireChannelRead(msg);
return;
}
PacketType packetType = PacketRegistry.getPacketType(protocol, msg.getClass());
if (packetType == null) {
ProtocolLogger.debug("skipping unknown inbound packet type for {0}", msg.getClass());
ctx.fireChannelRead(msg);
return;
}
// check if there are any listeners bound for the packet - if not just post the
// check if there are any listeners bound for the packet - if not just send the
// packet down the pipeline
if (!this.channelListener.hasInboundListener(packetType)) {
if (!this.injector.hasInboundListener(packetType)) {
ctx.fireChannelRead(msg);
return;
}
// call all inbound listeners
this.injector.processInboundPacket(ctx, msg, packetType);
// don't invoke next handler and transfer packet to injector for further processing
this.injector.processInbound(ctx, new PacketContainer(packetType, msg));
} else {
// just pass the message down the pipeline
ctx.fireChannelRead(msg);
@@ -14,21 +14,25 @@
*/
package com.comphenix.protocol.injector.netty.channel;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import io.netty.channel.ChannelHandler;
/**
* Represents an injector factory.
@@ -39,24 +43,23 @@ import org.bukkit.plugin.Plugin;
*/
public class InjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
// bukkit stuff
private final Plugin plugin;
private final Server server;
// protocol lib stuff
private final ErrorReporter errorReporter;
private final ListenerManager listenerManager;
// state of the factory
private boolean closed;
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
public InjectionFactory(Plugin plugin, ErrorReporter errorReporter, ListenerManager listenerManager) {
this.plugin = plugin;
this.server = server;
this.errorReporter = errorReporter;
this.listenerManager = listenerManager;
}
/**
@@ -72,11 +75,10 @@ public class InjectionFactory {
* Construct or retrieve a channel injector from an existing Bukkit player.
*
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
public Injector fromPlayer(Player player) {
if (this.closed) {
return new EmptyInjector(player);
}
@@ -114,10 +116,9 @@ public class InjectionFactory {
// construct a new injector as it seems like we have none yet
injector = new NettyChannelInjector(
player,
this.server,
networkManager,
channel,
listener,
this.listenerManager,
this,
this.errorReporter);
this.cacheInjector(player, injector);
@@ -155,31 +156,27 @@ public class InjectionFactory {
* Construct a new channel injector for the given channel.
*
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
public Injector fromChannel(Channel channel) {
if (this.closed) {
return EmptyInjector.WITHOUT_PLAYER;
}
Object netManager = this.findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
Object networkManager = this.findNetworkManager(channel);
Player temporaryPlayer = TemporaryPlayerFactory.createTemporaryPlayer();
NettyChannelInjector injector = new NettyChannelInjector(
temporaryPlayer,
this.server,
netManager,
networkManager,
channel,
listener,
this.listenerManager,
this,
this.errorReporter);
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
TemporaryPlayerFactory.setInjectorForPlayer(temporaryPlayer, injector);
return injector;
}
@@ -235,9 +232,9 @@ public class InjectionFactory {
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelMinimalInjector) {
return ((NettyChannelMinimalInjector) injector).getInjector();
Injector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelInjector) {
return (NettyChannelInjector) injector;
}
return null;
@@ -250,11 +247,12 @@ public class InjectionFactory {
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
MinecraftReflection.getNetworkManagerClass());
if (networkManager != null) {
return networkManager;
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (networkManagerClass.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
@@ -2,14 +2,19 @@ package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
@@ -21,86 +26,69 @@ 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.ListenerManager;
import com.comphenix.protocol.injector.NetworkProcessor;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.ByteBuddyGenerated;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.util.AttributeKey;
import org.bukkit.Server;
import org.bukkit.entity.Player;
public class NettyChannelInjector implements Injector {
// an accessor used when we're unable to retrieve the actual packet field in an outbound packet send
private static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
@Override
public Object get(Object instance) {
return null;
}
@Override
public void set(Object instance, Object value) {
}
@Override
public Field getField() {
return null;
}
};
private static final String INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
private static final String PROTOCOL_READER_NAME = "protocol_lib_protocol_reader";
private static final String INBOUND_INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
private static final String INBOUND_PROTOCOL_GETTER_NAME = "protocol_lib_inbound_protocol_getter";
private static final String WIRE_PACKET_ENCODER_NAME = "protocol_lib_wire_packet_encoder";
// all registered channel handlers to easier make sure we unregister them all from the pipeline
private static final String[] PROTOCOL_LIB_HANDLERS = new String[]{
WIRE_PACKET_ENCODER_NAME, INTERCEPTOR_NAME, PROTOCOL_READER_NAME
private static final String[] NETTY_HANDLER_NAMES = new String[]{
WIRE_PACKET_ENCODER_NAME, INBOUND_INTERCEPTOR_NAME, INBOUND_PROTOCOL_GETTER_NAME
};
private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
private static final ReportType REPORT_CANNOT_SEND_WRITE_PACKET = new ReportType("Unable to send wire packet %s to %s");
private static final ReportType REPORT_CANNOT_READ_PACKET = new ReportType("Unable to read packet %s for %s");
private static final ReportType REPORT_CANNOT_DISCONNECT = new ReportType("Unable to disconnect %s for %s");
private static final WirePacketEncoder WIRE_PACKET_ENCODER = new WirePacketEncoder();
private static final Map<Class<?>, FieldAccessor> PACKET_ACCESSORS = new ConcurrentHashMap<>(16, 0.9f);
private static final Class<?> LOGIN_PACKET_START_CLASS = PacketType.Login.Client.START.getPacketClass();
private static final Class<?> PACKET_PROTOCOL_CLASS = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass();
// use random attribute name because they need to be unique and would throw on reload
private static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey());
private static final AttributeKey<NettyChannelInjector> INJECTOR = AttributeKey.valueOf(getRandomKey());
// lazy initialized fields, if we don't need them we don't bother about them
private static FieldAccessor LOGIN_PROFILE_ACCESSOR;
private static FieldAccessor PROTOCOL_VERSION_ACCESSOR;
static NettyChannelInjector findInjector(Channel channel) {
return channel.attr(INJECTOR).get();
}
// bukkit stuff
private final Server server;
private static String getRandomKey() {
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
}
// protocol lib stuff we need
private final ErrorReporter errorReporter;
private final InjectionFactory injectionFactory;
private final ListenerManager listenerManager;
private final NetworkProcessor networkProcessor;
private final PacketListenerInvoker listenerInvoker;
// references
private final Object networkManager;
private final Channel wrappedChannel;
private final ChannelListener channelListener;
private final InjectionFactory injectionFactory;
private final Channel channel;
private final FieldAccessor channelField;
@@ -110,47 +98,49 @@ public class NettyChannelInjector implements Injector {
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
// status of this injector
private volatile boolean closed = false;
private final AtomicBoolean closed = new AtomicBoolean(false);
private volatile boolean injected = false;
// information about the player belonging to this injector
private String playerName;
private Player resolvedPlayer;
private UUID playerUniqueId;
private Player player;
// lazy initialized fields, if we don't need them we don't bother about them
private Object playerConnection;
private InboundProtocolReader inboundProtocolReader;
private volatile InboundProtocolReader inboundProtocolReader;
public NettyChannelInjector(
Player player,
Server server,
Object netManager,
Object networkManager,
Channel channel,
ChannelListener listener,
ListenerManager listenerManager,
InjectionFactory injector,
ErrorReporter errorReporter
) {
// bukkit stuff
this.server = server;
this.resolvedPlayer = player;
this.player = player;
if (player != null && !(player instanceof TemporaryPlayer)) {
this.playerName = player.getName();
this.playerUniqueId = player.getUniqueId();
}
// protocol lib stuff
this.errorReporter = errorReporter;
this.networkProcessor = new NetworkProcessor(errorReporter);
this.listenerInvoker = new PacketListenerInvoker(networkManager);
// references
this.networkManager = netManager;
this.wrappedChannel = channel;
this.channelListener = listener;
this.networkManager = networkManager;
this.channel = channel;
this.listenerManager = listenerManager;
this.injectionFactory = injector;
// register us into the channel
this.wrappedChannel.attr(INJECTOR).set(this);
this.channel.attr(INJECTOR).set(this);
// read the channel field from the network manager given to this method
// we re-read this field every time as plugins/spigot forks might give us different network manager types
Field channelField = FuzzyReflection.fromObject(netManager, true).getField(FuzzyFieldContract.newBuilder()
Field channelField = FuzzyReflection.fromObject(networkManager, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(Channel.class)
.banModifier(Modifier.STATIC)
.build());
@@ -159,135 +149,110 @@ public class NettyChannelInjector implements Injector {
// hook here into the close future to be 100% sure that this injector gets closed when the channel we wrap gets closed
// normally we listen to the disconnect event, but there is a very small period of time, between the login and actual
// join that is not covered by the disconnect event and may lead to unexpected injector states...
this.wrappedChannel.closeFuture().addListener(future -> this.close());
this.channel.closeFuture().addListener(future -> this.close());
}
static NettyChannelInjector findInjector(Channel channel) {
return channel.attr(INJECTOR).get();
}
static Object findChannelHandler(Channel channel, Class<?> type) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (type.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
return null;
}
private static String getRandomKey() {
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
}
private static boolean hasProtocolLibHandler(Channel channel) {
for (String handler : PROTOCOL_LIB_HANDLERS) {
if (channel.pipeline().get(handler) != null) {
return true;
}
}
return false;
@Override
public SocketAddress getAddress() {
return this.channel.remoteAddress();
}
@Override
public int getProtocolVersion() {
Integer protocolVersion = this.wrappedChannel.attr(PROTOCOL_VERSION).get();
Integer protocolVersion = this.channel.attr(PROTOCOL_VERSION).get();
return protocolVersion == null ? MinecraftProtocolVersion.getCurrentVersion() : protocolVersion;
}
@Override
public boolean inject() {
// we only do this on the channel event loop to prevent blocking the main server thread
// and to be sure that the netty pipeline view we get is up-to-date
if (this.wrappedChannel.eventLoop().inEventLoop()) {
// ensure that we should actually inject into the channel
if (this.closed || this.wrappedChannel instanceof ByteBuddyGenerated || !this.wrappedChannel.isActive()) {
return false;
}
// check here if we need to rewrite the channel field and do so
// minecraft overrides the channel field when the channel actually becomes active, so we need to ensure that our
// proxied channel is always on that field - therefore this rewrite is event before we check if we're already
// injected into the channel
this.rewriteChannelField();
// check if we already injected into the channel
if (hasProtocolLibHandler(this.wrappedChannel)) {
return false;
}
ChannelPipeline pipeline = this.wrappedChannel.pipeline();
// since 1.20.5 the encoder is renamed to outbound_config when the channel is
// waiting for the next protocol phase
String encoderName = pipeline.get("outbound_config") != null
? "outbound_config" : "encoder";
// inject our handlers
pipeline.addAfter(
encoderName,
WIRE_PACKET_ENCODER_NAME,
WIRE_PACKET_ENCODER);
// since 1.20.2 the packet decoder will remove or reconfigure the protocol for
// terminal packets which is why we need to read it before the packet gets
// decoded
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
this.inboundProtocolReader = new InboundProtocolReader(this);
pipeline.addBefore(
"decoder",
PROTOCOL_READER_NAME,
this.inboundProtocolReader);
}
// inject inbound packet interceptor
pipeline.addAfter(
"decoder",
INTERCEPTOR_NAME,
new InboundPacketInterceptor(this, this.channelListener));
this.injected = true;
return true;
} else {
// re-run in event loop, return false as we cannot be sure if the injection actually worked
this.ensureInEventLoop(this::inject);
return false;
public void inject() {
// ensure we are in the channel's event loop to be "thread-safe" within netty
// and our own code
if (!this.channel.eventLoop().inEventLoop()) {
channel.eventLoop().execute(this::inject);
return;
}
// don't need to inject if injector or channel got closed
if (this.closed.get() || !this.channel.isActive()) {
return;
}
// wrap channel inside the NetworkMananger to proxy write calls to our
// processOutbound method, this way we try to minimize the amount of thread
// jumps from event loop to minecraft main thread in order to process packets in
// main thread
Object networkManangerChannel = this.channelField.get(this.networkManager);
if (!(networkManangerChannel instanceof NettyChannelProxy)) {
EventLoop proxyEventLoop = new NettyEventLoopProxy(this.channel.eventLoop(), this);
Channel proxyChannel = new NettyChannelProxy(this.channel, proxyEventLoop, this);
this.channelField.set(this.networkManager, proxyChannel);
}
ChannelPipeline pipeline = this.channel.pipeline();
// since 1.20.5 the en-/decoder is renamed to out-/inbound_config when the
// channel is waiting for the next protocol phase (after terminal packet)
String encoderName = pipeline.get("outbound_config") != null
? "outbound_config"
: "encoder";
String decoderName = pipeline.get("inbound_config") != null
? "inbound_config"
: "decoder";
// try to add wire packet encoder
if (pipeline.context(WIRE_PACKET_ENCODER_NAME) == null) {
pipeline.addAfter(encoderName, WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER);
}
// try to add protocol reader, this is necessary because the en-/decoder will
// remove or reconfigure the protocol for terminal since 1.20.2+
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove() && pipeline.context(INBOUND_PROTOCOL_GETTER_NAME) == null) {
this.inboundProtocolReader = new InboundProtocolReader(this);
pipeline.addBefore(decoderName, INBOUND_PROTOCOL_GETTER_NAME, this.inboundProtocolReader);
}
// try to add inbound packet interceptor
if (pipeline.context(INBOUND_INTERCEPTOR_NAME) == null) {
pipeline.addAfter(decoderName, INBOUND_INTERCEPTOR_NAME, new InboundPacketInterceptor(this));
}
// mark injector as injected
this.injected = true;
}
@Override
public void uninject() {
// ensure that we injected into the channel before trying to remove anything from it
if (this.injected) {
// uninject on the event loop to ensure the instant visibility of the change and prevent blocks of other threads
if (this.wrappedChannel.eventLoop().inEventLoop()) {
this.injected = false;
private void uninject() {
// ensure we are in the channel's event loop to be "thread-safe" within netty
// and our own code
if (!this.channel.eventLoop().inEventLoop()) {
channel.eventLoop().execute(this::uninject);
return;
}
// remove known references to us
this.wrappedChannel.attr(INJECTOR).remove();
this.channelField.set(this.networkManager, this.wrappedChannel);
// replace wrapped channel in NetworkManager with original channel
this.channelField.set(this.networkManager, this.channel);
for (String handler : PROTOCOL_LIB_HANDLERS) {
try {
this.wrappedChannel.pipeline().remove(handler);
} catch (NoSuchElementException ignored) {
// ignore that one, probably an edge case
}
}
} else {
this.ensureInEventLoop(this::uninject);
// remove all ProtocolLib netty handler
ChannelPipeline pipeline = this.channel.pipeline();
for (String handlerName : NETTY_HANDLER_NAMES) {
if (pipeline.context(handlerName) != null) {
pipeline.remove(handlerName);
}
}
// mark injector as uninjected
this.injected = false;
}
@Override
public void close() {
// ensure that the injector wasn't close before
if (!this.closed) {
this.closed = true;
if (this.closed.compareAndSet(false, true)) {
// remove all of our references from the channel
this.uninject();
// remove any outgoing references
this.channel.attr(INJECTOR).set(null);
// cleanup
this.savedMarkers.clear();
this.skippedPackets.clear();
@@ -298,9 +263,9 @@ public class NettyChannelInjector implements Injector {
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
// do not send the packet if this injector was already closed / is not injected yet
if (this.closed || !this.injected) {
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
@@ -310,19 +275,12 @@ public class NettyChannelInjector implements Injector {
}
// save the given packet marker and send the packet
this.saveMarker(packet, marker);
if (marker != null) {
this.savedMarkers.put(packet, marker);
}
try {
if (this.resolvedPlayer instanceof ByteBuddyGenerated) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet);
} else {
// ensure that the player is properly connected before sending
Object playerConnection = this.getPlayerConnection();
if (playerConnection != null) {
MinecraftMethods.getSendPacketMethod().invoke(playerConnection, packet);
} else {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet);
}
}
this.listenerInvoker.send(packet);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET)
.messageParam(packet, this.playerName)
@@ -332,89 +290,110 @@ public class NettyChannelInjector implements Injector {
}
@Override
public void receiveClientPacket(Object packet) {
// do not do that if we're not injected or this injector was closed
if (this.closed || !this.injected) {
public void readServerboundPacket(Object packet) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
this.ensureInEventLoop(() -> {
try {
this.channel.pipeline().context(INBOUND_INTERCEPTOR_NAME).fireChannelRead(packet);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
}
});
}
@Override
public void sendWirePacket(WirePacket packet) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
Runnable receiveAction = () -> {
this.ensureInEventLoop(() -> {
try {
// try to invoke the method, this should normally not fail
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet);
this.channel.writeAndFlush(packet);
} catch (Exception exception) {
// 99% the user gave wrong information to the server
this.errorReporter.reportMinimal(this.injectionFactory.getPlugin(), "receiveClientPacket", exception);
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_WRITE_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
}
};
// execute the action on the event loop rather than any thread which we should potentially not block
if (this.wrappedChannel.eventLoop().inEventLoop()) {
receiveAction.run();
} else {
this.ensureInEventLoop(receiveAction);
}
});
}
public PacketType.Protocol getInboundProtocol() {
if (this.inboundProtocolReader != null) {
return this.inboundProtocolReader.getProtocol();
@Override
public void disconnect(String message) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
try {
this.listenerInvoker.disconnect(message);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DISCONNECT)
.messageParam(this.playerName, message)
.error(exception)
.build());
}
return getCurrentProtocol(PacketType.Sender.CLIENT);
}
@Override
public Protocol getCurrentProtocol(PacketType.Sender sender) {
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.wrappedChannel, sender);
}
@Override
public NetworkMarker getMarker(Object packet) {
return this.savedMarkers.get(packet);
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null && !this.closed) {
this.savedMarkers.put(packet, marker);
}
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.channel, sender);
}
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.resolvedPlayer != null) {
return this.resolvedPlayer;
if (this.player != null && !(this.player instanceof TemporaryPlayer)) {
return this.player;
}
// check if the name of the player is already known to the injector
if (this.playerName != null) {
this.resolvedPlayer = this.server.getPlayerExact(this.playerName);
Player bukkitPlayer = Bukkit.getPlayerExact(this.playerName);
if (bukkitPlayer != null) {
this.player = bukkitPlayer;
this.injectionFactory.cacheInjector(this.player, this);
}
}
// either we resolved it or we didn't...
return this.resolvedPlayer;
return this.player;
}
@Override
public String getPlayerName() {
return this.playerName;
}
@Override
public UUID getPlayerUniqueId() {
return this.playerUniqueId;
}
@Override
public void setPlayer(Player player) {
this.resolvedPlayer = player;
this.injectionFactory.invalidate(this.player, this.playerName);
this.player = player;
this.playerName = player.getName();
this.playerUniqueId = player.getUniqueId();
this.injectionFactory.cacheInjector(player, this);
this.injectionFactory.cacheInjector(player.getName(), this);
}
@Override
public void disconnect(String message) {
// we're still during pre-login, just close the connection
if (this.playerConnection == null || this.resolvedPlayer instanceof ByteBuddyGenerated) {
this.wrappedChannel.disconnect();
} else {
try {
// try to call the disconnect method on the player
MinecraftMethods.getDisconnectMethod(this.playerConnection.getClass()).invoke(this.playerConnection, message);
} catch (Exception exception) {
throw new IllegalArgumentException("Unable to disconnect the current injector", exception);
}
}
public boolean isConnected() {
return this.channel.isActive();
}
@Override
@@ -424,139 +403,118 @@ public class NettyChannelInjector implements Injector {
@Override
public boolean isClosed() {
return this.closed;
return this.closed.get();
}
void tryProcessLogin(Object packet) {
// check if the given packet is a login packet
if (LOGIN_PACKET_START_CLASS != null && LOGIN_PACKET_START_CLASS.equals(packet.getClass())) {
PacketType.Protocol getInboundProtocol() {
if (this.inboundProtocolReader != null) {
return this.inboundProtocolReader.getProtocol();
}
return getCurrentProtocol(PacketType.Sender.CLIENT);
}
/**
* Returns true if any plugin registed listeners or built-in listeners for the
* given packet type exist. See {@link #processInbound(ChannelHandlerContext, PacketContainer)}
*
* @param type the packet type
* @return true if the packet type has listeners; otherwise false
*/
boolean hasInboundListener(PacketType type) {
// always return true for types of the built-in listener (see #processInbound)
return type == PacketType.Handshake.Client.SET_PROTOCOL ||
type == PacketType.Login.Client.START ||
// check for plugin registed listener
this.listenerManager.hasInboundListener(type);
}
void processInbound(ChannelHandlerContext ctx, PacketContainer packet) {
// process set protocol packets for the protocol version
if (packet.getType() == PacketType.Handshake.Client.SET_PROTOCOL) {
Integer protocolVersion = packet.getIntegers().readSafely(0);
if (protocolVersion != null) {
this.channel.attr(PROTOCOL_VERSION).set(protocolVersion);
}
}
// process login packets for the player name
if (packet.getType() == PacketType.Login.Client.START) {
String username;
UUID uniqueId;
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
// 1.19 removed the profile from the packet and now sends the plain username directly
// ensure that the game profile accessor is available
if (LOGIN_PROFILE_ACCESSOR == null) {
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(LOGIN_PACKET_START_CLASS, String.class, true);
}
// 1.19 replaced the gameprofile with username and uuid field
username = packet.getStrings().readSafely(0);
uniqueId = readLoginUniqueId(packet);
} else {
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
username = profile != null ? profile.getName() : null;
uniqueId = profile != null ? profile.getUUID() : null;
}
// get the username from the packet
String username = (String) LOGIN_PROFILE_ACCESSOR.get(packet);
this.playerUniqueId = uniqueId;
// cache the injector and the player name
if (username != null) {
this.playerName = username;
this.injectionFactory.cacheInjector(username, this);
} else {
// ensure that the game profile accessor is available
if (LOGIN_PROFILE_ACCESSOR == null) {
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(
LOGIN_PACKET_START_CLASS,
MinecraftReflection.getGameProfileClass(),
true);
}
// the client only sends the name but the server wraps it into a GameProfile, so here we are
WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_PROFILE_ACCESSOR.get(packet));
// cache the injector and the player name
this.playerName = profile.getName();
this.injectionFactory.cacheInjector(profile.getName(), this);
}
return;
}
// protocol version begin
if (PACKET_PROTOCOL_CLASS != null && PACKET_PROTOCOL_CLASS.equals(packet.getClass())) {
// ensure the protocol version accessor is available
if (PROTOCOL_VERSION_ACCESSOR == null) {
try {
Field ver = FuzzyReflection.fromClass(PACKET_PROTOCOL_CLASS, true).getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.typeExact(int.class)
.build());
PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver);
} catch (IllegalArgumentException exception) {
// unable to resolve that field, continue no-op
PROTOCOL_VERSION_ACCESSOR = NO_OP_ACCESSOR;
}
}
// read the protocol version from the field if available
if (PROTOCOL_VERSION_ACCESSOR != NO_OP_ACCESSOR) {
int protocolVersion = (int) PROTOCOL_VERSION_ACCESSOR.get(packet);
this.wrappedChannel.attr(PROTOCOL_VERSION).set(protocolVersion);
}
}
}
private void rewriteChannelField() {
// check if we need to rewrite the channel or if the channel is already correct (prevent wrapping a wrapped channel)
Object currentChannel = this.channelField.get(this.networkManager);
if (currentChannel instanceof NettyChannelProxy) {
return;
}
// the field is not correct, rewrite now to our handler
Channel ch = new NettyChannelProxy(this.wrappedChannel, new NettyEventLoopProxy(this.wrappedChannel.eventLoop(), this) {
@Override
protected Runnable doProxyRunnable(Runnable original) {
return NettyChannelInjector.this.processOutbound(original);
}
@Override
protected <T> Callable<T> doProxyCallable(Callable<T> original) {
return NettyChannelInjector.this.processOutbound(original);
}
}, this);
this.channelField.set(this.networkManager, ch);
}
private void ensureInEventLoop(Runnable runnable) {
this.ensureInEventLoop(this.wrappedChannel.eventLoop(), runnable);
}
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
// invoke plugin listeners
if (this.listenerManager.hasInboundListener(packet.getType())) {
this.processInboundInternal(ctx, packet);
} else {
eventLoop.execute(runnable);
ctx.fireChannelRead(packet.getHandle());
}
}
void processInboundPacket(ChannelHandlerContext ctx, Object packet, PacketType packetType) {
if (this.channelListener.hasMainThreadListener(packetType) && !this.server.isPrimaryThread()) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
ProtocolLibrary.getScheduler().runTask(
() -> this.processInboundPacket(ctx, packet, packetType));
private static UUID readLoginUniqueId(PacketContainer packet) {
UUID uniqueId = packet.getUUIDs().readSafely(0);
if (uniqueId != null) {
return uniqueId;
}
StructureModifier<Optional> optionals = packet.getModifier().withType(Optional.class);
for (int i = 0; i < optionals.size(); i++) {
Optional<?> optional = optionals.readSafely(i);
if (optional != null && optional.orElse(null) instanceof UUID optionalUniqueId) {
return optionalUniqueId;
}
}
return null;
}
private void processInboundInternal(ChannelHandlerContext ctx, PacketContainer packetContainer) {
if (this.listenerManager.hasMainThreadListener(packetContainer.getType()) && !Bukkit.isPrimaryThread()) {
// not on the main thread but we are required to be reschedule the packet on the
// main thread
ProtocolLibrary.getScheduler().runTask(() -> this.processInboundInternal(ctx, packetContainer));
return;
}
// call packet handlers, a null result indicates that we shouldn't change anything
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent interceptionResult = this.channelListener.onPacketReceiving(this, packetContainer, null);
if (interceptionResult == null) {
// create event and invoke listeners
PacketEvent event = PacketEvent.fromClient(this, packetContainer, this.player);
this.listenerManager.invokeInboundPacketListeners(event);
// get packet of event
Object packet = event.getPacket().getHandle();
// fire the intercepted packet down the pipeline if it wasn't cancelled and isn't null
if (!event.isCancelled() && packet != null) {
this.ensureInEventLoop(ctx.channel().eventLoop(), () -> ctx.fireChannelRead(packet));
return;
}
// fire the intercepted packet down the pipeline if it wasn't cancelled
if (!interceptionResult.isCancelled()) {
this.ensureInEventLoop(
ctx.channel().eventLoop(),
() -> ctx.fireChannelRead(interceptionResult.getPacket().getHandle()));
// check if there were any post events added the packet after we fired it down the pipeline
// we use this way as we don't want to construct a new network manager accidentally
NetworkMarker marker = NetworkMarker.getNetworkMarker(interceptionResult);
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
if (marker != null) {
this.networkProcessor.invokePostEvent(interceptionResult, marker);
this.networkProcessor.invokePostEvent(event, marker);
}
}
}
<T> T processOutbound(T action) {
// get the accessor to the packet field
// if we are unable to look up the accessor then just return the runnable, probably nothing of our business
// try to get packet field accessor or return on failure
FieldAccessor packetAccessor = this.lookupPacketAccessor(action);
if (packetAccessor == NO_OP_ACCESSOR) {
if (packetAccessor == FieldAccessor.NO_OP_ACCESSOR) {
return action;
}
@@ -583,7 +541,7 @@ public class NettyChannelInjector implements Injector {
PacketType.Protocol protocol = this.getCurrentProtocol(PacketType.Sender.SERVER);
if (protocol == Protocol.UNKNOWN) {
ProtocolLogger.debug("skipping unknown protocol for {0}", packet.getClass());
ProtocolLogger.debug("skipping unknown outbound protocol for {0}", packet.getClass());
return action;
}
@@ -594,30 +552,29 @@ public class NettyChannelInjector implements Injector {
}
// no listener and no marker - no magic :)
if (!this.channelListener.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
if (!this.listenerManager.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
return action;
}
PacketContainer packetContainer = new PacketContainer(packetType, packet);
// ensure that we are on the main thread if we need to
if (this.channelListener.hasMainThreadListener(packetType) && !this.server.isPrimaryThread()) {
if (!Bukkit.isPrimaryThread() && hasMainThreadListener(this.listenerManager, protocol, packetContainer)) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
ProtocolLibrary.getScheduler().runTask(
() -> this.sendServerPacket(packet, null, true));
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, marker, true));
return null;
}
// call all listeners which are listening to the outbound packet, if any
// null indicates that no listener was affected by the packet, meaning that we can directly send the original packet
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent event = this.channelListener.onPacketSending(this, packetContainer, marker);
if (event == null) {
return action;
}
// create event and invoke listeners
PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player);
this.listenerManager.invokeOutboundPacketListeners(event);
// get packet of event
Object interceptedPacket = event.getPacket().getHandle();
// if the event wasn't cancelled by this action we must recheck if the packet changed during the method call
if (!event.isCancelled()) {
if (!event.isCancelled() && interceptedPacket != null) {
// rewrite the packet in the given action if the packet was changed during the event call
Object interceptedPacket = event.getPacket().getHandle();
if (interceptedPacket != packet) {
packetAccessor.set(action, interceptedPacket);
}
@@ -638,6 +595,44 @@ public class NettyChannelInjector implements Injector {
return null;
}
/**
* Checks if a listener that must be called on the main thread is going to be invoked for the given outbound packet.
* The packets inside a bundle are posted to their listeners together with the bundle, so their listeners count for
* the bundle as well.
*
* @param listenerManager the listener manager holding the registered listeners.
* @param protocol the current outbound protocol of the connection.
* @param packet the outbound packet.
* @return true if the packet must be processed on the main thread, false otherwise.
*/
static boolean hasMainThreadListener(ListenerManager listenerManager, Protocol protocol, PacketContainer packet) {
if (listenerManager.hasMainThreadListener(packet.getType())) {
return true;
}
if (packet.getType() != PacketType.Play.Server.BUNDLE) {
return false;
}
Iterable<?> subPackets = packet.getModifier().<Iterable<?>>withType(Iterable.class).readSafely(0);
if (subPackets == null) {
return false;
}
for (Object subPacket : subPackets) {
if (subPacket == null) {
continue;
}
PacketType subPacketType = PacketRegistry.getPacketType(protocol, subPacket.getClass());
if (subPacketType != null && listenerManager.hasMainThreadListener(subPacketType)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
private <T> T proxyAction(T action, PacketEvent event, NetworkMarker marker) {
// hack - we only know that the given action is either a runnable or callable, but we need to work out which thing
@@ -667,37 +662,29 @@ public class NettyChannelInjector implements Injector {
}
private FieldAccessor lookupPacketAccessor(Object action) {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), key -> {
try {
// find the field
Field packetField = FuzzyReflection.fromClass(clazz, true).getField(FuzzyFieldContract
.newBuilder()
Field packetField = FuzzyReflection.fromClass(key, true).getField(
FuzzyFieldContract.newBuilder()
.typeSuperOf(MinecraftReflection.getPacketClass())
.build());
return Accessors.getFieldAccessor(packetField);
} catch (IllegalArgumentException exception) {
// no such field found :(
return NO_OP_ACCESSOR;
// return noop accessor because computeIfAbsent interprets null as a missing key
return FieldAccessor.NO_OP_ACCESSOR;
}
});
}
private Object getPlayerConnection() {
// resolve the player connection if needed
if (this.playerConnection == null) {
Player target = this.getPlayer();
if (target == null) {
return null;
}
this.playerConnection = MinecraftFields.getPlayerConnection(target);
}
// cannot be null at this point
return this.playerConnection;
private void ensureInEventLoop(Runnable runnable) {
this.ensureInEventLoop(this.channel.eventLoop(), runnable);
}
public Channel getWrappedChannel() {
return this.wrappedChannel;
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
} else {
eventLoop.execute(runnable);
}
}
}
@@ -1,44 +0,0 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
final class NettyChannelMinimalInjector implements MinimalInjector {
private final NettyChannelInjector injector;
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
this.injector = injector;
}
@Override
public SocketAddress getAddress() {
return this.injector.getWrappedChannel().remoteAddress();
}
@Override
public void disconnect(String message) {
this.injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
this.injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return this.injector.getPlayer();
}
@Override
public boolean isConnected() {
return this.injector.getWrappedChannel().isActive();
}
public NettyChannelInjector getInjector() {
return this.injector;
}
}
@@ -5,6 +5,7 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
@@ -33,6 +34,11 @@ final class NettyChannelProxy implements Channel {
this.injector = injector;
}
@Override
public ChannelId id() {
return this.delegate.id();
}
@Override
public EventLoop eventLoop() {
return this.eventLoop;
@@ -238,6 +244,11 @@ final class NettyChannelProxy implements Channel {
return this.delegate.attr(key);
}
@Override
public <T> boolean hasAttr(AttributeKey<T> attributeKey) {
return this.delegate.hasAttr(attributeKey);
}
@Override
public int compareTo(@NotNull Channel o) {
return this.delegate.compareTo(o);
@@ -1,15 +1,5 @@
package com.comphenix.protocol.injector.netty.channel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -20,11 +10,24 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import io.netty.channel.AbstractEventLoop;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.AbstractEventExecutor;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
/**
* An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which
* schedule something on the event loop to methods which decide what should happen to the scheduled task.
*/
abstract class NettyEventLoopProxy implements EventLoop {
final class NettyEventLoopProxy extends AbstractEventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
@@ -40,7 +43,7 @@ abstract class NettyEventLoopProxy implements EventLoop {
private Runnable proxyRunnable(Runnable original) {
// execute the proxy and check if we need to do anything
Runnable proxied = this.doProxyRunnable(original);
Runnable proxied = this.injector.processOutbound(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
@@ -55,7 +58,7 @@ abstract class NettyEventLoopProxy implements EventLoop {
private <T> Callable<T> proxyCallable(Callable<T> original) {
// execute the proxy and check if we need to do anything
Callable<T> proxied = this.doProxyCallable(original);
Callable<T> proxied = this.injector.processOutbound(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
@@ -68,26 +71,6 @@ abstract class NettyEventLoopProxy implements EventLoop {
}
}
/**
* Proxies the given runnable. The returned runnable will be executed instead of the original. If this method returns
* null a no-op runnable will be scheduled instead, preventing the original action from happening.
*
* @param original the runnable to proxy.
* @return the runnable to execute instead, null to execute no action.
*/
protected abstract Runnable doProxyRunnable(Runnable original);
/**
* Proxies the given callable. The returned callable will be executed instead of the original. If this method returns
* null a callable which always returns null will be scheduled instead, preventing the original action from
* happening.
*
* @param original the callable to proxy.
* @param <T> the return type of the original callable.
* @return the callable to execute instead of the original, null to use a no-op callable instead.
*/
protected abstract <T> Callable<T> doProxyCallable(Callable<T> original);
@Override
public EventLoopGroup parent() {
return this.delegate.parent();
@@ -255,6 +238,15 @@ abstract class NettyEventLoopProxy implements EventLoop {
}
}
// ShreddedPaper uses this method
@Override
public void lazyExecute(Runnable command) {
Runnable proxied = this.proxyRunnable(command);
if (proxied != null) {
((AbstractEventExecutor)this.delegate).lazyExecute(proxied);
}
}
@Override
public void forEach(Consumer<? super EventExecutor> action) {
this.delegate.forEach(action);
@@ -270,6 +262,12 @@ abstract class NettyEventLoopProxy implements EventLoop {
return this.delegate.register(channel);
}
@Override
public ChannelFuture register(ChannelPromise channelPromise) {
return this.delegate.register(channelPromise);
}
@Deprecated
@Override
public ChannelFuture register(Channel channel, ChannelPromise promise) {
return this.delegate.register(channel, promise);
@@ -0,0 +1,216 @@
package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
/**
* This class facilitates the invocation of methods on the current packet listener.
* It attempts to execute the <code>send</code>, and <code>disconnect</code> methods, upon failure
* (either due to the absence of the method or the packet listener being of an incorrect type),
* it delegates the call to the network manager.
*
* <p>Supported packet listener types include CONFIGURATION and PLAY. If the packet
* listener does not match these types, or if the required method is missing, the
* operation falls back to similar methods available in the network manager.
*
* <p>It is important to note that this class does not handle exceptions internally.
* Instead, it propagates them to the caller. During the initialization phase, the
* class will throw an exception if the necessary methods in the network manager are
* not available, ensuring that these dependencies are addressed early in the runtime.
*/
public class PacketListenerInvoker {
private static final Class<?> PACKET_LISTENER_CLASS = MinecraftReflection.getMinecraftClass("network.PacketListener", "PacketListener");
private static final Class<?> GAME_PACKET_LISTENER_CLASS = MinecraftReflection.getPlayerConnectionClass();
private static final Class<?> COMMON_PACKET_LISTENER_CLASS = MinecraftReflection.getNullableNMS("server.network.ServerCommonPacketListenerImpl");
private static final Class<?> PREFERRED_PACKET_LISTENER_CLASS = COMMON_PACKET_LISTENER_CLASS != null ? COMMON_PACKET_LISTENER_CLASS : GAME_PACKET_LISTENER_CLASS;
private static final MethodAccessor PACKET_LISTENER_SEND = getPacketListenerSend();
private static final MethodAccessor PACKET_LISTENER_DISCONNECT = getPacketListenerDisconnect();
private static final boolean DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT = doesPacketListenerDisconnectUseComponent();
private static final MethodAccessor NETWORK_MANAGER_SEND = getNetworkManagerSend();
private static final MethodAccessor NETWORK_MANAGER_DISCONNECT = getNetworkManagerDisconnect();
private static final MethodAccessor NETWORK_MANAGER_PACKET_LISTENER = getNetworkManagerPacketListener();
public static void ensureStaticInitializedWithoutError() {
}
private static MethodAccessor getPacketListenerSend() {
FuzzyReflection packetListener = FuzzyReflection.fromClass(PREFERRED_PACKET_LISTENER_CLASS);
List<Method> send = packetListener.getMethodList(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build());
if (send.isEmpty()) {
ProtocolLogger.debug("Can't get packet listener send method");
return null;
}
return Accessors.getMethodAccessor(send.get(0));
}
private static MethodAccessor getPacketListenerDisconnect() {
FuzzyReflection packetListener = FuzzyReflection.fromClass(PREFERRED_PACKET_LISTENER_CLASS);
List<Method> disconnect = packetListener.getMethodList(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.build());
if (disconnect.isEmpty()) {
disconnect = packetListener.getMethodList(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.nameRegex("disconnect.*")
.parameterCount(1)
.parameterExactType(String.class, 0)
.build());
}
if (disconnect.isEmpty()) {
ProtocolLogger.debug("Can't get packet listener disconnect method");
return null;
}
return Accessors.getMethodAccessor(disconnect.get(0));
}
private static boolean doesPacketListenerDisconnectUseComponent() {
if (PACKET_LISTENER_DISCONNECT != null) {
Parameter reason = PACKET_LISTENER_DISCONNECT.getMethod().getParameters()[0];
return MinecraftReflection.isIChatBaseComponent(reason.getClass());
}
return false;
}
private static MethodAccessor getNetworkManagerSend() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
Method send = networkManager.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build());
return Accessors.getMethodAccessor(send);
}
private static MethodAccessor getNetworkManagerDisconnect() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
Method disconnect = networkManager.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.build());
return Accessors.getMethodAccessor(disconnect);
}
private static MethodAccessor getNetworkManagerPacketListener() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
Method packetListener = networkManager.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(PACKET_LISTENER_CLASS)
.parameterCount(0)
.build());
return Accessors.getMethodAccessor(packetListener);
}
private final Object networkManager;
private final AtomicReference<Object> packetListener = new AtomicReference<>(null);
PacketListenerInvoker(Object networkManager) {
if (!MinecraftReflection.is(MinecraftReflection.getNetworkManagerClass(), networkManager)) {
throw new IllegalArgumentException("Given NetworkManager isn't an isntance of NetworkManager");
}
this.networkManager = networkManager;
}
/**
* Retrieves the current packet listener associated with this network manager.
*
* <p>This method ensures thread-safety and returns the packet listener only if it is an
* instance of the preferred class. If the packet listener has changed or does not match
* the preferred class, it returns {@code null}.
*
* @return the current packet listener if it meets the required criteria, otherwise {@code null}.
*/
private Object getPacketListener() {
// Retrieve the current packet listener from the network manager using reflection.
Object packetListener = NETWORK_MANAGER_PACKET_LISTENER.invoke(this.networkManager);
// Perform a thread-safe check to see if the packet listener has changed since the last retrieval.
if (!this.packetListener.compareAndSet(packetListener, packetListener)) {
// If the packet listener has changed, attempt to update the cached listener to the new instance,
// or invalidate the cached object if it does not match the preferred type.
if (PREFERRED_PACKET_LISTENER_CLASS.isInstance(packetListener)) {
this.packetListener.set(packetListener);
} else {
this.packetListener.set(null);
}
}
// Return the currently cached packet listener, which may be null if it does not match the preferred type.
return this.packetListener.get();
}
/**
* Sends a packet using the current packet listener if available and valid; otherwise,
* falls back to the network manager.
*
* @param packet The packet to be sent.
*/
public void send(Object packet) {
Object packetListener = this.getPacketListener();
if (PACKET_LISTENER_SEND != null && packetListener != null) {
PACKET_LISTENER_SEND.invoke(packetListener, packet);
} else {
NETWORK_MANAGER_SEND.invoke(this.networkManager, packet);
}
}
/**
* Disconnects the player using the current packet listener if available and valid; otherwise,
* falls back to the network manager.
*
* @param reason The reason for the disconnection.
*/
public void disconnect(String reason) {
Object packetListener = this.getPacketListener();
boolean hasPacketListener = PACKET_LISTENER_DISCONNECT != null && packetListener != null;
Object wrapped = reason;
if (!hasPacketListener || DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT) {
wrapped = WrappedChatComponent.fromText(reason).getHandle();
}
if (hasPacketListener) {
PACKET_LISTENER_DISCONNECT.invoke(packetListener, wrapped);
} else {
NETWORK_MANAGER_DISCONNECT.invoke(this.networkManager, wrapped);
}
}
}
@@ -1,9 +1,10 @@
package com.comphenix.protocol.injector.netty.manager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -11,18 +12,15 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private final InjectionFactory factory;
private final NetworkManagerInjector listener;
private final TemporaryPlayerFactory playerFactory;
private final ErrorReporter errorReporter;
private final InjectionFactory injectionFactory;
public InjectionChannelInboundHandler(
InjectionFactory factory,
NetworkManagerInjector listener,
TemporaryPlayerFactory playerFactory
ErrorReporter errorReporter,
InjectionFactory injectionFactory
) {
this.factory = factory;
this.listener = listener;
this.playerFactory = playerFactory;
this.errorReporter = errorReporter;
this.injectionFactory = injectionFactory;
}
@Override
@@ -35,11 +33,11 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
// We're first checking if the factory is still open, just might be a delay between accepting the connection
// (which adds this handler to the pipeline) and the actual channelActive call. If the injector is closed at
// that point we might accidentally trigger class loads which result in exceptions.
if (!this.factory.isClosed()) {
if (!this.injectionFactory.isClosed()) {
try {
this.factory.fromChannel(ctx.channel(), this.listener, this.playerFactory).inject();
this.injectionFactory.fromChannel(ctx.channel()).inject();
} catch (Exception exception) {
this.listener.getReporter().reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
this.errorReporter.reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
.messageParam(ctx.channel())
.error(exception)
.build());
@@ -52,7 +50,7 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overheat
// we do it this way to prevent the lookup overhead
return true;
}
}
@@ -28,7 +28,7 @@ final class InjectionChannelInitializer extends ChannelInboundHandlerAdapter {
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overheat
// we do it this way to prevent the lookup overhead
return true;
}
}
@@ -7,21 +7,15 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.error.ErrorReporter;
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.netty.ChannelListener;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.internal.PlatformProvider;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
@@ -30,107 +24,48 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
public class NetworkManagerInjector implements ChannelListener {
public class NetworkManagerInjector {
private static final String INBOUND_INJECT_HANDLER_NAME = "protocol_lib_inbound_inject";
private static final TemporaryPlayerFactory PLAYER_FACTORY = new TemporaryPlayerFactory();
// all list fields which we've overridden and need to revert to a non-proxying list afterwards
private final Set<Pair<Object, FieldAccessor>> overriddenLists = new HashSet<>();
private final ErrorReporter errorReporter;
private final ListenerInvoker listenerInvoker;
private final InjectionFactory injectionFactory;
// injectors based on this "global" injector
private final PlayerInjectionHandler playerInjectionHandler;
// netty handler
private final InjectionChannelInboundHandler injectionHandler;
private final InjectionChannelInitializer pipelineInjectorHandler;
private boolean debug = false;
private Runnable paperChannelInitializerCleanup;
// status of this injector
private boolean closed = false;
private boolean injected = false;
public NetworkManagerInjector(Plugin plugin, Server server, ListenerInvoker listenerInvoker, ErrorReporter reporter) {
public NetworkManagerInjector(Plugin plugin, ListenerManager listenerManager, ErrorReporter reporter) {
this.errorReporter = reporter;
this.listenerInvoker = listenerInvoker;
this.injectionFactory = new InjectionFactory(plugin, server, reporter);
this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager);
// hooking netty handlers
InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler(
this.injectionFactory,
this,
PLAYER_FACTORY);
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
// other injectors
this.playerInjectionHandler = new NetworkManagerPlayerInjector(
this,
this.injectionHandler = new InjectionChannelInboundHandler(
this.errorReporter,
this.injectionFactory);
this.pipelineInjectorHandler = new InjectionChannelInitializer(
INBOUND_INJECT_HANDLER_NAME,
this.injectionHandler);
}
@Override
public PacketEvent onPacketSending(Injector injector, PacketContainer packet, NetworkMarker marker) {
// check if we need to intercept the packet
Class<?> packetClass = packet.getHandle().getClass();
if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || hasOutboundListener(packet.getType())) {
// wrap packet and construct the event
PacketEvent packetEvent = PacketEvent.fromServer(this, packet, marker, injector.getPlayer());
// post to all listeners, then return the packet event we constructed
this.listenerInvoker.invokePacketSending(packetEvent);
return packetEvent;
}
// no listener so there is no change we need to apply
return null;
public Injector getInjector(Player player) {
return this.injectionFactory.fromPlayer(player);
}
@Override
public PacketEvent onPacketReceiving(Injector injector, PacketContainer packet, NetworkMarker marker) {
if (marker != null || hasInboundListener(packet.getType())) {
PacketEvent packetEvent = PacketEvent.fromClient(this, packet, marker, injector.getPlayer());
// post to all listeners, then return the packet event we constructed
this.listenerInvoker.invokePacketReceiving(packetEvent);
return packetEvent;
}
// no listener so there is no change we need to apply
return null;
}
@Override
public boolean hasInboundListener(PacketType packetType) {
return this.listenerInvoker.hasInboundListener(packetType);
}
@Override
public boolean hasOutboundListener(PacketType packetType) {
return this.listenerInvoker.hasOutboundListener(packetType);
}
@Override
public boolean hasMainThreadListener(PacketType packetType) {
return this.listenerInvoker.hasMainThreadListener(packetType);
}
@Override
public ErrorReporter getReporter() {
return this.errorReporter;
}
@Override
public boolean isDebug() {
return this.debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
public boolean isUsingPaperChannelInitializer() {
return this.paperChannelInitializerCleanup != null;
}
@SuppressWarnings("unchecked")
@@ -139,6 +74,13 @@ public class NetworkManagerInjector implements ChannelListener {
return;
}
PlatformProvider platformProvider = PlatformProvider.get();
if (platformProvider.hasEarlyChannelInitialization()) {
this.paperChannelInitializerCleanup = platformProvider.registerChannelInitializer(this::injectChannel);
this.injected = true;
return;
}
// get all "server connections" defined in the minecraft server class
FuzzyReflection server = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
List<Method> serverConnectionGetter = server.getMethodList(FuzzyMethodContract.newBuilder()
@@ -201,6 +143,23 @@ public class NetworkManagerInjector implements ChannelListener {
this.injected = true;
}
private void injectChannel(Channel channel) {
addInjectionHandler(channel, MinecraftReflection.getNetworkManagerClass(), this.injectionHandler);
}
static void addInjectionHandler(Channel channel, Class<?> networkManagerClass, ChannelHandler injectionHandler) {
for (String handlerName : channel.pipeline().names()) {
ChannelHandler handler = channel.pipeline().get(handlerName);
if (networkManagerClass.isInstance(handler)) {
channel.pipeline().addBefore(handlerName, INBOUND_INJECT_HANDLER_NAME, injectionHandler);
return;
}
}
// Preserve the existing behavior for forks with an unexpected pipeline layout.
channel.pipeline().addLast(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
}
public void close() {
if (this.closed || !this.injected) {
return;
@@ -229,10 +188,13 @@ public class NetworkManagerInjector implements ChannelListener {
// clear up
this.overriddenLists.clear();
this.injectionFactory.close();
}
public PlayerInjectionHandler getPlayerInjectionHandler() {
return this.playerInjectionHandler;
try {
if (this.paperChannelInitializerCleanup != null) {
this.paperChannelInitializerCleanup.run();
}
} finally {
this.paperChannelInitializerCleanup = null;
this.injectionFactory.close();
}
}
}
@@ -1,68 +0,0 @@
package com.comphenix.protocol.injector.netty.manager;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import io.netty.channel.Channel;
final class NetworkManagerPlayerInjector implements PlayerInjectionHandler {
private final ChannelListener listener;
private final InjectionFactory injectionFactory;
public NetworkManagerPlayerInjector(
ChannelListener listener,
InjectionFactory injectionFactory
) {
this.listener = listener;
this.injectionFactory = injectionFactory;
}
@Override
public int getProtocolVersion(Player player) {
return this.injectionFactory.fromPlayer(player, this.listener).getProtocolVersion();
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
this.injectionFactory.fromPlayer(player, this.listener).inject();
}
@Override
public boolean uninjectPlayer(Player player) {
this.injectionFactory.fromPlayer(player, this.listener).uninject();
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
this.injectionFactory.fromPlayer(receiver, this.listener).sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public void receiveClientPacket(Player player, Object mcPacket) {
this.injectionFactory.fromPlayer(player, this.listener).receiveClientPacket(mcPacket);
}
@Override
public void updatePlayer(Player player) {
this.injectionFactory.fromPlayer(player, this.listener).inject();
}
@Override
public Channel getChannel(Player player) {
Injector injector = this.injectionFactory.fromPlayer(player, this.listener);
if (injector instanceof NettyChannelInjector) {
return ((NettyChannelInjector) injector).getWrappedChannel();
}
return null;
}
}
@@ -19,8 +19,6 @@ package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -31,18 +29,15 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.internal.ProtocolRegistry_1_20_5;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
@@ -70,7 +65,7 @@ public class PacketRegistry {
*
* @author Kristian
*/
private static class Register {
public static class Register {
// The main lookup table
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
@@ -82,14 +77,18 @@ public class PacketRegistry {
volatile Set<PacketType> clientPackets = new HashSet<>();
final List<MapContainer> containers = new ArrayList<>();
public Register() {
private Register() {
}
public void registerPacket(PacketType type, Class<?> clazz, Sender sender) {
typeToClass.put(type, Optional.of(clazz));
public void registerPacket(PacketType type, Class<?> packetClass, Sender sender, WrappedStreamCodec codec) {
typeToClass.put(type, Optional.of(packetClass));
classToType.put(packetClass, type);
classToType.put(clazz, type);
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(clazz, type);
if (codec != null) {
classToCodec.put(packetClass, codec);
}
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(packetClass, type);
if (sender == Sender.CLIENT) {
clientPackets.add(type);
@@ -293,206 +292,9 @@ public class PacketRegistry {
return result;
}
@SuppressWarnings("unchecked")
private static synchronized Register createRegisterV1_20_5() {
Object[] protocols = ENUM_PROTOCOL.getEnumConstants();
// PacketType<?> to class map
final Map<Object, Class<?>> packetTypeMap = new HashMap<>();
// List of all class containing PacketTypes
String[] packetTypesClassNames = new String[] {
"common.CommonPacketTypes",
"configuration.ConfigurationPacketTypes",
"cookie.CookiePacketTypes",
"game.GamePacketTypes",
"handshake.HandshakePacketTypes",
"login.LoginPacketTypes",
"ping.PingPacketTypes",
"status.StatusPacketTypes"
};
Class<?> packetTypeClass = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");
for (String packetTypesClassName : packetTypesClassNames) {
Class<?> packetTypesClass = MinecraftReflection
.getOptionalNMS("network.protocol." + packetTypesClassName)
.orElse(null);
if (packetTypesClass == null) {
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
continue;
}
// check every field for "static final PacketType<?>"
for (Field field : packetTypesClass.getDeclaredFields()) {
try {
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object packetType = field.get(null);
if (!packetTypeClass.isInstance(packetType)) {
continue;
}
// retrieve the generic type T of the PacketType<T> field
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (packet instanceof Class<?>) {
packetTypeMap.put(packetType, (Class<?>) packet);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
// ID to Packet class maps
final Map<Object, Map<Class<?>, Integer>> serverMaps = new LinkedHashMap<>();
final Map<Object, Map<Class<?>, Integer>> clientMaps = new LinkedHashMap<>();
// global registry instance
final Register result = new Register();
// List of all class containing ProtocolInfos
String[] protocolClassNames = new String[] {
"configuration.ConfigurationProtocols",
"game.GameProtocols",
"handshake.HandshakeProtocols",
"login.LoginProtocols",
"status.StatusProtocols"
};
Class<?> protocolInfoClass = MinecraftReflection.getProtocolInfoClass();
Class<?> protocolInfoUnboundClass = MinecraftReflection.getProtocolInfoUnboundClass();
Class<?> streamCodecClass = MinecraftReflection.getStreamCodecClass();
Class<?> idCodecClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec");
Class<?> idCodecEntryClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec$Entry", "network.codec.IdDispatchCodec$b");
Class<?> protocolDirectionClass = MinecraftReflection.getPacketFlowClass();
Function<?, ?> emptyFunction = input -> input;
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(protocolInfoClass);
MethodAccessor protocolAccessor = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
MethodAccessor directionAccessor = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("flow", protocolDirectionClass, new Class[0]));
MethodAccessor codecAccessor = Accessors.getMethodAccessor(
protocolInfoReflection.getMethodByReturnTypeAndParameters("codec", streamCodecClass, new Class[0]));
MethodAccessor bindAccessor = Accessors.getMethodAccessor(FuzzyReflection.fromClass(protocolInfoUnboundClass)
.getMethodByReturnTypeAndParameters("bind", protocolInfoClass, new Class[] { Function.class }));
FuzzyReflection idCodecReflection = FuzzyReflection.fromClass(idCodecClass, true);
FieldAccessor byIdAccessor = Accessors.getFieldAccessor(idCodecReflection
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(List.class).build()));
FieldAccessor toIdAccessor = Accessors.getFieldAccessor(idCodecReflection
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).build()));
FuzzyReflection idCodecEntryReflection = FuzzyReflection.fromClass(idCodecEntryClass, true);
MethodAccessor idCodecEntryTypeAccessor = Accessors.getMethodAccessor(idCodecEntryReflection
.getMethodByReturnTypeAndParameters("type", Object.class, new Class[0]));
MethodAccessor idCodecEntrySerializerAccessor = Accessors.getMethodAccessor(idCodecEntryReflection
.getMethodByReturnTypeAndParameters("serializer", streamCodecClass, new Class[0]));
for (String protocolClassName : protocolClassNames) {
Class<?> protocolClass = MinecraftReflection
.getOptionalNMS("network.protocol." + protocolClassName)
.orElse(null);
if (protocolClass == null) {
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocolClassName);
continue;
}
for (Field field : protocolClass.getDeclaredFields()) {
try {
// ignore none static and final fields
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object protocolInfo = field.get(null);
// bind unbound ProtocolInfo to empty function to get real ProtocolInfo
if (protocolInfoUnboundClass.isInstance(protocolInfo)) {
protocolInfo = bindAccessor.invoke(protocolInfo, new Object[] { emptyFunction });
}
// ignore any field that isn't a ProtocolInfo
if (!protocolInfoClass.isInstance(protocolInfo)) {
continue;
}
// get codec and check if codec is instance of IdDispatchCodec
// since that is the only support codec as of now
Object codec = codecAccessor.invoke(protocolInfo);
if (!idCodecClass.isInstance(codec)) {
continue;
}
// retrieve packetTypeMap and convert it to packetIdMap
Map<Class<?>, Integer> packetMap = new HashMap<>();
List<Object> serializerList = (List<Object>) byIdAccessor.get(codec);
Map<Object, Integer> packetTypeIdMap = (Map<Object, Integer>) toIdAccessor.get(codec);
for (Map.Entry<Object, Integer> entry : packetTypeIdMap.entrySet()) {
Class<?> packetClass = packetTypeMap.get(entry.getKey());
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + entry.getKey());
}
packetMap.put(packetClass, entry.getValue());
}
// retrieve packet codecs for packet construction and write methods
for (Object entry : serializerList) {
Object packetType = idCodecEntryTypeAccessor.invoke(entry);
Class<?> packetClass = packetTypeMap.get(packetType);
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + packetType);
}
Object serializer = idCodecEntrySerializerAccessor.invoke(entry);
result.classToCodec.put(packetClass, new WrappedStreamCodec(serializer));
}
// get EnumProtocol and Direction of protocol info
Object protocol = protocolAccessor.invoke(protocolInfo);
String direction = directionAccessor.invoke(protocolInfo).toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, packetMap);
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, packetMap);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
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);
}
}
ProtocolRegistry_1_20_5.fillRegister(result);
return result;
}
@@ -517,7 +319,7 @@ public class PacketRegistry {
PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass);
try {
register.registerPacket(type, packetClass, sender);
register.registerPacket(type, packetClass, sender, null);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
@@ -610,8 +412,8 @@ public class PacketRegistry {
private static Class<?> searchForPacket(List<String> classNames) {
for (String name : classNames) {
try {
Class<?> clazz = MinecraftReflection.getMinecraftClass(name);
if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
Class<?> clazz = resolvePacketClassName(name);
if (clazz != null && MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers())) {
return clazz;
}
@@ -622,6 +424,22 @@ public class PacketRegistry {
return null;
}
/**
* Resolve a single packet class name. Fully-qualified names (those already containing their
* package, e.g. {@code net.minecraft.network.protocol.game.XPacket}) are loaded directly, since
* {@link MinecraftReflection#getMinecraftClass(String)} would otherwise prepend the Minecraft
* package and fail to find them. Relative names continue to be resolved against the Minecraft
* package.
*/
private static Class<?> resolvePacketClassName(String name) {
Optional<Class<?>> direct = MinecraftReflection.getOptionalClass(name);
if (direct.isPresent()) {
return direct.get();
}
return MinecraftReflection.getMinecraftClass(name);
}
/**
* Retrieves the correct packet class from a given type.
*
@@ -0,0 +1,71 @@
package com.comphenix.protocol.injector.packet.internal;
import java.util.Arrays;
import java.util.List;
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.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
/**
* Class only works for version 1.20.5+
*/
public class IdCodecWrapper extends AbstractWrapper {
private static final Class<?> ID_CODEC_CLASS;
private static final FieldAccessor BY_ID_ACCESSOR;
private static final Class<?> ID_CODEC_ENTRY_CLASS;
private static final MethodAccessor ENTRY_TYPE_ACCESSOR;
private static final MethodAccessor ENTRY_SERIALIZER_ACCESSOR;
static {
ID_CODEC_CLASS = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec");
BY_ID_ACCESSOR = Accessors.getFieldAccessor(FuzzyReflection.fromClass(ID_CODEC_CLASS, true)
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(List.class).build()));
ID_CODEC_ENTRY_CLASS = Arrays.stream(ID_CODEC_CLASS.getNestMembers())
.filter(Class::isRecord)
.findFirst()
.orElseThrow(() -> new RuntimeException("Failed to find entry class for: " + ID_CODEC_CLASS));
FuzzyReflection entryReflection = FuzzyReflection.fromClass(ID_CODEC_ENTRY_CLASS, true);
ENTRY_TYPE_ACCESSOR = Accessors.getMethodAccessor(entryReflection
.getMethodByReturnTypeAndParameters("type", Object.class, new Class[0]));
ENTRY_SERIALIZER_ACCESSOR = Accessors.getMethodAccessor(entryReflection
.getMethodByReturnTypeAndParameters("serializer", MinecraftReflection.getStreamCodecClass(), new Class[0]));
}
public IdCodecWrapper(Object handle) {
super(ID_CODEC_CLASS);
setHandle(handle);
}
public List<Entry> getById() {
// list represents a packet-id to {type, codec} lookup
List<?> byId = (List<?>) BY_ID_ACCESSOR.get(this.handle);
return byId.stream().map(Entry::new).toList();
}
public class Entry extends AbstractWrapper {
public Entry(Object handle) {
super(ID_CODEC_ENTRY_CLASS);
setHandle(handle);
}
public Object type() {
return ENTRY_TYPE_ACCESSOR.invoke(this.handle);
}
public WrappedStreamCodec serializer() {
Object serializer = ENTRY_SERIALIZER_ACCESSOR.invoke(this.handle);
return new WrappedStreamCodec(serializer);
}
}
}
@@ -0,0 +1,44 @@
package com.comphenix.protocol.injector.packet.internal;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Class only works for version 1.21.5+
*/
public class ProtocolContexts {
private static final Class<?> GAME_PROTOCOL_CONTEXT_CLASS;
static {
GAME_PROTOCOL_CONTEXT_CLASS = MinecraftReflection.getNullableNMS(
"network.protocol.game.GameProtocols$a" /*Spigot Mapping*/,
"network.protocol.game.GameProtocols$Context" /*Mojang Mapping*/);
}
public static Object createGameProtocolContext() {
return createContext(GAME_PROTOCOL_CONTEXT_CLASS, (proxy, method, args) -> {
if (method.getName().equals("hasInfiniteMaterials") || (method.getReturnType() == boolean.class && method.getParameterCount() == 0)) {
return true;
}
return null;
});
}
private static Object createContext(Class<?> interfaceClass, InvocationHandler handler) {
if (interfaceClass == null) {
return null;
}
return Proxy.newProxyInstance(ProtocolContexts.class.getClassLoader(), new Class[] { interfaceClass }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return handler.invoke(proxy, method, args);
}
});
}
}
@@ -0,0 +1,102 @@
package com.comphenix.protocol.injector.packet.internal;
import java.util.Objects;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
/**
* Class only works for version 1.20.5+
*/
public class ProtocolInfoWrapper extends AbstractWrapper {
private static final @NotNull Class<?> SIMPLE_UNBOUND_PROTOCOL_CLASS;
private static final @NotNull MethodAccessor BIND_SIMPLE_ACCESSOR;
private static final @Nullable Class<?> UNBOUND_PROTOCOL_CLASS;
private static final @Nullable MethodAccessor BIND_CONTEXT_ACCESSOR;
private static final @NotNull MethodAccessor ID_ACCESSOR;
private static final @NotNull MethodAccessor FLOW_ACCESSOR;
private static final @NotNull MethodAccessor CODEC_ACCESSOR;
static {
SIMPLE_UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getMinecraftClass(
"network.protocol.SimpleUnboundProtocol" /* 1.21.5+ */,
"network.protocol.UnboundProtocol" /* 1.21.8+ Alternative */,
"network.ProtocolInfo$a" /* Spigot Mappings */,
"network.ProtocolInfo$Unbound" /* Mojang Mappings */);
BIND_SIMPLE_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(SIMPLE_UNBOUND_PROTOCOL_CLASS)
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class }));
UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getNullableNMS("network.protocol.UnboundProtocol");
if (UNBOUND_PROTOCOL_CLASS != null) {
BIND_CONTEXT_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(UNBOUND_PROTOCOL_CLASS)
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class, Object.class }));
} else {
BIND_CONTEXT_ACCESSOR = null;
}
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(MinecraftReflection.getProtocolInfoClass());
ID_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
FLOW_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("flow", MinecraftReflection.getPacketFlowClass(), new Class[0]));
CODEC_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("codec", MinecraftReflection.getStreamCodecClass(), new Class[0]));
}
public static boolean isUnboundProtocol(Object protocol) {
return SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol) || (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol));
}
public static ProtocolInfoWrapper fromUnbound(Object protocol, Object context) {
// bind to identity function in-order to map directly to FriendlyByteBuf instead of ByteBuf
if (SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
Object protocolInfo = BIND_SIMPLE_ACCESSOR.invoke(protocol, Function.identity());
return new ProtocolInfoWrapper(protocolInfo);
}
if (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
Object protocolInfo = BIND_CONTEXT_ACCESSOR.invoke(protocol, Function.identity(), Objects.requireNonNull(context));
return new ProtocolInfoWrapper(protocolInfo);
}
throw new RuntimeException("Unknown protocol implementation: " + protocol);
}
private ProtocolInfoWrapper(Object handle) {
super(MinecraftReflection.getProtocolInfoClass());
setHandle(handle);
}
public PacketType.Protocol id() {
Object protocol = ID_ACCESSOR.invoke(this.handle);
return PacketType.Protocol.fromVanilla((Enum<?>) protocol);
}
public PacketType.Sender flow() {
String packetFlow = FLOW_ACCESSOR.invoke(this.handle).toString();
if (packetFlow.contains("CLIENTBOUND")) {
return PacketType.Sender.SERVER;
} else if (packetFlow.contains("SERVERBOUND")) {
return PacketType.Sender.CLIENT;
}
throw new RuntimeException("Unknown packet flow: " + packetFlow);
}
public IdCodecWrapper codec() {
return new IdCodecWrapper(CODEC_ACCESSOR.invoke(this.handle));
}
}
@@ -0,0 +1,150 @@
package com.comphenix.protocol.injector.packet.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.PacketRegistry.Register;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Class only works for version 1.20.5+
*/
public class ProtocolRegistry_1_20_5 {
private static final Class<?> PACKET_TYPE_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");;
/**
* List of all class containing PacketTypes
*/
public static final List<String> PACKET_TYPES_CLASS_NAMES = List.of(
"common.CommonPacketTypes",
"configuration.ConfigurationPacketTypes",
"cookie.CookiePacketTypes",
"game.GamePacketTypes",
"handshake.HandshakePacketTypes",
"login.LoginPacketTypes",
"ping.PingPacketTypes",
"status.StatusPacketTypes");
/**
* List of all class containing ProtocolInfos
*/
public static final List<Protocol> PROTOCOLS = List.of(
new Protocol("configuration.ConfigurationProtocols", null),
new Protocol("game.GameProtocols", ProtocolContexts::createGameProtocolContext),
new Protocol("handshake.HandshakeProtocols", null),
new Protocol("login.LoginProtocols", null),
new Protocol("status.StatusProtocols", null));
public static void fillRegister(Register register) {
final Map<Object, Class<?>> packetTypeMap = getPacketTypes();
for (Protocol protocol : PROTOCOLS) {
Class<?> protocolClass = MinecraftReflection.getNullableNMS("network.protocol." + protocol.className());
if (protocolClass == null) {
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocol);
continue;
}
for (Field field : protocolClass.getDeclaredFields()) {
try {
// ignore none static and final fields
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object fieldValue = field.get(null);
// Skip any field that isn't a unbound ProtocolInfo.
// We need to bind the ProtocolInfo ourself as we apply our own stream mapping
// inside the ProtocolInfoWrapper::fromUnbound method
if (!ProtocolInfoWrapper.isUnboundProtocol(fieldValue)) {
continue;
}
// create optional context object to bind protocol to
Object context = protocol.context() != null
? protocol.context().get()
: null;
// wrap bound protocol info for ease of use
ProtocolInfoWrapper protocolInfo = ProtocolInfoWrapper.fromUnbound(fieldValue, context);
PacketType.Protocol packetProtocol = protocolInfo.id();
PacketType.Sender packetSender = protocolInfo.flow();
// register each packet to Register instance
List<IdCodecWrapper.Entry> entries = protocolInfo.codec().getById();
for (int id = 0; id < entries.size(); id++) {
IdCodecWrapper.Entry entry = entries.get(id);
Class<?> packetClass = packetTypeMap.get(entry.type());
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + entry.type());
}
PacketType packetType = PacketType.fromCurrent(packetProtocol, packetSender, id, packetClass);
// TODO(fix): validate that every codec expects FriendlyByteBuf
register.registerPacket(packetType, packetClass, packetSender, entry.serializer());
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
/**
* Returns a PacketType to packet class lookup map
*/
private static Map<Object, Class<?>> getPacketTypes() {
Map<Object, Class<?>> packetTypeMap = new HashMap<>();
for (String packetTypesClassName : PACKET_TYPES_CLASS_NAMES) {
Class<?> packetTypesClass = MinecraftReflection.getNullableNMS("network.protocol." + packetTypesClassName);
if (packetTypesClass == null) {
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
continue;
}
// check every field for "static final PacketType<?>"
for (Field field : packetTypesClass.getDeclaredFields()) {
try {
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object packetType = field.get(null);
if (!PACKET_TYPE_CLASS.isInstance(packetType)) {
continue;
}
// retrieve the generic type T of the PacketType<T> field
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (packet instanceof Class<?>) {
packetTypeMap.put(packetType, (Class<?>) packet);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
return packetTypeMap;
}
private record Protocol(@NotNull String className, @Nullable Supplier<Object> context) {
}
}
@@ -1,81 +0,0 @@
package com.comphenix.protocol.injector.player;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import io.netty.channel.Channel;
public interface PlayerInjectionHandler {
/**
* Retrieve the protocol version of the given player.
*
* @param player - the player.
* @return The protocol version, or {@link Integer#MIN_VALUE}.
*/
int getProtocolVersion(Player player);
/**
* 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 injection conflicts.
*/
void injectPlayer(Player player, ConflictStrategy strategy);
/**
* Uninject the given player.
*
* @param player - player to uninject.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
boolean uninjectPlayer(Player player);
/**
* Send the given packet to the given receiver.
*
* @param receiver - the player receiver.
* @param packet - the packet to send.
* @param marker - network marker.
* @param filters - whether or not to invoke the packet filters.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
/**
* Process a packet as if it were sent by the given player.
*
* @param player - the sender.
* @param mcPacket - the packet to process.
*/
void receiveClientPacket(Player player, Object mcPacket);
/**
* Ensure that packet readers are informed of this player reference.
*
* @param player - the player to update.
*/
void updatePlayer(Player player);
Channel getChannel(Player player);
/**
* How to handle a previously existing player injection.
*
* @author Kristian
*/
enum ConflictStrategy {
/**
* Override it.
*/
OVERRIDE,
/**
* Immediately exit.
*/
BAIL_OUT;
}
}
@@ -1,50 +0,0 @@
package com.comphenix.protocol.injector.temporary;
import com.comphenix.protocol.events.NetworkMarker;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
/**
* Represents an injector that only gives access to a player's socket.
*
* @author Kristian
*/
public interface MinimalInjector {
/**
* Retrieve the associated address of this player.
*
* @return The associated address.
*/
SocketAddress getAddress();
/**
* Attempt to disconnect the current client.
*
* @param message - the message to display.
*/
void disconnect(String message);
/**
* 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.
*/
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
/**
* Retrieve the hooked player.
*
* @return The hooked player.
*/
Player getPlayer();
/**
* Determines if the player is currently connected.
*
* @return true if the player is connected.
*/
boolean isConnected();
}
@@ -1,5 +1,9 @@
package com.comphenix.protocol.injector.temporary;
import java.util.Objects;
import com.comphenix.protocol.injector.netty.Injector;
/**
* A temporary player created by ProtocolLib when a true player instance does not exist.
* <p>
@@ -8,15 +12,17 @@ package com.comphenix.protocol.injector.temporary;
*/
public class TemporaryPlayer {
private volatile MinimalInjector injector;
protected volatile Injector injector;
MinimalInjector getInjector() {
public Injector getInjector() {
return this.injector;
}
void setInjector(MinimalInjector injector) {
if (injector == null) {
throw new IllegalArgumentException("Injector cannot be NULL.");
void setInjector(Injector injector) {
Objects.requireNonNull(injector, "injector can't be null");
if (this.injector != null) {
throw new IllegalStateException("Can't redefine injector for temporary player");
}
this.injector = injector;
@@ -17,17 +17,21 @@
package com.comphenix.protocol.injector.temporary;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
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;
@@ -36,8 +40,6 @@ 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;
/**
* Create fake player instances that represents pre-authenticated clients.
@@ -46,13 +48,15 @@ public class TemporaryPlayerFactory {
private static final Constructor<? extends Player> PLAYER_CONSTRUCTOR = setupProxyPlayerConstructor();
private TemporaryPlayerFactory() {}
/**
* Retrieve the injector from a given player if it contains one.
*
* @param player - the player that may contain a reference to a player injector.
* @return The referenced player injector, or NULL if none can be found.
*/
public static MinimalInjector getInjectorFromPlayer(Player player) {
public static Injector getInjectorFromPlayer(Player player) {
if (player instanceof TemporaryPlayer) {
return ((TemporaryPlayer) player).getInjector();
}
@@ -65,7 +69,7 @@ public class TemporaryPlayerFactory {
* @param player - the player to update.
* @param injector - the injector to store.
*/
public static void setInjectorInPlayer(Player player, MinimalInjector injector) {
public static void setInjectorForPlayer(Player player, Injector injector) {
((TemporaryPlayer) player).setInjector(injector);
}
@@ -76,11 +80,10 @@ public class TemporaryPlayerFactory {
public Object delegate(
@This Object obj,
@Origin Method method,
@FieldValue("server") Server server,
@FieldValue("injector") Injector injector,
@AllArguments Object... args
) throws Throwable {
String methodName = method.getName();
MinimalInjector injector = ((TemporaryPlayer) obj).getInjector();
if (injector == null) {
throw new IllegalStateException("Unable to find injector.");
@@ -92,7 +95,7 @@ public class TemporaryPlayerFactory {
} else if (methodName.equals("getAddress")) {
return injector.getAddress();
} else if (methodName.equals("getServer")) {
return server;
return Bukkit.getServer();
}
// Handle send message methods
@@ -130,7 +133,16 @@ public class TemporaryPlayerFactory {
if (methodName.equals("isOnline")) {
return injector.isConnected();
} else if (methodName.equals("getName")) {
String playerName = injector.getPlayerName();
if (playerName != null) {
return playerName;
}
return "UNKNOWN[" + injector.getAddress() + "]";
} else if (methodName.equals("getUniqueId")) {
UUID playerUniqueId = injector.getPlayerUniqueId();
if (playerUniqueId != null) {
return playerUniqueId;
}
}
// Ignore all other methods
@@ -144,21 +156,15 @@ public class TemporaryPlayerFactory {
try {
final Constructor<?> constructor = ByteBuddyFactory.getInstance()
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
.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);
.getDeclaredConstructor();
return (Constructor<? extends Player>) constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Temporary Player constructor!", e);
@@ -172,9 +178,9 @@ public class TemporaryPlayerFactory {
* @param message - a message.
* @return Always NULL.
*/
private static Object sendMessage(MinimalInjector injector, String message) {
private static Object sendMessage(Injector injector, String message) {
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendServerPacket(packet.getHandle(), null, false);
injector.sendClientboundPacket(packet.getHandle(), null, false);
}
return null;
@@ -188,36 +194,24 @@ public class TemporaryPlayerFactory {
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>getName()</li>
* <li>getUniqueId(), when supplied by the login protocol</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead.
* Before the login start packet is received, the player has no profile information
* and can only be identified by its address.
*
* @param server - the current server.
* @return A temporary player instance.
*/
public Player createTemporaryPlayer(final Server server) {
public static Player createTemporaryPlayer() {
try {
return PLAYER_CONSTRUCTOR.newInstance(server);
return PLAYER_CONSTRUCTOR.newInstance();
} catch (ReflectiveOperationException exception) {
throw new IllegalStateException("Unable to create temporary player", exception);
}
}
/**
* Construct a temporary player with the given associated socket injector.
*
* @param server - the parent server.
* @param injector - the referenced socket injector.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server, MinimalInjector injector) {
Player temporary = this.createTemporaryPlayer(server);
((TemporaryPlayer) temporary).setInjector(injector);
return temporary;
}
}
@@ -0,0 +1,35 @@
package com.comphenix.protocol.internal;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import org.bukkit.command.CommandExecutor;
import com.comphenix.protocol.ProtocolLib;
import io.netty.channel.Channel;
public interface PlatformProvider {
static PlatformProvider get() {
return Holder.INSTANCE;
}
boolean hasEarlyChannelInitialization();
Runnable registerChannelInitializer(Consumer<Channel> channelInitializer);
void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor);
final class Holder {
private static final PlatformProvider INSTANCE = ServiceLoader.load(
PlatformProvider.class,
PlatformProvider.class.getClassLoader())
.findFirst()
.orElseThrow(() -> new IllegalStateException("No ProtocolLib platform provider is available"));
private Holder() {
}
}
}
@@ -90,6 +90,8 @@ public class StructureModifier<T> {
protected Map<FieldAccessor, Integer> defaultFields;
// Cache of previous types
protected Map<Class<?>, StructureModifier<?>> subtypeCache;
// Cache of previous parameterized types
private Map<List<Type>, StructureModifier<?>> paramTypeCache = new HashMap<>();
// Whether or subclasses should handle conversion
protected boolean customConvertHandling;
@@ -201,6 +203,7 @@ public class StructureModifier<T> {
other.defaultFields,
other.converter,
other.subtypeCache);
this.paramTypeCache = other.paramTypeCache;
}
/**
@@ -531,8 +534,18 @@ public class StructureModifier<T> {
return (StructureModifier<R>) NO_OP_MODIFIER;
}
// parameterized lookups must not share a cache entry with the raw field type
List<Type> paramTypeKey = null;
if (paramTypes.length > 0) {
paramTypeKey = new ArrayList<>(paramTypes.length + 1);
paramTypeKey.add(fieldType);
paramTypeKey.addAll(Arrays.asList(paramTypes));
}
// Do we need to update the cache?
StructureModifier<R> result = (StructureModifier<R>) this.subtypeCache.get(fieldType);
StructureModifier<R> result = (StructureModifier<R>) (paramTypeKey == null
? this.subtypeCache.get(fieldType)
: this.paramTypeCache.get(paramTypeKey));
if (result == null) {
List<FieldAccessor> fields = new ArrayList<>();
Map<FieldAccessor, Integer> defaults = new HashMap<>();
@@ -571,7 +584,11 @@ public class StructureModifier<T> {
// Cache structure modifiers
result = this.withFieldType(fieldType, fields, defaults);
this.subtypeCache.put(fieldType, result);
if (paramTypeKey == null) {
this.subtypeCache.put(fieldType, result);
} else {
this.paramTypeCache.put(paramTypeKey, result);
}
}
// Add the target too
@@ -4,6 +4,21 @@ import java.lang.reflect.Constructor;
public interface ConstructorAccessor {
/**
* NoOp Accessor, does what is says: nothing.
*/
static final ConstructorAccessor NO_OP_ACCESSOR = new ConstructorAccessor() {
@Override
public Object invoke(Object... args) {
return null;
}
@Override
public Constructor<?> getConstructor() {
return null;
}
};
/**
* Invoke the underlying constructor.
*
@@ -9,6 +9,25 @@ import java.lang.reflect.Field;
*/
public interface FieldAccessor {
/**
* NoOp Accessor, does what is says: nothing.
*/
static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
@Override
public Object get(Object instance) {
return null;
}
@Override
public void set(Object instance, Object value) {
}
@Override
public Field getField() {
return null;
}
};
/**
* Retrieve the value of a field for a particular instance.
*
@@ -63,6 +63,7 @@ public class ImmutableDetector implements Cloner {
add(MinecraftReflection::getBlockClass);
add(MinecraftReflection::getItemClass);
add(MinecraftReflection::getSoundEffectClass);
add(MinecraftReflection::getWorldTypeClass);
if (MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
add(MinecraftReflection::getFluidTypeClass);
@@ -1,16 +1,18 @@
package com.comphenix.protocol.reflect.instances;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
public class MinecraftGenerator {
@@ -58,14 +60,20 @@ public class MinecraftGenerator {
try {
String name = type.getName();
if (name.contains("it.unimi.dsi.fastutil")) {
Class<?> clz = Class.forName(name.replace("Map", "OpenHashMap"));
return Accessors.getConstructorAccessorOrNull(clz);
// convert interface maps to OpenHashMaps
if (!name.endsWith("OpenHashMap")) {
name = name.replace("Map", "OpenHashMap");
}
Class<?> hashMapClass = Class.forName(name);
return Accessors.getConstructorAccessorOrNull(hashMapClass);
}
} catch (Exception ignored) {
}
return null;
return ConstructorAccessor.NO_OP_ACCESSOR;
});
if (ctor != null) {
if (ctor != ConstructorAccessor.NO_OP_ACCESSOR) {
try {
return ctor.invoke();
} catch (Exception ignored) {
@@ -0,0 +1,58 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
* <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.updater;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
/**
* Update check worker (top-level class so the runtime always loads a single {@code .class} entry).
*/
final class SpigotUpdateRunnable implements Runnable {
private final SpigotUpdater updater;
SpigotUpdateRunnable(SpigotUpdater updater) {
this.updater = updater;
}
@Override
public void run() {
try {
String version = this.updater.getSpigotVersion();
this.updater.setRemoteVersion(version);
if (this.updater.versionCheck(version)) {
this.updater.result = Updater.UpdateResult.SPIGOT_UPDATE_AVAILABLE;
} else {
this.updater.result = Updater.UpdateResult.NO_UPDATE;
}
} catch (Throwable ex) {
if (ProtocolLibrary.getConfig().isDebug()) {
ProtocolLibrary.getErrorReporter().reportDetailed(
this.updater,
Report.newBuilder(Updater.REPORT_CANNOT_UPDATE_PLUGIN).error(ex).callerParam(this));
}
ProtocolLibrary.disableUpdates();
} finally {
for (Runnable listener : this.updater.listeners) {
ProtocolLibrary.getScheduler().runTask(listener);
}
}
}
}
@@ -16,8 +16,6 @@
*/
package com.comphenix.protocol.updater;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.utility.Closer;
import org.bukkit.plugin.Plugin;
@@ -44,7 +42,7 @@ public final class SpigotUpdater extends Updater {
waitForThread();
this.type = type;
this.thread = new Thread(new SpigotUpdateRunnable());
this.thread = new Thread(new SpigotUpdateRunnable(this));
this.thread.start();
}
@@ -54,36 +52,8 @@ public final class SpigotUpdater extends Updater {
return String.format(result.toString(), remoteVersion, plugin.getDescription().getVersion(), RESOURCE_URL);
}
private class SpigotUpdateRunnable implements Runnable {
@Override
public void run() {
try {
String version = getSpigotVersion();
remoteVersion = version;
if (versionCheck(version)) {
result = UpdateResult.SPIGOT_UPDATE_AVAILABLE;
} else {
result = UpdateResult.NO_UPDATE;
}
} catch (Throwable ex) {
if (ProtocolLibrary.getConfig().isDebug()) {
ProtocolLibrary.getErrorReporter().reportDetailed(
SpigotUpdater.this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(ex).callerParam(this));
} else {
// People don't care
// plugin.getLogger().log(Level.WARNING, "Failed to check for updates: " + ex);
}
ProtocolLibrary.disableUpdates();
} finally {
// Invoke the listeners on the main thread
for (Runnable listener : listeners) {
ProtocolLibrary.getScheduler().runTask(listener);
}
}
}
void setRemoteVersion(String remoteVersion) {
this.remoteVersion = remoteVersion;
}
private static final String RESOURCE_URL = "https://www.spigotmc.org/resources/protocollib.1997/";
@@ -88,20 +88,23 @@ final class CachedPackage {
* @throws RuntimeException If we are unable to find the given class.
*/
public Optional<Class<?>> getPackageClass(String className, String... aliases) {
return cache.computeIfAbsent(className, x -> {
Optional<Class<?>> clazz = resolveClass(className);
if (clazz.isPresent()) {
return clazz;
}
Optional<Class<?>> cached = cache.get(className);
if (cached != null) {
return cached;
}
Optional<Class<?>> clazz = resolveClass(className);
if (!clazz.isPresent()) {
for (String alias : aliases) {
clazz = resolveClass(alias);
if (clazz.isPresent()) {
return clazz;
Optional<Class<?>> aliasClazz = resolveClass(alias);
if (aliasClazz.isPresent()) {
clazz = aliasClazz;
break;
}
}
}
return Optional.empty();
});
cache.put(className, clazz);
return clazz;
}
}
@@ -35,12 +35,13 @@ import net.bytebuddy.matcher.ElementMatchers;
public final class MinecraftMethods {
// For player connection
private volatile static MethodAccessor sendPacketMethod;
private volatile static MethodAccessor disconnectMethod;
private volatile static MethodAccessor playerConnectionSendMethod;
private volatile static MethodAccessor playerConnectionDisconnectMethod;
// For network manager
private volatile static MethodAccessor networkManagerHandle;
private volatile static MethodAccessor networkManagerSend;
private volatile static MethodAccessor networkManagerPacketRead;
private volatile static MethodAccessor networkManagerDisconnect;
// For packet
private volatile static MethodAccessor packetReadByteBuf;
@@ -56,21 +57,25 @@ public final class MinecraftMethods {
}
public static Function<ByteBuf, Object> getFriendlyBufBufConstructor() {
if (friendlyBufBufConstructor == null) {
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(registryByteBuf.get()).getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
.build()));
friendlyBufBufConstructor = (byteBuf) -> accessor.invoke(byteBuf, MinecraftRegistryAccess.get());
} else {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(MinecraftReflection.getPacketDataSerializerClass(), ByteBuf.class);
friendlyBufBufConstructor = (byteBuf) -> accessor.invoke(byteBuf);
}
}
return friendlyBufBufConstructor;
if (friendlyBufBufConstructor == null) {
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(
FuzzyReflection.fromClass(registryByteBuf.get()).getConstructor(
FuzzyMethodContract.newBuilder()
.parameterExactType(ByteBuf.class, 0)
.parameterExactType(MinecraftReflection.getRegistryAccessClass(), 1)
.build()));
friendlyBufBufConstructor = byteBuf -> accessor.invoke(byteBuf, MinecraftRegistryAccess.get());
} else {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(
MinecraftReflection.getPacketDataSerializerClass(),
ByteBuf.class);
friendlyBufBufConstructor = byteBuf -> accessor.invoke(byteBuf);
}
}
return friendlyBufBufConstructor;
}
/**
@@ -78,18 +83,18 @@ public final class MinecraftMethods {
*
* @return The send packet method.
*/
public static MethodAccessor getSendPacketMethod() {
if (sendPacketMethod == null) {
public static MethodAccessor getPlayerConnectionSendMethod() {
if (playerConnectionSendMethod == null) {
FuzzyReflection serverHandlerClass = FuzzyReflection.fromClass(MinecraftReflection.getPlayerConnectionClass());
try {
sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
playerConnectionSendMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(1)
.returnTypeVoid()
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build()));
} catch (IllegalArgumentException e) {
sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
playerConnectionSendMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
.nameRegex("sendPacket.*")
.returnTypeVoid()
.parameterCount(1)
@@ -97,33 +102,41 @@ public final class MinecraftMethods {
}
}
return sendPacketMethod;
return playerConnectionSendMethod;
}
/**
* Retrieve the disconnect method for a given player connection.
*
* @param playerConnection - the player connection.
* @return The
*/
public static MethodAccessor getDisconnectMethod(Class<?> playerConnection) {
if (disconnectMethod == null) {
FuzzyReflection playerConnectionClass = FuzzyReflection.fromClass(playerConnection);
try {
disconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
public static MethodAccessor getPlayerConnectionDisconnectMethod() {
if (playerConnectionDisconnectMethod == null) {
FuzzyReflection playerConnectionClass = FuzzyReflection.fromClass(MinecraftReflection.getPlayerConnectionClass());
if (MinecraftVersion.v1_21_0.atOrAbove()) {
playerConnectionDisconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeVoid()
.nameRegex("disconnect.*")
.parameterCount(1)
.parameterExactType(String.class, 0)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.build()));
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
Method disconnect = playerConnectionClass.getMethodByParameters("disconnect", String.class);
disconnectMethod = Accessors.getMethodAccessor(disconnect);
} else {
try {
playerConnectionDisconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeVoid()
.nameRegex("disconnect.*")
.parameterCount(1)
.parameterExactType(String.class, 0)
.build()));
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
Method disconnect = playerConnectionClass.getMethodByParameters("disconnect", String.class);
playerConnectionDisconnectMethod = Accessors.getMethodAccessor(disconnect);
}
}
}
return disconnectMethod;
return playerConnectionDisconnectMethod;
}
/**
@@ -131,8 +144,8 @@ public final class MinecraftMethods {
*
* @return The handle method.
*/
public static MethodAccessor getNetworkManagerHandleMethod() {
if (networkManagerHandle == null) {
public static MethodAccessor getNetworkManagerSendMethod() {
if (networkManagerSend == null) {
Method handleMethod = FuzzyReflection
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
.getMethod(FuzzyMethodContract.newBuilder()
@@ -141,10 +154,10 @@ public final class MinecraftMethods {
.parameterCount(1)
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build());
networkManagerHandle = Accessors.getMethodAccessor(handleMethod);
networkManagerSend = Accessors.getMethodAccessor(handleMethod);
}
return networkManagerHandle;
return networkManagerSend;
}
/**
@@ -163,6 +176,27 @@ public final class MinecraftMethods {
return networkManagerPacketRead;
}
/**
* Retrieve the handle/send packet method of network manager.
*
* @return The handle method.
*/
public static MethodAccessor getNetworkManagerDisconnectMethod() {
if (networkManagerDisconnect == null) {
Method handleMethod = FuzzyReflection
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.build());
networkManagerDisconnect = Accessors.getMethodAccessor(handleMethod);
}
return networkManagerDisconnect;
}
/**
* Retrieve the Packet.read(PacketDataSerializer) method.
*
@@ -93,6 +93,15 @@ public final class MinecraftProtocolVersion {
map.put(new MinecraftVersion(1, 20, 5), 766);
map.put(new MinecraftVersion(1, 21, 0), 767);
map.put(new MinecraftVersion(1, 21, 2), 768);
map.put(new MinecraftVersion(1, 21, 6), 771);
map.put(new MinecraftVersion(1, 21, 8), 772);
map.put(new MinecraftVersion(1, 21, 9), 773);
map.put(new MinecraftVersion(1, 21, 11), 774);
map.put(new MinecraftVersion(26, 1, 0), 775);
map.put(new MinecraftVersion(26, 2, 0), 776);
map.put(new MinecraftVersion(26, 3, 0), 777);
return map;
}
@@ -373,6 +373,11 @@ public final class MinecraftReflection {
if (clazz == null || object == null) {
return false;
}
// check for accidental class objects
if (object instanceof Class) {
return clazz.isAssignableFrom((Class<?>) object);
}
return clazz.isAssignableFrom(object.getClass());
}
@@ -502,6 +507,10 @@ public final class MinecraftReflection {
return is(getCraftItemStackClass(), obj);
}
public static boolean isIChatBaseComponent(Class<?> target) {
return is(getIChatBaseComponentClass(), target);
}
/**
* Retrieve the EntityPlayer (NMS) class.
*
@@ -544,6 +553,10 @@ public final class MinecraftReflection {
return getClass("com.mojang.authlib.GameProfile");
}
public static Class<?> getGameProfilePropertyMapClass() {
return getClass("com.mojang.authlib.properties.PropertyMap");
}
/**
* Retrieve the entity (NMS) class.
*
@@ -676,7 +689,8 @@ public final class MinecraftReflection {
* @throws IllegalStateException If the class could not be found or deduced.
*/
public static Class<?> getChatSerializerClass() {
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer");
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer",
"network.chat.Component$Serializer", "network.chat.ComponentSerialization", "IChatBaseComponent$ChatSerializer");
}
/**
@@ -907,15 +921,19 @@ public final class MinecraftReflection {
}
public static Class<?> getMinecraftKeyClass() {
return getMinecraftClass("resources.MinecraftKey", "resources.ResourceLocation", "MinecraftKey");
return getMinecraftClass("resources.MinecraftKey", "resources.Identifier", "resources.ResourceLocation", "MinecraftKey");
}
public static Class<?> getMobEffectListClass() {
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect");
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect", "world.effect.MobEffects");
}
public static Class<?> getDamageTypeClass() {
return getMinecraftClass("world.damagesource.DamageType");
}
public static Class<?> getSoundEffectClass() {
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect");
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect", "sounds.SoundEvents");
}
/**
@@ -1104,6 +1122,15 @@ public final class MinecraftReflection {
return getOptionalNMS("network.chat.numbers.StyledFormat");
}
/**
* Retrieve the NMS TeamColor class, added in 26.2.
*
* @return The TeamColor class.
*/
public static Optional<Class<?>> getTeamColorClass() {
return getOptionalNMS("world.scores.TeamColor");
}
/**
* Retrieve the Gson class used by Minecraft.
*
@@ -1403,8 +1430,13 @@ public final class MinecraftReflection {
try {
Method asMirrorMethod = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass());
asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex);
} catch (ReflectiveOperationException ex1) {
try {
Method asMirrorMethod = getCraftItemStackClass().getMethod("asBukkitMirror", getItemStackClass());
asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod);
} catch (ReflectiveOperationException ex2) {
throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex2);
}
}
}
@@ -1466,6 +1498,18 @@ public final class MinecraftReflection {
.orElseThrow(() -> new RuntimeException("Cannot find class " + className));
}
/**
* Attempt to load a class by its fully-qualified canonical name, without prepending the
* Minecraft package. Unlike {@link #getMinecraftClass(String)}, this can resolve names that
* already include their package (e.g. {@code net.minecraft.network.protocol.game.XPacket}).
*
* @param className - the fully-qualified class name.
* @return Optional that may contain the class.
*/
public static Optional<Class<?>> getOptionalClass(String className) {
return getClassSource().loadClass(className);
}
/**
* Retrieve the class object of a specific CraftBukkit class.
*
@@ -1670,7 +1714,7 @@ public final class MinecraftReflection {
}
public static Class<?> getEntityTypes() {
return getMinecraftClass("world.entity.EntityTypes", "world.entity.EntityType", "EntityTypes");
return getMinecraftClass("world.entity.EntityType", "world.entity.EntityTypes", "EntityTypes");
}
public static Class<?> getParticleParam() {
@@ -1889,4 +1933,31 @@ public final class MinecraftReflection {
return isMojangMapped;
}
/**
* Retrieves the PositionMoveRotation class (introduced in 1.21.2).
*
* @return The PositionMoveRotation class.
*/
public static Class<?> getPositionMoveRotationClass() {
return getMinecraftClass("world.entity.PositionMoveRotation");
}
/**
* Retrieves the VecDelta class (since 26.3).
*
* @return The VecDelta class.
*/
public static Optional<Class<?>> getVecDeltaClass() {
return getOptionalNMS("network.protocol.game.VecDelta");
}
/**
* Retrieves the PositionPath class (since 26.3).
*
* @return The PositionPath class.
*/
public static Optional<Class<?>> getPositionPathClass() {
return getOptionalNMS("world.entity.PositionPath");
}
}
@@ -2,6 +2,7 @@ package com.comphenix.protocol.utility;
import java.lang.reflect.Modifier;
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
import org.bukkit.Bukkit;
import com.comphenix.protocol.reflect.FuzzyReflection;
@@ -17,6 +18,7 @@ public class MinecraftRegistryAccess {
private static MethodAccessor GET_SERVER = null;
private static MethodAccessor REGISTRY_ACCESS = null;
private static MethodAccessor CREATE_SERIALIZATION_CONTEXT = null;
// lazy initialized
private static Object registryAccess = null;
@@ -36,6 +38,14 @@ public class MinecraftRegistryAccess {
.banModifier(Modifier.STATIC)
.returnDerivedOf(MinecraftReflection.getRegistryAccessClass())
.build()));
CREATE_SERIALIZATION_CONTEXT = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(MinecraftReflection.getHolderLookupProviderClass(), false)
.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnDerivedOf(MinecraftReflection.getDynamicOpsClass())
.parameterDerivedOf(MinecraftReflection.getDynamicOpsClass())
.build()));
}
}
@@ -57,4 +67,13 @@ public class MinecraftRegistryAccess {
return registryAccess;
}
/**
* Returns a RegistryOps using the given DynamicOps as the underlying ops.
*
* @return a RegistryOps
*/
public static WrappedDynamicOps createSerializationContext(WrappedDynamicOps dynamicOps) {
return WrappedDynamicOps.fromHandle(CREATE_SERIALIZATION_CONTEXT.invoke(get(), dynamicOps.getHandle()));
}
}
@@ -36,6 +36,56 @@ import org.bukkit.Server;
* @author Kristian
*/
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
/**
* Version 26.3 - wilderness bound
*/
public static final MinecraftVersion v26_3 = new MinecraftVersion("26.3");
/**
* Version 26.2 - chaos cubed
*/
public static final MinecraftVersion v26_2 = new MinecraftVersion("26.2");
/**
* Version 26.1 - tiny takeover
*/
public static final MinecraftVersion v26_1 = new MinecraftVersion("26.1");
/**
* Version 1.21.11 - mounts of mayhem
*/
public static final MinecraftVersion v1_21_11 = new MinecraftVersion("1.21.11");
/**
* Version 1.21.10 - hotfix for 1.21.9
*/
public static final MinecraftVersion v1_21_10 = new MinecraftVersion("1.21.10");
/**
* Version 1.21.9 - the copper age drop
*/
public static final MinecraftVersion v1_21_9 = new MinecraftVersion("1.21.9");
/**
* Version 1.21.6 - chase the skies
*/
public static final MinecraftVersion v1_21_6 = new MinecraftVersion("1.21.6");
/**
* Version 1.21.5 - spring to life drop
*/
public static final MinecraftVersion v1_21_5 = new MinecraftVersion("1.21.5");
/**
* Version 1.21.4 - the garden awakens drop
*/
public static final MinecraftVersion v1_21_4 = new MinecraftVersion("1.21.4");
/**
* Version 1.21.2 - the bundles of bravery drop
*/
public static final MinecraftVersion v1_21_2 = new MinecraftVersion("1.21.2");
/**
* Version 1.21.0 - the tricky trials update
*/
@@ -149,7 +199,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
/**
* The latest release version of minecraft.
*/
public static final MinecraftVersion LATEST = v1_21_0;
public static final MinecraftVersion LATEST = v26_3;
// used when serializing
private static final long serialVersionUID = -8695133558996459770L;
@@ -10,7 +10,9 @@ import java.util.Base64;
import com.comphenix.protocol.injector.netty.NettyByteBufAdapter;
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.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
@@ -31,6 +33,7 @@ public class StreamSerializer {
private static final StreamSerializer DEFAULT = new StreamSerializer();
// Cached methods
private static FieldAccessor STREAM_CODEC;
private static MethodAccessor READ_ITEM_METHOD;
private static MethodAccessor WRITE_ITEM_METHOD;
@@ -235,14 +238,35 @@ public class StreamSerializer {
Object serializer = MinecraftReflection.getPacketDataSerializer(buf);
if (READ_ITEM_METHOD == null) {
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(serializer.getClass(), false)
.getMethodByReturnTypeAndParameters("readItemStack", MinecraftReflection.getItemStackClass()));
if (MinecraftVersion.v1_21_0.atOrAbove()) {
if (STREAM_CODEC == null) {
STREAM_CODEC = Accessors.getFieldAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getItemStackClass())
.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getStreamCodecClass())
.build()).get(1)); //skip OPTIONAL_STREAM_CODEC
}
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection.fromObject(STREAM_CODEC.get(null), true)
.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactType(MinecraftReflection.getRegistryFriendlyByteBufClass().get())
.returnTypeExact(MinecraftReflection.getItemStackClass())
.build()));
} else {
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(serializer.getClass(), false)
.getMethodByReturnTypeAndParameters("readItemStack", MinecraftReflection.getItemStackClass()));
}
}
try {
// unwrap the item
Object nmsItem = READ_ITEM_METHOD.invoke(serializer);
Object nmsItem;
if (MinecraftVersion.v1_21_0.atOrAbove()) {
nmsItem = READ_ITEM_METHOD.invoke(STREAM_CODEC.get(null), serializer);
} else {
nmsItem = READ_ITEM_METHOD.invoke(serializer);
}
return nmsItem != null ? MinecraftReflection.getBukkitItemStack(nmsItem) : null;
} finally {
ReferenceCountUtil.safeRelease(buf);
@@ -262,11 +286,26 @@ public class StreamSerializer {
* @throws IOException If the operation fails due to reflection problems.
*/
public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException {
// TODO this functionality was replaced by the CODEC field in the nms ItemStack
if (WRITE_ITEM_METHOD == null) {
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true)
.getMethodByParameters("writeStack", MinecraftReflection.getItemStackClass()));
if (MinecraftVersion.v1_21_0.atOrAbove()) {
if (STREAM_CODEC == null) {
STREAM_CODEC = Accessors.getFieldAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getItemStackClass())
.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getStreamCodecClass())
.build()).get(1)); //skip OPTIONAL_STREAM_CODEC
}
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection.fromObject(STREAM_CODEC.get(null), true)
.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getRegistryFriendlyByteBufClass().get(), MinecraftReflection.getItemStackClass())
.returnTypeExact(void.class)
.build()));
} else {
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true)
.getMethodByParameters("writeStack", MinecraftReflection.getItemStackClass()));
}
}
ByteBuf buf = Unpooled.buffer();
@@ -274,7 +313,12 @@ public class StreamSerializer {
// Get the NMS version of the ItemStack and write it into the buffer
Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
WRITE_ITEM_METHOD.invoke(serializer, nmsItem);
if (MinecraftVersion.v1_21_0.atOrAbove()) {
WRITE_ITEM_METHOD.invoke(STREAM_CODEC.get(null), serializer, nmsItem);
} else {
WRITE_ITEM_METHOD.invoke(serializer, nmsItem);
}
// write the serialized content to the stream
output.write(this.getBytesAndRelease(buf));
@@ -282,15 +326,9 @@ public class StreamSerializer {
public byte[] getBytesAndRelease(ByteBuf buf) {
try {
if (buf.hasArray()) {
// heap buffer, we can access the array directly
return buf.array();
} else {
// direct buffer, we need to copy the bytes into an array
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
return bytes;
}
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
return bytes;
} finally {
ReferenceCountUtil.safeRelease(buf);
}
@@ -2,12 +2,14 @@ package com.comphenix.protocol.utility;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufProcessor;
import io.netty.util.ByteProcessor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.Charset;
@@ -54,6 +56,16 @@ public class ZeroBuffer extends ByteBuf {
return false;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public ByteBuf asReadOnly() {
return null;
}
@Override
public int readerIndex() {
return 0;
@@ -179,36 +191,71 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public short getShortLE(int i) {
return 0;
}
@Override
public int getUnsignedShort(int i) {
return 0;
}
@Override
public int getUnsignedShortLE(int i) {
return 0;
}
@Override
public int getMedium(int i) {
return 0;
}
@Override
public int getMediumLE(int i) {
return 0;
}
@Override
public int getUnsignedMedium(int i) {
return 0;
}
@Override
public int getUnsignedMediumLE(int i) {
return 0;
}
@Override
public int getInt(int i) {
return 0;
}
@Override
public int getIntLE(int i) {
return 0;
}
@Override
public long getUnsignedInt(int i) {
return 0;
}
@Override
public long getUnsignedIntLE(int i) {
return 0;
}
@Override
public long getLong(int i) {
return 0;
}
@Override
public long getLongLE(int i) {
return 0;
}
@Override
public char getChar(int i) {
return 0;
@@ -264,6 +311,16 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public int getBytes(int i, FileChannel fileChannel, long l, int i1) throws IOException {
return 0;
}
@Override
public CharSequence getCharSequence(int i, int i1, Charset charset) {
return "";
}
@Override
public ByteBuf setBoolean(int i, boolean b) {
return null;
@@ -279,21 +336,41 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf setShortLE(int i, int i1) {
return null;
}
@Override
public ByteBuf setMedium(int i, int i1) {
return null;
}
@Override
public ByteBuf setMediumLE(int i, int i1) {
return null;
}
@Override
public ByteBuf setInt(int i, int i1) {
return null;
}
@Override
public ByteBuf setIntLE(int i, int i1) {
return null;
}
@Override
public ByteBuf setLong(int i, long l) {
return null;
}
@Override
public ByteBuf setLongLE(int i, long l) {
return null;
}
@Override
public ByteBuf setChar(int i, int i1) {
return null;
@@ -349,11 +426,21 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public int setBytes(int i, FileChannel fileChannel, long l, int i1) {
return 0;
}
@Override
public ByteBuf setZero(int i, int i1) {
return null;
}
@Override
public int setCharSequence(int i, CharSequence charSequence, Charset charset) {
return 0;
}
@Override
public boolean readBoolean() {
return false;
@@ -374,36 +461,71 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public short readShortLE() {
return 0;
}
@Override
public int readUnsignedShort() {
return 0;
}
@Override
public int readUnsignedShortLE() {
return 0;
}
@Override
public int readMedium() {
return 0;
}
@Override
public int readMediumLE() {
return 0;
}
@Override
public int readUnsignedMedium() {
return 0;
}
@Override
public int readUnsignedMediumLE() {
return 0;
}
@Override
public int readInt() {
return 0;
}
@Override
public int readIntLE() {
return 0;
}
@Override
public long readUnsignedInt() {
return 0;
}
@Override
public long readUnsignedIntLE() {
return 0;
}
@Override
public long readLong() {
return 0;
}
@Override
public long readLongLE() {
return 0;
}
@Override
public char readChar() {
return 0;
@@ -429,6 +551,11 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf readRetainedSlice(int i) {
return null;
}
@Override
public ByteBuf readBytes(ByteBuf byteBuf) {
return null;
@@ -469,6 +596,16 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public CharSequence readCharSequence(int i, Charset charset) {
return "";
}
@Override
public int readBytes(FileChannel fileChannel, long l, int i) {
return 0;
}
@Override
public ByteBuf skipBytes(int i) {
return null;
@@ -489,21 +626,41 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf writeShortLE(int i) {
return null;
}
@Override
public ByteBuf writeMedium(int i) {
return null;
}
@Override
public ByteBuf writeMediumLE(int i) {
return null;
}
@Override
public ByteBuf writeInt(int i) {
return null;
}
@Override
public ByteBuf writeIntLE(int i) {
return null;
}
@Override
public ByteBuf writeLong(long l) {
return null;
}
@Override
public ByteBuf writeLongLE(long l) {
return null;
}
@Override
public ByteBuf writeChar(int i) {
return null;
@@ -559,11 +716,21 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public int writeBytes(FileChannel fileChannel, long l, int i) {
return 0;
}
@Override
public ByteBuf writeZero(int i) {
return null;
}
@Override
public int writeCharSequence(CharSequence charSequence, Charset charset) {
return 0;
}
@Override
public int indexOf(int i, int i1, byte b) {
return 0;
@@ -585,22 +752,22 @@ public class ZeroBuffer extends ByteBuf {
}
@Override
public int forEachByte(ByteBufProcessor byteBufProcessor) {
public int forEachByte(ByteProcessor byteProcessor) {
return 0;
}
@Override
public int forEachByte(int i, int i1, ByteBufProcessor byteBufProcessor) {
public int forEachByte(int i, int i1, ByteProcessor byteProcessor) {
return 0;
}
@Override
public int forEachByteDesc(ByteBufProcessor byteBufProcessor) {
public int forEachByteDesc(ByteProcessor byteProcessor) {
return 0;
}
@Override
public int forEachByteDesc(int i, int i1, ByteBufProcessor byteBufProcessor) {
public int forEachByteDesc(int i, int i1, ByteProcessor byteProcessor) {
return 0;
}
@@ -619,16 +786,31 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf retainedSlice() {
return null;
}
@Override
public ByteBuf slice(int i, int i1) {
return null;
}
@Override
public ByteBuf retainedSlice(int i, int i1) {
return null;
}
@Override
public ByteBuf duplicate() {
return null;
}
@Override
public ByteBuf retainedDuplicate() {
return null;
}
@Override
public int nioBufferCount() {
return 0;
@@ -738,4 +920,14 @@ public class ZeroBuffer extends ByteBuf {
public ByteBuf retain() {
return null;
}
@Override
public ByteBuf touch() {
return null;
}
@Override
public ByteBuf touch(Object o) {
return null;
}
}
@@ -86,6 +86,15 @@ public class AdventureComponentConverter {
return WrappedChatComponent.fromJson(SERIALIZER.serialize(component));
}
/**
* Converts an object that is instance of {@link Component} into a ProtocolLib wrapper
* @param component Component
* @return ProtocolLib wrapper
*/
public static WrappedChatComponent fromComponentObject(Object component) {
return fromComponent((Component) component);
}
/**
* Converts a {@link WrappedComponentStyle} into a {@link Style}
* @param wrapper ProtocolLib wrapper
@@ -70,6 +70,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
@@ -873,6 +874,13 @@ public class BukkitConverters {
return ignoreNull(new EquivalentConverter<EntityType>() {
@Override
public Object getGeneric(EntityType specific) {
if (MinecraftVersion.v26_2.atOrAbove()) {
// EntityType.byString was removed in 26.2, resolve through the registry instead
WrappedRegistry registry = WrappedRegistry.getRegistry(MinecraftReflection.getEntityTypes());
NamespacedKey key = specific.getKey();
return registry.get(new MinecraftKey(key.getNamespace(), key.getKey()));
}
if (entityTypeFromName == null) {
Class<?> entityTypesClass = MinecraftReflection.getEntityTypes();
entityTypeFromName = Accessors.getMethodAccessor(
@@ -1428,6 +1436,142 @@ public class BukkitConverters {
});
}
// Lazily-resolved CraftDamageType reflection. volatile + DCL keeps the publish safe
// and avoids re-running fuzzy lookups in concurrent injection paths.
private static volatile MethodAccessor damageTypeToNms = null;
private static volatile MethodAccessor damageTypeFromNms = null;
private static volatile MethodAccessor damageTypeHolderValue = null;
private static boolean damageTypeToNmsReturnsHolder;
private static boolean damageTypeFromNmsAcceptsHolder;
private static final Map<Object, Object> damageTypeHolders = new ConcurrentHashMap<>();
private static FuzzyReflection craftDamageTypeFuzzy() {
return FuzzyReflection.fromClass(getCraftBukkitClass("damage.CraftDamageType"), false);
}
private static MethodAccessor resolveDamageTypeToNms() {
MethodAccessor accessor = damageTypeToNms;
if (accessor != null) return accessor;
synchronized (BukkitConverters.class) {
accessor = damageTypeToNms;
if (accessor != null) return accessor;
try {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(org.bukkit.damage.DamageType.class)
.returnTypeExact(MinecraftReflection.getDamageTypeClass())
.requireModifier(Modifier.STATIC)
.build()));
} catch (IllegalArgumentException exception) {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(org.bukkit.damage.DamageType.class)
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.STATIC)
.build()));
damageTypeToNmsReturnsHolder = true;
}
damageTypeToNms = accessor;
return accessor;
}
}
private static MethodAccessor resolveDamageTypeFromNms() {
MethodAccessor accessor = damageTypeFromNms;
if (accessor != null) return accessor;
synchronized (BukkitConverters.class) {
accessor = damageTypeFromNms;
if (accessor != null) return accessor;
try {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getDamageTypeClass())
.returnTypeExact(org.bukkit.damage.DamageType.class)
.requireModifier(Modifier.STATIC)
.build()));
} catch (IllegalArgumentException exception) {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getHolderClass())
.returnTypeExact(org.bukkit.damage.DamageType.class)
.requireModifier(Modifier.STATIC)
.build()));
damageTypeFromNmsAcceptsHolder = true;
}
damageTypeFromNms = accessor;
return accessor;
}
}
private static Object getHolderValue(Object holder) {
MethodAccessor accessor = damageTypeHolderValue;
if (accessor == null) {
synchronized (BukkitConverters.class) {
accessor = damageTypeHolderValue;
if (accessor == null) {
accessor = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(MinecraftReflection.getHolderClass(), false)
.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.banModifier(Modifier.STATIC)
.returnTypeExact(Object.class)
.build()));
damageTypeHolderValue = accessor;
}
}
}
return accessor.invoke(holder);
}
private static Object getDamageTypeHolder(Object generic) {
Object holder = damageTypeHolders.get(generic);
if (holder != null) {
return holder;
}
MethodAccessor accessor = resolveDamageTypeToNms();
if (!damageTypeToNmsReturnsHolder) {
throw new IllegalStateException("CraftDamageType accepts holders but does not produce them");
}
for (org.bukkit.damage.DamageType damageType : org.bukkit.Registry.DAMAGE_TYPE) {
Object candidate = accessor.invoke(null, damageType);
Object candidateValue = getHolderValue(candidate);
damageTypeHolders.putIfAbsent(candidateValue, candidate);
if (Objects.equal(candidateValue, generic)) {
return candidate;
}
}
throw new IllegalArgumentException("Unable to resolve a holder for damage type " + generic);
}
public static EquivalentConverter<org.bukkit.damage.DamageType> getDamageTypeConverter() {
return ignoreNull(new EquivalentConverter<org.bukkit.damage.DamageType>() {
@Override
public Class<org.bukkit.damage.DamageType> getSpecificType() {
return org.bukkit.damage.DamageType.class;
}
@Override
public Object getGeneric(org.bukkit.damage.DamageType specific) {
Object generic = resolveDamageTypeToNms().invoke(null, specific);
if (damageTypeToNmsReturnsHolder) {
Object value = getHolderValue(generic);
damageTypeHolders.putIfAbsent(value, generic);
return value;
}
return generic;
}
@Override
public org.bukkit.damage.DamageType getSpecific(Object generic) {
MethodAccessor accessor = resolveDamageTypeFromNms();
if (damageTypeFromNmsAcceptsHolder) {
generic = getDamageTypeHolder(generic);
}
return (org.bukkit.damage.DamageType) accessor.invoke(null, generic);
}
});
}
private static Class<?> dimensionManager;
private static FauxEnumConverter<Dimension> dimensionConverter;
private static FauxEnumConverter<DimensionImpl> dimensionImplConverter;
@@ -18,6 +18,8 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -33,8 +35,8 @@ import net.bytebuddy.matcher.ElementMatchers;
* by default. Constructing a new wrapper instance will give out a handle to a completely new implemented type, that
* allows to set a key and some kind of data of any choice.
* <p>
* Note that constructing this class from a generic handle is only possible for the spigot-specific UnknownPayload type.
* All other payloads should be accessed via a structure modifier directly.
* Constructing this class from a generic handle requires either retained raw byte data, such as {@code DiscardedPayload},
* or a writable serialization method on the concrete payload type.
*
* @author Pasqual Koschmieder
*/
@@ -46,7 +48,16 @@ public final class CustomPacketPayloadWrapper {
private static final ConstructorAccessor PAYLOAD_WRAPPER_CONSTRUCTOR;
private static final MethodAccessor GET_ID_PAYLOAD_METHOD;
private static final MethodAccessor GET_ID_HOLDER_PAYLOAD_METHOD;
private static final MethodAccessor GET_ID_PAYLOAD_HOLDER_METHOD;
private static final MethodAccessor SERIALIZE_PAYLOAD_METHOD;
private static final Class<?> PAYLOAD_SERIALIZER_CLASS;
private static final ConcurrentHashMap<Class<?>, Optional<MethodAccessor>> SERIALIZE_PAYLOAD_METHODS =
new ConcurrentHashMap<>();
/** True when running on the nested-ID API where DiscardedPayload is used instead of the ByteBuddy proxy. */
private static final boolean USE_DISCARDED_PAYLOAD;
private static final boolean DISCARDED_PAYLOAD_USES_BYTE_ARRAY;
private static final EquivalentConverter<CustomPacketPayloadWrapper> CONVERTER;
@@ -55,22 +66,73 @@ public final class CustomPacketPayloadWrapper {
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
Method getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(MINECRAFT_KEY_CLASS)
.parameterCount(0)
.build());
GET_ID_PAYLOAD_METHOD = Accessors.getMethodAccessor(getPayloadId);
Method getPayloadId = null;
Method getPayloadIdHolder = null;
Method getPayloadIdFromHolder = null;
boolean useDiscardedPayload = false;
try {
getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(MINECRAFT_KEY_CLASS)
.parameterCount(0)
.build());
} catch (IllegalArgumentException ignored) {
// Paper 26: the payload exposes its identifier through one intermediate record.
Method[] accessors = findChainedIdAccessors(CUSTOM_PACKET_PAYLOAD_CLASS, MINECRAFT_KEY_CLASS);
getPayloadIdHolder = accessors[0];
getPayloadIdFromHolder = accessors[1];
useDiscardedPayload = true;
}
GET_ID_PAYLOAD_METHOD = getPayloadId != null ? Accessors.getMethodAccessor(getPayloadId) : null;
GET_ID_HOLDER_PAYLOAD_METHOD = getPayloadIdHolder != null
? Accessors.getMethodAccessor(getPayloadIdHolder) : null;
GET_ID_PAYLOAD_HOLDER_METHOD = getPayloadIdFromHolder != null
? Accessors.getMethodAccessor(getPayloadIdFromHolder) : null;
USE_DISCARDED_PAYLOAD = useDiscardedPayload;
Method serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterDerivedOf(ByteBuf.class, 0)
.build());
SERIALIZE_PAYLOAD_METHOD = Accessors.getMethodAccessor(serializePayloadData);
Method serializePayloadData = null;
try {
serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterDerivedOf(ByteBuf.class, 0)
.build());
} catch (IllegalArgumentException ignored) {
// Paper 26 declares the write method on each concrete payload instead.
}
SERIALIZE_PAYLOAD_METHOD = serializePayloadData != null
? Accessors.getMethodAccessor(serializePayloadData) : null;
if (serializePayloadData == null) {
ByteBuf buffer = Unpooled.buffer();
try {
PAYLOAD_SERIALIZER_CLASS = MinecraftReflection.getPacketDataSerializer(buffer).getClass();
} finally {
if (buffer.refCnt() > 0) {
buffer.release();
}
}
} else {
PAYLOAD_SERIALIZER_CLASS = null;
}
Constructor<?> payloadWrapperConstructor = makePayloadWrapper();
Constructor<?> payloadWrapperConstructor;
boolean discardedPayloadUsesByteArray = false;
if (useDiscardedPayload) {
Class<?> discardedPayloadClass = MinecraftReflection.getMinecraftClass(
"network.protocol.common.custom.DiscardedPayload");
try {
payloadWrapperConstructor = discardedPayloadClass.getConstructor(
MINECRAFT_KEY_CLASS, byte[].class);
discardedPayloadUsesByteArray = true;
} catch (NoSuchMethodException exception) {
payloadWrapperConstructor = discardedPayloadClass.getConstructor(
MINECRAFT_KEY_CLASS, ByteBuf.class);
}
} else {
payloadWrapperConstructor = makePayloadWrapper();
}
DISCARDED_PAYLOAD_USES_BYTE_ARRAY = discardedPayloadUsesByteArray;
PAYLOAD_WRAPPER_CONSTRUCTOR = Accessors.getConstructorAccessor(payloadWrapperConstructor);
CONVERTER = new EquivalentConverter<CustomPacketPayloadWrapper>() {
@@ -94,6 +156,41 @@ public final class CustomPacketPayloadWrapper {
}
}
private static Method[] findChainedIdAccessors(Class<?> payloadClass, Class<?> keyClass) {
Method payloadAccessor = null;
Method keyAccessor = null;
FuzzyMethodContract keyAccessorContract = FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(keyClass)
.parameterCount(0)
.build();
for (Method candidate : payloadClass.getMethods()) {
if (Modifier.isStatic(candidate.getModifiers()) || candidate.getParameterCount() != 0) {
continue;
}
Method candidateKeyAccessor;
try {
candidateKeyAccessor = FuzzyReflection.fromClass(candidate.getReturnType(), true)
.getMethod(keyAccessorContract);
} catch (IllegalArgumentException ignored) {
continue;
}
if (payloadAccessor != null) {
throw new IllegalArgumentException("Multiple CustomPacketPayload identifier accessors found");
}
payloadAccessor = candidate;
keyAccessor = candidateKeyAccessor;
}
if (payloadAccessor == null) {
throw new IllegalArgumentException("Unable to find a CustomPacketPayload identifier accessor");
}
return new Method[]{payloadAccessor, keyAccessor};
}
private static Constructor<?> makePayloadWrapper() throws Exception {
return new ByteBuddy()
.subclass(Object.class)
@@ -116,6 +213,64 @@ public final class CustomPacketPayloadWrapper {
.getConstructor(MinecraftReflection.getMinecraftKeyClass(), byte[].class);
}
private static Optional<MethodAccessor> findSerializePayloadMethod(Class<?> payloadClass) {
Method serializeMethod = null;
for (Method candidate : FuzzyReflection.fromClass(payloadClass, true).getMethods()) {
if (Modifier.isStatic(candidate.getModifiers())
|| candidate.getReturnType() != void.class
|| candidate.getParameterCount() != 1) {
continue;
}
Class<?> parameterClass = candidate.getParameterTypes()[0];
if (parameterClass == Object.class || !parameterClass.isAssignableFrom(PAYLOAD_SERIALIZER_CLASS)) {
continue;
}
if (serializeMethod != null) {
throw new IllegalArgumentException("Multiple CustomPacketPayload serialization methods found on "
+ payloadClass.getName());
}
serializeMethod = candidate;
}
if (serializeMethod == null) {
return Optional.empty();
}
serializeMethod.setAccessible(true);
return Optional.of(Accessors.getMethodAccessor(serializeMethod));
}
private static MethodAccessor getSerializePayloadMethod(Object payload) {
if (SERIALIZE_PAYLOAD_METHOD != null) {
return SERIALIZE_PAYLOAD_METHOD;
}
return SERIALIZE_PAYLOAD_METHODS
.computeIfAbsent(payload.getClass(), CustomPacketPayloadWrapper::findSerializePayloadMethod)
.orElse(null);
}
private static byte[] serializePayload(Object payload) {
MethodAccessor serializeMethod = getSerializePayloadMethod(payload);
if (serializeMethod == null) {
throw new UnsupportedOperationException(
"Cannot extract raw custom payload bytes from " + payload.getClass().getName()
+ ": payload has no byte array, ByteBuf, or serialization method");
}
ByteBuf buffer = Unpooled.buffer();
try {
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
serializeMethod.invoke(payload, serializer);
byte[] result = new byte[buffer.readableBytes()];
buffer.getBytes(buffer.readerIndex(), result);
return result;
} finally {
if (buffer.refCnt() > 0) {
buffer.release();
}
}
}
// ====== api methods ======
/**
@@ -171,29 +326,39 @@ public final class CustomPacketPayloadWrapper {
* @return a wrapper holding the minecraft key and payload of the given custom payload instance.
*/
public static CustomPacketPayloadWrapper fromUnknownPayload(Object payload) {
Object messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
MinecraftKey id = MinecraftKey.getConverter().getSpecific(messageId);
MinecraftKey id = getPayloadId(payload);
// we read and retain the underlying buffer in case the class uses a buffer to store the data
// this way, when passing the packet to further handling, the buffer is not released and can be re-used
StructureModifier<Object> modifier = new StructureModifier<>(payload.getClass()).withTarget(payload);
byte[] messagePayload = modifier.withType(ByteBuf.class).optionRead(0)
.map(buffer -> {
ByteBuf buf = (ByteBuf) buffer;
byte[] data = StreamSerializer.getDefault().getBytesAndRelease(buf.markReaderIndex().retain());
buf.resetReaderIndex();
return data;
})
.orElseGet(() -> {
ByteBuf buffer = Unpooled.buffer();
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
SERIALIZE_PAYLOAD_METHOD.invoke(payload, serializer);
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
});
byte[] messagePayload = (byte[]) modifier.withType(byte[].class).readSafely(0);
if (messagePayload != null) {
messagePayload = messagePayload.clone();
} else {
messagePayload = modifier.withType(ByteBuf.class).optionRead(0)
.map(buffer -> {
ByteBuf buf = (ByteBuf) buffer;
byte[] data = StreamSerializer.getDefault().getBytesAndRelease(buf.markReaderIndex().retain());
buf.resetReaderIndex();
return data;
})
.orElseGet(() -> serializePayload(payload));
}
return new CustomPacketPayloadWrapper(messagePayload, id);
}
private static MinecraftKey getPayloadId(Object payload) {
Object messageId;
if (GET_ID_PAYLOAD_METHOD != null) {
messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
} else {
Object idHolder = GET_ID_HOLDER_PAYLOAD_METHOD.invoke(payload);
messageId = GET_ID_PAYLOAD_HOLDER_METHOD.invoke(idHolder);
}
return MinecraftKey.getConverter().getSpecific(messageId);
}
/**
* Get the generic id of the wrapped message id.
*
@@ -230,6 +395,13 @@ public final class CustomPacketPayloadWrapper {
* @return a new payload wrapper instance using the provided message id and payload.
*/
public Object newHandle() {
if (USE_DISCARDED_PAYLOAD) {
if (DISCARDED_PAYLOAD_USES_BYTE_ARRAY) {
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), this.payload);
}
ByteBuf buf = Unpooled.copiedBuffer(this.payload);
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), buf);
}
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), this.payload);
}
@@ -11,6 +11,10 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLogger;
@@ -23,10 +27,6 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
/**
* Represents a generic enum converter.
* @author Kristian
@@ -36,6 +36,12 @@ public abstract class EnumWrappers {
public enum ClientCommand {
PERFORM_RESPAWN,
REQUEST_STATS,
REQUEST_GAMERULE_VALUES,
/**
* @deprecated Removed in 26.1
*/
@Deprecated
OPEN_INVENTORY_ACHIEVEMENT
}
@@ -140,6 +146,8 @@ public abstract class EnumWrappers {
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
UPDATE_LIST_ORDER,
UPDATE_HAT,
/**
* @deprecated Removed in 1.19.3
*/
@@ -178,7 +186,9 @@ public abstract class EnumWrappers {
DROP_ALL_ITEMS,
DROP_ITEM,
RELEASE_USE_ITEM,
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND");
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND"),
STAB,
CHANGE_DESTROY_DIRECTION;
final String[] aliases;
PlayerDigType(String... aliases) {
@@ -191,8 +201,18 @@ public abstract class EnumWrappers {
}
public enum PlayerAction implements AliasedEnum {
/**
* @deprecated Removed in 1.21.6
*/
@Deprecated
START_SNEAKING("PRESS_SHIFT_KEY"),
/**
* @deprecated Removed in 1.21.6
*/
@Deprecated
STOP_SNEAKING("RELEASE_SHIFT_KEY"),
STOP_SLEEPING,
START_SPRINTING,
STOP_SPRINTING,
@@ -332,7 +352,8 @@ public abstract class EnumWrappers {
NEUTRAL("neutral"),
PLAYERS("player"),
AMBIENT("ambient"),
VOICE("voice");
VOICE("voice"),
UI("ui");
private static final Map<String, SoundCategory> LOOKUP;
static {
@@ -364,7 +385,8 @@ public abstract class EnumWrappers {
LEGS,
CHEST,
HEAD,
BODY
BODY,
SADDLE
}
public enum Hand {
@@ -623,6 +645,86 @@ public abstract class EnumWrappers {
}
}
/**
* Represents the client's intentions when connecting to the server. Previously,
* the game utilized the {@code EnumProtocol}, which included the additional states
* HANDSHAKE and PLAY. These states are not incorporated in the ClientIntent enum
* as they were never valid values for client intent under the current or
* past implementations.
*
* @since 1.20.5
*/
public enum ClientIntent {
STATUS,
LOGIN,
TRANSFER;
}
public enum TeamCollisionRule {
ALWAYS("always"),
NEVER("never"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam");
public static TeamCollisionRule fromName(String name) {
for (TeamCollisionRule value : values()) {
if (value.name.equals(name)) {
return value;
}
}
throw new IllegalArgumentException("Unknown team collision rule: " + name);
}
private final String name;
TeamCollisionRule(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public enum TeamVisibility {
ALWAYS("always"),
NEVER("never"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
public static TeamVisibility fromName(String name) {
for (TeamVisibility value : values()) {
if (value.name.equals(name)) {
return value;
}
}
throw new IllegalArgumentException("Unknown team visibility: " + name);
}
private final String name;
TeamVisibility(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public enum HeightmapType {
WORLD_SURFACE_WG,
WORLD_SURFACE,
OCEAN_FLOOR_WG,
OCEAN_FLOOR,
MOTION_BLOCKING,
MOTION_BLOCKING_NO_LEAVES,
}
private static Class<?> PROTOCOL_CLASS = null;
private static Class<?> CLIENT_COMMAND_CLASS = null;
private static Class<?> CHAT_VISIBILITY_CLASS = null;
@@ -647,7 +749,12 @@ public abstract class EnumWrappers {
private static Class<?> DISPLAY_SLOT_CLASS = null;
private static Class<?> RENDER_TYPE_CLASS = null;
private static Class<?> CHAT_FORMATTING_CLASS = null;
private static Class<?> CLIENT_INTENT_CLASS = null;
private static Class<?> TEAM_COLLISION_RULE_CLASS = null;
private static Class<?> TEAM_VISIBILITY_CLASS = null;
private static Class<?> HEIGHTMAP_TYPE_CLASS = null;
private static boolean INITIALIZING = false;
private static boolean INITIALIZED = false;
private static Map<Class<?>, EquivalentConverter<?>> FROM_NATIVE = new HashMap<>();
private static Map<Class<?>, EquivalentConverter<?>> FROM_WRAPPER = new HashMap<>();
@@ -660,116 +767,150 @@ public abstract class EnumWrappers {
if (INITIALIZED)
return;
INITIALIZED = true;
synchronized (EnumWrappers.class) {
// Recheck initialization status inside the lock
if (INITIALIZING || INITIALIZED)
return;
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
// Prevent circular calls to initialize during initialization
// (certain methods below indirectly call initialize again)
INITIALIZING = true;
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
CHAT_VISIBILITY_CLASS = MinecraftReflection.getMinecraftClass("world.entity.player.EnumChatVisibility", "world.entity.player.ChatVisibility", "world.entity.player.ChatVisiblity"); // Some versions have a typo
} else {
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
}
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
try {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
} catch (Exception ex) {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
}
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
CHAT_VISIBILITY_CLASS = MinecraftReflection.getMinecraftClass("world.entity.player.EnumChatVisibility", "world.entity.player.ChatVisibility", "world.entity.player.ChatVisiblity"); // Some versions have a typo
} else {
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
}
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
} else {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
}
try {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
} catch (Exception ex) {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
}
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
} else {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
}
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
}
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
// the particle enum was removed in 1.13, later packets may contain unrelated enums
if (!MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
}
try {
// TODO enum names are more stable than their packet associations
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
} catch (Exception ex) {
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory", "sounds.SoundSource");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
// In 1.17 the hand and use action class is no longer a field in the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
try {
// TODO enum names are more stable than their packet associations
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
} catch (Exception ex) {
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
}
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
// In 1.17 the hand and use action class is no longer a field in the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
} else {
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
}
if (MinecraftVersion.v26_1.atOrAbove()) {
// TODO
} else {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
} else {
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
}
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
}
} else {
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
}
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot");
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
} else {
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
}
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot");
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
associate(HAND_CLASS, Hand.class, getHandConverter());
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter());
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
CLIENT_INTENT_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
TEAM_COLLISION_RULE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.ScoreboardTeamBase$EnumTeamPush" /* Spigot Mapping */,
"world.scores.Team$CollisionRule" /* Mojang Mapping */);
TEAM_VISIBILITY_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility" /* Spigot Mapping */,
"world.scores.Team$Visibility" /* Mojang Mapping */);
HEIGHTMAP_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.level.levelgen.HeightMap$Type" /* Spigot Mapping */,
"world.level.levelgen.Heightmap$Types" /* Mojang Mapping */);
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
associate(HAND_CLASS, Hand.class, getHandConverter());
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter());
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
associate(CLIENT_INTENT_CLASS, ClientIntent.class, getClientIntentConverter());
associate(TEAM_COLLISION_RULE_CLASS, TeamCollisionRule.class, getTeamCollisionRuleConverter());
associate(TEAM_VISIBILITY_CLASS, TeamVisibility.class, getTeamVisibilityConverter());
associate(HEIGHTMAP_TYPE_CLASS, HeightmapType.class, getHeightmapTypeConverter());
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
}
INITIALIZED = true;
}
}
@@ -933,6 +1074,26 @@ public abstract class EnumWrappers {
return CHAT_FORMATTING_CLASS;
}
public static Class<?> getClientIntentClass() {
initialize();
return CLIENT_INTENT_CLASS;
}
public static Class<?> getTeamCollisionRuleClass() {
initialize();
return TEAM_COLLISION_RULE_CLASS;
}
public static Class<?> getTeamVisibilityClass() {
initialize();
return TEAM_VISIBILITY_CLASS;
}
public static Class<?> getHeightmapTypeClass() {
initialize();
return HEIGHTMAP_TYPE_CLASS;
}
// Get the converters
public static EquivalentConverter<Protocol> getProtocolConverter() {
return new EnumConverter<>(getProtocolClass(), Protocol.class);
@@ -1026,6 +1187,22 @@ public abstract class EnumWrappers {
return new EnumConverter<>(getChatFormattingClass(), ChatFormatting.class);
}
public static EquivalentConverter<ClientIntent> getClientIntentConverter() {
return new EnumConverter<>(getClientIntentClass(), ClientIntent.class);
}
public static EquivalentConverter<TeamCollisionRule> getTeamCollisionRuleConverter() {
return new EnumConverter<>(getTeamCollisionRuleClass(), TeamCollisionRule.class);
}
public static EquivalentConverter<TeamVisibility> getTeamVisibilityConverter() {
return new EnumConverter<>(getTeamVisibilityClass(), TeamVisibility.class);
}
public static EquivalentConverter<HeightmapType> getHeightmapTypeConverter() {
return new EnumConverter<>(getHeightmapTypeClass(), HeightmapType.class);
}
/**
* @since 1.13+
* @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found)
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more