Compare commits

..
58 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
119 changed files with 5007 additions and 857 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.
+4 -2
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
java-version: '25'
cache: 'gradle'
- name: Run gradle build lifecycle
@@ -27,4 +27,6 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ProtocolLib
path: build/libs/ProtocolLib.jar
path: |
build/libs/ProtocolLib.jar
build/libs/ProtocolLib-Spigot.jar
+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 }}
+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.
+57 -10
View File
@@ -14,15 +14,19 @@ lead to more subtle bugs.
* [Spigot Page](https://spigotmc.org/resources/protocollib.1997/)
* [Releases](https://github.com/dmulloy2/ProtocolLib/releases)
* [JavaDoc](https://dmulloy2.net/ProtocolLib/javadoc/)
* [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
@@ -65,6 +69,48 @@ dependencies {
}
```
#### 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
@@ -91,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);
}
}
}
+36 -71
View File
@@ -1,26 +1,22 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import io.github.patrick.gradle.remapper.RemapTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
`maven-publish`
`signing`
id("com.gradleup.shadow") version "9.0.0-beta13"
id("io.github.patrick.remapper") version "1.4.2"
id("com.vanniktech.maven.publish") version "0.34.0"
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 = "1.21.8"
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val buildNumber = System.getenv("BUILD_NUMBER") ?: ""
val isJenkins = buildNumber.isNotEmpty()
val isJitPack = System.getenv("JITPACK")?.equals("true", ignoreCase = true) ?: false
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
repositories {
if (!isJenkins) {
if (!isCI) {
mavenLocal()
}
@@ -45,75 +41,35 @@ repositories {
}
dependencies {
implementation("net.bytebuddy:byte-buddy:1.17.5")
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:remapped-mojang")
compileOnly("io.netty:netty-all:4.0.23.Final")
/*
* TODO(fix): once you update kyori:adventure-text-serializer-gson please uncomment the TODO in
* com.comphenix.protocol.wrappers.WrappedComponentStyleTest if the following issue got fixed:
* https://github.com/KyoriPowered/adventure/issues/1194
*/
compileOnly("net.kyori:adventure-text-serializer-gson:4.21.0")
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: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:${mcVersion}-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")
compileOnly("commons-lang:commons-lang:2.6")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
sourceSets {
test {
java.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
}
tasks {
processResources {
val fullVersion = if (isSnapshot && isJenkins) "${version}-${buildNumber}" else version
eachFile {
expand("version" to fullVersion)
}
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
dependsOn(":paper:test")
}
shadowJar {
dependencies {
include(dependency("net.bytebuddy:byte-buddy:.*"))
}
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes(
"paperweight-mappings-namespace" to "mojang"
)
}
archiveFileName = "ProtocolLib.jar"
}
remap {
dependsOn("shadowJar")
inputTask.set(getByName<ShadowJar>("shadowJar"))
version.set(mcVersion)
action.set(RemapTask.Action.MOJANG_TO_SPIGOT)
}
assemble {
dependsOn("remap")
check {
dependsOn(":paper:check", ":spigot:check")
}
javadoc {
@@ -122,12 +78,21 @@ tasks {
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()
signAllPublications()
if (!isSnapshot && !isJitPack) {
signAllPublications()
}
coordinates("$group", project.name, "$version")
+2 -1
View File
@@ -1,4 +1,5 @@
version=5.4.0-SNAPSHOT
version=5.5.0-SNAPSHOT
mcVersion=26.3
mavenCentralUsername=
mavenCentralPassword=
signing.keyId=
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);
}
@@ -133,114 +133,124 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
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_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1B, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1C, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1D, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1E, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x1F, "EntityPositionSync");
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 MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x30, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x31, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x32, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x33, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x34, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x35, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x36, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x37, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x38, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3D, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3E, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3F, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x41, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x42, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x43, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x44, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x45, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x46, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x47, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x48, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x49, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x4A, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x4B, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x4C, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x4D, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x4E, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x4F, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x50, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x51, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x52, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x53, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x54, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x55, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x56, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x57, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x58, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x59, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x5A, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5B, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x5C, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x5D, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x5E, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x60, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x61, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x62, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x63, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x64, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x65, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x66, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x67, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x68, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x69, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x6A, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x6B, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x6C, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x6D, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x6E, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x6F, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x70, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x71, "StoreCookie");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x72, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x73, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x74, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x75, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x76, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x77, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x78, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x79, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x7A, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x7B, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x7C, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x7D, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x7E, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x7F, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x80, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x81, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x82, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x83, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x84, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x85, "ShowDialog");
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
@@ -416,7 +426,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 232, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
private static final Server INSTANCE = new Server();
@@ -439,71 +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 SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x02, "SelectBundleItem");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x03, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeGameMode");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x06, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x08, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x09, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0A, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0B, "ClientCommand", "CPacketClientStatus");
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientTickEnd");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0E, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0F, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x10, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x14, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x15, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x17, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x18, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x19, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1A, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1B, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1C, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1D, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x21, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x23, "PickItemFromBlock");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromEntity", "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x25, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x26, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x27, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerLoaded");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2C, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2D, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x2E, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x2F, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x30, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x31, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x32, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x33, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x34, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x35, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x36, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x37, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x38, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x39, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3A, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3B, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x3C, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x3D, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType TEST_INSTANCE_BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x3E, "TestInstanceBlockAction");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x3F, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x40, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x41, "CustomClickAction");
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
@@ -523,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
*/
@@ -704,15 +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 CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x11, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x12, "ShowDialog");
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
@@ -745,6 +771,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
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));
@@ -35,12 +35,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.21.8";
public static final String MAXIMUM_MINECRAFT_VERSION = "26.3";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.21.8) 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 = "2025-07-17";
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();
}
}
@@ -27,6 +27,7 @@ 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;
@@ -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
@@ -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.");
@@ -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;
@@ -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;
}
@@ -55,6 +55,8 @@ 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;
@@ -64,6 +66,7 @@ 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;
@@ -597,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());
}
/**
@@ -1165,9 +1166,35 @@ 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()
@@ -1297,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
@@ -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;
}
@@ -152,12 +150,10 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return lastSeen;
}
@Override
public Location getRespawnLocation() {
return null;
}
@Override
public Location getLocation() {
return null;
}
@@ -221,42 +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;
}
@Override
public @NotNull PlayerProfile getPlayerProfile() {
return null;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@@ -283,17 +271,14 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
@@ -330,7 +315,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
@@ -349,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) {
@@ -359,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];
@@ -43,6 +43,7 @@ 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;
@@ -151,29 +152,20 @@ public class PacketFilterManager implements ListenerManager, 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.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
});
}
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
}
@Override
@@ -200,34 +192,40 @@ public class PacketFilterManager implements ListenerManager, 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.invokeInboundPacketListeners(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.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
});
}
// post to the player inject, reset our cancel state change
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
}
@Override
@@ -428,14 +426,20 @@ public class PacketFilterManager implements ListenerManager, 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) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
@@ -516,12 +520,25 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
this.postPacketToListeners(this.outboundListeners, event, true);
}
}
private boolean requiresMainThread(PacketContainer packet) {
return this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread();
}
private void runMonitorListeners(PacketContainer packet, Runnable notifyMonitor) {
if (this.requiresMainThread(packet)) {
ProtocolLibrary.getScheduler().runTask(notifyMonitor);
} else {
notifyMonitor.run();
}
}
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,6 +1,7 @@
package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
@@ -67,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.
*
@@ -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,6 +1,7 @@
package com.comphenix.protocol.injector.netty.channel;
import java.net.SocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
@@ -67,6 +68,16 @@ final class EmptyInjector implements Injector {
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;
@@ -4,7 +4,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.SocketAddress;
import java.util.Map;
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;
@@ -29,7 +31,9 @@ import com.comphenix.protocol.injector.NetworkProcessor;
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;
@@ -99,6 +103,7 @@ public class NettyChannelInjector implements Injector {
// information about the player belonging to this injector
private String playerName;
private UUID playerUniqueId;
private Player player;
// lazy initialized fields, if we don't need them we don't bother about them
@@ -114,6 +119,10 @@ public class NettyChannelInjector implements Injector {
) {
// bukkit stuff
this.player = player;
if (player != null && !(player instanceof TemporaryPlayer)) {
this.playerName = player.getName();
this.playerUniqueId = player.getUniqueId();
}
// protocol lib stuff
this.errorReporter = errorReporter;
@@ -242,7 +251,7 @@ public class NettyChannelInjector implements Injector {
this.uninject();
// remove any outgoing references
this.channel.attr(INJECTOR).remove();
this.channel.attr(INJECTOR).set(null);
// cleanup
this.savedMarkers.clear();
@@ -289,8 +298,7 @@ public class NettyChannelInjector implements Injector {
this.ensureInEventLoop(() -> {
try {
// try to invoke the method, this should normally not fail
this.listenerInvoker.read(packet);
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)
@@ -344,14 +352,15 @@ public class NettyChannelInjector implements Injector {
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.player != null) {
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.player = Bukkit.getPlayerExact(this.playerName);
if (this.player != null) {
Player bukkitPlayer = Bukkit.getPlayerExact(this.playerName);
if (bukkitPlayer != null) {
this.player = bukkitPlayer;
this.injectionFactory.cacheInjector(this.player, this);
}
}
@@ -360,12 +369,23 @@ public class NettyChannelInjector implements Injector {
return this.player;
}
@Override
public String getPlayerName() {
return this.playerName;
}
@Override
public UUID getPlayerUniqueId() {
return this.playerUniqueId;
}
@Override
public void setPlayer(Player 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);
@@ -420,14 +440,19 @@ public class NettyChannelInjector implements Injector {
// 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 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;
}
this.playerUniqueId = uniqueId;
if (username != null) {
this.playerName = username;
this.injectionFactory.cacheInjector(username, this);
@@ -442,6 +467,22 @@ public class NettyChannelInjector implements Injector {
}
}
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
@@ -515,15 +556,16 @@ public class NettyChannelInjector implements Injector {
return action;
}
PacketContainer packetContainer = new PacketContainer(packetType, packet);
// ensure that we are on the main thread if we need to
if (this.listenerManager.hasMainThreadListener(packetType) && !Bukkit.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.sendClientboundPacket(packet, null, true));
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, marker, true));
return null;
}
// create event and invoke listeners
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player);
this.listenerManager.invokeOutboundPacketListeners(event);
@@ -553,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
@@ -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);
@@ -10,11 +10,13 @@ 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;
@@ -25,7 +27,7 @@ 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.
*/
final class NettyEventLoopProxy implements EventLoop {
final class NettyEventLoopProxy extends AbstractEventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
@@ -236,6 +238,15 @@ final 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);
@@ -251,6 +262,12 @@ final 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);
@@ -14,13 +14,11 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import io.netty.channel.ChannelHandlerContext;
/**
* This class facilitates the invocation of methods on the current packet listener.
* It attempts to execute the <code>send</code>, <code>read</code>, and <code>disconnect</code>
* methods and, 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.
* 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
@@ -43,7 +41,6 @@ public class PacketListenerInvoker {
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_READ = getNetworkManagerRead();
private static final MethodAccessor NETWORK_MANAGER_DISCONNECT = getNetworkManagerDisconnect();
private static final MethodAccessor NETWORK_MANAGER_PACKET_LISTENER = getNetworkManagerPacketListener();
@@ -117,15 +114,6 @@ public class PacketListenerInvoker {
return Accessors.getMethodAccessor(send);
}
private static MethodAccessor getNetworkManagerRead() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true);
Method read = networkManager
.getMethodByParameters("read", ChannelHandlerContext.class, MinecraftReflection.getPacketClass());
return Accessors.getMethodAccessor(read);
}
private static MethodAccessor getNetworkManagerDisconnect() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
@@ -190,7 +178,7 @@ public class PacketListenerInvoker {
}
/**
* Sends a packet using the current packet listener if available and valid; otherwise,
* 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.
@@ -204,15 +192,6 @@ public class PacketListenerInvoker {
}
}
/**
* Reads a packet directly using the network manager.
*
* @param packet The packet to be read.
*/
public void read(Object packet) {
NETWORK_MANAGER_READ.invoke(this.networkManager, null, packet);
}
/**
* Disconnects the player using the current packet listener if available and valid; otherwise,
* falls back to the network manager.
@@ -15,6 +15,7 @@ import com.comphenix.protocol.error.ErrorReporter;
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.internal.PlatformProvider;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
@@ -23,7 +24,9 @@ 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 {
@@ -36,7 +39,9 @@ public class NetworkManagerInjector {
private final InjectionFactory injectionFactory;
// netty handler
private final InjectionChannelInboundHandler injectionHandler;
private final InjectionChannelInitializer pipelineInjectorHandler;
private Runnable paperChannelInitializerCleanup;
// status of this injector
private boolean closed = false;
@@ -47,22 +52,35 @@ public class NetworkManagerInjector {
this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager);
// hooking netty handlers
InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler(
this.injectionHandler = new InjectionChannelInboundHandler(
this.errorReporter,
this.injectionFactory);
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
this.pipelineInjectorHandler = new InjectionChannelInitializer(
INBOUND_INJECT_HANDLER_NAME,
this.injectionHandler);
}
public Injector getInjector(Player player) {
return this.injectionFactory.fromPlayer(player);
}
public boolean isUsingPaperChannelInitializer() {
return this.paperChannelInitializerCleanup != null;
}
@SuppressWarnings("unchecked")
public void inject() {
if (this.closed || this.injected) {
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()
@@ -125,6 +143,23 @@ public class NetworkManagerInjector {
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;
@@ -153,6 +188,13 @@ public class NetworkManagerInjector {
// clear up
this.overriddenLists.clear();
this.injectionFactory.close();
try {
if (this.paperChannelInitializerCleanup != null) {
this.paperChannelInitializerCleanup.run();
}
} finally {
this.paperChannelInitializerCleanup = null;
this.injectionFactory.close();
}
}
}
@@ -412,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;
}
@@ -424,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.
*
@@ -19,6 +19,7 @@ package com.comphenix.protocol.injector.temporary;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -132,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
@@ -184,14 +194,16 @@ 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.
*
* @return A temporary player instance.
*/
@@ -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
@@ -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);
@@ -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;
}
}
@@ -57,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;
}
/**
@@ -96,6 +96,12 @@ public final class MinecraftProtocolVersion {
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;
}
@@ -553,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.
*
@@ -917,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");
}
/**
@@ -1114,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.
*
@@ -1413,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);
}
}
}
@@ -1476,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.
*
@@ -1680,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() {
@@ -1899,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,36 @@ 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
*/
@@ -169,7 +199,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
/**
* The latest release version of minecraft.
*/
public static final MinecraftVersion LATEST = v1_21_6;
public static final MinecraftVersion LATEST = v26_3;
// used when serializing
private static final long serialVersionUID = -8695133558996459770L;
@@ -326,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;
}
}
@@ -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);
}
@@ -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
}
@@ -180,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) {
@@ -796,7 +804,10 @@ public abstract class EnumWrappers {
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);
// 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);
}
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
@@ -805,7 +816,7 @@ public abstract class EnumWrappers {
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory", "sounds.SoundSource");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
@@ -821,13 +832,17 @@ public abstract class EnumWrappers {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
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());
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
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);
@@ -105,17 +105,22 @@ public class LegacyDataWatcher extends AbstractWrapper implements IDataWatcher {
*/
@Deprecated
public LegacyDataWatcher(List<WrappedWatchableObject> objects) {
this(newHandle(fakeEntity(), objects));
this(newHandle(fakeEntity()));
for (WrappedWatchableObject object : objects) {
WrappedDataWatcherObject watcherObject = MinecraftReflection.watcherObjectExists()
? object.getWatcherObject()
: WrappedDataWatcherObject.fromIndex(object.getIndex());
setObject(watcherObject, object, false);
}
}
private static Object newHandle(Object entity, List<WrappedWatchableObject> objects) {
private static Object newHandle(Object entity) {
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(),
MinecraftReflection.getArrayClass(MinecraftReflection.getDataWatcherItemClass()));
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass());
}
Object[] genericItems = new Object[0];// (Object[]) ITEMS_CONVERTER.getGeneric(objects);
return constructor.invoke(entity, genericItems);
return constructor.invoke(entity);
}
private static Object fakeEntity() {
@@ -0,0 +1,29 @@
package com.comphenix.protocol.wrappers;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
/**
* Mutable version of PropertyMap
* Credit to PaperMC: https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/io/papermc/paper/profile/MutablePropertyMap.java
*/
public class MutablePropertyMap extends PropertyMap {
private final Multimap<String, Property> properties = HashMultimap.create();
public MutablePropertyMap() {
super(ImmutableMultimap.of());
}
public MutablePropertyMap(final Multimap<String, Property> properties) {
this();
this.putAll(properties);
}
@Override
protected Multimap<String, Property> delegate() {
return this.properties;
}
}
@@ -45,7 +45,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
private static MethodAccessor TO_LEGACY_DATA;
private static MethodAccessor GET_BLOCK;
private static MethodAccessor BLOCK_FROM_MATERIAL;
private static MethodAccessor GET_BLOCK_DATA;
private static MethodAccessor DEFAULT_BLOCK_DATA;
private static MethodAccessor FROM_LEGACY_DATA;
private static MethodAccessor GET_HANDLE;
@@ -100,7 +100,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
.parameterCount(0)
.returnTypeExact(IBLOCK_DATA)
.build();
GET_BLOCK_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
DEFAULT_BLOCK_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData"));
contract = FuzzyMethodContract
@@ -131,7 +131,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
@Override
public void setType(Material material) {
Object block = BLOCK_FROM_MATERIAL.invoke(null, material);
setHandle(GET_BLOCK_DATA.invoke(block));
setHandle(DEFAULT_BLOCK_DATA.invoke(block));
}
@Override
@@ -141,7 +141,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
@Override
public void setTypeAndData(Material material, int data) {
setHandle(TO_LEGACY_DATA.invoke(null, material, (byte) data));
setHandle(FROM_LEGACY_DATA.invoke(null, material, (byte) data));
}
@Override
@@ -151,7 +151,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
private static WrappedBlockData createNewData(Material material) {
Object block = BLOCK_FROM_MATERIAL.invoke(null, material);
return new NewBlockData(GET_BLOCK_DATA.invoke(block));
return new NewBlockData(DEFAULT_BLOCK_DATA.invoke(block));
}
private static WrappedBlockData createNewData(Material material, int data) {
@@ -6,7 +6,6 @@ import java.util.Optional;
import com.comphenix.protocol.wrappers.codecs.WrappedCodec;
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.bukkit.ChatColor;
@@ -42,6 +41,7 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
private static ConstructorAccessor CONSTRUCT_TEXT_COMPONENT = null;
private static WrappedCodec CODEC;
private static WrappedDynamicOps REGISTRY_JSON_OPS;
static {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER, true);
@@ -49,6 +49,7 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
if (MinecraftVersion.v1_21_6.atOrAbove()) {
Field codecField = fuzzy.getFieldByType("CODEC", MinecraftReflection.getCodecClass());
CODEC = WrappedCodec.fromHandle(Accessors.getFieldAccessor(codecField).get(null));
REGISTRY_JSON_OPS = MinecraftRegistryAccess.createSerializationContext(WrappedDynamicOps.json(false));
} else if (MinecraftVersion.v1_20_5.atOrAbove()) {
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(String.class)
@@ -98,7 +99,7 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
private static Object serialize(Object handle) {
if (CODEC != null) {
Object jobj = CODEC.encode(handle, WrappedDynamicOps.json(false)).getOrThrow(JsonParseException::new);
Object jobj = CODEC.encode(handle, REGISTRY_JSON_OPS).getOrThrow(JsonParseException::new);
return jobj.toString();
}
@@ -111,7 +112,7 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
private static Object deserialize(String json) {
if (CODEC != null) {
return CODEC.parse(JsonParser.parseString(json), WrappedDynamicOps.json(false)).getOrThrow(JsonParseException::new);
return CODEC.parse(JsonParser.parseString(json), REGISTRY_JSON_OPS).getOrThrow(JsonParseException::new);
}
if (MinecraftVersion.v1_20_5.atOrAbove()) {
@@ -35,18 +35,23 @@ public class WrappedGameProfile extends AbstractWrapper {
GAME_PROFILE, String.class, String.class);
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class);
private static final ConstructorAccessor CREATE_UUID_STRING_PROPERTIES = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class, MinecraftReflection.getGameProfilePropertyMapClass());
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAccessorOrNull(
GAME_PROFILE, "id", String.class);
private static final MethodAccessor GET_ID = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getId");
private static final MethodAccessor GET_NAME = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getName");
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getProperties");
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "isComplete");
private static final MethodAccessor GET_ID = findMethodAccessor(GAME_PROFILE, "id", "getId");
private static final MethodAccessor GET_NAME = findMethodAccessor(GAME_PROFILE, "name", "getName");
private static final MethodAccessor GET_PROPERTIES = findMethodAccessor(
GAME_PROFILE, "properties", "getProperties");
private static final MethodAccessor IS_COMPLETE = findMethodAccessor(
GAME_PROFILE, "complete", "isComplete");
static MethodAccessor findMethodAccessor(Class<?> type, String methodName, String fallbackName) {
MethodAccessor accessor = Accessors.getMethodAccessorOrNull(type, methodName);
return accessor != null ? accessor : Accessors.getMethodAccessorOrNull(type, fallbackName);
}
// Fetching game profile
private static FieldAccessor PLAYER_PROFILE;
@@ -117,6 +122,51 @@ public class WrappedGameProfile extends AbstractWrapper {
this(parseUUID(id), name);
}
private static Object createHandle(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
if (CREATE_STRING_STRING != null) {
return CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name);
}
if (CREATE_UUID_STRING == null) {
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
}
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
return CREATE_UUID_STRING.invoke(uuid, name);
}
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
// it's not allowed to pass null for both, so we need to pre-check that
if (uuid == null && (name == null || name.isEmpty())) {
throw new IllegalArgumentException("Name and ID cannot both be blank");
}
// 1.21.9+ made PropertyMap's underlying map immutable, so we need to override it with a mutable map
if (MinecraftVersion.v1_21_9.atOrAbove()) {
return CREATE_UUID_STRING_PROPERTIES.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name,
convertPropertyMap(properties));
}
return CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name);
}
private static Object convertPropertyMap(Multimap<String, WrappedSignedProperty> properties) {
com.comphenix.protocol.wrappers.MutablePropertyMap map =
new com.comphenix.protocol.wrappers.MutablePropertyMap();
if (properties == null || properties.isEmpty()) {
return map;
}
for (String key : properties.keySet()) {
for (WrappedSignedProperty property : properties.get(key)) {
map.put(key, (com.mojang.authlib.properties.Property) property.getHandle());
}
}
return map;
}
/**
* Construct a new game profile with the given properties.
* <p>
@@ -127,24 +177,12 @@ public class WrappedGameProfile extends AbstractWrapper {
*/
public WrappedGameProfile(UUID uuid, String name) {
super(GAME_PROFILE);
setHandle(createHandle(uuid, name, null));
}
if (CREATE_STRING_STRING != null) {
setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name));
} else if (CREATE_UUID_STRING != null) {
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
// it's not allowed to pass null for both, so we need to pre-check that
if (uuid == null && (name == null || name.isEmpty())) {
throw new IllegalArgumentException("Name and ID cannot both be blank");
}
setHandle(CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name));
} else {
setHandle(CREATE_UUID_STRING.invoke(uuid, name));
}
} else {
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
}
public WrappedGameProfile(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
super(GAME_PROFILE);
setHandle(createHandle(uuid, name, properties));
}
/**
@@ -157,7 +195,12 @@ public class WrappedGameProfile extends AbstractWrapper {
if (handle == null)
return null;
return new WrappedGameProfile(handle);
WrappedGameProfile delegate = new WrappedGameProfile(handle);
if (MinecraftVersion.v1_21_9.atOrAbove()) {
return new WrappedGameProfile(delegate.getUUID(), delegate.getName(), delegate.getProperties());
} else {
return delegate;
}
}
/**
@@ -269,7 +312,15 @@ public class WrappedGameProfile extends AbstractWrapper {
if (result == null) {
Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle);
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(GuavaWrappers.getBukkitMultimap(properties)) {
Multimap inner;
if (MinecraftVersion.v1_21_9.atOrAbove()) {
inner = properties;
} else {
inner = GuavaWrappers.getBukkitMultimap(properties);
}
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(inner) {
@Override
protected Object toInner(WrappedSignedProperty outer) {
return outer.handle;
@@ -3,8 +3,11 @@ package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import com.comphenix.protocol.reflect.EquivalentConverter;
import org.jetbrains.annotations.Nullable;
@@ -49,11 +52,19 @@ public final class WrappedLevelChunkData {
static {
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(reflection.getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(MinecraftReflection.getPacketDataSerializerClass())
.parameterExactType(int.class)
.parameterExactType(int.class)
.build()));
if (MinecraftVersion.v26_3.atOrAbove()) {
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(reflection.getConstructor(FuzzyMethodContract.newBuilder()
.parameterExactType(Map.class)
.parameterExactType(byte[].class)
.parameterExactType(List.class)
.build()));
} else {
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(reflection.getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(MinecraftReflection.getPacketDataSerializerClass())
.parameterExactType(int.class)
.parameterExactType(int.class)
.build()));
}
BLOCK_ENTITIES_DATA_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(List.class)
.build()));
@@ -180,7 +191,12 @@ public final class WrappedLevelChunkData {
*/
@Deprecated
public static ChunkData fromValues(NbtCompound heightmapsTag, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
ChunkData data;
if (MinecraftVersion.v26_3.atOrAbove()) {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(Collections.emptyMap(), new byte[0], Collections.emptyList()));
} else {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
}
data.setHeightmapsTag(heightmapsTag);
data.setBuffer(buffer);
@@ -198,7 +214,12 @@ public final class WrappedLevelChunkData {
* @return a newly created wrapper
*/
public static ChunkData fromValues(Map<EnumWrappers.HeightmapType, long[]> heightmaps, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
ChunkData data;
if (MinecraftVersion.v26_3.atOrAbove()) {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(Collections.emptyMap(), new byte[0], Collections.emptyList()));
} else {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
}
data.setHeightmaps(heightmaps);
data.setBuffer(buffer);
@@ -433,18 +454,29 @@ public final class WrappedLevelChunkData {
static {
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
List<Field> posFields = reflection.getFieldList(FuzzyFieldContract.newBuilder().typeExact(int.class).build());
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, int.class, int.class,
MinecraftReflection.getBlockEntityTypeClass(), MinecraftReflection.getNBTCompoundClass());
PACKED_XZ_ACCESSOR = Accessors.getFieldAccessor(posFields.get(0));
Y_ACCESSOR = Accessors.getFieldAccessor(posFields.get(1));
if (MinecraftVersion.v26_3.atOrAbove()) {
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, byte.class, short.class,
MinecraftReflection.getBlockEntityTypeClass(), Optional.class);
PACKED_XZ_ACCESSOR = new IntegerFieldAccessor(Accessors.getFieldAccessor(HANDLE_TYPE, byte.class, true), Number::byteValue);
Y_ACCESSOR = new IntegerFieldAccessor(Accessors.getFieldAccessor(HANDLE_TYPE, short.class, true), Number::shortValue);
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(Optional.class)
.build()));
} else {
List<Field> posFields = reflection.getFieldList(FuzzyFieldContract.newBuilder().typeExact(int.class).build());
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, int.class, int.class,
MinecraftReflection.getBlockEntityTypeClass(), MinecraftReflection.getNBTCompoundClass());
PACKED_XZ_ACCESSOR = Accessors.getFieldAccessor(posFields.get(0));
Y_ACCESSOR = Accessors.getFieldAccessor(posFields.get(1));
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getNBTCompoundClass())
.build()));
}
TYPE_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getBlockEntityTypeClass())
.build()));
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getNBTCompoundClass())
.build()));
}
public BlockEntityInfo(Object handle) {
@@ -533,6 +565,9 @@ public final class WrappedLevelChunkData {
@Nullable
public NbtCompound getAdditionalData() {
Object tagHandle = TAG_ACCESSOR.get(handle);
if (MinecraftVersion.v26_3.atOrAbove()) {
tagHandle = ((Optional<Object>) tagHandle).orElse(null);
}
return tagHandle == null ? null : NbtFactory.fromNMSCompound(tagHandle);
}
@@ -543,7 +578,11 @@ public final class WrappedLevelChunkData {
* @param additionalData the additional data for this block entity, can be {@code null}
*/
public void setAdditionalData(@Nullable NbtCompound additionalData) {
TAG_ACCESSOR.set(handle, additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle());
if (MinecraftVersion.v26_3.atOrAbove()) {
TAG_ACCESSOR.set(handle, Optional.ofNullable(additionalData).map(data -> NbtFactory.fromBase(data).getHandle()));
} else {
TAG_ACCESSOR.set(handle, additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle());
}
}
/**
@@ -568,12 +607,46 @@ public final class WrappedLevelChunkData {
* @param additionalData An NBT-Tag containing additional information. Can be {@code null}.
*/
public static BlockEntityInfo fromValues(int sectionX, int sectionZ, int y, MinecraftKey typeKey, @Nullable NbtCompound additionalData) {
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
if (MinecraftVersion.v26_3.atOrAbove()) {
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
(byte) (sectionX << 4 | sectionZ),
(short) y,
REGISTRY.get(typeKey),
Optional.ofNullable(additionalData).map(data -> NbtFactory.fromBase(data).getHandle())
));
} else {
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
sectionX << 4 | sectionZ,
y,
REGISTRY.get(typeKey),
additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle()
));
));
}
}
}
private final static class IntegerFieldAccessor implements FieldAccessor {
private final FieldAccessor inner;
private final Function<Number, Object> toType;
public IntegerFieldAccessor(FieldAccessor inner, Function<Number, Object> toType) {
this.inner = inner;
this.toType = toType;
}
@Override
public Object get(Object instance) {
return ((Number) this.inner.get(instance)).intValue();
}
@Override
public void set(Object instance, Object value) {
this.inner.set(instance, this.toType.apply((Integer) value));
}
@Override
public Field getField() {
return this.inner.getField();
}
}
}
@@ -27,6 +27,15 @@ public class WrappedParticle<T> {
private static MethodAccessor toNMS;
private static MethodAccessor toCraftData;
private static final class ItemStackTemplateAccessor {
private static final Class<?> TYPE = MinecraftReflection.getMinecraftClass("world.item.ItemStackTemplate");
private static final MethodAccessor CREATE_ITEM_STACK = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(TYPE).getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(MinecraftReflection.getItemStackClass())
.parameterCount(0)
.build()));
}
private static void ensureMethods() {
if (toBukkit != null && toNMS != null) {
return;
@@ -176,6 +185,15 @@ public class WrappedParticle<T> {
}
private static Object getItem(Object handle) {
if (MinecraftVersion.v26_1.atOrAbove()) {
Object template = new StructureModifier<>(handle.getClass())
.withTarget(handle)
.withType(ItemStackTemplateAccessor.TYPE)
.read(0);
Object itemStack = ItemStackTemplateAccessor.CREATE_ITEM_STACK.invoke(template);
return BukkitConverters.getItemStackConverter().getSpecific(itemStack);
}
return new StructureModifier<>(handle.getClass())
.withTarget(handle)
.withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter())
@@ -0,0 +1,183 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import org.bukkit.util.Vector;
/**
* Wrapper around the NMS {@code PositionMoveRotation} record, introduced in Minecraft 1.21.2.
* <p>
* This type is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC}
* to represent combined position, movement delta and rotation.
* <p>
* Example usage:
* <pre>{@code
* WrappedPositionMoveRotation posRot = WrappedPositionMoveRotation.create(
* new Vector(x, y, z), // position
* new Vector(0, 0, 0), // deltaMovement
* yaw, // yRot
* pitch // xRot
* );
* packet.getPositionMoveRotation().write(0, posRot);
* }</pre>
*/
public class WrappedPositionMoveRotation extends AbstractWrapper {
private static Class<?> NMS_CLASS;
private static ConstructorAccessor CONSTRUCTOR;
private static StructureModifier<Object> BASE_MODIFIER;
private final StructureModifier<Object> modifier;
private WrappedPositionMoveRotation(Object handle) {
super(getNmsClass());
setHandle(handle);
this.modifier = getBaseModifier().withTarget(handle);
}
private static Class<?> getNmsClass() {
if (NMS_CLASS == null) {
NMS_CLASS = MinecraftReflection.getPositionMoveRotationClass();
}
return NMS_CLASS;
}
private static StructureModifier<Object> getBaseModifier() {
if (BASE_MODIFIER == null) {
BASE_MODIFIER = new StructureModifier<>(getNmsClass());
}
return BASE_MODIFIER;
}
private static ConstructorAccessor getConstructor() {
if (CONSTRUCTOR == null) {
CONSTRUCTOR = Accessors.getConstructorAccessor(
getNmsClass(),
MinecraftReflection.getVec3DClass(),
MinecraftReflection.getVec3DClass(),
float.class,
float.class
);
}
return CONSTRUCTOR;
}
/**
* Creates a new {@code WrappedPositionMoveRotation} from the given NMS handle.
*
* @param handle - the NMS {@code PositionMoveRotation} instance.
* @return the wrapper.
*/
public static WrappedPositionMoveRotation fromHandle(Object handle) {
return new WrappedPositionMoveRotation(handle);
}
/**
* Creates a new {@code WrappedPositionMoveRotation} with the given values.
*
* @param position - the absolute position.
* @param deltaMovement - the movement delta (velocity).
* @param yRot - the yaw rotation.
* @param xRot - the pitch rotation.
* @return a new wrapper instance.
*/
public static WrappedPositionMoveRotation create(Vector position, Vector deltaMovement, float yRot, float xRot) {
EquivalentConverter<Vector> conv = BukkitConverters.getVectorConverter();
Object handle = getConstructor().invoke(
conv.getGeneric(position),
conv.getGeneric(deltaMovement),
yRot,
xRot
);
return new WrappedPositionMoveRotation(handle);
}
/**
* Retrieves the absolute position component.
*
* @return the position as a Bukkit {@link Vector}.
*/
public Vector getPosition() {
return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
}
/**
* Sets the absolute position component.
*
* @param position - the new position.
*/
public void setPosition(Vector position) {
modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(0, position);
}
/**
* Retrieves the movement delta (velocity) component.
*
* @return the delta movement as a Bukkit {@link Vector}.
*/
public Vector getDeltaMovement() {
return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(1);
}
/**
* Sets the movement delta (velocity) component.
*
* @param deltaMovement - the new delta movement.
*/
public void setDeltaMovement(Vector deltaMovement) {
modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(1, deltaMovement);
}
/**
* Retrieves the yaw rotation (y-axis rotation).
*
* @return the yaw as a float.
*/
public float getYRot() {
StructureModifier<Float> floats = modifier.withType(float.class);
return floats.read(0);
}
/**
* Sets the yaw rotation (y-axis rotation).
*
* @param yRot - the new yaw value.
*/
public void setYRot(float yRot) {
modifier.withType(float.class).write(0, yRot);
}
/**
* Retrieves the pitch rotation (x-axis rotation).
*
* @return the pitch as a float.
*/
public float getXRot() {
StructureModifier<Float> floats = modifier.withType(float.class);
return floats.read(1);
}
/**
* Sets the pitch rotation (x-axis rotation).
*
* @param xRot - the new pitch value.
*/
public void setXRot(float xRot) {
modifier.withType(float.class).write(1, xRot);
}
/**
* Returns an {@link EquivalentConverter} that converts between {@code WrappedPositionMoveRotation}
* and the underlying NMS handle.
*
* @return the converter.
*/
public static EquivalentConverter<WrappedPositionMoveRotation> getConverter() {
return Converters.handle(AbstractWrapper::getHandle,
WrappedPositionMoveRotation::fromHandle, WrappedPositionMoveRotation.class);
}
}
@@ -0,0 +1,207 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Preconditions;
import org.bukkit.util.Vector;
/**
* Wrapper around the NMS {@code PositionPath} type, introduced in Minecraft 26.3.
* <p>
* Used by {@code ENTITY_POSITION_SYNC} to store the entity position. A path is either linear (a single position) or
* stepped (several positions reached at given ticks).
* <pre>{@code
* packet.getPositionPaths().write(0, WrappedPositionPath.linear(new Vector(x, y, z)));
* }</pre>
*/
public final class WrappedPositionPath extends AbstractWrapper {
private static Class<?> STEPPED_CLASS;
private static ConstructorAccessor LINEAR_CONSTRUCTOR;
private static ConstructorAccessor STEPPED_CONSTRUCTOR;
private static ConstructorAccessor STEP_CONSTRUCTOR;
private static StructureModifier<Object> LINEAR_MODIFIER;
private static StructureModifier<Object> STEPPED_MODIFIER;
private static StructureModifier<Object> STEP_MODIFIER;
private static volatile boolean initialized;
private WrappedPositionPath(Object handle) {
super(getPositionPathClass());
setHandle(handle);
}
private static Class<?> getPositionPathClass() {
return MinecraftReflection.getPositionPathClass()
.orElseThrow(() -> new UnsupportedOperationException("PositionPath requires Minecraft 26.3 or newer"));
}
private static void initialize() {
if (initialized) {
return;
}
Class<?> pathClass = getPositionPathClass();
Class<?> vec3Class = MinecraftReflection.getVec3DClass();
Class<?> linearClass = null;
Class<?> stepClass = null;
for (Class<?> nested : pathClass.getDeclaredClasses()) {
if (!pathClass.isAssignableFrom(nested)) {
continue;
}
Field steps = getListField(nested);
if (steps != null) {
STEPPED_CLASS = nested;
stepClass = (Class<?>) ((ParameterizedType) steps.getGenericType()).getActualTypeArguments()[0];
} else {
linearClass = nested;
}
}
Preconditions.checkState(linearClass != null && STEPPED_CLASS != null, "Unable to find PositionPath implementations");
LINEAR_CONSTRUCTOR = Accessors.getConstructorAccessor(linearClass, vec3Class);
STEPPED_CONSTRUCTOR = Accessors.getConstructorAccessor(STEPPED_CLASS, List.class);
STEP_CONSTRUCTOR = Accessors.getConstructorAccessor(stepClass, vec3Class, int.class);
LINEAR_MODIFIER = new StructureModifier<>(linearClass);
STEPPED_MODIFIER = new StructureModifier<>(STEPPED_CLASS);
STEP_MODIFIER = new StructureModifier<>(stepClass);
initialized = true;
}
private static Field getListField(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (List.class.equals(field.getType())) {
return field;
}
}
return null;
}
public static WrappedPositionPath fromHandle(Object handle) {
return new WrappedPositionPath(handle);
}
/**
* Creates a linear path.
*
* @param position - the absolute position.
* @return the path.
*/
public static WrappedPositionPath linear(Vector position) {
Preconditions.checkNotNull(position, "position cannot be NULL");
initialize();
return new WrappedPositionPath(LINEAR_CONSTRUCTOR.invoke(BukkitConverters.getVectorConverter().getGeneric(position)));
}
/**
* Creates a stepped path ending at the position of the last step.
*
* @param steps - the steps in order, must not be empty.
* @return the path.
*/
public static WrappedPositionPath stepped(List<Step> steps) {
Preconditions.checkArgument(steps != null && !steps.isEmpty(), "steps cannot be empty");
initialize();
EquivalentConverter<Vector> vectorConverter = BukkitConverters.getVectorConverter();
List<Object> handles = new ArrayList<>(steps.size());
for (Step step : steps) {
handles.add(STEP_CONSTRUCTOR.invoke(vectorConverter.getGeneric(step.getPosition()), step.getTickOffset()));
}
return new WrappedPositionPath(STEPPED_CONSTRUCTOR.invoke(Collections.unmodifiableList(handles)));
}
public boolean isStepped() {
initialize();
return STEPPED_CLASS.isInstance(handle);
}
/**
* @return the final position of the path.
*/
public Vector getEndPosition() {
StructureModifier<Object> modifier = isStepped() ? STEPPED_MODIFIER : LINEAR_MODIFIER;
return modifier.withTarget(handle)
.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter())
.read(0);
}
/**
* @return the steps of a stepped path.
* @throws IllegalStateException if the path is linear.
*/
public List<Step> getSteps() {
Preconditions.checkState(isStepped(), "Linear paths have no steps");
List<?> handles = STEPPED_MODIFIER.withTarget(handle).<List<?>>withType(List.class).read(0);
List<Step> steps = new ArrayList<>(handles.size());
for (Object stepHandle : handles) {
StructureModifier<Object> step = STEP_MODIFIER.withTarget(stepHandle);
Vector position = step.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
steps.add(new Step(position, step.<Integer>withType(int.class).read(0)));
}
return Collections.unmodifiableList(steps);
}
public static EquivalentConverter<WrappedPositionPath> getConverter() {
return Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle,
WrappedPositionPath::fromHandle, WrappedPositionPath.class));
}
public static final class Step {
private final Vector position;
private final int tickOffset;
public Step(Vector position, int tickOffset) {
this.position = Preconditions.checkNotNull(position, "position cannot be NULL").clone();
this.tickOffset = tickOffset;
}
public Vector getPosition() {
return position.clone();
}
public int getTickOffset() {
return tickOffset;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Step)) return false;
Step that = (Step) obj;
return tickOffset == that.tickOffset && position.equals(that.position);
}
@Override
public int hashCode() {
return Objects.hash(position, tickOffset);
}
@Override
public String toString() {
return "Step[position=" + position + ", tickOffset=" + tickOffset + "]";
}
}
}
@@ -16,15 +16,26 @@ import com.comphenix.protocol.utility.MinecraftReflection;
public class WrappedStatistic extends AbstractWrapper {
private static final Class<?> STATISTIC = MinecraftReflection.getStatisticClass();
private static final Class<?> STATISTIC_LIST = MinecraftReflection.getStatisticListClass();
private static final FieldAccessor GET_NAME_FIELD;
private static final MethodAccessor GET_NAME_METHOD;
static {
FieldAccessor[] nameFields = Accessors.getFieldAccessorArray(STATISTIC, String.class, true);
if (nameFields.length > 0) {
GET_NAME_FIELD = nameFields[0];
GET_NAME_METHOD = null;
} else {
GET_NAME_FIELD = null;
GET_NAME_METHOD = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(STATISTIC.getSuperclass(), true)
.getMethodByReturnTypeAndParameters("getName", String.class));
}
try {
FIND_STATISTICS = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(STATISTIC_LIST).getMethodByReturnTypeAndParameters(
"findStatistic", STATISTIC, new Class<?>[]{String.class}));
MAP_ACCESSOR = Accessors.getFieldAccessor(STATISTIC_LIST, Map.class, true);
GET_NAME = Accessors.getFieldAccessor(STATISTIC, String.class, true);
} catch (Exception ex) {
// TODO - find an alternative
}
@@ -32,7 +43,6 @@ public class WrappedStatistic extends AbstractWrapper {
private static MethodAccessor FIND_STATISTICS;
private static FieldAccessor MAP_ACCESSOR;
private static FieldAccessor GET_NAME;
private final String name;
@@ -40,7 +50,9 @@ public class WrappedStatistic extends AbstractWrapper {
super(STATISTIC);
setHandle(handle);
this.name = (String) GET_NAME.get(handle);
this.name = GET_NAME_FIELD != null
? (String) GET_NAME_FIELD.get(handle)
: (String) GET_NAME_METHOD.invoke(handle);
}
/**
@@ -3,6 +3,8 @@ package com.comphenix.protocol.wrappers;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -90,12 +92,26 @@ public class WrappedTeamParameters extends AbstractWrapper {
@NotNull
public EnumWrappers.ChatFormatting getColor() {
if (MinecraftVersion.v26_2.atOrAbove()) {
Optional<?> optional = modifier.<Optional<?>>withType(Optional.class).read(0);
Object teamColor = optional != null ? optional.orElse(null) : null;
if (teamColor == null) {
return EnumWrappers.ChatFormatting.RESET;
}
return EnumWrappers.ChatFormatting.valueOf(((Enum<?>) teamColor).name());
}
return modifier
.withType(EnumWrappers.getChatFormattingClass(), EnumWrappers.getChatFormattingConverter())
.read(0);
}
public int getOptions() {
if (MinecraftVersion.v26_2.atOrAbove()) {
return (byte) modifier.withType(byte.class).read(0);
}
return (int) modifier.withType(int.class).read(0);
}
@@ -210,9 +226,27 @@ public class WrappedTeamParameters extends AbstractWrapper {
wrapped.modifier.withType(String.class).writeSafely(1, collisionRule.toString());
}
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
wrapped.modifier.withType(int.class).write(0, options);
if (MinecraftVersion.v26_2.atOrAbove()) {
wrapped.modifier.withType(Optional.class).write(0, Optional.ofNullable(toNmsTeamColor(color)));
wrapped.modifier.withType(byte.class).write(0, (byte) options);
} else {
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
wrapped.modifier.withType(int.class).write(0, options);
}
return wrapped;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static Object toNmsTeamColor(EnumWrappers.ChatFormatting color) {
Class<?> teamColorClass = MinecraftReflection.getTeamColorClass()
.orElseThrow(() -> new IllegalStateException("TeamColor class doesn't exist on this server version"));
try {
return Enum.valueOf((Class) teamColorClass, color.name());
} catch (IllegalArgumentException ex) {
// formatting-only values (e.g. RESET) don't map to a team color
return null;
}
}
}
}
@@ -0,0 +1,237 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Preconditions;
/**
* Wrapper around the NMS {@code VecDelta} type, introduced in Minecraft 26.3.
* <p>
* Used by {@code REL_ENTITY_MOVE} and {@code REL_ENTITY_MOVE_LOOK} to store the position change. A delta is either
* linear (a single offset) or stepped (several offsets reached at given ticks). Offsets use the same encoding as the
* short fields of older versions.
* <pre>{@code
* packet.getVecDeltas().write(0, WrappedVecDelta.linear(deltaX, deltaY, deltaZ));
* }</pre>
*/
public final class WrappedVecDelta extends AbstractWrapper {
private static Class<?> STEPPED_CLASS;
private static ConstructorAccessor LINEAR_CONSTRUCTOR;
private static ConstructorAccessor STEPPED_CONSTRUCTOR;
private static ConstructorAccessor STEP_CONSTRUCTOR;
private static StructureModifier<Object> LINEAR_MODIFIER;
private static StructureModifier<Object> STEPPED_MODIFIER;
private static StructureModifier<Object> STEP_MODIFIER;
private static volatile boolean initialized;
private WrappedVecDelta(Object handle) {
super(getVecDeltaClass());
setHandle(handle);
}
private static Class<?> getVecDeltaClass() {
return MinecraftReflection.getVecDeltaClass()
.orElseThrow(() -> new UnsupportedOperationException("VecDelta requires Minecraft 26.3 or newer"));
}
private static void initialize() {
if (initialized) {
return;
}
Class<?> vecDeltaClass = getVecDeltaClass();
Class<?> linearClass = null;
Class<?> stepClass = null;
for (Class<?> nested : vecDeltaClass.getDeclaredClasses()) {
if (!vecDeltaClass.isAssignableFrom(nested)) {
continue;
}
Field steps = getListField(nested);
if (steps != null) {
STEPPED_CLASS = nested;
stepClass = (Class<?>) ((ParameterizedType) steps.getGenericType()).getActualTypeArguments()[0];
} else {
linearClass = nested;
}
}
Preconditions.checkState(linearClass != null && STEPPED_CLASS != null, "Unable to find VecDelta implementations");
LINEAR_CONSTRUCTOR = Accessors.getConstructorAccessor(linearClass, short.class, short.class, short.class);
STEPPED_CONSTRUCTOR = Accessors.getConstructorAccessor(STEPPED_CLASS, List.class);
STEP_CONSTRUCTOR = Accessors.getConstructorAccessor(stepClass, short.class, short.class, short.class, int.class);
LINEAR_MODIFIER = new StructureModifier<>(linearClass);
STEPPED_MODIFIER = new StructureModifier<>(STEPPED_CLASS);
STEP_MODIFIER = new StructureModifier<>(stepClass);
initialized = true;
}
private static Field getListField(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (List.class.equals(field.getType())) {
return field;
}
}
return null;
}
public static WrappedVecDelta fromHandle(Object handle) {
return new WrappedVecDelta(handle);
}
/**
* Creates a linear delta.
*
* @param deltaX - the encoded x offset.
* @param deltaY - the encoded y offset.
* @param deltaZ - the encoded z offset.
* @return the delta.
*/
public static WrappedVecDelta linear(short deltaX, short deltaY, short deltaZ) {
initialize();
return new WrappedVecDelta(LINEAR_CONSTRUCTOR.invoke(deltaX, deltaY, deltaZ));
}
/**
* Creates a stepped delta.
*
* @param steps - the steps in order, must not be empty.
* @return the delta.
*/
public static WrappedVecDelta stepped(List<Step> steps) {
Preconditions.checkArgument(steps != null && !steps.isEmpty(), "steps cannot be empty");
initialize();
List<Object> handles = new ArrayList<>(steps.size());
for (Step step : steps) {
handles.add(STEP_CONSTRUCTOR.invoke(step.getDeltaX(), step.getDeltaY(), step.getDeltaZ(), step.getTicks()));
}
return new WrappedVecDelta(STEPPED_CONSTRUCTOR.invoke(Collections.unmodifiableList(handles)));
}
public boolean isStepped() {
initialize();
return STEPPED_CLASS.isInstance(handle);
}
/**
* @return the encoded x offset of a linear delta.
* @throws IllegalStateException if the delta is stepped.
*/
public short getDeltaX() {
return readLinear(0);
}
/**
* @return the encoded y offset of a linear delta.
* @throws IllegalStateException if the delta is stepped.
*/
public short getDeltaY() {
return readLinear(1);
}
/**
* @return the encoded z offset of a linear delta.
* @throws IllegalStateException if the delta is stepped.
*/
public short getDeltaZ() {
return readLinear(2);
}
private short readLinear(int index) {
Preconditions.checkState(!isStepped(), "Stepped deltas have no single offset");
return LINEAR_MODIFIER.withTarget(handle).<Short>withType(short.class).read(index);
}
/**
* @return the steps of a stepped delta.
* @throws IllegalStateException if the delta is linear.
*/
public List<Step> getSteps() {
Preconditions.checkState(isStepped(), "Linear deltas have no steps");
List<?> handles = STEPPED_MODIFIER.withTarget(handle).<List<?>>withType(List.class).read(0);
List<Step> steps = new ArrayList<>(handles.size());
for (Object stepHandle : handles) {
StructureModifier<Object> step = STEP_MODIFIER.withTarget(stepHandle);
StructureModifier<Short> shorts = step.withType(short.class);
steps.add(new Step(shorts.read(0), shorts.read(1), shorts.read(2), step.<Integer>withType(int.class).read(0)));
}
return Collections.unmodifiableList(steps);
}
public static EquivalentConverter<WrappedVecDelta> getConverter() {
return Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle,
WrappedVecDelta::fromHandle, WrappedVecDelta.class));
}
public static final class Step {
private final short deltaX;
private final short deltaY;
private final short deltaZ;
private final int ticks;
public Step(short deltaX, short deltaY, short deltaZ, int ticks) {
this.deltaX = deltaX;
this.deltaY = deltaY;
this.deltaZ = deltaZ;
this.ticks = ticks;
}
public short getDeltaX() {
return deltaX;
}
public short getDeltaY() {
return deltaY;
}
public short getDeltaZ() {
return deltaZ;
}
public int getTicks() {
return ticks;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Step)) return false;
Step that = (Step) obj;
return deltaX == that.deltaX && deltaY == that.deltaY && deltaZ == that.deltaZ && ticks == that.ticks;
}
@Override
public int hashCode() {
return Objects.hash(deltaX, deltaY, deltaZ, ticks);
}
@Override
public String toString() {
return "Step[deltaX=" + deltaX + ", deltaY=" + deltaY + ", deltaZ=" + deltaZ + ", ticks=" + ticks + "]";
}
}
}
@@ -5,8 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.util.Base64;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
@@ -23,7 +22,7 @@ public class NbtTextSerializer {
* A default instance of this serializer.
*/
public static final NbtTextSerializer DEFAULT = new NbtTextSerializer();
private NbtBinarySerializer binarySerializer;
public NbtTextSerializer() {
@@ -59,7 +58,7 @@ public class NbtTextSerializer {
binarySerializer.serialize(value, dataOutput);
// Serialize that array
return Base64Coder.encodeLines(outputStream.toByteArray());
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
/**
@@ -70,7 +69,7 @@ public class NbtTextSerializer {
* @throws IOException If we are unable to parse the input.
*/
public <TType> NbtWrapper<TType> deserialize(String input) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input));
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(input));
return binarySerializer.deserialize(new DataInputStream(inputStream));
}
@@ -7,6 +7,8 @@ import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.MinecraftGenerator;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
@@ -23,6 +25,7 @@ public final class ServerPingRecord implements ServerPingImpl {
private static Class<?> SERVER_PING;
private static Class<?> PLAYER_SAMPLE_CLASS;
private static Class<?> SERVER_DATA_CLASS;
private static Class<?> NAME_AND_ID_CLASS;
private static Class<?> GSON_CLASS;
private static MethodAccessor GSON_TO_JSON;
@@ -56,13 +59,37 @@ public final class ServerPingRecord implements ServerPingImpl {
PLAYER_SAMPLE_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
SERVER_DATA_CLASS = MinecraftReflection.getServerPingServerDataClass();
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING.getConstructors()[0]);
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), Optional.class, Optional.class, Optional.class, boolean.class);
DATA_WRAPPER = AutoWrapper.wrap(ServerData.class, SERVER_DATA_CLASS);
SAMPLE_WRAPPER = AutoWrapper.wrap(PlayerSample.class, PLAYER_SAMPLE_CLASS);
FAVICON_WRAPPER = AutoWrapper.wrap(Favicon.class, MinecraftReflection.getMinecraftClass("network.protocol.status.ServerPing$a", "network.protocol.status.ServerStatus$Favicon"));
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
if (MinecraftVersion.v1_21_10.atOrAbove()) {
NAME_AND_ID_CLASS = MinecraftReflection.getMinecraftClass("server.players.NameAndId");
NAME_AND_ID_WRAPPER = AutoWrapper.wrap(NameAndId.class, NAME_AND_ID_CLASS);
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedGameProfile specific) {
NameAndId wrapper = new NameAndId(specific.getUUID(), specific.getName());
return NAME_AND_ID_WRAPPER.getGeneric(wrapper);
}
@Override
public WrappedGameProfile getSpecific(Object generic) {
NameAndId wrapper = NAME_AND_ID_WRAPPER.getSpecific(generic);
return new WrappedGameProfile(wrapper.id, wrapper.name);
}
@Override
public Class<WrappedGameProfile> getSpecificType() {
return WrappedGameProfile.class;
}
});
} else {
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
}
DEFAULT_DESCRIPTION = WrappedChatComponent.fromLegacyText("A Minecraft Server");
@@ -124,11 +151,24 @@ public final class ServerPingRecord implements ServerPingImpl {
}
}
public static final class NameAndId {
public UUID id;
public String name;
public NameAndId(UUID id, String name) {
this.id = id;
this.name = name;
}
public NameAndId() {
this(MinecraftGenerator.SYS_UUID, "");
}
}
private static AutoWrapper<PlayerSample> SAMPLE_WRAPPER;
private static AutoWrapper<ServerData> DATA_WRAPPER;
private static AutoWrapper<Favicon> FAVICON_WRAPPER;
private static AutoWrapper<NameAndId> NAME_AND_ID_WRAPPER;
private WrappedChatComponent description;
private PlayerSample playerSample;
@@ -3,15 +3,19 @@ package com.comphenix.protocol;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.utility.MinecraftReflectionTestUtil;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
import io.papermc.paper.configuration.GlobalConfiguration;
import io.papermc.paper.configuration.WorldConfiguration;
import io.papermc.paper.registry.PaperRegistryAccess;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import net.minecraft.SharedConstants;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
@@ -20,24 +24,29 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.permissions.LevelBasedPermissionSet;
import net.minecraft.server.permissions.PermissionSet;
import net.minecraft.tags.TagKey;
import net.minecraft.tags.TagLoader;
import net.minecraft.util.Util;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.DataPackConfig;
import net.minecraft.world.level.WorldDataConfiguration;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.material.Fluid;
import org.apache.logging.log4j.LogManager;
@@ -46,18 +55,18 @@ import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R5.CraftLootTable;
import org.bukkit.craftbukkit.v1_21_R5.CraftRegistry;
import org.bukkit.craftbukkit.v1_21_R5.CraftServer;
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_21_R5.tag.CraftBlockTag;
import org.bukkit.craftbukkit.v1_21_R5.tag.CraftEntityTag;
import org.bukkit.craftbukkit.v1_21_R5.tag.CraftFluidTag;
import org.bukkit.craftbukkit.v1_21_R5.tag.CraftItemTag;
import org.bukkit.craftbukkit.v1_21_R5.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.v1_21_R5.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.v1_21_R5.util.Versioning;
import org.bukkit.craftbukkit.CraftLootTable;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.tag.CraftBlockTag;
import org.bukkit.craftbukkit.tag.CraftEntityTag;
import org.bukkit.craftbukkit.tag.CraftFluidTag;
import org.bukkit.craftbukkit.tag.CraftItemTag;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.util.Versioning;
import org.jetbrains.annotations.NotNull;
import org.spigotmc.SpigotWorldConfig;
@@ -76,6 +85,15 @@ public class BukkitInitialization {
private boolean initialized;
private boolean packaged;
private static ServerLevel mockServerLevel;
/**
* @return the mocked NMS server level set up during initialization, usable as a Level for constructing test entities
*/
public static ServerLevel getMockServerLevel() {
return mockServerLevel;
}
private BukkitInitialization() {
System.out.println("Created new BukkitInitialization on " + Thread.currentThread().getName());
}
@@ -114,22 +132,69 @@ public class BukkitInitialization {
// Minecraft Data Init
SharedConstants.tryDetectVersion(); // .tryDetectVersion()
Bootstrap.bootStrap(); // .bootStrap()
Bootstrap.validate();
PackRepository repo = ServerPacksSource.createVanillaTrustedRepository();
repo.reload();
MinecraftServer.configurePackRepository(
repo,
new WorldDataConfiguration(
new DataPackConfig(
FeatureFlags.REGISTRY.toNames(FeatureFlags.REGISTRY.allFlags())
.stream()
.map(Identifier::getPath)
.toList(),
List.of()),
FeatureFlags.REGISTRY.allFlags()),
true,
false);
ResourceManager resourceManager = new MultiPackResourceManager(
PackType.SERVER_DATA,
repo.getAvailablePacks().stream().map(Pack::open).collect(Collectors.toList())
repo.openAllSelected()
);
LayeredRegistryAccess<RegistryLayer> layeredAccess1 = RegistryLayer.createRegistryAccess();
List<net.minecraft.core.Registry.PendingTags<?>> tags = TagLoader.loadTagsForExistingRegistries(resourceManager, layeredAccess1.getLayer(RegistryLayer.STATIC));
RegistryAccess.Frozen access1 = layeredAccess1.getAccessForLoading(RegistryLayer.WORLDGEN);
RegistryAccess.Frozen access1 = layeredAccess1.getAccessForLoading(RegistryLayer.WORLD);
List<HolderLookup.RegistryLookup<?>> list1 = TagLoader.buildUpdatedLookups(access1, tags);
RegistryAccess.Frozen access2 = RegistryDataLoader.load(resourceManager, list1, RegistryDataLoader.WORLDGEN_REGISTRIES);
LayeredRegistryAccess<RegistryLayer> layeredAccess2 = layeredAccess1.replaceFrom(RegistryLayer.WORLDGEN, access2);
RegistryAccess.Frozen access2;
try {
access2 = RegistryDataLoader.load(
resourceManager,
list1,
RegistryDataLoader.WORLD_REGISTRIES,
Util.backgroundExecutor()).get();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
LayeredRegistryAccess<RegistryLayer> layeredAccess2 =
layeredAccess1.replaceFrom(RegistryLayer.WORLD, access2);
List<HolderLookup.RegistryLookup<?>> dimensionLookups =
Stream.concat(list1.stream(), access2.listRegistries()).toList();
RegistryAccess.Frozen dimensions = RegistryDataLoader.load(
resourceManager,
dimensionLookups,
RegistryDataLoader.DIMENSION_REGISTRIES,
Util.backgroundExecutor()).join();
layeredAccess2 = layeredAccess2.replaceFrom(RegistryLayer.DIMENSIONS, dimensions);
try {
Class.forName(Registry.class.getName());
} catch (ClassNotFoundException exception) {
throw new RuntimeException(exception);
}
RegistryAccess.Frozen registryCustom = layeredAccess2.compositeAccess().freeze();
PaperRegistryAccess paperRegistryAccess = PaperRegistryAccess.instance();
GlobalConfiguration globalConfiguration = new GlobalConfiguration();
globalConfiguration.collisions = globalConfiguration.new Collisions();
Accessors.getMethodAccessor(
GlobalConfiguration.class,
"set",
GlobalConfiguration.class).invoke(null, globalConfiguration);
ReloadableServerResources dataPackResources = ReloadableServerResources.loadResources(
resourceManager,
@@ -137,11 +202,11 @@ public class BukkitInitialization {
tags,
FeatureFlags.REGISTRY.allFlags() /* REGISTRY.allFlags() */,
Commands.CommandSelection.DEDICATED /* DEDICATED */,
0,
LevelBasedPermissionSet.ALL,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
dataPackResources.updateStaticRegistryTags();
dataPackResources.updateComponentsAndStaticRegistryTags();
/* try {
IRegistry.class.getName();
@@ -157,6 +222,13 @@ public class BukkitInitialization {
DedicatedServer mockedGameServer = mock(DedicatedServer.class);
when(mockedGameServer.registryAccess()).thenReturn(registryCustom);
try {
FieldAccessor serverInstance = Accessors.getFieldAccessor(
MinecraftServer.class.getDeclaredField("SERVER"));
serverInstance.set(null, mockedGameServer);
} catch (NoSuchFieldException exception) {
throw new RuntimeException(exception);
}
when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft"));
when(mockedServer.getName()).thenReturn("Mock Server");
@@ -173,7 +245,7 @@ public class BukkitInitialization {
});
when(mockedServer.getRegistry(any())).thenAnswer(invocation -> {
Class<Keyed> registryType = invocation.getArgument(0);
Object registry = CraftRegistry.createRegistry(registryType, registryCustom);
Object registry = paperRegistryAccess.getRegistry(registryType);
if (registry == null) {
System.err.println("WARN: Missing registry for " + registryType);
@@ -186,7 +258,7 @@ public class BukkitInitialization {
when(mockedServer.getTag(any(), any(), any())).then(mock -> {
String registry = mock.getArgument(0);
Class<?> clazz = mock.getArgument(2);
ResourceLocation key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
Identifier key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
switch (registry) {
case org.bukkit.Tag.REGISTRY_BLOCKS -> {
@@ -225,6 +297,18 @@ public class BukkitInitialization {
ServerLevel nmsWorld = mock(ServerLevel.class);
SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class);
ConstructorAccessor worldConfigurationConstructor = Accessors.getConstructorAccessor(
WorldConfiguration.class,
SpigotWorldConfig.class,
Identifier.class);
WorldConfiguration worldConfiguration = (WorldConfiguration) worldConfigurationConstructor.invoke(
mockWorldConfig,
Identifier.parse("minecraft:test"));
worldConfiguration.entities = worldConfiguration.new Entities();
worldConfiguration.entities.spawning =
worldConfiguration.entities.new Spawning();
worldConfiguration.entities.spawning.despawnTime = new Reference2ObjectOpenHashMap<>();
when(nmsWorld.paperConfig()).thenReturn(worldConfiguration);
try {
FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig"));
@@ -235,6 +319,7 @@ public class BukkitInitialization {
CraftWorld world = mock(CraftWorld.class);
when(world.getHandle()).thenReturn(nmsWorld);
mockServerLevel = nmsWorld;
List<World> worlds = Collections.singletonList(world);
when(mockedServer.getWorlds()).thenReturn(worlds);
@@ -290,5 +375,35 @@ public class BukkitInitialization {
List<T> empty = Collections.emptyList();
return empty.stream();
}
@Override
public Stream<NamespacedKey> keyStream() {
return Stream.empty();
}
@Override
public NamespacedKey getKey(T value) {
return null;
}
@Override
public boolean hasTag(io.papermc.paper.registry.tag.TagKey<T> key) {
return false;
}
@Override
public io.papermc.paper.registry.tag.Tag<T> getTag(io.papermc.paper.registry.tag.TagKey<T> key) {
return null;
}
@Override
public java.util.Collection<io.papermc.paper.registry.tag.Tag<T>> getTags() {
return Collections.emptyList();
}
@Override
public int size() {
return 0;
}
}
}
@@ -16,6 +16,7 @@ package com.comphenix.protocol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -35,6 +36,8 @@ import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.minecraft.network.protocol.common.ClientboundPostEffectsPacket;
import net.minecraft.network.protocol.common.ClientboundStoreCookiePacket;
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
/**
@@ -254,6 +257,27 @@ public class PacketTypeTest {
assertEquals(type, roundTrip);
}
}
@Test
public void testMinecraft263PacketMappings() {
assertEquals(0x25, PacketType.Play.Server.ADD_TRANSIENT_BLOCK.getCurrentId());
assertEquals(0x26, PacketType.Play.Server.UNLOAD_CHUNK.getCurrentId());
assertNotEquals(PacketType.Play.Server.ADD_TRANSIENT_BLOCK, PacketType.Play.Server.UNLOAD_CHUNK);
assertEquals(0x53, PacketType.Play.Server.POST_EFFECTS.getCurrentId());
assertEquals(0x7B, PacketType.Play.Server.SWING_ANIMATION.getCurrentId());
assertEquals(0x2E, PacketType.Play.Client.PUNCH.getCurrentId());
assertNotEquals(PacketType.Play.Client.PUNCH, PacketType.Play.Client.RECIPE_SETTINGS);
}
@Test
public void testMinecraft263ConfigurationPacketMappings() {
assertEquals(0x0A, PacketType.Configuration.Server.POST_EFFECTS.getCurrentId());
assertEquals(ClientboundPostEffectsPacket.class, PacketType.Configuration.Server.POST_EFFECTS.getPacketClass());
assertEquals(0x0B, PacketType.Configuration.Server.STORE_COOKIE.getCurrentId());
assertEquals(ClientboundStoreCookiePacket.class, PacketType.Configuration.Server.STORE_COOKIE.getPacketClass());
assertEquals(0x14, PacketType.Configuration.Server.CODE_OF_CONDUCT.getCurrentId());
}
@Test
public void testPacketCodec() {
@@ -0,0 +1,54 @@
package com.comphenix.protocol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import com.comphenix.protocol.paper.PaperPlatformProvider;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
class PlatformCommandRegistrarTest {
@Test
void registersLegacyExecutorAsPaperCommand() throws CommandSyntaxException {
ProtocolLib plugin = mock(ProtocolLib.class);
CommandExecutor executor = mock(CommandExecutor.class);
ArgumentCaptor<BasicCommand> commandCaptor = ArgumentCaptor.forClass(BasicCommand.class);
new PaperPlatformProvider().registerCommand(plugin, "filter", executor);
verify(plugin).registerCommand(
eq("filter"),
eq("Add or remove programmable filters to the packet listeners."),
eq(List.of("packet_filter")),
commandCaptor.capture());
CommandSourceStack source = mock(CommandSourceStack.class);
CommandSender sender = mock(CommandSender.class);
when(source.getSender()).thenReturn(sender);
ArgumentCaptor<Command> legacyCommand = ArgumentCaptor.forClass(Command.class);
String[] arguments = {"add"};
when(executor.onCommand(eq(sender), legacyCommand.capture(), eq("filter"), eq(arguments)))
.thenReturn(false);
commandCaptor.getValue().execute(source, arguments);
assertEquals("filter", legacyCommand.getValue().getName());
assertEquals("protocol.admin", commandCaptor.getValue().permission());
verify(sender).sendMessage("/filter add|remove name [ID start]-[ID stop]");
}
}
@@ -0,0 +1,36 @@
package com.comphenix.protocol.async;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import org.bukkit.plugin.Plugin;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
class AsyncFilterManagerTest {
@BeforeAll
static void initializeBukkit() {
BukkitInitialization.initializeAll();
}
@Test
void testSendingTypesUseServerProcessingQueue() {
AsyncFilterManager manager = new AsyncFilterManager(
mock(ErrorReporter.class), mock(ProtocolScheduler.class));
manager.setManager(mock(ProtocolManager.class));
manager.registerAsyncHandler(new PacketAdapter(
mock(Plugin.class), PacketType.Play.Server.SYSTEM_CHAT) { }, false);
assertTrue(manager.getSendingTypes().contains(PacketType.Play.Server.SYSTEM_CHAT));
assertFalse(manager.getReceivingTypes().contains(PacketType.Play.Server.SYSTEM_CHAT));
}
}
@@ -5,6 +5,7 @@ import com.comphenix.protocol.utility.IntegerMath;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class IntegerMapTest {
@Test
@@ -21,4 +22,15 @@ class IntegerMapTest {
}
assertEquals(map.size(), 512);
}
}
@Test
public void testRemoveOutsideBounds() {
IntegerMap<Boolean> map = new IntegerMap<>();
map.put(0, true);
assertNull(map.remove(-1));
assertEquals(1, map.size());
assertNull(map.remove(8));
assertEquals(1, map.size());
}
}
@@ -0,0 +1,20 @@
package com.comphenix.protocol.concurrency;
import org.bukkit.entity.Player;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
class ConcurrentPlayerMapTest {
@Test
void testPutIfAbsentWithUnavailableAddress() {
ConcurrentPlayerMap<Object> map = ConcurrentPlayerMap.usingAddress();
Player player = mock(Player.class);
assertThrows(IllegalStateException.class, () -> map.putIfAbsent(player, new Object()));
assertTrue(map.isEmpty());
}
}
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -41,8 +42,9 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundPlayerCommandPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import org.apache.commons.lang.SerializationUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@@ -71,6 +73,7 @@ import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.ComponentConverter;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import com.comphenix.protocol.wrappers.EnumWrappers;
@@ -102,18 +105,22 @@ import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.common.custom.BrandPayload;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket.AttributeSnapshot;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.resources.Identifier;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation;
import net.minecraft.world.entity.animal.CatVariant;
import net.minecraft.world.entity.animal.CatVariants;
import net.minecraft.world.entity.animal.feline.CatVariant;
import net.minecraft.world.entity.animal.feline.CatVariants;
import net.minecraft.world.entity.animal.frog.FrogVariant;
import net.minecraft.world.entity.animal.frog.FrogVariants;
@@ -178,8 +185,8 @@ public class PacketContainerTest {
@Test
public void testGetShorts() {
PacketContainer itemData = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE);
this.testPrimitive(itemData.getShorts(), 0, (short) 0, (short) 1);
PacketContainer creativeSlot = new PacketContainer(PacketType.Play.Client.SET_CREATIVE_SLOT);
this.testPrimitive(creativeSlot.getShorts(), 0, (short) 0, (short) 1);
}
@Test
@@ -424,6 +431,64 @@ public class PacketContainerTest {
Assertions.assertNotSame(payload, cloned);
}
@Test
public void testCustomPayloadConcreteSerialization() {
BrandPayload payload = new BrandPayload("Hello World!");
CustomPacketPayloadWrapper wrapper = CustomPacketPayloadWrapper.fromUnknownPayload(payload);
assertEquals(BrandPayload.TYPE.id().toString(), wrapper.getId().getFullKey());
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(wrapper.getPayload()));
try {
assertEquals("Hello World!", buffer.readUtf());
assertEquals(0, buffer.readableBytes());
} finally {
buffer.release();
}
}
@Test
public void testCustomPayloadSerializationFailureReleasesBuffer() {
FailingPayload payload = new FailingPayload();
assertThrows(RuntimeException.class, () -> CustomPacketPayloadWrapper.fromUnknownPayload(payload));
assertEquals(0, payload.buffer.refCnt());
}
@Test
public void testCustomPayloadCreatesDiscardedPayload() {
byte[] data = {0x00, 0x01, 0x05, 0x07};
com.comphenix.protocol.wrappers.MinecraftKey id =
new com.comphenix.protocol.wrappers.MinecraftKey("protocollib", "test");
CustomPacketPayloadWrapper wrapper = new CustomPacketPayloadWrapper(data, id);
Object handle = wrapper.newHandle();
assertInstanceOf(DiscardedPayload.class, handle);
CustomPacketPayloadWrapper converted = CustomPacketPayloadWrapper.fromUnknownPayload(handle);
assertEquals(id, converted.getId());
assertArrayEquals(data, converted.getPayload());
}
private static final class FailingPayload implements CustomPacketPayload {
private static final Type<FailingPayload> TYPE =
new Type<>(Identifier.fromNamespaceAndPath("protocollib", "failing"));
private ByteBuf buffer;
@SuppressWarnings("unused")
private void write(FriendlyByteBuf buffer) {
this.buffer = buffer;
throw new IllegalStateException("expected failure");
}
@Override
public Type<FailingPayload> type() {
return TYPE;
}
}
/*
@Test
public void testUnknownPayloadDeserialize() {
@@ -523,15 +588,14 @@ public class PacketContainerTest {
// Initialize some test data
List<AttributeModifier> modifiers = Lists.newArrayList(
new AttributeModifier(ResourceLocation.parse("protocollib:test"),10,
Operation.ADD_VALUE));
new AttributeModifier(Identifier.parse("protocollib:test"),10, Operation.ADD_VALUE));
// Obtain an AttributeSnapshot instance. This is complicated by the fact that AttributeSnapshots
// are inner classes (which is ultimately pointless because AttributeSnapshots don't access any
// members of the packet itself)
ClientboundUpdateAttributesPacket packet = (ClientboundUpdateAttributesPacket) attribute.getHandle();
net.minecraft.core.Registry<Attribute> registry = BuiltInRegistries.ATTRIBUTE;
Attribute base = registry.getValue(ResourceLocation.parse("max_health"));
Attribute base = registry.getValue(Identifier.parse("max_health"));
AttributeSnapshot snapshot = new AttributeSnapshot(registry.wrapAsHolder(base), 20.0D, modifiers);
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
@@ -755,7 +819,8 @@ public class PacketContainerTest {
assertEquals(2, (int) packet.getGameStateIDs().read(0));
}
@Test
// @Test
// TODO: needs to be fixed for 26.1
public void testUseEntity() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.USE_ENTITY);
@@ -849,14 +914,14 @@ public class PacketContainerTest {
new WrappedGameProfile(new UUID(0, 0), "system"),
null,
(WrappedRemoteChatSessionData) null);
updatePlayerInfoActions.getPlayerInfoDataLists().write(1, Collections.singletonList(data));
StructureModifier<List<PlayerInfoData>> playerInfoDataLists = updatePlayerInfoActions.getPlayerInfoDataLists();
Assertions.assertEquals(1, playerInfoDataLists.size());
playerInfoDataLists.write(0, Collections.singletonList(data));
Set<EnumWrappers.PlayerInfoAction> readActions = updatePlayerInfoActions.getPlayerInfoActions().read(0);
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.ADD_PLAYER));
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.UPDATE_LATENCY));
Assertions.assertTrue(readActions.contains(EnumWrappers.PlayerInfoAction.UPDATE_LISTED));
Assertions.assertEquals(actions, readActions);
Collection<PlayerInfoData> readData = updatePlayerInfoActions.getPlayerInfoDataLists().read(1);
Collection<PlayerInfoData> readData = playerInfoDataLists.read(0);
Assertions.assertEquals(1, readData.size());
PlayerInfoData firstData = readData.iterator().next();
@@ -879,6 +944,32 @@ public class PacketContainerTest {
assertArrayEquals(signature, read.getBytes());
}
@Test
public void testAbsentChatMessageSignature() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Client.CHAT);
StructureModifier<WrappedMessageSignature> signatures = chatPacket.getMessageSignatures();
signatures.write(0, new WrappedMessageSignature(new byte[256]));
signatures.write(0, null);
assertNull(signatures.read(0));
assertEquals(Optional.empty(), chatPacket.getModifier().withType(Optional.class).read(0));
}
@Test
public void testMessageSignaturesDoNotShadowOptionals() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Server.CHAT);
byte[] signature = new byte[256];
signature[0] = 42;
assertEquals(1, chatPacket.getMessageSignatures().size());
assertEquals(2, chatPacket.getOptionals(Converters.passthrough(Object.class)).size());
assertEquals(1, chatPacket.getMessageSignatures().size());
chatPacket.getMessageSignatures().write(0, new WrappedMessageSignature(signature));
assertArrayEquals(signature, chatPacket.getMessageSignatures().read(0).getBytes());
}
private void assertPacketsEqualAndSerializable(PacketContainer constructed, PacketContainer cloned) {
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
assertEquals(firstMod.size(), secondMod.size());
@@ -6,8 +6,8 @@ import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftEntity;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -1,10 +1,16 @@
package com.comphenix.protocol.injector;
import java.math.BigInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import com.comphenix.protocol.BukkitInitialization;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class StructureCacheTests {
@@ -23,4 +29,42 @@ public class StructureCacheTests {
assertTrue(ex.getMessage().contains("Cannot inject already loaded type"));
}
}
@Test
public void testReusesImmutableConstant() {
assertSame(BigInteger.ZERO, StructureCache.newInstance(BigInteger.class));
}
@Test
public void testDoesNotReuseMutableStaticInstance() {
assertNotSame(MutableStaticInstance.INSTANCE, StructureCache.newInstance(MutableStaticInstance.class));
}
@Test
public void testCreatorLookupCanReenterCache() {
assertTrue(StructureCache.canCreateInstance(ReentrantConstructor.class));
assertNull(ReentrantConstructor.failure);
}
private static final class ReentrantConstructor {
private static final AtomicBoolean ENTERED = new AtomicBoolean();
private static volatile RuntimeException failure;
private ReentrantConstructor() {
if (ENTERED.compareAndSet(false, true)) {
try {
StructureCache.canCreateInstance(ReentrantConstructor.class);
} catch (RuntimeException ex) {
failure = ex;
}
}
}
}
private static final class MutableStaticInstance {
public static final MutableStaticInstance INSTANCE = new MutableStaticInstance();
private MutableStaticInstance() {
}
}
}
@@ -10,8 +10,22 @@ import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import com.comphenix.protocol.wrappers.MinecraftKey;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.protocol.common.ServerboundKeepAlivePacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.network.protocol.game.ClientboundOpenBookPacket;
import net.minecraft.world.InteractionHand;
import org.bukkit.Material;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -52,6 +66,65 @@ public class WirePacketTest {
assertEquals(failures, new ArrayList<>());
}
@Test
public void testBytesFromClientboundPacket() {
ClientboundOpenBookPacket handle = new ClientboundOpenBookPacket(InteractionHand.OFF_HAND);
PacketContainer packet = new PacketContainer(PacketType.Play.Server.OPEN_BOOK, handle);
assertArrayEquals(new byte[] {1}, WirePacket.bytesFromPacket(packet));
}
@Test
public void testBytesFromServerboundPacket() {
ServerboundKeepAlivePacket handle = new ServerboundKeepAlivePacket(0x0102030405060708L);
PacketContainer packet = new PacketContainer(PacketType.Play.Client.KEEP_ALIVE, handle);
assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, WirePacket.bytesFromPacket(packet));
}
@Test
public void testBytesFromPacketWithItemStack() {
ClientboundContainerSetSlotPacket handle = new ClientboundContainerSetSlotPacket(1, 2, 3,
CraftItemStack.asNMSCopy(new ItemStack(Material.GOLDEN_SHOVEL)));
PacketContainer packet = new PacketContainer(PacketType.Play.Server.SET_SLOT, handle);
// the item stack can only be written to a buffer which knows the registries
RegistryFriendlyByteBuf expected = new RegistryFriendlyByteBuf(Unpooled.buffer(),
CraftRegistry.getMinecraftRegistry());
ClientboundContainerSetSlotPacket.STREAM_CODEC.encode(expected, handle);
assertArrayEquals(ByteBufUtil.getBytes(expected), WirePacket.bytesFromPacket(packet));
}
@Test
public void testFromPacketHandle() {
ClientboundOpenBookPacket handle = new ClientboundOpenBookPacket(InteractionHand.MAIN_HAND);
PacketContainer packet = new PacketContainer(PacketType.Play.Server.OPEN_BOOK, handle);
WirePacket wire = WirePacket.fromPacket(handle);
assertEquals(PacketType.Play.Server.OPEN_BOOK.getCurrentId(), wire.getId());
assertArrayEquals(new byte[] {0}, wire.getBytes());
assertEquals(WirePacket.fromPacket(packet), wire);
}
@Test
public void testBytesFromCustomPayloadPacket() {
byte[] data = {0x00, 0x01, 0x05, 0x07};
PacketContainer packet = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
packet.getCustomPacketPayloads().write(0,
new CustomPacketPayloadWrapper(data, new MinecraftKey("protocollib", "test")));
FriendlyByteBuf expected = new FriendlyByteBuf(Unpooled.buffer());
expected.writeUtf("protocollib:test");
expected.writeBytes(data);
assertArrayEquals(ByteBufUtil.getBytes(expected), WirePacket.bytesFromPacket(packet));
// the packet has to stay writable, it is still sent after it was logged
assertArrayEquals(ByteBufUtil.getBytes(expected), WirePacket.bytesFromPacket(packet));
assertArrayEquals(data, packet.getCustomPacketPayloads().read(0).getPayload());
}
@Test
public void testSerialization() {
int id = 42;
@@ -0,0 +1,150 @@
package com.comphenix.protocol.injector.collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.Converters;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
public class OutboundPacketListenerSetTest {
@BeforeAll
public static void beforeClass() {
BukkitInitialization.initializeAll();
}
@Test
public void testCancelledSubPacketsCancelBundle() {
OutboundPacketListenerSet listenerSet = createListenerSet(event -> event.setCancelled(true));
PacketEvent bundleEvent = createBundleEvent();
listenerSet.invoke(bundleEvent);
assertTrue(bundleEvent.isCancelled());
}
@Test
public void testSubPacketEventReferencesBundle() {
AtomicReference<PacketEvent> subPacketEvent = new AtomicReference<>();
OutboundPacketListenerSet listenerSet = createListenerSet(subPacketEvent::set);
PacketEvent bundleEvent = createBundleEvent();
listenerSet.invoke(bundleEvent);
assertSame(bundleEvent, subPacketEvent.get().getBundle());
}
@Test
public void testUnchangedBundleIsNotCopied() {
OutboundPacketListenerSet listenerSet = createListenerSet(event -> { });
PacketEvent bundleEvent = createBundleEvent();
PacketContainer bundle = bundleEvent.getPacket();
Object packets = getRawPackets(bundle);
listenerSet.invoke(bundleEvent);
assertSame(bundle, bundleEvent.getPacket());
assertSame(packets, getRawPackets(bundleEvent.getPacket()));
}
@Test
public void testCancelledSubPacketDoesNotModifySharedBundle() {
PacketContainer cancelled = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer retained = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer sharedBundle = createBundle(cancelled, retained);
PacketEvent modifiedEvent = createBundleEvent(sharedBundle);
PacketEvent sharedEvent = createBundleEvent(PacketContainer.fromPacket(sharedBundle.getHandle()));
Object sharedPackets = getRawPackets(sharedBundle);
OutboundPacketListenerSet listenerSet = createListenerSet(event -> {
if (event.getPacket().getHandle() == cancelled.getHandle()) {
event.setCancelled(true);
}
});
listenerSet.invoke(modifiedEvent);
assertNotSame(sharedBundle.getHandle(), modifiedEvent.getPacket().getHandle());
assertSame(sharedBundle.getHandle(), sharedEvent.getPacket().getHandle());
assertSame(sharedPackets, getRawPackets(sharedEvent.getPacket()));
assertPacketHandles(modifiedEvent.getPacket(), retained);
assertPacketHandles(sharedEvent.getPacket(), cancelled, retained);
}
@Test
public void testReplacedSubPacketDoesNotModifySharedBundle() {
PacketContainer replaced = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer retained = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer replacement = new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer sharedBundle = createBundle(replaced, retained);
PacketEvent modifiedEvent = createBundleEvent(sharedBundle);
PacketEvent sharedEvent = createBundleEvent(PacketContainer.fromPacket(sharedBundle.getHandle()));
Object sharedPackets = getRawPackets(sharedBundle);
OutboundPacketListenerSet listenerSet = createListenerSet(event -> {
if (event.getPacket().getHandle() == replaced.getHandle()) {
event.setPacket(replacement);
}
});
listenerSet.invoke(modifiedEvent);
assertNotSame(sharedBundle.getHandle(), modifiedEvent.getPacket().getHandle());
assertSame(sharedBundle.getHandle(), sharedEvent.getPacket().getHandle());
assertSame(sharedPackets, getRawPackets(sharedEvent.getPacket()));
assertPacketHandles(modifiedEvent.getPacket(), replacement, retained);
assertPacketHandles(sharedEvent.getPacket(), replaced, retained);
}
private static OutboundPacketListenerSet createListenerSet(Consumer<PacketEvent> callback) {
OutboundPacketListenerSet listenerSet = new OutboundPacketListenerSet(null, mock(ErrorReporter.class));
listenerSet.addListener(new PacketAdapter(mock(Plugin.class), PacketType.Play.Server.SYSTEM_CHAT) {
@Override
public void onPacketSending(PacketEvent event) {
callback.accept(event);
}
});
return listenerSet;
}
private static PacketEvent createBundleEvent() {
return createBundleEvent(createBundle(new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT)));
}
private static PacketEvent createBundleEvent(PacketContainer bundle) {
return PacketEvent.fromServer(OutboundPacketListenerSetTest.class, bundle, mock(Player.class));
}
private static PacketContainer createBundle(PacketContainer... packets) {
PacketContainer bundle = new PacketContainer(PacketType.Play.Server.BUNDLE);
bundle.getPacketBundles().write(0, List.of(packets));
return bundle;
}
private static Object getRawPackets(PacketContainer bundle) {
return bundle.getModifier().withType(Iterable.class).read(0);
}
private static void assertPacketHandles(PacketContainer bundle, PacketContainer... expected) {
List<PacketContainer> packets = Converters.toList(bundle.getPacketBundles().read(0));
assertEquals(expected.length, packets.size());
for (int index = 0; index < expected.length; index++) {
assertSame(expected[index].getHandle(), packets.get(index).getHandle());
}
}
}
@@ -0,0 +1,170 @@
package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Field;
import java.util.List;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketPostListener;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import net.minecraft.network.protocol.Packet;
import org.bukkit.Bukkit;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class NettyChannelInjectorTest {
@BeforeAll
public static void beforeClass() {
BukkitInitialization.initializeAll();
}
@Test
public void testBundleNeedsMainThreadForSubPacketListener() {
ListenerManager listenerManager = createListenerManager(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer bundle = createBundle(
new PacketContainer(PacketType.Play.Server.KEEP_ALIVE),
new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT));
assertTrue(NettyChannelInjector.hasMainThreadListener(listenerManager, Protocol.PLAY, bundle));
}
@Test
public void testBundleNeedsMainThreadForBundleListener() {
ListenerManager listenerManager = createListenerManager(PacketType.Play.Server.BUNDLE);
PacketContainer bundle = createBundle(new PacketContainer(PacketType.Play.Server.KEEP_ALIVE));
assertTrue(NettyChannelInjector.hasMainThreadListener(listenerManager, Protocol.PLAY, bundle));
}
@Test
public void testBundleWithoutListenedSubPacketsStaysOffMainThread() {
ListenerManager listenerManager = createListenerManager(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer bundle = createBundle(
new PacketContainer(PacketType.Play.Server.KEEP_ALIVE),
new PacketContainer(PacketType.Play.Server.KEEP_ALIVE));
assertFalse(NettyChannelInjector.hasMainThreadListener(listenerManager, Protocol.PLAY, bundle));
}
@Test
public void testSinglePacketOnlyChecksItsOwnType() {
ListenerManager listenerManager = createListenerManager(PacketType.Play.Server.SYSTEM_CHAT);
assertTrue(NettyChannelInjector.hasMainThreadListener(listenerManager, Protocol.PLAY,
new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT)));
assertFalse(NettyChannelInjector.hasMainThreadListener(listenerManager, Protocol.PLAY,
new PacketContainer(PacketType.Play.Server.KEEP_ALIVE)));
}
@Test
public void testRescheduledBundleKeepsNetworkMarker() throws Exception {
ListenerManager listenerManager = createListenerManager(PacketType.Play.Server.SYSTEM_CHAT);
PacketContainer bundle = createBundle(
new PacketContainer(PacketType.Play.Server.KEEP_ALIVE),
new PacketContainer(PacketType.Play.Server.SYSTEM_CHAT));
SendAction action = new SendAction((Packet<?>) bundle.getHandle());
NetworkMarker marker = new NetworkMarker(ConnectionSide.SERVER_SIDE, PacketType.Play.Server.BUNDLE);
PacketPostListener postListener = mock(PacketPostListener.class);
marker.addPostListener(postListener);
// the real Connection can't be loaded in tests, so the injector gets a stand-in and a mocked invoker
NettyChannelInjector injector;
try (MockedConstruction<PacketListenerInvoker> ignored = mockConstruction(PacketListenerInvoker.class)) {
injector = spy(new NettyChannelInjector(null, new FakeNetworkManager(), new EmbeddedChannel(),
listenerManager, mock(InjectionFactory.class), mock(ErrorReporter.class)));
}
doReturn(Protocol.PLAY).when(injector).getCurrentProtocol(PacketType.Sender.SERVER);
Field injected = NettyChannelInjector.class.getDeclaredField("injected");
injected.setAccessible(true);
injected.setBoolean(injector, true);
ProtocolScheduler scheduler = mock(ProtocolScheduler.class);
try (MockedStatic<ProtocolLibrary> protocolLibrary = mockStatic(ProtocolLibrary.class, CALLS_REAL_METHODS)) {
protocolLibrary.when(ProtocolLibrary::getScheduler).thenReturn(scheduler);
// a plugin sends the bundle with a marker off the main thread
when(Bukkit.getServer().isPrimaryThread()).thenReturn(false);
injector.sendClientboundPacket(action.packet, marker, true);
assertNull(injector.processOutbound(action));
ArgumentCaptor<Runnable> rescheduled = ArgumentCaptor.forClass(Runnable.class);
verify(scheduler).runTask(rescheduled.capture());
verify(listenerManager, never()).invokeOutboundPacketListeners(any());
// the rescheduled send runs on the main thread and is written again
when(Bukkit.getServer().isPrimaryThread()).thenReturn(true);
rescheduled.getValue().run();
Runnable sent = injector.processOutbound(action);
assertNotNull(sent);
sent.run();
} finally {
when(Bukkit.getServer().isPrimaryThread()).thenReturn(true);
}
assertTrue(action.ran);
verify(postListener).onPostEvent(any(PacketEvent.class));
}
private static final class FakeNetworkManager {
private Channel channel;
}
private static final class SendAction implements Runnable {
private final Packet<?> packet;
private boolean ran;
private SendAction(Packet<?> packet) {
this.packet = packet;
}
@Override
public void run() {
this.ran = true;
}
}
private static ListenerManager createListenerManager(PacketType mainThreadPacketType) {
ListenerManager listenerManager = mock(ListenerManager.class);
when(listenerManager.hasMainThreadListener(mainThreadPacketType)).thenReturn(true);
return listenerManager;
}
private static PacketContainer createBundle(PacketContainer... packets) {
PacketContainer bundle = new PacketContainer(PacketType.Play.Server.BUNDLE);
bundle.getPacketBundles().write(0, List.of(packets));
return bundle;
}
}
@@ -0,0 +1,78 @@
package com.comphenix.protocol.injector.netty.manager;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
class NetworkManagerInjectorTest {
@Test
void addsInjectionHandlerBeforeNetworkManager() {
EmbeddedChannel channel = new EmbeddedChannel();
channel.pipeline().addLast("before", new ChannelInboundHandlerAdapter());
channel.pipeline().addLast("network_manager", new TestNetworkManager());
channel.pipeline().addLast("after", new ChannelInboundHandlerAdapter());
NetworkManagerInjector.addInjectionHandler(
channel,
TestNetworkManager.class,
new ChannelInboundHandlerAdapter());
assertEquals(
channel.pipeline().names().indexOf("network_manager") - 1,
channel.pipeline().names().indexOf("protocol_lib_inbound_inject"));
}
@Test
void injectsAfterNetworkManagerFinishesChannelActivation() {
AtomicBoolean networkManagerInitialized = new AtomicBoolean();
AtomicBoolean observedInitializedNetworkManager = new AtomicBoolean();
EmbeddedChannel channel = new EmbeddedChannel();
channel.pipeline().addLast("network_manager", new TestNetworkManager() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
networkManagerInitialized.set(true);
}
});
NetworkManagerInjector.addInjectionHandler(
channel,
TestNetworkManager.class,
new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
observedInitializedNetworkManager.set(networkManagerInitialized.get());
}
});
channel.pipeline().fireChannelActive();
assertTrue(observedInitializedNetworkManager.get());
}
@Test
void addsInjectionHandlerLastWhenNetworkManagerIsMissing() {
EmbeddedChannel channel = new EmbeddedChannel();
channel.pipeline().addLast("existing", new ChannelInboundHandlerAdapter());
NetworkManagerInjector.addInjectionHandler(
channel,
TestNetworkManager.class,
new ChannelInboundHandlerAdapter());
assertEquals(
channel.pipeline().names().indexOf("existing") + 1,
channel.pipeline().names().indexOf("protocol_lib_inbound_inject"));
}
private static class TestNetworkManager extends ChannelInboundHandlerAdapter {
}
}
@@ -0,0 +1,42 @@
package com.comphenix.protocol.injector.netty.manager;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.internal.PlatformProvider;
import com.comphenix.protocol.paper.PaperPlatformProvider;
import io.netty.channel.Channel;
import io.netty.channel.embedded.EmbeddedChannel;
import io.papermc.paper.network.ChannelInitializeListenerHolder;
class PlatformChannelInitializerTest {
@AfterEach
void resetHolder() {
ChannelInitializeListenerHolder.removeListener(PaperPlatformProvider.CHANNEL_INITIALIZER_KEY);
}
@Test
void registersInvokesAndRemovesListener() {
AtomicReference<Channel> initializedChannel = new AtomicReference<>();
PlatformProvider provider = PlatformProvider.get();
Runnable cleanup = provider.registerChannelInitializer(initializedChannel::set);
assertTrue(provider.hasEarlyChannelInitialization());
assertTrue(ChannelInitializeListenerHolder.hasListener(PaperPlatformProvider.CHANNEL_INITIALIZER_KEY));
Channel channel = new EmbeddedChannel();
ChannelInitializeListenerHolder.callListeners(channel);
assertSame(channel, initializedChannel.get());
cleanup.run();
assertFalse(ChannelInitializeListenerHolder.hasListener(PaperPlatformProvider.CHANNEL_INITIALIZER_KEY));
}
}
@@ -5,6 +5,7 @@ import java.util.List;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.utility.MinecraftVersion;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -29,7 +30,8 @@ public class PacketRegistryTests {
public void testAllPacketsRegistered() {
List<PacketType> missing = new ArrayList<>();
for (PacketType type : PacketType.values()) {
if (type.isDeprecated()) {
if (type.isDeprecated()
|| type.getCurrentVersion().compareTo(MinecraftVersion.getCurrentVersion()) > 0) {
continue;
}
if (!PacketRegistry.tryGetPacketClass(type).isPresent()) {
@@ -1,5 +1,8 @@
package com.comphenix.protocol.injector.temporary;
import java.util.UUID;
import com.comphenix.protocol.injector.netty.Injector;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.junit.jupiter.api.BeforeEach;
@@ -7,13 +10,18 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
public class TemporaryPlayerFactoryTest {
@Mock
Server server;
@Mock
Injector injector;
@BeforeEach
public void initMocks() {
MockitoAnnotations.openMocks(this);
@@ -24,4 +32,18 @@ public class TemporaryPlayerFactoryTest {
Player player = TemporaryPlayerFactory.createTemporaryPlayer();
assertThrows(IllegalStateException.class, player::getPlayer);
}
@Test
public void testLoginProfile() {
Player player = TemporaryPlayerFactory.createTemporaryPlayer();
UUID uniqueId = UUID.randomUUID();
TemporaryPlayerFactory.setInjectorForPlayer(player, this.injector);
when(this.injector.getPlayer()).thenReturn(player);
when(this.injector.getPlayerName()).thenReturn("dmulloy2");
when(this.injector.getPlayerUniqueId()).thenReturn(uniqueId);
assertEquals("dmulloy2", player.getName());
assertEquals(uniqueId, player.getUniqueId());
}
}
@@ -15,7 +15,7 @@ import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.junit.jupiter.api.AfterAll;
@@ -131,7 +131,7 @@ public class MinecraftReflectionTest {
// The NMS handle for CraftItemStack is null with Material.AIR, make sure it is handled correctly
assertNotNull(
MinecraftReflection.getMinecraftItemStack(CraftItemStack.asCraftCopy(new ItemStack(Material.AIR))));
MinecraftReflection.getMinecraftItemStack(CraftItemStack.getCraftStack(new ItemStack(Material.AIR))));
}
@Test
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more