Compare commits

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

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

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

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

Fixes #3665

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

Co-authored-by: justcarlux <manuelitojuan691@gmail.com>
2026-08-23 14:30:03 -05:00
kik 78b10165ff Fix: Java 17 bytecode target on paper module (#3662)
Fixes #3661
2026-08-19 12:51:56 -05:00
Dan Mulloyandrkfsociety dda284cd7a Fix custom payload handling on modern servers
Co-authored-by: rkfsociety <romankuzminvital@gmail.com>
2026-08-17 21:50:35 -05:00
Dan MulloyandSpagles debdd6d9a2 Fix async packet handling and utility edge cases
Co-authored-by: Spagles <106791090+Spagles@users.noreply.github.com>
2026-08-17 21:23:40 -05:00
Dan Mulloy 2c53c29cf7 Only copy bundle packets when something changes
Fixes #3596
2026-08-17 20:58:33 -05:00
Dan Mulloy 1ef8431764 Fix some issues with bundle packets
Addresses #3596
2026-08-17 20:43:23 -05:00
Dan Mulloy 90eb2a6558 Fix item particles in 26.1+
Fixes #3658
2026-08-17 20:29:16 -05:00
Dan Mulloy 67ce937109 Create modern Paper plugin artifact (#3656) 2026-08-02 20:34:39 -05:00
Dan Mulloy 98856a8b9f Restore compatibility with Java 17
fixes #3643
2026-08-02 19:35:14 -05:00
Dan Mulloy 5ec23ef33b Update guidelines for agents 2026-07-18 14:30:48 -05:00
Matvei Slotvinskiy 589ee12e12 perf: avoid per-call lambda allocation in CachedPackage#getPackageClass (#3651) 2026-07-18 14:04:58 +00:00
LoneDev 6cf7c7d8c3 Fixed typo (#2744) 2026-07-12 21:46:37 -05:00
gamerover98 3139e82aea Fixed TinyProtocol for 1.19+ (#2499) 2026-07-13 02:46:06 +00:00
yesidodo73 3385fe664b fix: select player info data list field (#3650) 2026-07-11 17:20:38 -05:00
Diogo Correia 0e267a5f1a fix: explicitly declare parameters of ServerPingRecord constructor (#3644)
Some Spigot forks (e.g., Leaf) add additional constructors with
different parameters, which causes the previous naive implementation of
getting the first available constructor to fail.
2026-06-24 10:14:39 -05:00
Dan Mulloy 2d3b3e3363 Update to Minecraft 26.2 (#3642) 2026-06-20 10:52:30 -05:00
Aldin 8eeb82f89d fix: only sign non snapshots and non jitpack versions (#3641) 2026-06-20 10:38:13 -05:00
Aldin b0b9b666a7 fix: only sign release versions to support jitpack snapshots builds again (#3639) 2026-06-19 23:05:20 -05:00
NekoEp1sode b723ff3351 fix: NettyChannelInjector.getPlayer() overwrites TemporaryPlayer with null (#3629) 2026-05-31 13:30:13 +00:00
Maurice Eisenblätter 72fd0709b6 Fix duplicate MONITOR listener invocation after async packet processing (#3630) 2026-05-31 09:26:34 -04:00
Richard Strunk 5a9afed462 Fix 26.1.2 problem with invalid constructor selection (#3626) 2026-05-12 14:17:40 -05:00
LeonTG 9404ce32db Fix Injector.getPlayer() using TemporaryPlayer when a Bukkit player is available (#3614) 2026-04-25 23:08:08 +00:00
Copilotanddmulloy2 6c6bde522e Add WrappedPositionMoveRotation for 1.21.2+ entity teleport packets (#3610)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dmulloy2 <3039119+dmulloy2@users.noreply.github.com>
2026-03-27 20:12:42 -05:00
Dan Mulloy f911ee904c Bump version to 5.5.0-SNAPSHOT 2026-03-26 20:42:11 -05:00
Dan Mulloy 22012d3dc9 Mark 26.1 as supported 2026-03-26 20:35:46 -05:00
Dan Mulloy c52b4bb10c Use Java's base64 encoder 2026-03-26 20:30:20 -05:00
Dan Mulloy b9f96469f5 Fix or ignore remaining tests 2026-03-26 20:22:18 -05:00
Dan Mulloy e5413ccc5b Update packet types and protocol versions 2026-03-26 19:52:09 -05:00
Dan Mulloy b37a3bcb13 Start update to 26.1 2026-03-24 22:21:54 -05:00
Dan Mulloy 19cb81b6ab Don't modify read-only bundle packets
Fixes #3603
2026-03-18 15:30:44 -04:00
coolomet f606cc954d Add support for ShreddedPaper's lazy packet execution (#3592) 2026-02-01 23:04:50 -06:00
Antti Koponen 774e679571 Mark 1.21.11 as supported (#3589) 2026-02-02 05:00:59 +00:00
icefear 1257506929 feat: add support for 1.21.11 (#3578) 2025-12-16 16:45:00 -06:00
Dan Mulloy 5773bcbc16 Fix WrappedServerPing#getPlayers in 1.21.10
Fixes #3570
2025-11-16 20:44:10 -06:00
Dan Mulloy 468082c9f9 Bump byte buddy version to fix Java 25
Fixes #3566
2025-10-12 19:28:46 -05:00
Dan Mulloy a11b403c4b Fix GameProfile properties in 1.21.9+
Fixes #3564
2025-10-12 19:26:19 -05:00
Dan Mulloy aa4fab1632 Update to 1.21.9 2025-10-05 18:20:39 -05:00
Dan Mulloy 771a530fe2 Fix CME on disable
Fixes #3544
2025-08-16 22:38:57 -05:00
Diogo Correia ca9b5bbb80 fix: use RegistryOps when (de)serializing chat component on 1.21.6+ (#3546)
This fixes an error during serialization when a hover event has an item
with `components` (e.g., enchantments).
2025-08-13 01:13:43 +00:00
NotSecureFly d790ded81f Use the correct reflection method in setTypeAndData method, and rename GET_BLOCK_DATA to DEFAULT_BLOCK_DATA for clarity (#3530) 2025-08-13 01:10:44 +00:00
Tim203 f357ac85d0 Preserve the pipeline order when async receiving packets (#3351) 2025-08-13 01:07:29 +00:00
Dan Mulloy 66cdb5b69d Move dev builds from Jenkins to GitHub (#3542) 2025-08-10 16:38:54 -05:00
Dan Mulloy 1aeb002afe Update version to 5.4.0 for release 2025-08-06 20:25:03 -05:00
Dan Mulloy 79d44f0d6e Minor build fixes 2025-08-06 20:06:30 -05:00
Dan Mulloy 9bde9b4231 Update group in Gradle example 2025-08-06 11:58:30 -05:00
Dan Mulloy 714252df6f Publish to Maven Central 2025-08-06 11:04:00 -05:00
Leviaria e56bcf624d Added support for Minecraft 1.21.8 (#3523) 2025-07-27 19:30:42 -05:00
Diogo Correia 596e5e4246 fix: incorrect version gating on WrappedTeamParamters (#3496) 2025-06-27 19:22:06 -05:00
Diogo Correia f3f895995a fix: initialization error of ChunkData wrapper on MC 1.21.5+ (#3495) 2025-06-27 12:23:21 -05:00
Diogo Correia 67e7375b2f fix: initialization error for WrappedChatComponent on 1.20.4-1.21.5 (#3493) 2025-06-27 10:17:03 -05:00
Pasqual Koschmieder 9417eee444 drop unnecessary minecraft import from EnumWrappers (#3476) 2025-06-19 12:57:42 -05:00
Dan Mulloy a432fae4dd 1.21.6 update (#3475) 2025-06-18 15:33:20 -05:00
JavaFruit 8a5e59d4f2 fix: re-add string overloads for nametagVisibility and collisionRule in WrappedTeamParameters.Builder (#3445) 2025-05-23 20:03:53 +02:00
Maurice Eisenblätter 90a386b743 Fix some 1.21.5 related issues (unit tests) (#3432) 2025-05-09 19:07:58 -05:00
Pasqual Koschmieder a80762feed update dependencies for java 24 support (#3425) 2025-05-06 14:07:29 -05:00
Brokkonaut a936183b76 Fix WrappedList - NBT lists may now be heterogeneous (#3394) 2025-04-22 19:21:43 -05:00
Pasqual Koschmieder 804aa5df11 fix: data watcher serializer initialization (#3384) 2025-03-31 09:46:08 -05:00
Maurice Eisenblätter 1e73df8600 Initial support for 1.21.5 (#3381) 2025-03-29 17:26:16 -05:00
Ryan 4314a55866 fix: Show helpful message when no supported updater loads (#3358) 2025-03-25 19:37:11 -05:00
Diogo Correia 2c0d632dc2 fix: listOrder not preserved when converting PlayerInfoData (#3347)
The field `listOrder` was always set to zero in the wrapper instead of
being read from the NMS object. This commit fixes that, while adding the
respective constructor and getter.
2025-02-04 01:11:03 +00:00
Pasqual Koschmieder fd5d8c576e fix/improve publishing (#3350) 2025-02-03 19:03:37 -06:00
Diogo Correia 0256953056 fix: unable to convert PlayerInfoData if gamemode is null (#3334) 2024-12-30 19:30:26 -06:00
Dan Mulloy 24262d3f7d Update link to wiki.vg 2024-12-09 14:41:23 -06:00
Brokkonaut 7a85663ded Update to 1.21.4 (#3319) 2024-12-09 14:17:17 -06:00
Dan Mulloy bcff6c6719 Update to 1.21.3 2024-11-08 11:03:51 -06:00
Dan Mulloy 3155e76d06 Mojang Mappings 2024-10-27 21:33:15 -05:00
Dan Mulloy 6845acd89d Update to 1.21.3 2024-10-26 15:21:23 -05:00
166 changed files with 6852 additions and 1840 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.
+8 -4
View File
@@ -1,8 +1,10 @@
name: ProtocolLib full build lifecycle
on:
- push
- pull_request
push:
branches-ignore:
- 'gh-pages'
pull_request:
jobs:
build:
@@ -15,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
@@ -25,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 }}
-1
View File
@@ -181,4 +181,3 @@ logs/
build/
logs/
.java-version
gradle.properties
+124
View File
@@ -0,0 +1,124 @@
# ProtocolLib Agent Guide
This file applies to the entire repository. Read `.github/CONTRIBUTING.md` before changing Minecraft support, packet
definitions, reflection, or public wrappers.
## Repository Basics
- The main project uses the Gradle wrapper. Do not use `TinyProtocol/pom.xml` to build ProtocolLib.
- The Java toolchain and current Spigot dependency are defined in `build.gradle.kts`.
- Keep the Java 25 build toolchain separate from the Java 17 production bytecode target.
- Production code is under `src/main/java`; JUnit tests and NMS test initialization are under `src/test/java`.
- Keep changes focused. Do not combine protocol fixes with unrelated cleanup or formatting.
- Preserve public API and binary compatibility unless the task explicitly requires a breaking change.
## Build and Test Commands
```shell
./gradlew test
./gradlew build shadowJar
```
Run the smallest relevant test while developing:
```shell
./gradlew test --tests fully.qualified.TestClass
./gradlew test --tests fully.qualified.TestClass.testMethod
```
After changing production Java code, run the targeted tests and then the complete test suite. Use `build shadowJar` when
the task affects packaging, dependencies, release support, or the final plugin artifact.
Do not introduce another build system, test runner, formatter, or lint tool unless the repository adopts it separately.
## Important Code Areas
- `PacketType.java`: public packet constants, current IDs, aliases, and deprecated packet definitions.
- `injector/packet/internal/ProtocolRegistry_1_20_5.java`: current packet discovery and codec registration.
- `utility/MinecraftVersion.java`: named Minecraft versions and the latest supported release.
- `utility/MinecraftProtocolVersion.java`: Minecraft release to protocol-number mappings.
- `ProtocolLibrary.java`: supported version bounds and latest release date.
- `utility/MinecraftReflection.java`: centralized NMS and CraftBukkit class discovery.
- `reflect/StructureModifier.java`: indexed packet field access and conversion contract.
- `wrappers/`: stable public representations of unstable NMS structures.
- `events/PacketContainer.java` and `events/AbstractStructure.java`: packet construction and typed field access.
- `src/test/java/com/comphenix/protocol/BukkitInitialization.java`: test bootstrap for CraftBukkit and NMS access.
## Minecraft Release Updates
Follow the complete checklist in `.github/CONTRIBUTING.md`. In particular:
- Update all version metadata together; do not bump only the Gradle dependency.
- Reconcile every serverbound and clientbound packet in every protocol phase with `PacketType`.
- Add packet types for newly split packets instead of forcing incompatible shapes through an older packet constant.
- Preserve removed public `PacketType` constants in the deprecated section.
- Update `MinecraftProtocolVersion` only when the protocol number actually changed.
Every packet exposed by the current NMS protocol registries must have a matching `PacketType`. An undefined or incorrectly
renamed packet can abort registration of later packets in the same registry.
## Version Compatibility
- Put version checks at the narrowest affected boundary.
- Treat the current supported server API shape as the primary path. Use older supported shapes only as narrow fallbacks.
- Keep the pre-change code path intact whenever possible.
- Use named `MinecraftVersion` constants with `atOrAbove()` instead of scattered version strings.
- A public method should keep the same signature and broad contract across versions.
- If Minecraft removed a concept, return an unsupported or absent result consistent with existing APIs. Do not claim an
unrelated packet or class is the removed type.
- When Minecraft splits one packet into several packets, expose and use the new packet types explicitly.
- Preserve unrelated packet fields when adapting or rewriting one logical value.
The current test dependency exercises the latest supported server. Review older guarded paths carefully because they may
not execute in the local test run.
## Packet and Reflection Rules
- Treat the current server packet classes, record components, constructors, and codecs as authoritative.
- Prefer existing `MinecraftReflection`, `FuzzyReflection`, and accessor helpers over new direct reflection.
- Prefer structural and type-based matching where obfuscated names are unstable.
- Search for an existing class resolver, converter, or accessor before adding another one.
- Cache stable reflective lookups following nearby patterns; do not repeatedly scan classes on packet hot paths.
- Do not hide reflection failures with broad catches, silent defaults, or unrelated fallback types.
- Account for static fields, inherited fields, records, nested implementations, and duplicate field types when writing
fuzzy contracts.
## `StructureModifier` Contract
Typed accessors returned from packet APIs must behave like normal `StructureModifier` instances.
- `size()`, `getFields()`, `getField()`, target metadata, and field metadata must agree.
- `readSafely()` returns `null` for an absent index.
- `writeSafely()` ignores an absent index without throwing.
- `withTarget()` must preserve the modifier's behavior for the new target.
- `writeDefaults()` must remain safe.
- Converters must be applied symmetrically on reads and writes.
If a packet no longer contains a real backing field, prefer an empty modifier when compatible. If a virtual field is
required for API compatibility, initialize all modifier metadata and test its inherited methods.
## Wrapper and Converter Rules
- Wrapper factories, converters, getters, setters, and `deepClone()` must describe the same logical value.
- Preserve converter symmetry: converting specific to generic and back must not unexpectedly lose represented state.
- If a wrapper handle changes from a nested value to an entire packet, define logical `equals()` and `hashCode()` so
unrelated packet metadata does not affect equality.
- Clones that represent the same logical value should compare equal.
- Do not fabricate legacy states that the modern wire format cannot distinguish; document or surface the limitation.
- Record or immutable packet updates must retain entity IDs, flags, and other components not owned by the wrapper.
## Tests
- Call `BukkitInitialization.initializeAll()` before tests that resolve CraftBukkit or NMS classes.
- Prefer regression tests using the actual Spigot packet classes supplied by the test dependency.
- Test reads, writes, safe access, cloning, equality, and preservation of unrelated fields when changing wrappers.
- Test modifier metadata and inherited methods when introducing a custom `StructureModifier`.
- Re-enable disabled tests when fixing the incompatibility that caused them to be disabled.
- Do not weaken assertions or encode known incorrect behavior merely to make a new Minecraft version pass.
## Documentation and Review
- Update Javadocs when version support, nullability, or unsupported behavior changes.
- Update `.github/CONTRIBUTING.md` when the repository-wide build or release workflow changes.
- Keep this file operational and durable; do not add task-specific notes, temporary workarounds, or current PR details.
- Before finishing, inspect the complete diff for accidental API changes and unrelated edits.
+64 -23
View File
@@ -2,7 +2,7 @@
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modifying Minecraft directly. A common technique is to modify incoming
and outgoing [packets](https://wiki.vg/Protocol), or inject custom packets into the
and outgoing packets, or inject custom packets into the
stream. This is quite cumbersome to do, however, and most implementations will break
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
@@ -12,16 +12,21 @@ lead to more subtle bugs.
### Resources
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
* [Spigot Page](https://spigotmc.org/resources/protocollib.1997/)
* [Releases](https://github.com/dmulloy2/ProtocolLib/releases)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib/)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
* [Protocol Wiki](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol)
`ProtocolLib.jar` is the primary Paper plugin. Spigot servers must use the compatibility artifact,
`ProtocolLib-Spigot.jar`. The Maven artifact is the platform-neutral library shared by both distributions.
### Compilation
ProtocolLib is built with [Gradle](https://gradle.org/). If you have it installed, just run
`./gradlew build` in the root project folder. Other gradle targets you may be interested in
include `clean`, `test`, and `shadowJar`. `shadowJar` will create a jar with all dependencies
(ByteBuddy) included.
(ByteBuddy) included for both Paper and Spigot.
### A new API
@@ -42,18 +47,11 @@ depend: [ ProtocolLib ]
You can also add ProtocolLib as a Maven dependency:
````xml
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.1.0</version>
<version>5.4.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -63,14 +61,56 @@ Or use the maven dependency with gradle:
```gradle
repositories {
maven { url "https://repo.dmulloy2.net/repository/public/" }
mavenCentral()
}
dependencies {
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
compileOnly 'net.dmulloy2:ProtocolLib:5.4.0'
}
```
#### Snapshot builds
Snapshot builds are available for testing changes before the next release. To use `5.5.0-SNAPSHOT`,
add the [public repository](https://repo.dmulloy2.net/repository/public/) and update your dependency version.
For Maven:
```xml
<repositories>
<repository>
<id>dmulloy2-public</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.5.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
For Gradle:
```gradle
repositories {
mavenCentral()
maven {
url = uri('https://repo.dmulloy2.net/repository/public/')
}
}
dependencies {
compileOnly 'net.dmulloy2:ProtocolLib:5.5.0-SNAPSHOT'
}
```
#### Accessing the API
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
````java
@@ -97,25 +137,26 @@ protocolManager.addPacketListener(new PacketAdapter(
});
````
It's also possible to read and modify the content of these packets. For instance, you can create a global
censor by listening for Packet3Chat events:
It's also possible to read and modify the content of packets, including the ones sent by a client. For
instance, you can censor the text players write on signs by listening for `UPDATE_SIGN` packets:
````java
// Censor
// Censor signs
protocolManager.addPacketListener(new PacketAdapter(
this,
ListenerPriority.NORMAL,
PacketType.Play.Client.CHAT
PacketType.Play.Client.UPDATE_SIGN
) {
@Override
public void onPacketReceiving(PacketEvent event) {
PacketContainer packet = event.getPacket();
String message = packet.getStrings().read(0);
String[] lines = packet.getStringArrays().read(0);
if (message.contains("shit") || message.contains("damn")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
for (int i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace("shit", "****").replace("damn", "****");
}
packet.getStringArrays().write(0, lines);
}
});
````
@@ -64,7 +64,7 @@ public abstract class TinyProtocol {
// Packets we have to intercept
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getClass("{nms}.PacketLoginInStart", "net.minecraft.network.protocol.login.PacketLoginInStart");
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
private static final FieldAccessor<String> getPlayerName = new PlayerNameAccessor();
// Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
@@ -492,6 +492,59 @@ public abstract class TinyProtocol {
}
}
/**
* Get the player name from the login start packet.
* This fixes the issue from 1.19 where the GameProfile field has been removed from this login packet.
*
* @author gamerover98
*/
private static class PlayerNameAccessor implements FieldAccessor<String> {
private FieldAccessor<String> getPlayerName;
private FieldAccessor<GameProfile> getGameProfile;
PlayerNameAccessor() {
try {
this.getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
} catch (IllegalArgumentException illegalArgumentException) {
// nothing to do.
}
try {
//HOT-FIX for 1.19+
this.getPlayerName = Reflection.getField(PACKET_LOGIN_IN_START, String.class, 0);
} catch (IllegalArgumentException illegalArgumentException) {
// nothing to do.
}
if (getGameProfile == null && getPlayerName == null) {
throw new UnsupportedOperationException("The current server version is not supported by TinyProtocol");
}
}
@Override
public String get(Object target) {
if (getPlayerName != null) {
String playerName = getPlayerName.get(target);
return playerName.substring(0, Math.min(16, playerName.length()));
}
return getGameProfile.get(target).getName();
}
@Override
public void set(Object target, Object value) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public boolean hasField(Object target) {
return getPlayerName != null
? getPlayerName.hasField(target)
: getGameProfile.hasField(target);
}
}
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
@@ -533,8 +586,7 @@ public abstract class TinyProtocol {
private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
GameProfile profile = getGameProfile.get(packet);
channelLookup.put(profile.getName(), channel);
channelLookup.put(getPlayerName.get(packet), channel);
}
}
}
-130
View File
@@ -1,130 +0,0 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'com.gradleup.shadow' version '8.3.2'
}
group = 'com.comphenix.protocol'
version = '5.3.0'
description = 'Provides access to the Minecraft protocol'
def isSnapshot = version.endsWith('-SNAPSHOT')
repositories {
// mavenLocal() // can speed up build, but may fail in CI
mavenCentral()
maven {
url 'https://repo.dmulloy2.net/repository/public/'
}
maven {
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
}
maven {
url 'https://libraries.minecraft.net/'
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.15.1'
compileOnly 'org.spigotmc:spigot-api:1.21.1-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.21.1-R0.1-SNAPSHOT'
compileOnly 'io.netty:netty-all:4.0.23.Final'
compileOnly 'net.kyori:adventure-text-serializer-gson:4.14.0'
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0'
testImplementation 'org.mockito:mockito-core:5.6.0'
testImplementation 'io.netty:netty-common:4.1.97.Final'
testImplementation 'io.netty:netty-transport:4.1.97.Final'
testImplementation 'org.spigotmc:spigot:1.21.1-R0.1-SNAPSHOT'
testImplementation 'net.kyori:adventure-text-serializer-gson:4.14.0'
testImplementation 'net.kyori:adventure-text-serializer-plain:4.14.0'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withJavadocJar()
withSourcesJar()
}
jar {
manifest {
attributes(
'paperweight-mappings-namespace': 'mojang'
)
}
}
shadowJar {
dependencies {
include(dependency('net.bytebuddy:byte-buddy:.*'))
}
relocate 'net.bytebuddy', 'com.comphenix.net.bytebuddy'
archiveFileName = 'ProtocolLib.jar'
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = 'full'
}
}
compileTestJava {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
processResources {
def includeBuild = isSnapshot && System.getenv('BUILD_NUMBER')
def fullVersion = includeBuild
? version + '-' + System.getenv('BUILD_NUMBER')
: version
eachFile { expand(['version': fullVersion]) }
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
afterEvaluate {
artifactId = 'ProtocolLib'
}
}
}
repositories {
maven {
url isSnapshot
? 'https://repo.dmulloy2.net/repository/snapshots/'
: 'https://repo.dmulloy2.net/repository/releases/'
credentials {
username project.nexusUsername
password project.nexusPassword
}
}
}
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
tasks.withType(Javadoc) {
options.encoding = 'UTF-8'
}
+134
View File
@@ -0,0 +1,134 @@
plugins {
`java-library`
`maven-publish`
signing
id("io.github.patrick.remapper") version "1.4.3"
id("com.vanniktech.maven.publish") version "0.36.0"
}
group = "net.dmulloy2"
description = "Provides access to the Minecraft protocol"
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val isJitPack = System.getenv("JITPACK")?.equals("true", ignoreCase = true) ?: false
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
repositories {
if (!isCI) {
mavenLocal()
}
mavenCentral()
maven {
url = uri("https://repo.codemc.io/repository/nms/")
}
maven {
url = uri("https://hub.spigotmc.org/nexus/content/groups/public/")
}
maven {
url = uri("https://libraries.minecraft.net/")
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation("net.bytebuddy:byte-buddy:1.18.2")
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
compileOnly("commons-lang:commons-lang:2.6")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
sourceSets {
test {
java.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
}
tasks {
test {
dependsOn(":paper:test")
}
check {
dependsOn(":paper:check", ":spigot:check")
}
javadoc {
options.encoding = "UTF-8"
}
compileJava {
options.encoding = "UTF-8"
options.release.set(17)
}
}
tasks.register("shadowJar") {
group = "build"
description = "Builds the Paper and Spigot plugin distributions."
dependsOn(":paper:shadowJar", ":spigot:shadowJar")
}
mavenPublishing {
publishToMavenCentral()
if (!isSnapshot && !isJitPack) {
signAllPublications()
}
coordinates("$group", project.name, "$version")
pom {
name.set(project.name)
description.set(project.description)
inceptionYear.set("2012")
url.set("https://github.com/dmulloy2/ProtocolLib")
developers {
developer {
id.set("dmulloy2")
name.set("Dan Mulloy")
url.set("https://dmulloy2.net/")
email.set("dev@dmulloy2.net")
}
}
licenses {
license {
name.set("GNU GENERAL PUBLIC LICENSE - Version 2, June 1991")
url.set("https://www.gnu.org/licenses/gpl-2.0.txt")
distribution.set("repo")
}
}
scm {
tag.set("HEAD")
url.set("https://github.com/dmulloy2/ProtocolLib")
connection.set("scm:git:git://github.com/dmulloy2/ProtocolLib.git")
developerConnection.set("scm:git:git@github.com:dmulloy2/ProtocolLib.git")
}
issueManagement {
system.set("GitHub Issues")
url.set("https://github.com/dmulloy2/ProtocolLib/issues")
}
}
}
+7 -2
View File
@@ -1,2 +1,7 @@
nexusUsername=""
nexusPassword=""
version=5.5.0-SNAPSHOT
mcVersion=26.3
mavenCentralUsername=
mavenCentralPassword=
signing.keyId=
signing.password=
signing.secretKeyRingFile=
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+13 -12
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+104
View File
@@ -0,0 +1,104 @@
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
id("com.gradleup.shadow") version "9.4.0"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
group = "net.dmulloy2"
version = rootProject.version
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.codemc.io/repository/nms/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://libraries.minecraft.net/") {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation(project(":"))
paperweight.paperDevBundle("$mcVersion.build.+")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("commons-lang:commons-lang:2.6")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-common:4.2.8.Final")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("net.kyori:adventure-text-serializer-gson:4.25.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.25.0")
testImplementation("commons-lang:commons-lang:2.6")
}
paperweight.reobfArtifactConfiguration =
io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
configurations.matching { it.name == "compileClasspath" || it.name == "runtimeClasspath" || it.name == "testCompileClasspath" || it.name == "testRuntimeClasspath" }.configureEach {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
sourceSets {
test {
java.srcDir(rootProject.file("src/test/java"))
resources.srcDir(rootProject.file("src/test/resources"))
}
}
tasks {
processResources {
val fullVersion = if (isSnapshot && isCI) "${version}-${commitHash.take(7)}" else version
eachFile {
expand("version" to fullVersion)
}
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
compileJava {
options.release.set(17)
}
shadowJar {
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
archiveFileName.set("ProtocolLib.jar")
}
}
@@ -0,0 +1,105 @@
package com.comphenix.protocol.paper;
import java.util.List;
import java.util.function.Consumer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.internal.PlatformProvider;
import io.netty.channel.Channel;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.network.ChannelInitializeListenerHolder;
import net.kyori.adventure.key.Key;
public final class PaperPlatformProvider implements PlatformProvider {
public static final Key CHANNEL_INITIALIZER_KEY = Key.key("protocollib", "channel-initializer");
@Override
public boolean hasEarlyChannelInitialization() {
return true;
}
@Override
public Runnable registerChannelInitializer(Consumer<Channel> channelInitializer) {
ChannelInitializeListenerHolder.addListener(CHANNEL_INITIALIZER_KEY, channelInitializer::accept);
return () -> ChannelInitializeListenerHolder.removeListener(CHANNEL_INITIALIZER_KEY);
}
@Override
public void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor) {
CommandMetadata metadata = getMetadata(name);
Command command = new RegisteredCommand(name, metadata);
plugin.registerCommand(
name,
metadata.description(),
metadata.aliases(),
new RegisteredBasicCommand(name, metadata, command, executor));
}
private static CommandMetadata getMetadata(String name) {
return switch (name) {
case "protocol" -> new CommandMetadata(
"Performs administrative tasks regarding ProtocolLib.",
"/<command> config|check|update|timings|listeners|version|dump",
"protocol.admin",
List.of());
case "packet" -> new CommandMetadata(
"Add or remove a simple packet listener.",
"/<command> add|remove|names client|server [ID start]-[ID stop] [detailed]",
"protocol.admin",
List.of());
case "filter" -> new CommandMetadata(
"Add or remove programmable filters to the packet listeners.",
"/<command> add|remove name [ID start]-[ID stop]",
"protocol.admin",
List.of("packet_filter"));
case "packetlog" -> new CommandMetadata(
"Logs hex representations of packets to a file or console",
"/<command> <protocol> <sender> <packet> [location]",
"protocol.admin",
List.of());
default -> throw new IllegalArgumentException("Unknown ProtocolLib command " + name);
};
}
private record CommandMetadata(String description, String usage, String permission, List<String> aliases) {
}
private record RegisteredBasicCommand(
String name,
CommandMetadata metadata,
Command command,
CommandExecutor executor) implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] arguments) {
if (!this.executor.onCommand(source.getSender(), this.command, this.name, arguments)) {
source.getSender().sendMessage(this.metadata.usage().replace("<command>", this.name));
}
}
@Override
public String permission() {
return this.metadata.permission();
}
}
private static final class RegisteredCommand extends Command {
private RegisteredCommand(String name, CommandMetadata metadata) {
super(name, metadata.description(), metadata.usage(), metadata.aliases());
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
throw new UnsupportedOperationException();
}
}
}
@@ -0,0 +1 @@
com.comphenix.protocol.paper.PaperPlatformProvider
+22
View File
@@ -0,0 +1,22 @@
name: ProtocolLib
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2]
main: com.comphenix.protocol.ProtocolLib
load: STARTUP
api-version: "26.3"
folia-supported: true
permissions:
protocol.*:
description: Gives access to everything.
children:
protocol.admin: true
protocol.info: true
protocol.admin:
description: Able to initiate the update process and configure debug mode.
default: op
protocol.info:
description: Can read update notifications and error reports.
default: op
+2
View File
@@ -3,3 +3,5 @@
*/
rootProject.name = 'ProtocolLib'
include 'paper', 'spigot'
+79
View File
@@ -0,0 +1,79 @@
import org.gradle.api.attributes.java.TargetJvmVersion
plugins {
`java-library`
id("com.gradleup.shadow") version "9.4.0"
}
group = "net.dmulloy2"
version = rootProject.version
val mcVersion: String by project
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.codemc.io/repository/nms/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://libraries.minecraft.net/") {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation(project(":"))
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
}
configurations.matching { it.name == "compileClasspath" || it.name == "runtimeClasspath" || it.name == "testCompileClasspath" || it.name == "testRuntimeClasspath" }.configureEach {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
tasks {
processResources {
eachFile {
expand("version" to project.version)
}
}
test {
useJUnitPlatform()
}
compileJava {
options.release.set(17)
}
shadowJar {
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
archiveFileName.set("ProtocolLib-Spigot.jar")
}
}
@@ -0,0 +1,34 @@
package com.comphenix.protocol.spigot;
import java.util.function.Consumer;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.internal.PlatformProvider;
import io.netty.channel.Channel;
public final class SpigotPlatformProvider implements PlatformProvider {
@Override
public boolean hasEarlyChannelInitialization() {
return false;
}
@Override
public Runnable registerChannelInitializer(Consumer<Channel> channelInitializer) {
throw new UnsupportedOperationException("Early channel initialization is unavailable");
}
@Override
public void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor) {
PluginCommand command = plugin.getCommand(name);
if (command == null) {
throw new IllegalStateException("plugin.yml might be corrupt.");
}
command.setExecutor(executor);
}
}
@@ -0,0 +1 @@
com.comphenix.protocol.spigot.SpigotPlatformProvider
@@ -1,7 +1,7 @@
name: ProtocolLib
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2, comphenix]
authors: [dmulloy2]
main: com.comphenix.protocol.ProtocolLib
load: STARTUP
@@ -44,4 +44,4 @@ permissions:
default: op
protocol.info:
description: Can read update notifications and error reports.
default: op
default: op
@@ -0,0 +1,26 @@
package com.comphenix.protocol;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.spigot.SpigotPlatformProvider;
class PlatformCommandRegistrarTest {
@Test
void registersLegacyExecutorFromPluginDescriptor() {
ProtocolLib plugin = mock(ProtocolLib.class);
CommandExecutor executor = mock(CommandExecutor.class);
PluginCommand command = mock(PluginCommand.class);
when(plugin.getCommand("protocol")).thenReturn(command);
new SpigotPlatformProvider().registerCommand(plugin, "protocol", executor);
verify(command).setExecutor(executor);
}
}
@@ -0,0 +1,17 @@
package com.comphenix.protocol.injector.netty.manager;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.internal.PlatformProvider;
class PlatformChannelInitializerTest {
@Test
void reportsPaperInitializerUnavailable() {
PlatformProvider provider = PlatformProvider.get();
assertInstanceOf(com.comphenix.protocol.spigot.SpigotPlatformProvider.class, provider);
assertFalse(provider.hasEarlyChannelInitialization());
}
}
@@ -120,25 +120,9 @@ class CommandProtocol extends CommandBase {
return;
}
// Perform on an async thread
Runnable notify = new Runnable() {
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
updateFinished();
}
};
// Top-level class instead of anonymous inner class so that PaperPluginClassLoader
// can resolve the .class entry when BukkitScheduler dispatches it on the main thread.
Runnable notify = new UpdateNotifyRunnable(sender, command, updater, config, plugin, this);
updater.start(type);
updater.addListener(notify);
}
@@ -14,18 +14,18 @@ import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
import com.comphenix.protocol.PacketTypeLookup.ClassLookup;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.scheduler.UniversalRunnable;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
/**
* Represents the type of a packet in a specific protocol.
* <p>
@@ -109,130 +109,148 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType BUNDLE = new PacketType(PROTOCOL, SENDER, 0x00, "BundleDelimiter", "Delimiter", "BundleDelimiterPacket");
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x01, "AddEntity", "SpawnEntity", "SPacketSpawnObject");
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x02, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x03, "Animate", "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x04, "AwardStats", "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, "BlockDestruction", "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "BlockEntityData", "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "BlockEvent", "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x09, "BlockUpdate", "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0A, "BossEvent", "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0B, "ChangeDifficulty", "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHUNK_BATCH_FINISHED = new PacketType(PROTOCOL, SENDER, 0x0C, "ChunkBatchFinished");
public static final PacketType CHUNK_BATCH_START = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunkBatchStart");
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0E, "ChunksBiomes", "ClientboundChunksBiomesPacket");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0F, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x10, "CommandSuggestions", "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x11, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetData", "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x15, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x16, "CookieRequest");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x17, "Cooldown", "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x18, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x19, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x1A, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1C, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1D, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1E, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1F, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x20, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x22, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x23, "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x24, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x25, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x26, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x27, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x28, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x29, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x2A, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x2B, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x2C, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2D, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x30, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x31, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x32, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x33, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x34, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x35, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x36, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x37, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x38, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3D, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3E, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x3F, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerPosition", "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x41, "Recipe", "Recipes", "SPacketRecipeBook");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x42, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x43, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x44, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x45, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x46, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x47, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x48, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x49, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x4A, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x4B, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x4C, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x4D, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x4E, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x4F, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x50, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x51, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x52, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x53, "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x54, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x55, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x56, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x57, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x58, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x59, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x5A, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x5B, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x5C, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x5D, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5E, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x60, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x61, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x62, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x63, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x64, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x65, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x66, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x67, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x68, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x69, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x6A, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x6B, "StoreCookie");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x6C, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x6D, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x6E, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x6F, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x70, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x71, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x72, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x73, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x74, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x75, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x76, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x77, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x78, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x79, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x7A, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x7B, "ServerLinks");
// ---- Removed in 1.9
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x02, "Animate", "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x03, "AwardStats", "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x04, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x05, "BlockDestruction", "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x06, "BlockEntityData", "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x07, "BlockEvent", "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x08, "BlockUpdate", "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x09, "BossEvent", "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0A, "ChangeDifficulty", "ServerDifficulty", "SPacketServerDifficulty");
public static final PacketType CHUNK_BATCH_FINISHED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchFinished");
public static final PacketType CHUNK_BATCH_START = new PacketType(PROTOCOL, SENDER, 0x0C, "ChunkBatchStart");
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunksBiomes", "ClientboundChunksBiomesPacket");
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0E, "ClearTitles");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestions", "TabComplete", "SPacketTabComplete");
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x10, "Commands");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetData", "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x15, "CookieRequest");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x16, "Cooldown", "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x17, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x18, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x19, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DEBUG_BLOCK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugBlockValue");
public static final PacketType DEBUG_CHUNK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugChunkValue");
public static final PacketType DEBUG_ENTITY_VALUE = new PacketType(PROTOCOL, SENDER, 0x1C, "DebugEntityValue");
public static final PacketType DEBUG_EVENT = new PacketType(PROTOCOL, SENDER, 0x1D, "DebugEvent");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1E, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1F, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x20, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x21, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x22, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x23, "EntityPositionSync");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x24, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType ADD_TRANSIENT_BLOCK = new PacketType(PROTOCOL, SENDER, 0x25, MinecraftVersion.v26_3, "AddTransientBlock");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x26, MinecraftVersion.v26_3, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x27, MinecraftVersion.v26_3, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType GAME_RULE_VALUES = new PacketType(PROTOCOL, SENDER, 0x28, MinecraftVersion.v26_3, "GameRuleValues");
public static final PacketType GAME_TEST_HIGHLIGHT_POS = new PacketType(PROTOCOL, SENDER, 0x29, MinecraftVersion.v26_3, "GameTestHighlightPos");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x2A, MinecraftVersion.v26_3, "MountScreenOpen", "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2B, MinecraftVersion.v26_3, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x2C, MinecraftVersion.v26_3, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x2D, MinecraftVersion.v26_3, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x2E, MinecraftVersion.v26_3, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x2F, MinecraftVersion.v26_3, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x30, MinecraftVersion.v26_3, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x31, MinecraftVersion.v26_3, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x32, MinecraftVersion.v26_3, "Login", "SPacketJoinGame");
public static final PacketType LOW_DISK_SPACE_WARNING = new PacketType(PROTOCOL, SENDER, 0x33, MinecraftVersion.v26_3, "LowDiskSpaceWarning");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x34, MinecraftVersion.v26_3, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x35, MinecraftVersion.v26_3, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x36, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x37, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x38, MinecraftVersion.v26_3, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x39, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x3A, MinecraftVersion.v26_3, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x3B, MinecraftVersion.v26_3, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x3C, MinecraftVersion.v26_3, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x3D, MinecraftVersion.v26_3, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x3E, MinecraftVersion.v26_3, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x3F, MinecraftVersion.v26_3, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x40, MinecraftVersion.v26_3, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x41, MinecraftVersion.v26_3, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x42, MinecraftVersion.v26_3, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x43, MinecraftVersion.v26_3, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x44, MinecraftVersion.v26_3, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x45, MinecraftVersion.v26_3, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x46, MinecraftVersion.v26_3, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x47, MinecraftVersion.v26_3, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x48, MinecraftVersion.v26_3, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x49, MinecraftVersion.v26_3, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x4A, MinecraftVersion.v26_3, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x4B, MinecraftVersion.v26_3, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x4C, MinecraftVersion.v26_3, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x4D, MinecraftVersion.v26_3, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x4E, MinecraftVersion.v26_3, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x4F, MinecraftVersion.v26_3, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x50, MinecraftVersion.v26_3, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x51, MinecraftVersion.v26_3, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x52, MinecraftVersion.v26_3, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType POST_EFFECTS = new PacketType(PROTOCOL, SENDER, 0x53, MinecraftVersion.v26_3, "PostEffects");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x54, MinecraftVersion.v26_3, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x55, MinecraftVersion.v26_3, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x56, MinecraftVersion.v26_3, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x57, MinecraftVersion.v26_3, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x58, MinecraftVersion.v26_3, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x59, MinecraftVersion.v26_3, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x5A, MinecraftVersion.v26_3, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x5B, MinecraftVersion.v26_3, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x5C, MinecraftVersion.v26_3, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x5D, MinecraftVersion.v26_3, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5E, MinecraftVersion.v26_3, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x5F, MinecraftVersion.v26_3, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x60, MinecraftVersion.v26_3, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x61, MinecraftVersion.v26_3, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x62, MinecraftVersion.v26_3, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x63, MinecraftVersion.v26_3, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x64, MinecraftVersion.v26_3, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x65, MinecraftVersion.v26_3, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x66, MinecraftVersion.v26_3, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x67, MinecraftVersion.v26_3, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x68, MinecraftVersion.v26_3, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x69, MinecraftVersion.v26_3, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x6A, MinecraftVersion.v26_3, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x6B, MinecraftVersion.v26_3, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x6C, MinecraftVersion.v26_3, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x6D, MinecraftVersion.v26_3, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x6E, MinecraftVersion.v26_3, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x6F, MinecraftVersion.v26_3, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x70, MinecraftVersion.v26_3, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x71, MinecraftVersion.v26_3, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x72, MinecraftVersion.v26_3, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x73, MinecraftVersion.v26_3, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x74, MinecraftVersion.v26_3, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x75, MinecraftVersion.v26_3, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x76, MinecraftVersion.v26_3, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x77, MinecraftVersion.v26_3, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x78, MinecraftVersion.v26_3, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x79, MinecraftVersion.v26_3, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x7A, MinecraftVersion.v26_3, "StoreCookie");
public static final PacketType SWING_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x7B, MinecraftVersion.v26_3, "SwingAnimation");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x7C, MinecraftVersion.v26_3, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x7D, MinecraftVersion.v26_3, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x7E, MinecraftVersion.v26_3, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x7F, MinecraftVersion.v26_3, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x80, MinecraftVersion.v26_3, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x81, MinecraftVersion.v26_3, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x82, MinecraftVersion.v26_3, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x83, MinecraftVersion.v26_3, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x84, MinecraftVersion.v26_3, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x85, MinecraftVersion.v26_3, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x86, MinecraftVersion.v26_3, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x87, MinecraftVersion.v26_3, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x88, MinecraftVersion.v26_3, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x89, MinecraftVersion.v26_3, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x8A, MinecraftVersion.v26_3, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x8B, MinecraftVersion.v26_3, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x8C, MinecraftVersion.v26_3, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x8D, MinecraftVersion.v26_3, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8E, MinecraftVersion.v26_3, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8F, MinecraftVersion.v26_3, "ShowDialog");
/**
* @deprecated Removed in 1.9
@@ -252,8 +270,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType UPDATE_ENTITY_NBT = new PacketType(PROTOCOL, SENDER, 253, "UpdateEntityNBT");
// ----- Renamed packets
/**
* @deprecated Renamed to {@link #WINDOW_DATA}
*/
@@ -278,8 +294,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType OPEN_SIGN_ENTITY = OPEN_SIGN_EDITOR.clone();
// ----- Replaced in 1.9.4
/**
* @deprecated Replaced by {@link #TILE_ENTITY_DATA}
*/
@@ -287,8 +301,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType UPDATE_SIGN = MinecraftReflection.signUpdateExists() ? new PacketType(PROTOCOL, SENDER, 252, "UpdateSign") :
TILE_ENTITY_DATA.clone();
// ---- Removed in 1.14
/**
* @deprecated Removed in 1.14
*/
@@ -403,6 +415,18 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 234, "ResourcePackSend", "SPacketResourcePackSend");
/**
* @deprecated Removed in 1.21.2: replaced with individual packets for adding/removing recipes
*/
@Deprecated
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 233, "Recipe", "Recipes", "SPacketRecipeBook");
/**
* @deprecated Removed in 1.21.5; replace by SPAWN_ENTITY
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 232, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
private static final Server INSTANCE = new Server();
// Prevent accidental construction
@@ -424,63 +448,74 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "AcceptTeleportation", "TeleportAccept", "CPacketConfirmTeleport");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x02, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x03, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x05, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x06, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x07, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x08, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x09, "ClientCommand", "CPacketClientStatus");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0A, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0B, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0C, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x0D, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0E, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0F, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x10, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x11, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x12, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x13, "DebugSampleSubscription");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x14, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x15, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x16, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x17, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x18, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x19, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1A, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1B, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x1C, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x1D, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x1E, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x1F, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x20, "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x21, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x22, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x23, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x24, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x25, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x26, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x27, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x28, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x29, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x2A, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x2B, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x2C, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x2D, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x2E, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x30, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x31, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x32, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x33, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x34, "SetStructureBlock", "Struct");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x35, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x36, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x37, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType ATTACK = new PacketType(PROTOCOL, SENDER, 0x01, "Attack");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x02, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x03, "SelectBundleItem");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x05, "ChangeGameMode");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x08, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x09, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x0A, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientCommand", "CPacketClientStatus");
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientTickEnd");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x10, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x15, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x16, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SUBSCRIPTION_REQUEST = new PacketType(PROTOCOL, SENDER, 0x17, "DebugSubscriptionRequest");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x18, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x19, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x1A, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1B, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1C, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1D, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x21, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x23, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromBlock");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x25, "PickItemFromEntity", "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x26, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x27, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2C, "PlayerLoaded");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2D, "Pong");
public static final PacketType PUNCH = new PacketType(PROTOCOL, SENDER, 0x2E, MinecraftVersion.v26_3, "Punch");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2F, MinecraftVersion.v26_3, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x30, MinecraftVersion.v26_3, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x31, MinecraftVersion.v26_3, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x32, MinecraftVersion.v26_3, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x33, MinecraftVersion.v26_3, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x34, MinecraftVersion.v26_3, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x35, MinecraftVersion.v26_3, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x36, MinecraftVersion.v26_3, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x37, MinecraftVersion.v26_3, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x38, MinecraftVersion.v26_3, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x39, MinecraftVersion.v26_3, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_GAME_RULE = new PacketType(PROTOCOL, SENDER, 0x3A, MinecraftVersion.v26_3, "SetGameRule");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x3B, MinecraftVersion.v26_3, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x3C, MinecraftVersion.v26_3, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3D, MinecraftVersion.v26_3, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3E, MinecraftVersion.v26_3, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType SPECTATE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x3F, MinecraftVersion.v26_3, "SpectatorAction", "SpectateEntity");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x40, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType TEST_INSTANCE_BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x41, "TestInstanceBlockAction");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x42, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x43, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x44, "CustomClickAction");
/**
* @deprecated Removed in 1.17
@@ -500,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
*/
@@ -609,7 +656,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@ForceAsync
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, "LoginDisconnect", "Disconnect", "SPacketDisconnect");
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "Hello", "EncryptionBegin", "SPacketEncryptionRequest");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "GameProfile", "Success", "SPacketLoginSuccess");
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "LoginFinished", "GameProfile", "Success", "SPacketLoginSuccess");
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, "LoginCompression", "SetCompression", "SPacketEnableCompression");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, "CustomQuery", "CustomPayload", "SPacketCustomPayload");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x05, "CookieRequest");
@@ -681,13 +728,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType REGISTRY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "RegistryData");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x08, "ResourcePackPop");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x09, "ResourcePackPush");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0A, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0B, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0C, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x0F, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x10, "ServerLinks");
public static final PacketType POST_EFFECTS = new PacketType(PROTOCOL, SENDER, 0x0A, "PostEffects");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0B, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0C, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0E, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0F, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x10, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x11, "ServerLinks");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x12, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x13, "ShowDialog");
public static final PacketType CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x14, "CodeOfConduct");
/**
* @deprecated Removed in 1.20.4: replaced with new packets for removing and sending resource packs
@@ -719,6 +770,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x05, "Pong");
public static final PacketType RESOURCE_PACK_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ResourcePack");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x07, "ServerboundSelectKnownPacks");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "CustomClickAction");
public static final PacketType ACCEPT_CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x09, "AcceptCodeOfConduct");
private static final Client INSTANCE = new Client();
@@ -49,10 +49,11 @@ import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.internal.PlatformProvider;
/**
* The main entry point for ProtocolLib.
*
@@ -444,14 +445,7 @@ public class ProtocolLib extends JavaPlugin {
return;
}
PluginCommand command = this.getCommand(name);
// Try to load the command
if (command != null) {
command.setExecutor(executor);
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
PlatformProvider.get().registerCommand(this, name, executor);
} catch (RuntimeException e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
@@ -504,6 +498,17 @@ public class ProtocolLib extends JavaPlugin {
}
private void checkUpdates() {
// Do not run updater if no supported updater is loaded
if (this.updater == null) {
highlyVisibleError(
" WARNING ",
" ProtocolLib is unable to check for updates. ",
" You are likely not using a supported version (Such as CraftBukkit). "
);
ProtocolLibrary.disableUpdates();
return;
}
// Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
@@ -35,12 +35,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.21.1";
public static final String MAXIMUM_MINECRAFT_VERSION = "26.3";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (26.3) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2024-08-08";
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();
}
}
}
@@ -19,8 +19,6 @@ package com.comphenix.protocol.injector.packet;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -31,18 +29,15 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.internal.ProtocolRegistry_1_20_5;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
@@ -70,7 +65,7 @@ public class PacketRegistry {
*
* @author Kristian
*/
private static class Register {
public static class Register {
// The main lookup table
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
@@ -82,14 +77,18 @@ public class PacketRegistry {
volatile Set<PacketType> clientPackets = new HashSet<>();
final List<MapContainer> containers = new ArrayList<>();
public Register() {
private Register() {
}
public void registerPacket(PacketType type, Class<?> clazz, Sender sender) {
typeToClass.put(type, Optional.of(clazz));
public void registerPacket(PacketType type, Class<?> packetClass, Sender sender, WrappedStreamCodec codec) {
typeToClass.put(type, Optional.of(packetClass));
classToType.put(packetClass, type);
classToType.put(clazz, type);
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(clazz, type);
if (codec != null) {
classToCodec.put(packetClass, codec);
}
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(packetClass, type);
if (sender == Sender.CLIENT) {
clientPackets.add(type);
@@ -293,206 +292,9 @@ public class PacketRegistry {
return result;
}
@SuppressWarnings("unchecked")
private static synchronized Register createRegisterV1_20_5() {
Object[] protocols = ENUM_PROTOCOL.getEnumConstants();
// PacketType<?> to class map
final Map<Object, Class<?>> packetTypeMap = new HashMap<>();
// List of all class containing PacketTypes
String[] packetTypesClassNames = new String[] {
"common.CommonPacketTypes",
"configuration.ConfigurationPacketTypes",
"cookie.CookiePacketTypes",
"game.GamePacketTypes",
"handshake.HandshakePacketTypes",
"login.LoginPacketTypes",
"ping.PingPacketTypes",
"status.StatusPacketTypes"
};
Class<?> packetTypeClass = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");
for (String packetTypesClassName : packetTypesClassNames) {
Class<?> packetTypesClass = MinecraftReflection
.getOptionalNMS("network.protocol." + packetTypesClassName)
.orElse(null);
if (packetTypesClass == null) {
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
continue;
}
// check every field for "static final PacketType<?>"
for (Field field : packetTypesClass.getDeclaredFields()) {
try {
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object packetType = field.get(null);
if (!packetTypeClass.isInstance(packetType)) {
continue;
}
// retrieve the generic type T of the PacketType<T> field
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (packet instanceof Class<?>) {
packetTypeMap.put(packetType, (Class<?>) packet);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
// ID to Packet class maps
final Map<Object, Map<Class<?>, Integer>> serverMaps = new LinkedHashMap<>();
final Map<Object, Map<Class<?>, Integer>> clientMaps = new LinkedHashMap<>();
// global registry instance
final Register result = new Register();
// List of all class containing ProtocolInfos
String[] protocolClassNames = new String[] {
"configuration.ConfigurationProtocols",
"game.GameProtocols",
"handshake.HandshakeProtocols",
"login.LoginProtocols",
"status.StatusProtocols"
};
Class<?> protocolInfoClass = MinecraftReflection.getProtocolInfoClass();
Class<?> protocolInfoUnboundClass = MinecraftReflection.getProtocolInfoUnboundClass();
Class<?> streamCodecClass = MinecraftReflection.getStreamCodecClass();
Class<?> idCodecClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec");
Class<?> idCodecEntryClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec$Entry", "network.codec.IdDispatchCodec$b");
Class<?> protocolDirectionClass = MinecraftReflection.getPacketFlowClass();
Function<?, ?> emptyFunction = input -> input;
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(protocolInfoClass);
MethodAccessor protocolAccessor = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
MethodAccessor directionAccessor = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("flow", protocolDirectionClass, new Class[0]));
MethodAccessor codecAccessor = Accessors.getMethodAccessor(
protocolInfoReflection.getMethodByReturnTypeAndParameters("codec", streamCodecClass, new Class[0]));
MethodAccessor bindAccessor = Accessors.getMethodAccessor(FuzzyReflection.fromClass(protocolInfoUnboundClass)
.getMethodByReturnTypeAndParameters("bind", protocolInfoClass, new Class[] { Function.class }));
FuzzyReflection idCodecReflection = FuzzyReflection.fromClass(idCodecClass, true);
FieldAccessor byIdAccessor = Accessors.getFieldAccessor(idCodecReflection
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(List.class).build()));
FieldAccessor toIdAccessor = Accessors.getFieldAccessor(idCodecReflection
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).build()));
FuzzyReflection idCodecEntryReflection = FuzzyReflection.fromClass(idCodecEntryClass, true);
MethodAccessor idCodecEntryTypeAccessor = Accessors.getMethodAccessor(idCodecEntryReflection
.getMethodByReturnTypeAndParameters("type", Object.class, new Class[0]));
MethodAccessor idCodecEntrySerializerAccessor = Accessors.getMethodAccessor(idCodecEntryReflection
.getMethodByReturnTypeAndParameters("serializer", streamCodecClass, new Class[0]));
for (String protocolClassName : protocolClassNames) {
Class<?> protocolClass = MinecraftReflection
.getOptionalNMS("network.protocol." + protocolClassName)
.orElse(null);
if (protocolClass == null) {
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocolClassName);
continue;
}
for (Field field : protocolClass.getDeclaredFields()) {
try {
// ignore none static and final fields
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object protocolInfo = field.get(null);
// bind unbound ProtocolInfo to empty function to get real ProtocolInfo
if (protocolInfoUnboundClass.isInstance(protocolInfo)) {
protocolInfo = bindAccessor.invoke(protocolInfo, new Object[] { emptyFunction });
}
// ignore any field that isn't a ProtocolInfo
if (!protocolInfoClass.isInstance(protocolInfo)) {
continue;
}
// get codec and check if codec is instance of IdDispatchCodec
// since that is the only support codec as of now
Object codec = codecAccessor.invoke(protocolInfo);
if (!idCodecClass.isInstance(codec)) {
continue;
}
// retrieve packetTypeMap and convert it to packetIdMap
Map<Class<?>, Integer> packetMap = new HashMap<>();
List<Object> serializerList = (List<Object>) byIdAccessor.get(codec);
Map<Object, Integer> packetTypeIdMap = (Map<Object, Integer>) toIdAccessor.get(codec);
for (Map.Entry<Object, Integer> entry : packetTypeIdMap.entrySet()) {
Class<?> packetClass = packetTypeMap.get(entry.getKey());
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + entry.getKey());
}
packetMap.put(packetClass, entry.getValue());
}
// retrieve packet codecs for packet construction and write methods
for (Object entry : serializerList) {
Object packetType = idCodecEntryTypeAccessor.invoke(entry);
Class<?> packetClass = packetTypeMap.get(packetType);
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + packetType);
}
Object serializer = idCodecEntrySerializerAccessor.invoke(entry);
result.classToCodec.put(packetClass, new WrappedStreamCodec(serializer));
}
// get EnumProtocol and Direction of protocol info
Object protocol = protocolAccessor.invoke(protocolInfo);
String direction = directionAccessor.invoke(protocolInfo).toString();
if (direction.contains("CLIENTBOUND")) { // Sent by Server
serverMaps.put(protocol, packetMap);
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
clientMaps.put(protocol, packetMap);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
for (Object protocol : protocols) {
Enum<?> enumProtocol = (Enum<?>) protocol;
PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol);
// Associate known types
if (serverMaps.containsKey(protocol)) {
associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER);
}
if (clientMaps.containsKey(protocol)) {
associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT);
}
}
ProtocolRegistry_1_20_5.fillRegister(result);
return result;
}
@@ -517,7 +319,7 @@ public class PacketRegistry {
PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass);
try {
register.registerPacket(type, packetClass, sender);
register.registerPacket(type, packetClass, sender, null);
} catch (Exception ex) {
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
}
@@ -610,8 +412,8 @@ public class PacketRegistry {
private static Class<?> searchForPacket(List<String> classNames) {
for (String name : classNames) {
try {
Class<?> clazz = MinecraftReflection.getMinecraftClass(name);
if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
Class<?> clazz = resolvePacketClassName(name);
if (clazz != null && MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers())) {
return clazz;
}
@@ -622,6 +424,22 @@ public class PacketRegistry {
return null;
}
/**
* Resolve a single packet class name. Fully-qualified names (those already containing their
* package, e.g. {@code net.minecraft.network.protocol.game.XPacket}) are loaded directly, since
* {@link MinecraftReflection#getMinecraftClass(String)} would otherwise prepend the Minecraft
* package and fail to find them. Relative names continue to be resolved against the Minecraft
* package.
*/
private static Class<?> resolvePacketClassName(String name) {
Optional<Class<?>> direct = MinecraftReflection.getOptionalClass(name);
if (direct.isPresent()) {
return direct.get();
}
return MinecraftReflection.getMinecraftClass(name);
}
/**
* Retrieves the correct packet class from a given type.
*
@@ -0,0 +1,71 @@
package com.comphenix.protocol.injector.packet.internal;
import java.util.Arrays;
import java.util.List;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
/**
* Class only works for version 1.20.5+
*/
public class IdCodecWrapper extends AbstractWrapper {
private static final Class<?> ID_CODEC_CLASS;
private static final FieldAccessor BY_ID_ACCESSOR;
private static final Class<?> ID_CODEC_ENTRY_CLASS;
private static final MethodAccessor ENTRY_TYPE_ACCESSOR;
private static final MethodAccessor ENTRY_SERIALIZER_ACCESSOR;
static {
ID_CODEC_CLASS = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec");
BY_ID_ACCESSOR = Accessors.getFieldAccessor(FuzzyReflection.fromClass(ID_CODEC_CLASS, true)
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(List.class).build()));
ID_CODEC_ENTRY_CLASS = Arrays.stream(ID_CODEC_CLASS.getNestMembers())
.filter(Class::isRecord)
.findFirst()
.orElseThrow(() -> new RuntimeException("Failed to find entry class for: " + ID_CODEC_CLASS));
FuzzyReflection entryReflection = FuzzyReflection.fromClass(ID_CODEC_ENTRY_CLASS, true);
ENTRY_TYPE_ACCESSOR = Accessors.getMethodAccessor(entryReflection
.getMethodByReturnTypeAndParameters("type", Object.class, new Class[0]));
ENTRY_SERIALIZER_ACCESSOR = Accessors.getMethodAccessor(entryReflection
.getMethodByReturnTypeAndParameters("serializer", MinecraftReflection.getStreamCodecClass(), new Class[0]));
}
public IdCodecWrapper(Object handle) {
super(ID_CODEC_CLASS);
setHandle(handle);
}
public List<Entry> getById() {
// list represents a packet-id to {type, codec} lookup
List<?> byId = (List<?>) BY_ID_ACCESSOR.get(this.handle);
return byId.stream().map(Entry::new).toList();
}
public class Entry extends AbstractWrapper {
public Entry(Object handle) {
super(ID_CODEC_ENTRY_CLASS);
setHandle(handle);
}
public Object type() {
return ENTRY_TYPE_ACCESSOR.invoke(this.handle);
}
public WrappedStreamCodec serializer() {
Object serializer = ENTRY_SERIALIZER_ACCESSOR.invoke(this.handle);
return new WrappedStreamCodec(serializer);
}
}
}
@@ -0,0 +1,44 @@
package com.comphenix.protocol.injector.packet.internal;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Class only works for version 1.21.5+
*/
public class ProtocolContexts {
private static final Class<?> GAME_PROTOCOL_CONTEXT_CLASS;
static {
GAME_PROTOCOL_CONTEXT_CLASS = MinecraftReflection.getNullableNMS(
"network.protocol.game.GameProtocols$a" /*Spigot Mapping*/,
"network.protocol.game.GameProtocols$Context" /*Mojang Mapping*/);
}
public static Object createGameProtocolContext() {
return createContext(GAME_PROTOCOL_CONTEXT_CLASS, (proxy, method, args) -> {
if (method.getName().equals("hasInfiniteMaterials") || (method.getReturnType() == boolean.class && method.getParameterCount() == 0)) {
return true;
}
return null;
});
}
private static Object createContext(Class<?> interfaceClass, InvocationHandler handler) {
if (interfaceClass == null) {
return null;
}
return Proxy.newProxyInstance(ProtocolContexts.class.getClassLoader(), new Class[] { interfaceClass }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return handler.invoke(proxy, method, args);
}
});
}
}
@@ -0,0 +1,102 @@
package com.comphenix.protocol.injector.packet.internal;
import java.util.Objects;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
/**
* Class only works for version 1.20.5+
*/
public class ProtocolInfoWrapper extends AbstractWrapper {
private static final @NotNull Class<?> SIMPLE_UNBOUND_PROTOCOL_CLASS;
private static final @NotNull MethodAccessor BIND_SIMPLE_ACCESSOR;
private static final @Nullable Class<?> UNBOUND_PROTOCOL_CLASS;
private static final @Nullable MethodAccessor BIND_CONTEXT_ACCESSOR;
private static final @NotNull MethodAccessor ID_ACCESSOR;
private static final @NotNull MethodAccessor FLOW_ACCESSOR;
private static final @NotNull MethodAccessor CODEC_ACCESSOR;
static {
SIMPLE_UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getMinecraftClass(
"network.protocol.SimpleUnboundProtocol" /* 1.21.5+ */,
"network.protocol.UnboundProtocol" /* 1.21.8+ Alternative */,
"network.ProtocolInfo$a" /* Spigot Mappings */,
"network.ProtocolInfo$Unbound" /* Mojang Mappings */);
BIND_SIMPLE_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(SIMPLE_UNBOUND_PROTOCOL_CLASS)
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class }));
UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getNullableNMS("network.protocol.UnboundProtocol");
if (UNBOUND_PROTOCOL_CLASS != null) {
BIND_CONTEXT_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(UNBOUND_PROTOCOL_CLASS)
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class, Object.class }));
} else {
BIND_CONTEXT_ACCESSOR = null;
}
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(MinecraftReflection.getProtocolInfoClass());
ID_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
FLOW_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("flow", MinecraftReflection.getPacketFlowClass(), new Class[0]));
CODEC_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("codec", MinecraftReflection.getStreamCodecClass(), new Class[0]));
}
public static boolean isUnboundProtocol(Object protocol) {
return SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol) || (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol));
}
public static ProtocolInfoWrapper fromUnbound(Object protocol, Object context) {
// bind to identity function in-order to map directly to FriendlyByteBuf instead of ByteBuf
if (SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
Object protocolInfo = BIND_SIMPLE_ACCESSOR.invoke(protocol, Function.identity());
return new ProtocolInfoWrapper(protocolInfo);
}
if (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
Object protocolInfo = BIND_CONTEXT_ACCESSOR.invoke(protocol, Function.identity(), Objects.requireNonNull(context));
return new ProtocolInfoWrapper(protocolInfo);
}
throw new RuntimeException("Unknown protocol implementation: " + protocol);
}
private ProtocolInfoWrapper(Object handle) {
super(MinecraftReflection.getProtocolInfoClass());
setHandle(handle);
}
public PacketType.Protocol id() {
Object protocol = ID_ACCESSOR.invoke(this.handle);
return PacketType.Protocol.fromVanilla((Enum<?>) protocol);
}
public PacketType.Sender flow() {
String packetFlow = FLOW_ACCESSOR.invoke(this.handle).toString();
if (packetFlow.contains("CLIENTBOUND")) {
return PacketType.Sender.SERVER;
} else if (packetFlow.contains("SERVERBOUND")) {
return PacketType.Sender.CLIENT;
}
throw new RuntimeException("Unknown packet flow: " + packetFlow);
}
public IdCodecWrapper codec() {
return new IdCodecWrapper(CODEC_ACCESSOR.invoke(this.handle));
}
}
@@ -0,0 +1,150 @@
package com.comphenix.protocol.injector.packet.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.PacketRegistry.Register;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Class only works for version 1.20.5+
*/
public class ProtocolRegistry_1_20_5 {
private static final Class<?> PACKET_TYPE_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");;
/**
* List of all class containing PacketTypes
*/
public static final List<String> PACKET_TYPES_CLASS_NAMES = List.of(
"common.CommonPacketTypes",
"configuration.ConfigurationPacketTypes",
"cookie.CookiePacketTypes",
"game.GamePacketTypes",
"handshake.HandshakePacketTypes",
"login.LoginPacketTypes",
"ping.PingPacketTypes",
"status.StatusPacketTypes");
/**
* List of all class containing ProtocolInfos
*/
public static final List<Protocol> PROTOCOLS = List.of(
new Protocol("configuration.ConfigurationProtocols", null),
new Protocol("game.GameProtocols", ProtocolContexts::createGameProtocolContext),
new Protocol("handshake.HandshakeProtocols", null),
new Protocol("login.LoginProtocols", null),
new Protocol("status.StatusProtocols", null));
public static void fillRegister(Register register) {
final Map<Object, Class<?>> packetTypeMap = getPacketTypes();
for (Protocol protocol : PROTOCOLS) {
Class<?> protocolClass = MinecraftReflection.getNullableNMS("network.protocol." + protocol.className());
if (protocolClass == null) {
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocol);
continue;
}
for (Field field : protocolClass.getDeclaredFields()) {
try {
// ignore none static and final fields
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object fieldValue = field.get(null);
// Skip any field that isn't a unbound ProtocolInfo.
// We need to bind the ProtocolInfo ourself as we apply our own stream mapping
// inside the ProtocolInfoWrapper::fromUnbound method
if (!ProtocolInfoWrapper.isUnboundProtocol(fieldValue)) {
continue;
}
// create optional context object to bind protocol to
Object context = protocol.context() != null
? protocol.context().get()
: null;
// wrap bound protocol info for ease of use
ProtocolInfoWrapper protocolInfo = ProtocolInfoWrapper.fromUnbound(fieldValue, context);
PacketType.Protocol packetProtocol = protocolInfo.id();
PacketType.Sender packetSender = protocolInfo.flow();
// register each packet to Register instance
List<IdCodecWrapper.Entry> entries = protocolInfo.codec().getById();
for (int id = 0; id < entries.size(); id++) {
IdCodecWrapper.Entry entry = entries.get(id);
Class<?> packetClass = packetTypeMap.get(entry.type());
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + entry.type());
}
PacketType packetType = PacketType.fromCurrent(packetProtocol, packetSender, id, packetClass);
// TODO(fix): validate that every codec expects FriendlyByteBuf
register.registerPacket(packetType, packetClass, packetSender, entry.serializer());
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
/**
* Returns a PacketType to packet class lookup map
*/
private static Map<Object, Class<?>> getPacketTypes() {
Map<Object, Class<?>> packetTypeMap = new HashMap<>();
for (String packetTypesClassName : PACKET_TYPES_CLASS_NAMES) {
Class<?> packetTypesClass = MinecraftReflection.getNullableNMS("network.protocol." + packetTypesClassName);
if (packetTypesClass == null) {
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
continue;
}
// check every field for "static final PacketType<?>"
for (Field field : packetTypesClass.getDeclaredFields()) {
try {
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object packetType = field.get(null);
if (!PACKET_TYPE_CLASS.isInstance(packetType)) {
continue;
}
// retrieve the generic type T of the PacketType<T> field
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (packet instanceof Class<?>) {
packetTypeMap.put(packetType, (Class<?>) packet);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
return packetTypeMap;
}
private record Protocol(@NotNull String className, @Nullable Supplier<Object> context) {
}
}
@@ -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;
}
/**
@@ -93,6 +93,15 @@ public final class MinecraftProtocolVersion {
map.put(new MinecraftVersion(1, 20, 5), 766);
map.put(new MinecraftVersion(1, 21, 0), 767);
map.put(new MinecraftVersion(1, 21, 2), 768);
map.put(new MinecraftVersion(1, 21, 6), 771);
map.put(new MinecraftVersion(1, 21, 8), 772);
map.put(new MinecraftVersion(1, 21, 9), 773);
map.put(new MinecraftVersion(1, 21, 11), 774);
map.put(new MinecraftVersion(26, 1, 0), 775);
map.put(new MinecraftVersion(26, 2, 0), 776);
map.put(new MinecraftVersion(26, 3, 0), 777);
return map;
}
@@ -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.
*
@@ -685,7 +689,8 @@ public final class MinecraftReflection {
* @throws IllegalStateException If the class could not be found or deduced.
*/
public static Class<?> getChatSerializerClass() {
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer");
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer",
"network.chat.Component$Serializer", "network.chat.ComponentSerialization", "IChatBaseComponent$ChatSerializer");
}
/**
@@ -916,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");
}
/**
@@ -1113,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.
*
@@ -1412,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);
}
}
}
@@ -1475,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.
*
@@ -1679,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() {
@@ -1898,4 +1933,31 @@ public final class MinecraftReflection {
return isMojangMapped;
}
/**
* Retrieves the PositionMoveRotation class (introduced in 1.21.2).
*
* @return The PositionMoveRotation class.
*/
public static Class<?> getPositionMoveRotationClass() {
return getMinecraftClass("world.entity.PositionMoveRotation");
}
/**
* Retrieves the VecDelta class (since 26.3).
*
* @return The VecDelta class.
*/
public static Optional<Class<?>> getVecDeltaClass() {
return getOptionalNMS("network.protocol.game.VecDelta");
}
/**
* Retrieves the PositionPath class (since 26.3).
*
* @return The PositionPath class.
*/
public static Optional<Class<?>> getPositionPathClass() {
return getOptionalNMS("world.entity.PositionPath");
}
}
@@ -2,6 +2,7 @@ package com.comphenix.protocol.utility;
import java.lang.reflect.Modifier;
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
import org.bukkit.Bukkit;
import com.comphenix.protocol.reflect.FuzzyReflection;
@@ -17,6 +18,7 @@ public class MinecraftRegistryAccess {
private static MethodAccessor GET_SERVER = null;
private static MethodAccessor REGISTRY_ACCESS = null;
private static MethodAccessor CREATE_SERIALIZATION_CONTEXT = null;
// lazy initialized
private static Object registryAccess = null;
@@ -36,6 +38,14 @@ public class MinecraftRegistryAccess {
.banModifier(Modifier.STATIC)
.returnDerivedOf(MinecraftReflection.getRegistryAccessClass())
.build()));
CREATE_SERIALIZATION_CONTEXT = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(MinecraftReflection.getHolderLookupProviderClass(), false)
.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnDerivedOf(MinecraftReflection.getDynamicOpsClass())
.parameterDerivedOf(MinecraftReflection.getDynamicOpsClass())
.build()));
}
}
@@ -57,4 +67,13 @@ public class MinecraftRegistryAccess {
return registryAccess;
}
/**
* Returns a RegistryOps using the given DynamicOps as the underlying ops.
*
* @return a RegistryOps
*/
public static WrappedDynamicOps createSerializationContext(WrappedDynamicOps dynamicOps) {
return WrappedDynamicOps.fromHandle(CREATE_SERIALIZATION_CONTEXT.invoke(get(), dynamicOps.getHandle()));
}
}
@@ -36,6 +36,56 @@ import org.bukkit.Server;
* @author Kristian
*/
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
/**
* Version 26.3 - wilderness bound
*/
public static final MinecraftVersion v26_3 = new MinecraftVersion("26.3");
/**
* Version 26.2 - chaos cubed
*/
public static final MinecraftVersion v26_2 = new MinecraftVersion("26.2");
/**
* Version 26.1 - tiny takeover
*/
public static final MinecraftVersion v26_1 = new MinecraftVersion("26.1");
/**
* Version 1.21.11 - mounts of mayhem
*/
public static final MinecraftVersion v1_21_11 = new MinecraftVersion("1.21.11");
/**
* Version 1.21.10 - hotfix for 1.21.9
*/
public static final MinecraftVersion v1_21_10 = new MinecraftVersion("1.21.10");
/**
* Version 1.21.9 - the copper age drop
*/
public static final MinecraftVersion v1_21_9 = new MinecraftVersion("1.21.9");
/**
* Version 1.21.6 - chase the skies
*/
public static final MinecraftVersion v1_21_6 = new MinecraftVersion("1.21.6");
/**
* Version 1.21.5 - spring to life drop
*/
public static final MinecraftVersion v1_21_5 = new MinecraftVersion("1.21.5");
/**
* Version 1.21.4 - the garden awakens drop
*/
public static final MinecraftVersion v1_21_4 = new MinecraftVersion("1.21.4");
/**
* Version 1.21.2 - the bundles of bravery drop
*/
public static final MinecraftVersion v1_21_2 = new MinecraftVersion("1.21.2");
/**
* Version 1.21.0 - the tricky trials update
*/
@@ -149,7 +199,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
/**
* The latest release version of minecraft.
*/
public static final MinecraftVersion LATEST = v1_21_0;
public static final MinecraftVersion LATEST = v26_3;
// used when serializing
private static final long serialVersionUID = -8695133558996459770L;
@@ -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
}
@@ -140,6 +146,8 @@ public abstract class EnumWrappers {
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
UPDATE_LIST_ORDER,
UPDATE_HAT,
/**
* @deprecated Removed in 1.19.3
*/
@@ -178,7 +186,9 @@ public abstract class EnumWrappers {
DROP_ALL_ITEMS,
DROP_ITEM,
RELEASE_USE_ITEM,
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND");
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND"),
STAB,
CHANGE_DESTROY_DIRECTION;
final String[] aliases;
PlayerDigType(String... aliases) {
@@ -191,8 +201,18 @@ public abstract class EnumWrappers {
}
public enum PlayerAction implements AliasedEnum {
/**
* @deprecated Removed in 1.21.6
*/
@Deprecated
START_SNEAKING("PRESS_SHIFT_KEY"),
/**
* @deprecated Removed in 1.21.6
*/
@Deprecated
STOP_SNEAKING("RELEASE_SHIFT_KEY"),
STOP_SLEEPING,
START_SPRINTING,
STOP_SPRINTING,
@@ -332,7 +352,8 @@ public abstract class EnumWrappers {
NEUTRAL("neutral"),
PLAYERS("player"),
AMBIENT("ambient"),
VOICE("voice");
VOICE("voice"),
UI("ui");
private static final Map<String, SoundCategory> LOOKUP;
static {
@@ -364,7 +385,8 @@ public abstract class EnumWrappers {
LEGS,
CHEST,
HEAD,
BODY
BODY,
SADDLE
}
public enum Hand {
@@ -638,6 +660,71 @@ public abstract class EnumWrappers {
TRANSFER;
}
public enum TeamCollisionRule {
ALWAYS("always"),
NEVER("never"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam");
public static TeamCollisionRule fromName(String name) {
for (TeamCollisionRule value : values()) {
if (value.name.equals(name)) {
return value;
}
}
throw new IllegalArgumentException("Unknown team collision rule: " + name);
}
private final String name;
TeamCollisionRule(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public enum TeamVisibility {
ALWAYS("always"),
NEVER("never"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
public static TeamVisibility fromName(String name) {
for (TeamVisibility value : values()) {
if (value.name.equals(name)) {
return value;
}
}
throw new IllegalArgumentException("Unknown team visibility: " + name);
}
private final String name;
TeamVisibility(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public enum HeightmapType {
WORLD_SURFACE_WG,
WORLD_SURFACE,
OCEAN_FLOOR_WG,
OCEAN_FLOOR,
MOTION_BLOCKING,
MOTION_BLOCKING_NO_LEAVES,
}
private static Class<?> PROTOCOL_CLASS = null;
private static Class<?> CLIENT_COMMAND_CLASS = null;
private static Class<?> CHAT_VISIBILITY_CLASS = null;
@@ -663,6 +750,9 @@ public abstract class EnumWrappers {
private static Class<?> RENDER_TYPE_CLASS = null;
private static Class<?> CHAT_FORMATTING_CLASS = null;
private static Class<?> CLIENT_INTENT_CLASS = null;
private static Class<?> TEAM_COLLISION_RULE_CLASS = null;
private static Class<?> TEAM_VISIBILITY_CLASS = null;
private static Class<?> HEIGHTMAP_TYPE_CLASS = null;
private static boolean INITIALIZING = false;
private static boolean INITIALIZED = false;
@@ -714,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) {
@@ -723,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);
}
@@ -739,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);
@@ -770,6 +867,17 @@ public abstract class EnumWrappers {
CLIENT_INTENT_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
TEAM_COLLISION_RULE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.ScoreboardTeamBase$EnumTeamPush" /* Spigot Mapping */,
"world.scores.Team$CollisionRule" /* Mojang Mapping */);
TEAM_VISIBILITY_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility" /* Spigot Mapping */,
"world.scores.Team$Visibility" /* Mojang Mapping */);
HEIGHTMAP_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.level.levelgen.HeightMap$Type" /* Spigot Mapping */,
"world.level.levelgen.Heightmap$Types" /* Mojang Mapping */);
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
@@ -794,6 +902,9 @@ public abstract class EnumWrappers {
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
associate(CLIENT_INTENT_CLASS, ClientIntent.class, getClientIntentConverter());
associate(TEAM_COLLISION_RULE_CLASS, TeamCollisionRule.class, getTeamCollisionRuleConverter());
associate(TEAM_VISIBILITY_CLASS, TeamVisibility.class, getTeamVisibilityConverter());
associate(HEIGHTMAP_TYPE_CLASS, HeightmapType.class, getHeightmapTypeConverter());
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
@@ -968,6 +1079,21 @@ public abstract class EnumWrappers {
return CLIENT_INTENT_CLASS;
}
public static Class<?> getTeamCollisionRuleClass() {
initialize();
return TEAM_COLLISION_RULE_CLASS;
}
public static Class<?> getTeamVisibilityClass() {
initialize();
return TEAM_VISIBILITY_CLASS;
}
public static Class<?> getHeightmapTypeClass() {
initialize();
return HEIGHTMAP_TYPE_CLASS;
}
// Get the converters
public static EquivalentConverter<Protocol> getProtocolConverter() {
return new EnumConverter<>(getProtocolClass(), Protocol.class);
@@ -1065,6 +1191,18 @@ public abstract class EnumWrappers {
return new EnumConverter<>(getClientIntentClass(), ClientIntent.class);
}
public static EquivalentConverter<TeamCollisionRule> getTeamCollisionRuleConverter() {
return new EnumConverter<>(getTeamCollisionRuleClass(), TeamCollisionRule.class);
}
public static EquivalentConverter<TeamVisibility> getTeamVisibilityConverter() {
return new EnumConverter<>(getTeamVisibilityClass(), TeamVisibility.class);
}
public static EquivalentConverter<HeightmapType> getHeightmapTypeConverter() {
return new EnumConverter<>(getHeightmapTypeClass(), HeightmapType.class);
}
/**
* @since 1.13+
* @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found)
@@ -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;
}
}
@@ -16,12 +16,12 @@
*/
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import javax.annotation.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
@@ -29,8 +29,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
import javax.annotation.Nullable;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
/**
* Represents an immutable PlayerInfoData in the PLAYER_INFO packet.
@@ -41,7 +40,9 @@ public class PlayerInfoData {
private final UUID profileId;
private final int latency;
private final int listOrder;
private final boolean listed;
private final boolean showHat;
private final NativeGameMode gameMode;
private final WrappedGameProfile profile;
private final WrappedChatComponent displayName;
@@ -74,6 +75,21 @@ public class PlayerInfoData {
this(profileId, latency, listed, gameMode, profile, displayName, (WrappedRemoteChatSessionData) null);
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.19.3 or later.
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
* @param listed whether the player is listed in the tab list
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
* @param remoteChatSession the remote chat session for this profile or null
*/
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
this(profileId, latency, listed, gameMode, profile, displayName, false, remoteChatSession);
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.19. This is incompatible on 1.19.3.
* @see PlayerInfoData#PlayerInfoData(UUID, int, boolean, NativeGameMode, WrappedGameProfile, WrappedChatComponent, WrappedRemoteChatSessionData)
@@ -96,10 +112,12 @@ public class PlayerInfoData {
this.displayName = displayName;
this.profileKeyData = profileKeyData;
this.remoteChatSessionData = null;
this.showHat = false;
this.listOrder = 0;
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.19.3 or later.
* Constructs a new PlayerInfoData for Minecraft 1.21.2 or later.
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
@@ -107,9 +125,43 @@ public class PlayerInfoData {
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
* @param listOrder the priority of this entry in the tab list
* @param remoteChatSession the remote chat session for this profile or null
*/
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, int listOrder, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
this(profileId, latency, listed, gameMode, profile, displayName, false, listOrder, remoteChatSession);
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.21.4 or later.
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
* @param listed whether the player is listed in the tab list
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
* @param showHat whether a hat should be shown
* @param remoteChatSession the remote chat session for this profile or null
*/
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, boolean showHat, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
this(profileId, latency, listed, gameMode, profile, displayName, showHat, 0, remoteChatSession);
}
/**
* Constructs a new PlayerInfoData for Minecraft 1.21.4 or later.
*
* @param profileId the id of the profile (has to be non-null)
* @param latency the latency in milliseconds
* @param listed whether the player is listed in the tab list
* @param gameMode the game mode
* @param profile the game profile
* @param displayName display name in tab list (optional)
* @param showHat whether a hat should be shown
* @param listOrder the priority of this entry in the tab list
* @param remoteChatSession the remote chat session for this profile or null
*/
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, boolean showHat, int listOrder, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
this.profileId = profileId;
this.latency = latency;
this.listed = listed;
@@ -118,6 +170,8 @@ public class PlayerInfoData {
this.displayName = displayName;
this.profileKeyData = null;
this.remoteChatSessionData = remoteChatSession;
this.showHat = showHat;
this.listOrder = listOrder;
}
/**
@@ -179,6 +233,22 @@ public class PlayerInfoData {
return displayName;
}
/**
* Gets if a hat is shown (since 1.21.4)
* @return if the hat is shown
*/
public boolean isShowHat() {
return showHat;
}
/**
* Gets the priority of this entry in the tab list (since 1.21.2)
* @return the priority of this entry in the tab list
*/
public int getListOrder() {
return listOrder;
}
/**
* Returns the public key of the profile (since 1.19). Returns the public key of the remote chat session since 1.19.3
* @return The public key of the profile.
@@ -202,7 +272,9 @@ public class PlayerInfoData {
* @return A new converter.
*/
public static EquivalentConverter<PlayerInfoData> getConverter() {
return new EquivalentConverter<PlayerInfoData>() {
return new EquivalentConverter<>() {
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
@Override
public Object getGeneric(PlayerInfoData specific) {
if (constructor == null) {
@@ -225,13 +297,20 @@ public class PlayerInfoData {
args.add(EnumWrappers.getGameModeClass());
args.add(MinecraftReflection.getIChatBaseComponentClass());
if (MinecraftVersion.v1_21_4.atOrAbove()) {
args.add(boolean.class);
}
if (MinecraftVersion.v1_21_2.atOrAbove()) {
args.add(int.class);
}
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
args.add(MinecraftReflection.getRemoteChatSessionDataClass());
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
args.add(MinecraftReflection.getProfilePublicKeyDataClass());
}
constructor = MinecraftReflection.getPlayerInfoDataClass().getConstructor(args.toArray(new Class<?>[0]));
constructor = MinecraftReflection.getPlayerInfoDataClass().getConstructor(args.toArray(EMPTY_CLASS_ARRAY));
} catch (Exception e) {
throw new RuntimeException("Cannot find PlayerInfoData constructor.", e);
}
@@ -240,32 +319,72 @@ public class PlayerInfoData {
// Attempt to construct the underlying PlayerInfoData
try {
Object gameMode = EnumWrappers.getGameModeConverter().getGeneric(specific.gameMode);
Object gameMode = Converters.ignoreNull(EnumWrappers.getGameModeConverter()).getGeneric(specific.gameMode);
Object displayName = specific.displayName != null ? specific.displayName.handle : null;
Object profile = specific.profile != null ? specific.profile.handle : null;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return constructor.newInstance(
specific.profileId,
profile,
specific.listed,
specific.latency,
gameMode,
displayName,
specific.remoteChatSessionData != null ? BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(specific.remoteChatSessionData) : null
);
Object remoteChatSessionData = specific.remoteChatSessionData != null ? BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(specific.remoteChatSessionData) : null;
Object[] args;
if (MinecraftVersion.v1_21_4.atOrAbove()) {
args = new Object[] {
specific.profileId,
profile,
specific.listed,
specific.latency,
gameMode,
displayName,
specific.showHat,
specific.listOrder,
remoteChatSessionData
};
} else if (MinecraftVersion.v1_21_2.atOrAbove()) {
args = new Object[] {
specific.profileId,
profile,
specific.listed,
specific.latency,
gameMode,
displayName,
specific.listOrder,
remoteChatSessionData
};
} else if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
args = new Object[] {
specific.profileId,
profile,
specific.listed,
specific.latency,
gameMode,
displayName,
remoteChatSessionData
};
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
return constructor.newInstance(
profile,
specific.latency,
gameMode,
displayName,
specific.profileKeyData == null ? null : specific.profileKeyData.handle);
args = new Object[] {
profile,
specific.latency,
gameMode,
displayName,
specific.profileKeyData == null ? null : specific.profileKeyData.handle
};
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
return constructor.newInstance(profile, specific.latency, gameMode, displayName);
args = new Object[] {
profile,
specific.latency,
gameMode,
displayName
};
} else {
return constructor.newInstance(null, profile, specific.latency, gameMode, displayName);
args = new Object[] {
null,
profile,
specific.latency,
gameMode,
displayName
};
}
return constructor.newInstance(args);
} catch (Exception e) {
throw new RuntimeException("Failed to construct PlayerInfoData.", e);
}
@@ -275,37 +394,60 @@ public class PlayerInfoData {
public PlayerInfoData getSpecific(Object generic) {
if (MinecraftReflection.isPlayerInfoData(generic)) {
StructureModifier<Object> modifier = new StructureModifier<>(generic.getClass(), null, false)
.withTarget(generic);
.withTarget(generic);
StructureModifier<WrappedGameProfile> gameProfiles = modifier.withType(
MinecraftReflection.getGameProfileClass(), BukkitConverters.getWrappedGameProfileConverter());
MinecraftReflection.getGameProfileClass(), BukkitConverters.getWrappedGameProfileConverter());
WrappedGameProfile gameProfile = gameProfiles.read(0);
StructureModifier<Integer> ints = modifier.withType(int.class);
int latency = ints.read(0);
StructureModifier<NativeGameMode> gameModes = modifier.withType(
EnumWrappers.getGameModeClass(), EnumWrappers.getGameModeConverter());
EnumWrappers.getGameModeClass(), Converters.ignoreNull(EnumWrappers.getGameModeConverter()));
NativeGameMode gameMode = gameModes.read(0);
StructureModifier<WrappedChatComponent> displayNames = modifier.withType(
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
WrappedChatComponent displayName = displayNames.read(0);
if(MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
if (MinecraftVersion.v1_21_4.atOrAbove()) {
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
latency,
modifier.<Boolean>withType(boolean.class).read(0),
gameMode,
gameProfile,
displayName,
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
);
latency,
modifier.<Boolean>withType(boolean.class).read(0),
gameMode,
gameProfile,
displayName,
modifier.<Boolean>withType(boolean.class).read(1),
ints.read(1),
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
);
}
if (MinecraftVersion.v1_21_2.atOrAbove()) {
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
latency,
modifier.<Boolean>withType(boolean.class).read(0),
gameMode,
gameProfile,
displayName,
ints.read(1),
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
);
}
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
latency,
modifier.<Boolean>withType(boolean.class).read(0),
gameMode,
gameProfile,
displayName,
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
);
}
WrappedProfileKeyData key = null;
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
StructureModifier<WrappedProfileKeyData> keyData = modifier.withType(
MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter());
MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter());
key = keyData.read(0);
}
@@ -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) {
@@ -1,8 +1,13 @@
package com.comphenix.protocol.wrappers;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.util.Optional;
import com.comphenix.protocol.wrappers.codecs.WrappedCodec;
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.bukkit.ChatColor;
import com.comphenix.protocol.reflect.FuzzyReflection;
@@ -35,41 +40,51 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
private static ConstructorAccessor CONSTRUCT_TEXT_COMPONENT = null;
static {
FuzzyReflection reflection = FuzzyReflection.fromClass(SERIALIZER, true);
private static WrappedCodec CODEC;
private static WrappedDynamicOps REGISTRY_JSON_OPS;
// Retrieve the correct methods
if (MinecraftVersion.v1_20_5.atOrAbove()) {
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(reflection.getMethod(FuzzyMethodContract.newBuilder()
static {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER, true);
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)
.parameterDerivedOf(COMPONENT)
.parameterDerivedOf(MinecraftReflection.getHolderLookupProviderClass())
.build()));
} else {
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(reflection.getMethodByReturnTypeAndParameters("serialize", /* a */
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("serialize", /* a */
String.class, new Class<?>[] { COMPONENT }));
}
GSON = Accessors.getFieldAccessor(reflection.getFieldByType("gson", GSON_CLASS)).get(null);
if (!MinecraftVersion.v1_20_4.atOrAbove()) {
GSON = Accessors.getFieldAccessor(fuzzy.getFieldByType("gson", GSON_CLASS)).get(null);
}
if (MinecraftVersion.v1_20_5.atOrAbove()) {
DESERIALIZE = Accessors.getMethodAccessor(reflection.getMethod(FuzzyMethodContract.newBuilder()
.returnDerivedOf(COMPONENT)
.parameterExactType(String.class)
.parameterDerivedOf(MinecraftReflection.getHolderLookupProviderClass())
.build()));
} else if (MinecraftVersion.v1_20_4.atOrAbove()) {
DESERIALIZE = Accessors.getMethodAccessor(reflection
.getMethodByReturnTypeAndParameters("fromJson", MUTABLE_COMPONENT_CLASS.get(), new Class[] { String.class }));
} else {
try {
DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getChatDeserializer(), true)
.getMethodByReturnTypeAndParameters("deserialize", Object.class, new Class<?>[] { GSON_CLASS, String.class, Class.class, boolean.class }));
} catch (IllegalArgumentException ex) {
// We'll handle it in the ComponentParser
DESERIALIZE = null;
}
}
if (!MinecraftVersion.v1_21_6.atOrAbove()) {
if (MinecraftVersion.v1_20_5.atOrAbove()) {
DESERIALIZE = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnDerivedOf(COMPONENT)
.parameterExactType(String.class)
.parameterDerivedOf(MinecraftReflection.getHolderLookupProviderClass())
.build()));
} else if (MinecraftVersion.v1_20_4.atOrAbove()) {
DESERIALIZE = Accessors.getMethodAccessor(fuzzy
.getMethodByReturnTypeAndParameters("fromJson", MUTABLE_COMPONENT_CLASS.get(), new Class[]{String.class}));
} else {
try {
DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getChatDeserializer(), true)
.getMethodByReturnTypeAndParameters("deserialize", Object.class, new Class<?>[]{GSON_CLASS, String.class, Class.class, boolean.class}));
} catch (IllegalArgumentException ex) {
// We'll handle it in the ComponentParser
DESERIALIZE = null;
}
}
}
// Get a component from a standard Minecraft message
CONSTRUCT_COMPONENT = Accessors.getMethodAccessor(MinecraftReflection.getCraftChatMessage(), "fromString", String.class, boolean.class);
@@ -83,6 +98,11 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
}
private static Object serialize(Object handle) {
if (CODEC != null) {
Object jobj = CODEC.encode(handle, REGISTRY_JSON_OPS).getOrThrow(JsonParseException::new);
return jobj.toString();
}
if (MinecraftVersion.v1_20_5.atOrAbove()) {
return SERIALIZE_COMPONENT.invoke(null, handle, MinecraftRegistryAccess.get());
}
@@ -91,6 +111,10 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
}
private static Object deserialize(String json) {
if (CODEC != null) {
return CODEC.parse(JsonParser.parseString(json), REGISTRY_JSON_OPS).getOrThrow(JsonParseException::new);
}
if (MinecraftVersion.v1_20_5.atOrAbove()) {
return DESERIALIZE.invoke(null, json, MinecraftRegistryAccess.get());
}
@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@@ -40,7 +41,6 @@ import com.comphenix.protocol.wrappers.EnumWrappers.EntityPose;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang.Validate;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
@@ -66,7 +66,7 @@ public class WrappedDataWatcher implements IDataWatcher {
* Constructs a new DataWatcher wrapper around a NMS handle. The resulting
* DataWatcher will likely have existing values that can be removed with
* {@link #clear()}.
*
*
* @param handle DataWatcher handle
*/
public WrappedDataWatcher(Object handle) {
@@ -90,7 +90,7 @@ public class WrappedDataWatcher implements IDataWatcher {
* Constructs a new DataWatcher using a real entity. The resulting
* DataWatcher will not have any keys or values and new ones will have to
* be added using watcher objects.
*
*
* @param entity The entity
*/
public WrappedDataWatcher(Entity entity) {
@@ -102,7 +102,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Constructs a new DataWatcher using a fake egg entity and a given
* list of watchable objects.
*
*
* @param objects The list of objects
*/
public WrappedDataWatcher(List<WrappedWatchableObject> objects) {
@@ -152,7 +152,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Gets the item at a given index.
*
*
* @param index Index to get
* @return The watchable object, or null if none exists
*/
@@ -170,7 +170,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Removes the item at a given index.
*
*
* @param index Index to remove
* @return The previous value, or null if none existed
*/
@@ -180,7 +180,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Whether or not this DataWatcher has an object at a given index.
*
*
* @param index Index to check for
* @return True if it does, false if not
*/
@@ -209,7 +209,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Get a watched byte.
*
*
* @param index - index of the watched byte.
* @return The watched byte, or NULL if this value doesn't exist.
*/
@@ -225,7 +225,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Get a watched short.
*
*
* @param index - index of the watched short.
* @return The watched short, or NULL if this value doesn't exist.
*/
@@ -237,7 +237,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Get a watched integer.
*
*
* @param index - index of the watched integer.
* @return The watched integer, or NULL if this value doesn't exist.
*/
@@ -263,7 +263,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Get a watched float.
*
*
* @param index - index of the watched float.
* @return The watched float, or NULL if this value doesn't exist.
*/
@@ -279,7 +279,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Get a watched string.
*
*
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
*/
@@ -315,7 +315,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Get a watched string.
*
*
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
*/
@@ -477,7 +477,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Retrieve a watchable object by index.
*
*
* @param index Index of the object to retrieve.
* @return The watched object or null if it doesn't exist.
*/
@@ -487,7 +487,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Retrieve a watchable object by watcher object.
*
*
* @param object The watcher object
* @return The watched object or null if it doesn't exist.
*/
@@ -500,11 +500,11 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Sets the DataWatcher Item at a given index to a new value. In 1.9 and up,
* you cannot register objects without a watcher object.
*
*
* @param index Index of the object to set
* @param value New value
* @param update Whether or not to inform the client
*
*
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object, boolean)
* @throws IllegalArgumentException in 1.9 and up if there isn't already an
* object at this index
@@ -526,12 +526,12 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Sets the DataWatcher Item at a given index to a new value.
*
*
* @param index Index of the object to set
* @param serializer Serializer from {@link Registry#get(Class)}
* @param value New value
* @param update Whether or not to inform the client
*
*
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object)
*/
public void setObject(int index, Serializer serializer, Object value, boolean update) {
@@ -547,11 +547,11 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Sets the DataWatcher Item at a given index to a new value.
*
*
* @param index Index of the object to set
* @param value New value
* @param update Whether or not to inform the client
*
*
* @see WrappedDataWatcher#setObject(int, Object, boolean)
*/
public void setObject(int index, WrappedWatchableObject value, boolean update) {
@@ -567,11 +567,11 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Sets the DataWatcher Item associated with a given watcher object to a new value.
*
*
* @param object Associated watcher object
* @param value Wrapped value
* @param update Whether or not to inform the client
*
*
* @see #setObject(WrappedDataWatcherObject, Object)
*/
public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update) {
@@ -589,10 +589,10 @@ public class WrappedDataWatcher implements IDataWatcher {
* Sets the DataWatcher Item associated with a given watcher object to a
* new value. If there is not already an object at this index, the
* specified watcher object must have a serializer.
*
*
* @param object Associated watcher object
* @param value New value
*
*
* @throws IllegalArgumentException If the watcher object is null or must
* have a serializer and does not have one.
*/
@@ -611,7 +611,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Clone the content of the current DataWatcher.
*
*
* @return A cloned data watcher.
*/
public WrappedDataWatcher deepClone() {
@@ -628,7 +628,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Retrieve the data watcher associated with an entity.
*
*
* @param entity - the entity to read from.
* @return Associated data watcher.
*/
@@ -682,7 +682,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Retrieves the type ID associated with a given class. No longer supported
* in 1.9 and up due to the removal of type IDs.
*
*
* @param clazz Class to find ID for
* @return The ID, or null if not found
*/
@@ -693,7 +693,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Retrieves the class associated with a given type ID. No longer
* supported in 1.9 and up due to the removal of type IDs.
*
*
* @param typeID ID to find Class for
* @return The Class, or null if not found
*/
@@ -729,7 +729,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Represents a DataWatcherObject in 1.9. In order to register an object,
* the serializer must be specified.
*
*
* @author dmulloy2
*/
public static class WrappedDataWatcherObject {
@@ -745,7 +745,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Creates a new watcher object from a NMS handle.
*
*
* @param handle The handle
*/
public WrappedDataWatcherObject(Object handle) {
@@ -755,7 +755,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Creates a new watcher object from an index and serializer.
*
*
* @param index Index
* @param serializer Serializer, see {@link Registry}
*/
@@ -795,7 +795,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Gets this watcher object's index.
*
*
* @return The index
*/
public int getIndex() {
@@ -805,7 +805,7 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Gets this watcher object's serializer. Will return null if the
* serializer was never specified.
*
*
* @return The serializer, or null
*/
public Serializer getSerializer() {
@@ -911,23 +911,36 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Represents a DataWatcherSerializer in 1.9. If a Serializer is optional,
* values must be wrapped in a {@link Optional}.
*
*
* @author dmulloy2
*/
public static class Serializer extends AbstractWrapper {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherSerializerClass();
private final Class<?> type;
private final Type type;
private final boolean optional;
/**
* Constructs a new Serializer
*
*
* @param type Type it serializes
* @param handle NMS handle
* @param optional Whether or not it's {@link Optional}
* @deprecated Use {@link Serializer#Serializer(Type, Object, boolean)} instead.
*/
@Deprecated(forRemoval = true)
public Serializer(Class<?> type, Object handle, boolean optional) {
this((Type) type, handle, optional);
}
/**
* Constructs a new Serializer
*
* @param type Type it serializes
* @param handle NMS handle
* @param optional Whether or not it's {@link Optional}
*/
public Serializer(Class<?> type, Object handle, boolean optional) {
public Serializer(Type type, Object handle, boolean optional) {
super(HANDLE_TYPE);
this.type = type;
this.optional = optional;
@@ -937,17 +950,32 @@ public class WrappedDataWatcher implements IDataWatcher {
/**
* Gets the type this serializer serializes.
*
*
* @return The type
* @deprecated use {@link #getGenericType()} instead.
*/
@Deprecated(forRemoval = true)
public Class<?> getType() {
return type;
if (this.type instanceof Class<?> c) {
return c;
}
throw new IllegalStateException("Serializer does not serialize a class");
}
/**
* Gets the type this serializer serializes.
*
* @return The type.
*/
public Type getGenericType() {
return this.type;
}
/**
* Whether or not this serializer is optional, that is whether or not
* the return type is wrapped in a {@link Optional}.
*
*
* @return True if it is, false if not
*/
public boolean isOptional() {
@@ -986,11 +1014,19 @@ public class WrappedDataWatcher implements IDataWatcher {
public static class Registry {
private static boolean INITIALIZED = false;
private static Map<Class<?>, Serializer> RAW_REGISTRY = null;
private static Map<Class<?>, Serializer> OPTIONAL_REGISTRY = null;
private static Map<Type, Serializer> RAW_REGISTRY = null;
private static Map<Type, Serializer> OPTIONAL_REGISTRY = null;
/**
* Gets the first serializer associated with a given class.
* @deprecated use {@link #get(Type)} instead.
*/
@Deprecated(forRemoval = true)
public static Serializer get(Class<?> clazz) {
return get((Type) clazz);
}
/**
* Gets the first serializer associated with a given type.
*
* <p><b>Note</b>: If {@link Serializer#isOptional() the serializer is optional},
* values <i>must</i> be wrapped in an {@link Optional}.</p>
@@ -998,39 +1034,46 @@ public class WrappedDataWatcher implements IDataWatcher {
* <p>If there are multiple serializers for a given class (i.e. BlockPosition),
* you should use {@link #get(Class, boolean)} for more precision.</p>
*
* @param clazz Class to find serializer for
* @param type Type to find serializer for
* @return The serializer, or null if none exists
*/
public static Serializer get(Class<?> clazz) {
Validate.notNull(clazz,"Class cannot be null!");
public static Serializer get(Type type) {
Validate.notNull(type,"Type cannot be null!");
initialize();
Serializer serializer = RAW_REGISTRY.getOrDefault(clazz,
OPTIONAL_REGISTRY.getOrDefault(clazz, null));
Serializer serializer = RAW_REGISTRY.getOrDefault(type, OPTIONAL_REGISTRY.get(type));
if (serializer == null) {
throw new IllegalArgumentException("No serializer found for " + clazz);
throw new IllegalArgumentException("No serializer found for " + type);
}
return serializer;
}
/**
* Gets the first serializer associated with a given class and optional state.
* @deprecated use {@link #get(Type, boolean)} instead.
*/
@Deprecated(forRemoval = true)
public static Serializer get(Class<?> clazz, boolean optional) {
return get((Type) clazz, optional);
}
/**
* Gets the first serializer associated with a given type and optional state.
* <br/>
* <b>Note</b>: If the serializer is optional, values <i>must</i> be
* wrapped in an {@link Optional}
*
* @param clazz Class to find serializer for
* @param type Type to find serializer for
* @param optional Optional state
* @return The serializer, or null if none exists
*/
public static Serializer get(Class<?> clazz, boolean optional) {
Validate.notNull(clazz, "Class cannot be null!");
public static Serializer get(Type type, boolean optional) {
Validate.notNull(type, "Type cannot be null!");
initialize();
Serializer serializer = optional ? OPTIONAL_REGISTRY.get(clazz) : RAW_REGISTRY.get(clazz);
Serializer serializer = optional ? OPTIONAL_REGISTRY.get(type) : RAW_REGISTRY.get(type);
if (serializer == null) {
throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz));
throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + type + ">" : type));
}
return serializer;
@@ -1067,52 +1110,41 @@ public class WrappedDataWatcher implements IDataWatcher {
return;
}
Map<Class<?>, Serializer> rawRegistry = new HashMap<>();
Map<Class<?>, Serializer> optionalRegistry = new HashMap<>();
Map<Type, Serializer> rawRegistry = new HashMap<>();
Map<Type, Serializer> optionalRegistry = new HashMap<>();
List<Field> candidates = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherRegistryClass(), true)
.getFieldListByType(MinecraftReflection.getDataWatcherSerializerClass());
for (Field candidate : candidates) {
Type generic = candidate.getGenericType();
if (generic instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) generic;
Type[] args = type.getActualTypeArguments();
Type arg = args[0];
Class<?> innerClass;
boolean optional = false;
if (arg instanceof Class<?>) {
innerClass = (Class<?>) arg;
} else if (arg instanceof ParameterizedType) {
innerClass = (Class<?>) ((ParameterizedType) arg).getActualTypeArguments()[0];
optional = true;
} else {
throw new IllegalStateException("Failed to find inner class of field " + candidate);
}
if (candidate.getGenericType() instanceof ParameterizedType cpt) {
Object serializer;
try {
serializer = candidate.get(null);
Objects.requireNonNull(serializer, "Failed to read serializer: " + candidate.getName());
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Failed to read field " + candidate);
}
if (serializer == null) {
throw new RuntimeException("Failed to read serializer: " + candidate.getName());
// all types are wrapped in EntityDataSerializer<?>
Type serializingType = cpt.getActualTypeArguments()[0];
if (serializingType instanceof ParameterizedType spt) {
Class<?> root = (Class<?>) spt.getRawType();
if (root == Optional.class) {
// special case: EntityDataSerializer<Optional<?>>
Type actualType = spt.getActualTypeArguments()[0];
Serializer wrappedSerializer = new Serializer(actualType, serializer, true);
optionalRegistry.put(actualType, wrappedSerializer);
continue;
}
}
if (optional) {
optionalRegistry.put(innerClass, new Serializer(innerClass, serializer, true));
} else {
rawRegistry.put(innerClass, new Serializer(innerClass, serializer, false));
}
Serializer wrappedSerializer = new Serializer(serializingType, serializer, false);
rawRegistry.put(serializingType, wrappedSerializer);
}
}
RAW_REGISTRY = ImmutableMap.copyOf(rawRegistry);
OPTIONAL_REGISTRY = ImmutableMap.copyOf(optionalRegistry);
RAW_REGISTRY = Map.copyOf(rawRegistry);
OPTIONAL_REGISTRY = Map.copyOf(optionalRegistry);
}
// ---- Helper methods
@@ -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,13 @@ 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;
import com.comphenix.protocol.injector.StructureCache;
@@ -41,22 +46,39 @@ public final class WrappedLevelChunkData {
private static final FieldAccessor BLOCK_ENTITIES_DATA_ACCESSOR;
private static final FieldAccessor HEIGHTMAPS_ACCESSOR;
private static final EquivalentConverter<Map<EnumWrappers.HeightmapType, long[]>> HEIGHTMAPS_CONVERTER;
private static final FieldAccessor BUFFER_ACCESSOR;
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()));
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getNBTCompoundClass())
.build()));
if (MinecraftVersion.v1_21_5.atOrAbove()) {
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(Map.class)
.build()));
HEIGHTMAPS_CONVERTER = BukkitConverters.getMapConverter(EnumWrappers.getHeightmapTypeConverter(), Converters.passthrough(long[].class));
} else {
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getNBTCompoundClass())
.build()));
HEIGHTMAPS_CONVERTER = null;
}
BUFFER_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder().typeExact(byte[].class).build()));
}
@@ -68,22 +90,50 @@ public final class WrappedLevelChunkData {
/**
* The heightmap of this chunk.
* <p>
* Removed in Minecraft 1.21.5.
*
* @return an NBT-Tag
* @deprecated Use {@link ChunkData#getHeightmaps()} instead.
*/
@Deprecated
public NbtCompound getHeightmapsTag() {
return NbtFactory.fromNMSCompound(HEIGHTMAPS_ACCESSOR.get(handle));
}
/**
* Sets the heightmap tag of this chunk.
* <p>
* Removed in Minecraft 1.21.5.
*
* @param heightmapsTag the new heightmaps tag.
* @deprecated Use {@link ChunkData#setHeightmaps(Map)} instead.
*/
@Deprecated
public void setHeightmapsTag(NbtCompound heightmapsTag) {
HEIGHTMAPS_ACCESSOR.set(handle, NbtFactory.fromBase(heightmapsTag).getHandle());
}
/**
* The heightmap of this chunk.
*
* @return a map containing the heightmaps
*/
public Map<EnumWrappers.HeightmapType, long[]> getHeightmaps() {
return HEIGHTMAPS_CONVERTER.getSpecific(HEIGHTMAPS_ACCESSOR.get(handle));
}
/**
* Sets the heightmap tag of this chunk.
* <p>
* Removed in Minecraft 1.21.5.
*
* @param heightmaps the new heightmaps.
*/
public void setHeightmaps(Map<EnumWrappers.HeightmapType, long[]> heightmaps) {
HEIGHTMAPS_ACCESSOR.set(handle, HEIGHTMAPS_CONVERTER.getGeneric(heightmaps));
}
/**
* The actual structural data of this chunk as bytes.
*
@@ -130,14 +180,23 @@ public final class WrappedLevelChunkData {
/**
* Creates a new wrapper using predefined values.
* <p>
* Removed in Minecraft 1.21.5.
*
* @param heightmapsTag the heightmaps tag
* @param buffer the buffer
* @param blockEntityInfo a list of wrapped block entities
* @return a newly created wrapper
* @deprecated Use {@link ChunkData#fromValues(Map, byte[], List)} instead.
*/
@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);
@@ -145,6 +204,29 @@ public final class WrappedLevelChunkData {
return new ChunkData(data);
}
/**
* Creates a new wrapper using predefined values.
*
* @param heightmaps the heightmaps
* @param buffer the buffer
* @param blockEntityInfo a list of wrapped block entities
* @return a newly created wrapper
*/
public static ChunkData fromValues(Map<EnumWrappers.HeightmapType, long[]> heightmaps, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
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);
data.setBlockEntityInfo(blockEntityInfo);
return new ChunkData(data);
}
}
/**
@@ -372,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) {
@@ -472,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);
}
@@ -482,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());
}
}
/**
@@ -507,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())
@@ -183,59 +201,80 @@ public class WrappedParticle<T> {
}
private static Object getRedstone(Object handle) {
int r, g, b;
float size;
Color color;
float scale;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
if (MinecraftVersion.v1_21_2.atOrAbove()) {
int rgb = (int) modifier.withType(int.class).read(0);
color = Color.fromRGB(rgb);
scale = (float) modifier.withType(float.class).read(0);
} else if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
org.joml.Vector3f rgb = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(0);
r = (int) (rgb.x() * 255);
g = (int) (rgb.y() * 255);
b = (int) (rgb.z() * 255);
size = (float) modifier.withType(float.class).read(0);
int red = (int) (rgb.x() * 255);
int green = (int) (rgb.y() * 255);
int blue = (int) (rgb.z() * 255);
color = Color.fromRGB(red, green, blue);
scale = (float) modifier.withType(float.class).read(0);
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
if (VECTOR_3FA == null) {
VECTOR_3FA = MinecraftReflection.getLibraryClass("com.mojang.math.Vector3fa");
}
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
Object rgb = modifier.withType(VECTOR_3FA).read(0);
StructureModifier<Object> rgbModifier = new StructureModifier<>(VECTOR_3FA).withTarget(rgb);
r = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
g = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
b = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
size = (float) modifier.withType(float.class).read(0);
int red = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
int green = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
int blue = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
color = Color.fromRGB(red, green, blue);
scale = (float) modifier.withType(float.class).read(0);
} else {
StructureModifier<Float> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class);
r = (int) (modifier.read(0) * 255);
g = (int) (modifier.read(1) * 255);
b = (int) (modifier.read(2) * 255);
size = modifier.read(3);
StructureModifier<Float> floatModifier = modifier.withType(float.class);
int red = (int) (floatModifier.read(0) * 255);
int green = (int) (floatModifier.read(1) * 255);
int blue = (int) (floatModifier.read(2) * 255);
color = Color.fromRGB(red, green, blue);
scale = floatModifier.read(3);
}
return new Particle.DustOptions(Color.fromRGB(r, g, b), size);
return new Particle.DustOptions(color, scale);
}
private static Object getDustTransition(Object handle) {
int fromR, fromG, fromB, toR, toG, toB;
float size;
Color fromColor, toColor;
float scale;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
if (MinecraftVersion.v1_21_2.atOrAbove()) {
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
int fromRgb = (int) modifier.withType(int.class).read(0);
fromColor = Color.fromRGB(fromRgb);
int toRgb = (int) modifier.withType(int.class).read(1);
toColor = Color.fromRGB(toRgb);
scale = (float) modifier.withType(float.class).read(0);
} else if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
org.joml.Vector3f toRGB = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(1);
org.joml.Vector3f fromRGB = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(0);
size = (float) modifier.withType(float.class).read(0);
scale = (float) modifier.withType(float.class).read(0);
fromR = (int) (fromRGB.x() * 255);
fromG = (int) (fromRGB.y() * 255);
fromB = (int) (fromRGB.z() * 255);
int fromR = (int) (fromRGB.x() * 255);
int fromG = (int) (fromRGB.y() * 255);
int fromB = (int) (fromRGB.z() * 255);
fromColor = Color.fromRGB(fromR, fromG, fromB);
toR = (int) (toRGB.x() * 255);
toG = (int) (toRGB.y() * 255);
toB = (int) (toRGB.z() * 255);
int toR = (int) (toRGB.x() * 255);
int toG = (int) (toRGB.y() * 255);
int toB = (int) (toRGB.z() * 255);
toColor = Color.fromRGB(toR, toG, toB);
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
if (VECTOR_3FA == null) {
VECTOR_3FA = MinecraftReflection.getLibraryClass("com.mojang.math.Vector3fa");
@@ -245,37 +284,42 @@ public class WrappedParticle<T> {
Object toRGB = modifier.withType(VECTOR_3FA).read(0);
Object fromRGB = modifier.withType(VECTOR_3FA).read(1);
size = (float) modifier.withType(float.class).read(0);
scale = (float) modifier.withType(float.class).read(0);
StructureModifier<Object> rgbModifier = new StructureModifier<>(VECTOR_3FA).withTarget(fromRGB);
StructureModifier<Object> rgbModifier2 = new StructureModifier<>(VECTOR_3FA).withTarget(toRGB);
fromR = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
fromG = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
fromB = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
int fromR = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
int fromG = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
int fromB = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
fromColor = Color.fromRGB(fromR, fromG, fromB);
toR = (int) (rgbModifier2.<Float>withType(float.class).read(0) * 255);
toG = (int) (rgbModifier2.<Float>withType(float.class).read(1) * 255);
toB = (int) (rgbModifier2.<Float>withType(float.class).read(2) * 255);
int toR = (int) (rgbModifier2.<Float>withType(float.class).read(0) * 255);
int toG = (int) (rgbModifier2.<Float>withType(float.class).read(1) * 255);
int toB = (int) (rgbModifier2.<Float>withType(float.class).read(2) * 255);
toColor = Color.fromRGB(toR, toG, toB);
} else {
StructureModifier<Float> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class);
toR = (int) (modifier.read(0) * 255);
toG = (int) (modifier.read(1) * 255);
toB = (int) (modifier.read(2) * 255);
size = modifier.read(3);
fromR = (int) (modifier.read(4) * 255);
fromG = (int) (modifier.read(5) * 255);
fromB = (int) (modifier.read(6) * 255);
int toR = (int) (modifier.read(0) * 255);
int toG = (int) (modifier.read(1) * 255);
int toB = (int) (modifier.read(2) * 255);
toColor = Color.fromRGB(toR, toG, toB);
scale = modifier.read(3);
int fromR = (int) (modifier.read(4) * 255);
int fromG = (int) (modifier.read(5) * 255);
int fromB = (int) (modifier.read(6) * 255);
fromColor = Color.fromRGB(fromR, fromG, fromB);
}
return new Particle.DustTransition(Color.fromRGB(fromR, fromG, fromB), Color.fromRGB(toR, toG, toB), size);
return new Particle.DustTransition(fromColor, toColor, scale);
}
public static <T> WrappedParticle<T> create(Particle particle, T data) {
ensureMethods();
Object bukkitData = data;
if (data instanceof WrappedBlockData) {
WrappedBlockData blockData = (WrappedBlockData) data;
if (data instanceof WrappedBlockData blockData) {
bukkitData = toCraftData.invoke(null, blockData.getHandle());
}
@@ -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);
}
/**
@@ -1,11 +1,17 @@
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;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.EnumWrappers.TeamCollisionRule;
import com.comphenix.protocol.wrappers.EnumWrappers.TeamVisibility;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A wrapper around the team parameters NMS class.
@@ -35,11 +41,16 @@ public class WrappedTeamParameters extends AbstractWrapper {
}
private final StructureModifier<Object> modifier;
private final StructureModifier<TeamVisibility> visiblityModifier;
private final StructureModifier<TeamCollisionRule> collisionRuleModifier;
public WrappedTeamParameters(Object handle) {
super(getNmsClassOrThrow());
setHandle(handle);
this.modifier = new StructureModifier<>(getNmsClassOrThrow()).withTarget(handle);
this.visiblityModifier = this.modifier.withType(EnumWrappers.getTeamVisibilityClass(), EnumWrappers.getTeamVisibilityConverter());
this.collisionRuleModifier = this.modifier.withType(EnumWrappers.getTeamCollisionRuleClass(), EnumWrappers.getTeamCollisionRuleConverter());
}
@NotNull
@@ -58,22 +69,49 @@ public class WrappedTeamParameters extends AbstractWrapper {
}
@NotNull
public String getNametagVisibility() {
return modifier.<String>withType(String.class).read(0);
public TeamVisibility getNametagVisibility() {
TeamVisibility visibility = this.visiblityModifier.readSafely(0);
if (visibility == null) {
return TeamVisibility.fromName(modifier.<String>withType(String.class).read(0));
} else {
return visibility;
}
}
@NotNull
public String getCollisionRule() {
return modifier.<String>withType(String.class).read(1);
public TeamCollisionRule getCollisionRule() {
TeamCollisionRule collisionRule = this.collisionRuleModifier.readSafely(0);
if (collisionRule == null) {
return TeamCollisionRule.fromName(modifier.<String>withType(String.class).read(1));
} else {
return collisionRule;
}
}
@NotNull
public EnumWrappers.ChatFormatting getColor() {
Object handle = modifier.withType(EnumWrappers.getChatFormattingClass()).read(0);
return EnumWrappers.getChatFormattingConverter().getSpecific(handle);
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);
}
@@ -88,7 +126,8 @@ public class WrappedTeamParameters extends AbstractWrapper {
public static class Builder {
private WrappedChatComponent displayName, prefix, suffix;
private String nametagVisibility, collisionRule;
private TeamVisibility nametagVisibility;
private TeamCollisionRule collisionRule;
private EnumWrappers.ChatFormatting color;
private int options;
@@ -122,18 +161,36 @@ public class WrappedTeamParameters extends AbstractWrapper {
return this;
}
public Builder nametagVisibility(@NotNull String nametagVisibility) {
public Builder nametagVisibility(@NotNull TeamVisibility nametagVisibility) {
Preconditions.checkNotNull(nametagVisibility);
this.nametagVisibility = nametagVisibility;
return this;
}
public Builder collisionRule(@NotNull String collisionRule) {
/**
* Compatibility stub for FeatherBoard – accepts raw String visibility.
*/
public Builder nametagVisibility(@NotNull String visibility) {
Preconditions.checkNotNull(visibility, "visibility string cannot be null");
this.nametagVisibility = TeamVisibility.fromName(visibility);
return this;
}
public Builder collisionRule(@NotNull TeamCollisionRule collisionRule) {
Preconditions.checkNotNull(collisionRule);
this.collisionRule = collisionRule;
return this;
}
/**
* Compatibility stub for FeatherBoard – accepts raw String collision rules.
*/
public Builder collisionRule(@NotNull String rule) {
Preconditions.checkNotNull(rule, "collision rule cannot be null");
this.collisionRule = TeamCollisionRule.fromName(rule);
return this;
}
public Builder color(@NotNull EnumWrappers.ChatFormatting color) {
Preconditions.checkNotNull(color);
this.color = color;
@@ -160,11 +217,36 @@ public class WrappedTeamParameters extends AbstractWrapper {
wrapped.writeComponent(0, displayName);
wrapped.writeComponent(1, prefix);
wrapped.writeComponent(2, suffix);
wrapped.modifier.withType(String.class).write(0, nametagVisibility);
wrapped.modifier.withType(String.class).write(1, collisionRule);
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
wrapped.modifier.withType(int.class).write(0, options);
if (MinecraftVersion.v1_21_5.atOrAbove()) {
wrapped.visiblityModifier.writeSafely(0, nametagVisibility);
wrapped.collisionRuleModifier.writeSafely(0, collisionRule);
} else {
wrapped.modifier.withType(String.class).writeSafely(0, nametagVisibility.toString());
wrapped.modifier.withType(String.class).writeSafely(1, collisionRule.toString());
}
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 + "]";
}
}
}
@@ -23,7 +23,7 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
String EMPTY_NAME = "";
/**
* Get the type of each element.
* Get the type of each element. For heterogeneous lists this will return {@link NbtType#TAG_COMPOUND TAG_COMPOUND}.
* <p>
* This will be {@link NbtType#TAG_END TAG_END} if the NBT list has just been created.
* @return Element type.
@@ -32,8 +32,12 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
/**
* Set the type of each element.
* <p>
* In 1.21.5 and up lists can be heterogeneous, here the type is only used to convert elements added with {@link NbtList#addClosest(Object)}
* @param type - type of each element.
* @deprecated since 1.21.5 lists can be heterogeneous
*/
@Deprecated
void setElementType(NbtType type);
/**
@@ -41,9 +45,19 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
* <p>
* Note that the list must be typed by setting {@link #setElementType(NbtType)} before calling this function.
* @param value - the value to add.
* @deprecated since 1.21.5 lists can be heterogeneous. Use {@link NbtList#addClosest(Object, NbtType)}
*/
@Deprecated
void addClosest(Object value);
/**
* Add a value to a typed list by attempting to convert it to the nearest value.
* <p>
* @param value - the value to add.
* @param type - the resulting type of the entry
*/
void addClosest(Object value, NbtType type);
/**
* Add a NBT list or NBT compound to the list.
* @param element Element to add
@@ -1,29 +1,27 @@
package com.comphenix.protocol.wrappers.nbt;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.block.BlockState;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftRegistryAccess;
import com.comphenix.protocol.utility.MinecraftVersion;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.block.BlockState;
/**
* Manipulate tile entities.
@@ -34,6 +32,10 @@ class TileEntityAccessor<T extends BlockState> {
private static final boolean BLOCK_DATA_INCL = MinecraftVersion.NETHER_UPDATE.atOrAbove()
&& !MinecraftVersion.CAVES_CLIFFS_1.atOrAbove();
private static final boolean USE_HOLDER_LOOKUP = MinecraftVersion.v1_21_5.atOrAbove();
private static final boolean USE_TAG_VALUE_INPUT = MinecraftVersion.v1_21_6.atOrAbove();
private static final Class<?> TAG_VALUE_INPUT = MinecraftReflection.getNullableNMS("world.level.storage.TagValueInput");
/**
* Token indicating that the given block state doesn't contain any tile entities.
@@ -45,7 +47,7 @@ class TileEntityAccessor<T extends BlockState> {
*/
private static final Map<Class<?>, TileEntityAccessor<?>> cachedAccessors = new HashMap<>();
private static Constructor<?> nbtCompoundParserConstructor;
private MethodAccessor createInputWrapper;
private FieldAccessor tileEntityField;
private MethodAccessor readCompound;
@@ -104,11 +106,50 @@ class TileEntityAccessor<T extends BlockState> {
}
void findMethods(Class<?> type, T state) {
if (BLOCK_DATA_INCL) {
Class<?> tileEntityClass = MinecraftReflection.getTileEntityClass();
Class<?> iBlockData = MinecraftReflection.getIBlockDataClass();
Class<?> nbtCompound = MinecraftReflection.getNBTCompoundClass();
Class<?> tileEntityClass = MinecraftReflection.getTileEntityClass();
Class<?> nbtCompound = MinecraftReflection.getNBTCompoundClass();
if (USE_HOLDER_LOOKUP) {
Class<?> holderLookup = MinecraftReflection.getHolderLookupProviderClass();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(tileEntityClass, false);
// should be equiv. to `CompoundTag saveWithFullMetadata(HolderLookup.Provider)`
writeCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.returnTypeExact(nbtCompound)
.parameterExactArray(holderLookup)
.build()));
if (USE_TAG_VALUE_INPUT) {
createInputWrapper = Accessors.getMethodAccessor(FuzzyReflection.fromClass(TAG_VALUE_INPUT).getMethod(
FuzzyMethodContract.newBuilder()
.requireModifier(Modifier.STATIC)
.parameterExactArray(MinecraftReflection.getMinecraftClass("util.ProblemReporter"), holderLookup, nbtCompound)
.build()));
// should be equiv. to `void loadWithComponents(ValueInput)`
readCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.returnTypeVoid()
.parameterSuperOf(TAG_VALUE_INPUT)
.build()));
} else {
// should be equiv. to `void loadWithComponents(CompoundTag, HolderLookup.Provider)`
readCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.returnTypeVoid()
.parameterExactArray(nbtCompound, holderLookup)
.build()));
}
} else if (BLOCK_DATA_INCL) {
Class<?> iBlockData = MinecraftReflection.getIBlockDataClass();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(tileEntityClass, false);
writeCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
FuzzyMethodContract.newBuilder()
@@ -214,12 +255,17 @@ class TileEntityAccessor<T extends BlockState> {
* @return The compound.
*/
public NbtCompound readBlockState(T state) {
NbtCompound output = NbtFactory.ofCompound("");
Object tileEntity = tileEntityField.get(state);
// Write the block state to the output compound
writeCompound.invoke(tileEntity, NbtFactory.fromBase(output).getHandle());
return output;
if (USE_HOLDER_LOOKUP) {
Object tag = writeCompound.invoke(tileEntity, MinecraftRegistryAccess.get());
return new WrappedCompound(tag);
} else {
NbtCompound output = NbtFactory.ofCompound("");
writeCompound.invoke(tileEntity, NbtFactory.fromBase(output).getHandle());
return output;
}
}
/**
@@ -232,7 +278,14 @@ class TileEntityAccessor<T extends BlockState> {
Object tileEntity = tileEntityField.get(state);
// Ensure the block state is set to the compound
if (BLOCK_DATA_INCL) {
if (USE_HOLDER_LOOKUP) {
if (USE_TAG_VALUE_INPUT) {
Object tagValueInput = createInputWrapper.invoke(null, null, MinecraftRegistryAccess.get(), NbtFactory.fromBase(compound).getHandle());
readCompound.invoke(tileEntity, tagValueInput);
} else {
readCompound.invoke(tileEntity, NbtFactory.fromBase(compound).getHandle(), MinecraftRegistryAccess.get());
}
} else if (BLOCK_DATA_INCL) {
Object blockData = BukkitUnwrapper.getInstance().unwrapItem(state);
readCompound.invoke(tileEntity, blockData, NbtFactory.fromBase(compound).getHandle());
} else {
@@ -17,6 +17,7 @@
package com.comphenix.protocol.wrappers.nbt;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.collection.ConvertedList;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
import com.google.common.base.Joiner;
@@ -103,7 +104,9 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
*/
public WrappedList(Object handle) {
this.container = new WrappedElement<>(handle);
this.elementType = container.getSubType();
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
this.elementType = container.getSubType();
}
}
/**
@@ -113,7 +116,9 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
*/
public WrappedList(Object handle, String name) {
this.container = new WrappedElement<>(handle, name);
this.elementType = container.getSubType();
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
this.elementType = container.getSubType();
}
}
@Override
@@ -141,13 +146,28 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
@Override
public NbtType getElementType() {
return elementType;
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
return elementType;
} else {
List<NbtBase<TType>> value = getValue();
NbtType type = NbtType.TAG_END;
for (NbtBase<TType> entry : value) {
if (type == NbtType.TAG_END) {
type = entry.getType();
} else if (type != entry.getType()) {
return NbtType.TAG_COMPOUND;
}
}
return type;
}
}
@Override
public void setElementType(NbtType type) {
this.elementType = type;
container.setSubType(type);
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
container.setSubType(type);
}
}
@Override
@@ -171,14 +191,16 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
if (!element.getName().equals(EMPTY_NAME))
throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list.");
// Check element type
if (getElementType() != NbtType.TAG_END) {
if (!element.getType().equals(getElementType())) {
throw new IllegalArgumentException(
"Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
// Check element type
if (getElementType() != NbtType.TAG_END) {
if (!element.getType().equals(getElementType())) {
throw new IllegalArgumentException(
"Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
}
} else {
container.setSubType(element.getType());
}
} else {
container.setSubType(element.getType());
}
}
@@ -235,16 +257,21 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
}
@Override
@SuppressWarnings("unchecked")
public void addClosest(Object value) {
if (getElementType() == NbtType.TAG_END)
if (elementType == NbtType.TAG_END)
throw new IllegalStateException("This list has not been typed yet.");
addClosest(value, elementType);
}
@Override
@SuppressWarnings("unchecked")
public void addClosest(Object value, NbtType type) {
if (value instanceof Number) {
Number number = (Number) value;
// Convert the number
switch (getElementType()) {
switch (type) {
case TAG_BYTE: add(number.byteValue()); break;
case TAG_SHORT: add(number.shortValue()); break;
case TAG_INT: add(number.intValue()); break;
@@ -262,7 +289,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
} else {
// Just add it
add((NbtBase<TType>) NbtFactory.ofWrapper(getElementType(), EMPTY_NAME, value));
add((NbtBase<TType>) NbtFactory.ofWrapper(type, EMPTY_NAME, value));
}
}
@@ -355,7 +382,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
}
// Update the sub type as well
if (lastElement != null) {
if (lastElement != null && !MinecraftVersion.v1_21_5.atOrAbove()) {
container.setSubType(lastElement.getType());
}
}
@@ -261,10 +261,9 @@ public class NbtConfigurationSerializer {
if (node instanceof List) {
NbtList<Object> list = NbtFactory.ofList(decoded[0]);
list.setElementType(type);
for (Object value : (List<Object>) node) {
list.addClosest(toNodeValue(value, type));
list.addClosest(toNodeValue(value, type), type);
}
// Add the list
@@ -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;
@@ -1,8 +1,16 @@
package com.comphenix.integration.protocol;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.ExactReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.apache.logging.log4j.Logger;
@@ -13,13 +21,6 @@ import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginLoadOrder;
import org.bukkit.plugin.PluginManager;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -1,10 +1,5 @@
package com.comphenix.integration.protocol;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -15,6 +10,13 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.io.ByteStreams;
public class SimpleMinecraftClient {
private static final int CONNECT_TIMEOUT = 2500;
@@ -1,17 +1,18 @@
package com.comphenix.integration.protocol;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import org.bukkit.plugin.Plugin;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPingPacket {
@@ -3,60 +3,70 @@ 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.CommandDispatcher;
import net.minecraft.core.IRegistry;
import net.minecraft.core.IRegistryCustom;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.DataPackResources;
import net.minecraft.server.DispenserRegistry;
import net.minecraft.resources.Identifier;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.WorldLoader;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.WorldServer;
import net.minecraft.server.packs.EnumResourcePackType;
import net.minecraft.server.packs.repository.ResourcePackLoader;
import net.minecraft.server.packs.repository.ResourcePackRepository;
import net.minecraft.server.packs.repository.ResourcePackSourceVanilla;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.PackType;
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.world.entity.EntityTypes;
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.FluidType;
import net.minecraft.world.level.material.Fluid;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftLootTable;
import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry;
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftBlockTag;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftEntityTag;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftFluidTag;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftItemTag;
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.v1_21_R1.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;
@@ -75,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());
}
@@ -111,36 +130,89 @@ public class BukkitInitialization {
instance.setPackage();
// Minecraft Data Init
SharedConstants.a(); // .tryDetectVersion()
DispenserRegistry.a(); // .bootStrap()
SharedConstants.tryDetectVersion(); // .tryDetectVersion()
Bootstrap.bootStrap(); // .bootStrap()
Bootstrap.validate();
ResourcePackRepository resourcePackRepository = ResourcePackSourceVanilla.c(); // .createVanillaTrustedRepository()
resourcePackRepository.a(); // .reload()
PackRepository repo = ServerPacksSource.createVanillaTrustedRepository();
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 ResourceManager(
EnumResourcePackType.b /* SERVER_DATA */,
resourcePackRepository.c() /* getAvailablePacks() */ .stream().map(ResourcePackLoader::f /* openFull() */).collect(Collectors.toList()));
LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = RegistryLayer.a(); // .createRegistryAccess()
layeredRegistryAccess = WorldLoader.b(resourceManager, layeredRegistryAccess, RegistryLayer.b /* WORLDGEN */, RegistryDataLoader.a /* WORLDGEN_REGISTRIES */); // .loadAndReplaceLayer()
IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().d(); // .compositeAccess().freeze()
// IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().c(); // .compositeAccess().freeze()
ResourceManager resourceManager = new MultiPackResourceManager(
PackType.SERVER_DATA,
repo.openAllSelected()
);
DataPackResources dataPackResources = DataPackResources.a(
resourceManager,
layeredRegistryAccess,
FeatureFlags.d.a() /* REGISTRY.allFlags() */,
CommandDispatcher.ServerType.b /* DEDICATED */,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
dataPackResources.g();
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.WORLD);
List<HolderLookup.RegistryLookup<?>> list1 = TagLoader.buildUpdatedLookups(access1, tags);
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,
layeredAccess2,
tags,
FeatureFlags.REGISTRY.allFlags() /* REGISTRY.allFlags() */,
Commands.CommandSelection.DEDICATED /* DEDICATED */,
LevelBasedPermissionSet.ALL,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
dataPackResources.updateComponentsAndStaticRegistryTags();
/* try {
IRegistry.class.getName();
} catch (Throwable ex) {
ex.printStackTrace();
}
} */
String releaseTarget = MinecraftReflectionTestUtil.RELEASE_TARGET;
String serverVersion = CraftServer.class.getPackage().getImplementationVersion();
@@ -149,7 +221,14 @@ public class BukkitInitialization {
CraftServer mockedServer = mock(CraftServer.class);
DedicatedServer mockedGameServer = mock(DedicatedServer.class);
when(mockedGameServer.bc()/*registryAccess*/).thenReturn(registryCustom);
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");
@@ -162,11 +241,11 @@ public class BukkitInitialization {
when(mockedServer.getUnsafe()).thenReturn(CraftMagicNumbers.INSTANCE);
when(mockedServer.getLootTable(any())).thenAnswer(invocation -> {
NamespacedKey key = invocation.getArgument(0);
return new CraftLootTable(key, dataPackResources.b().b(CraftLootTable.bukkitKeyToMinecraft(key)));
return new CraftLootTable(key, dataPackResources.fullRegistries().getLootTable(CraftLootTable.bukkitKeyToMinecraft(key)));
});
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);
@@ -179,35 +258,35 @@ public class BukkitInitialization {
when(mockedServer.getTag(any(), any(), any())).then(mock -> {
String registry = mock.getArgument(0);
Class<?> clazz = mock.getArgument(2);
MinecraftKey key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
Identifier key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
switch (registry) {
case org.bukkit.Tag.REGISTRY_BLOCKS -> {
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have block type");
TagKey<Block> blockTagKey = TagKey.a(Registries.f, key);
if (BuiltInRegistries.e.b(blockTagKey).isPresent()) {
return new CraftBlockTag(BuiltInRegistries.e, blockTagKey);
TagKey<Block> blockTagKey = TagKey.create(Registries.BLOCK, key);
if (BuiltInRegistries.BLOCK.get(blockTagKey).isPresent()) {
return new CraftBlockTag(BuiltInRegistries.BLOCK, blockTagKey);
}
}
case org.bukkit.Tag.REGISTRY_ITEMS -> {
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have item type");
TagKey<Item> itemTagKey = TagKey.a(Registries.K, key);
if (BuiltInRegistries.g.b(itemTagKey).isPresent()) {
return new CraftItemTag(BuiltInRegistries.g, itemTagKey);
TagKey<Item> itemTagKey = TagKey.create(Registries.ITEM, key);
if (BuiltInRegistries.ITEM.get(itemTagKey).isPresent()) {
return new CraftItemTag(BuiltInRegistries.ITEM, itemTagKey);
}
}
case org.bukkit.Tag.REGISTRY_FLUIDS -> {
Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type");
TagKey<FluidType> fluidTagKey = TagKey.a(Registries.D, key);
if (BuiltInRegistries.c.b(fluidTagKey).isPresent()) {
return new CraftFluidTag(BuiltInRegistries.c, fluidTagKey);
TagKey<Fluid> fluidTagKey = TagKey.create(Registries.FLUID, key);
if (BuiltInRegistries.FLUID.get(fluidTagKey).isPresent()) {
return new CraftFluidTag(BuiltInRegistries.FLUID, fluidTagKey);
}
}
case org.bukkit.Tag.REGISTRY_ENTITY_TYPES -> {
Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type");
TagKey<EntityTypes<?>> entityTagKey = TagKey.a(Registries.z, key);
if (BuiltInRegistries.f.b(entityTagKey).isPresent()) {
return new CraftEntityTag(BuiltInRegistries.f, entityTagKey);
TagKey<EntityType<?>> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key);
if (BuiltInRegistries.ENTITY_TYPE.get(entityTagKey).isPresent()) {
return new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey);
}
}
default -> throw new IllegalArgumentException();
@@ -216,8 +295,20 @@ public class BukkitInitialization {
return null;
});
WorldServer nmsWorld = mock(WorldServer.class);
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"));
@@ -228,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);
@@ -237,8 +329,7 @@ public class BukkitInitialization {
CraftRegistry.setMinecraftRegistry(registryCustom);
// Init Enchantments
Enchantments.A.getClass();
// Enchantment.stopAcceptingRegistrations();
Enchantments.AQUA_AFFINITY.getClass();
initialized = true;
}
@@ -284,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,10 +16,13 @@ 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;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -28,15 +31,14 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.PacketType.Play.Server;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.minecraft.network.protocol.login.PacketLoginInStart;
import net.minecraft.network.protocol.common.ClientboundPostEffectsPacket;
import net.minecraft.network.protocol.common.ClientboundStoreCookiePacket;
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
/**
* @author dmulloy2
@@ -62,7 +64,6 @@ public class PacketTypeTest {
}
public static void main(String[] args) throws Exception {
// public void generateNewPackets() throws Exception {
BukkitInitialization.initializeAll();
PacketType.onDynamicCreate = (type, className) -> {
@@ -257,10 +258,41 @@ public class PacketTypeTest {
}
}
@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() {
Set<PacketType> notSerializable = Set.of(PacketType.Play.Server.BUNDLE);
for (PacketType type : PacketType.values()) {
if (!notSerializable.contains(type) && type.isSupported()) {
assertNotNull(PacketRegistry.getStreamCodec(type.getPacketClass()), "missing serializer for " + type);
}
}
}
@Test
public void testLoginStart() {
// This packet is critical for handleLoin
assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass());
assertEquals(ServerboundHelloPacket.class, PacketType.Login.Client.START.getPacketClass());
}
@Test
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff. Show more