forked from upstream/ProtocolLib
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb125fb1f | ||
|
|
0f7670e13d | ||
|
|
47f465d3f5 | ||
|
|
501e187342 | ||
|
|
6720a70dbd | ||
|
|
3d8e597244 | ||
|
|
58d45e79b1 | ||
|
|
d89a5deb3e | ||
|
|
1ea47d63ba | ||
|
|
3c616caf74 | ||
|
|
583353e9fa | ||
|
|
f0e959a1ad | ||
|
|
cbe7fd87e4 | ||
|
|
3fab29d3c6 | ||
|
|
fec45cf8fc | ||
|
|
5c6c773935 | ||
|
|
78b10165ff | ||
|
|
dda284cd7a | ||
|
|
debdd6d9a2 | ||
|
|
2c53c29cf7 | ||
|
|
1ef8431764 | ||
|
|
90eb2a6558 | ||
|
|
67ce937109 | ||
|
|
98856a8b9f | ||
|
|
5ec23ef33b | ||
|
|
589ee12e12 | ||
|
|
6cf7c7d8c3 | ||
|
|
3139e82aea | ||
|
|
3385fe664b | ||
|
|
0e267a5f1a | ||
|
|
2d3b3e3363 | ||
|
|
8eeb82f89d | ||
|
|
b0b9b666a7 | ||
|
|
b723ff3351 | ||
|
|
72fd0709b6 | ||
|
|
5a9afed462 | ||
|
|
9404ce32db | ||
|
|
6c6bde522e | ||
|
|
f911ee904c | ||
|
|
22012d3dc9 | ||
|
|
c52b4bb10c | ||
|
|
b9f96469f5 | ||
|
|
e5413ccc5b | ||
|
|
b37a3bcb13 | ||
|
|
19cb81b6ab | ||
|
|
f606cc954d | ||
|
|
774e679571 | ||
|
|
1257506929 | ||
|
|
5773bcbc16 | ||
|
|
468082c9f9 | ||
|
|
a11b403c4b | ||
|
|
aa4fab1632 | ||
|
|
771a530fe2 | ||
|
|
ca9b5bbb80 | ||
|
|
d790ded81f | ||
|
|
f357ac85d0 | ||
|
|
66cdb5b69d | ||
|
|
1aeb002afe | ||
|
|
79d44f0d6e | ||
|
|
9bde9b4231 | ||
|
|
714252df6f | ||
|
|
e56bcf624d | ||
|
|
596e5e4246 | ||
|
|
f3f895995a | ||
|
|
67e7375b2f | ||
|
|
9417eee444 | ||
|
|
a432fae4dd | ||
|
|
8a5e59d4f2 | ||
|
|
90a386b743 | ||
|
|
a80762feed | ||
|
|
a936183b76 | ||
|
|
804aa5df11 | ||
|
|
1e73df8600 | ||
|
|
4314a55866 | ||
|
|
2c0d632dc2 | ||
|
|
fd5d8c576e | ||
|
|
0256953056 | ||
|
|
24262d3f7d | ||
|
|
7a85663ded | ||
|
|
bcff6c6719 | ||
|
|
3155e76d06 | ||
|
|
6845acd89d | ||
|
|
3442d3fb8f | ||
|
|
1e3fa2d36e | ||
|
|
c9ee2f16e2 | ||
|
|
7f615378e4 | ||
|
|
d636be81e7 | ||
|
|
3cb261f466 | ||
|
|
7338183ed6 | ||
|
|
fb085676a9 | ||
|
|
9fc68d4b6c | ||
|
|
f227b17f5f | ||
|
|
4aa344bab2 | ||
|
|
da65c9d3c1 | ||
|
|
47bcd53fd0 | ||
|
|
2cad996252 | ||
|
|
46d40d752e | ||
|
|
a006b70c6f | ||
|
|
e726f6eb0d | ||
|
|
389233f599 | ||
|
|
4051caca0b | ||
|
|
914bc9aa7f | ||
|
|
8272ecb33d | ||
|
|
d13ce24507 | ||
|
|
b6a7d5f91d | ||
|
|
f6b3af4221 | ||
|
|
616431cc33 | ||
|
|
77ada09c11 | ||
|
|
389314cc2f | ||
|
|
56fb51e94f | ||
|
|
76432f9d36 | ||
|
|
f0468e04d9 | ||
|
|
052e7a3dc0 | ||
|
|
9ae73d9e20 | ||
|
|
11f4810884 | ||
|
|
fe51dea707 | ||
|
|
4afbe845ff | ||
|
|
0daf389aff | ||
|
|
5c19f647fa | ||
|
|
e07f01ece8 | ||
|
|
7d4458bb5f | ||
|
|
41a0c9da41 | ||
|
|
720bb83234 | ||
|
|
1325ec6a98 | ||
|
|
b0c4b7fe45 | ||
|
|
a2f02652e9 | ||
|
|
f9d3266777 | ||
|
|
70e4812fde | ||
|
|
d0c7382d7f |
No files matched your search
@@ -1 +1,2 @@
|
||||
* text=auto eol=lf
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
+73
-36
@@ -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.
|
||||
@@ -1,28 +1,32 @@
|
||||
name: ProtocolLib full build lifecycle
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '19'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Run gradle build lifecycle
|
||||
run: ./gradlew build shadowJar --no-daemon
|
||||
|
||||
- name: Upload plugin file
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ProtocolLib
|
||||
path: build/libs/ProtocolLib.jar
|
||||
path: |
|
||||
build/libs/ProtocolLib.jar
|
||||
build/libs/ProtocolLib-Spigot.jar
|
||||
@@ -20,17 +20,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '19'
|
||||
java-version: '21'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: 'java'
|
||||
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
run: ./gradlew build -x test --no-daemon
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -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 }}
|
||||
@@ -181,4 +181,3 @@ logs/
|
||||
build/
|
||||
logs/
|
||||
.java-version
|
||||
gradle.properties
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
group = 'com.comphenix.protocol'
|
||||
version = '5.2.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.14.9'
|
||||
compileOnly 'org.spigotmc:spigot-api:1.20.4-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.spigotmc:spigot:1.20.4-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.20.4-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()
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependencies {
|
||||
include(dependency('net.bytebuddy:byte-buddy:.*'))
|
||||
}
|
||||
relocate 'net.bytebuddy', 'com.comphenix.net.bytebuddy'
|
||||
archiveFileName = 'ProtocolLib.jar'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
@@ -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
@@ -1,2 +1,7 @@
|
||||
nexusUsername=""
|
||||
nexusPassword=""
|
||||
version=5.5.0-SNAPSHOT
|
||||
mcVersion=26.3
|
||||
mavenCentralUsername=
|
||||
mavenCentralPassword=
|
||||
signing.keyId=
|
||||
signing.password=
|
||||
signing.secretKeyRingFile=
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.comphenix.protocol.paper.PaperPlatformProvider
|
||||
@@ -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
|
||||
@@ -3,3 +3,5 @@
|
||||
*/
|
||||
|
||||
rootProject.name = 'ProtocolLib'
|
||||
|
||||
include 'paper', 'spigot'
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
@@ -35,8 +36,8 @@ import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import com.comphenix.protocol.error.DetailedErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimingReportGenerator;
|
||||
import com.comphenix.protocol.timing.TimingReport;
|
||||
import com.comphenix.protocol.timing.TimingTrackerManager;
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
import com.comphenix.protocol.updater.Updater.UpdateType;
|
||||
import com.comphenix.protocol.utility.Closer;
|
||||
@@ -119,39 +120,22 @@ 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);
|
||||
}
|
||||
|
||||
private void toggleTimings(CommandSender sender, String[] args) {
|
||||
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||
boolean state = !manager.isTiming(); // toggle
|
||||
boolean isNotTracking = !TimingTrackerManager.isTracking();
|
||||
|
||||
// Parse the boolean parameter
|
||||
if (args.length == 2) {
|
||||
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
|
||||
|
||||
if (parsed != null) {
|
||||
state = parsed;
|
||||
isNotTracking = parsed;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||
return;
|
||||
@@ -161,31 +145,25 @@ class CommandProtocol extends CommandBase {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now change the state
|
||||
if (state) {
|
||||
if (manager.startTiming())
|
||||
if (isNotTracking) {
|
||||
if (TimingTrackerManager.startTracking())
|
||||
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||
else
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||
} else {
|
||||
if (manager.stopTiming()) {
|
||||
saveTimings(manager);
|
||||
if (TimingTrackerManager.stopTracking()) {
|
||||
saveTimings(TimingTrackerManager.createReportAndReset());
|
||||
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimings(TimedListenerManager manager) {
|
||||
|
||||
private void saveTimings(TimingReport report) {
|
||||
try {
|
||||
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||
TimingReportGenerator generator = new TimingReportGenerator();
|
||||
|
||||
// Print to a text file
|
||||
generator.saveTo(destination, manager);
|
||||
manager.clear();
|
||||
|
||||
Path path = plugin.getDataFolder().toPath().resolve("timings_" + System.currentTimeMillis() + ".txt");
|
||||
report.saveTo(path);
|
||||
} catch (IOException e) {
|
||||
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,17 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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;
|
||||
@@ -17,9 +26,6 @@ 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>
|
||||
@@ -50,7 +56,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
private static final Sender SENDER = Sender.CLIENT;
|
||||
|
||||
@ForceAsync
|
||||
public static final PacketType SET_PROTOCOL = new PacketType(PROTOCOL, SENDER, 0x00, "SetProtocol", "C00Handshake");
|
||||
public static final PacketType SET_PROTOCOL = new PacketType(PROTOCOL, SENDER, 0x00, "ClientIntentionPacket", "SetProtocol", "C00Handshake");
|
||||
|
||||
private static final Client INSTANCE = new Client();
|
||||
|
||||
@@ -101,125 +107,150 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static class Server extends PacketTypeEnum {
|
||||
private static final Sender SENDER = Sender.SERVER;
|
||||
|
||||
public static final PacketType BUNDLE = new PacketType(PROTOCOL, SENDER, 0x00, "Delimiter", "BundleDelimiterPacket");
|
||||
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x01, "SpawnEntity", "SPacketSpawnObject");
|
||||
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x02, "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
|
||||
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x03, "Animation", "SPacketAnimation");
|
||||
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x04, "Statistic", "SPacketStatistics");
|
||||
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "BlockChangedAck");
|
||||
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, "BlockBreakAnimation", "SPacketBlockBreakAnim");
|
||||
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "TileEntityData", "SPacketUpdateTileEntity");
|
||||
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "BlockAction", "SPacketBlockAction");
|
||||
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x09, "BlockChange", "SPacketBlockChange");
|
||||
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0A, "Boss", "SPacketUpdateBossInfo");
|
||||
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0B, "ServerDifficulty", "SPacketServerDifficulty");
|
||||
public static final PacketType 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, "TabComplete", "SPacketTabComplete");
|
||||
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x11, "Commands");
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "CloseWindow", "SPacketCloseWindow");
|
||||
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x13, "WindowItems", "SPacketWindowItems");
|
||||
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x14, "WindowData", "SPacketWindowProperty");
|
||||
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x15, "SetSlot", "SPacketSetSlot");
|
||||
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x16, "SetCooldown", "SPacketCooldown");
|
||||
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 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 DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1A, "DeleteChat");
|
||||
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1B, "KickDisconnect", "SPacketDisconnect");
|
||||
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1C, "DisguisedChat");
|
||||
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1D, "EntityStatus", "SPacketEntityStatus");
|
||||
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1E, "Explosion", "SPacketExplosion");
|
||||
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1F, "UnloadChunk", "SPacketUnloadChunk");
|
||||
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x20, "GameStateChange", "SPacketChangeGameState");
|
||||
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x21, "OpenWindowHorse");
|
||||
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x22, "HurtAnimation", "ClientboundHurtAnimationPacket");
|
||||
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x23, "InitializeBorder");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x24, "KeepAlive", "SPacketKeepAlive");
|
||||
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x25, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
|
||||
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x26, "WorldEvent", "SPacketEffect");
|
||||
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x27, "WorldParticles", "SPacketParticles");
|
||||
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x28, "LightUpdate");
|
||||
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x29, "Login", "SPacketJoinGame");
|
||||
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x2A, "Map", "SPacketMaps");
|
||||
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2B, "OpenWindowMerchant");
|
||||
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2C, "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
|
||||
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2D, "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
|
||||
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x2E, "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
|
||||
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2F, "VehicleMove", "SPacketMoveVehicle");
|
||||
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x30, "OpenBook");
|
||||
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x31, "OpenWindow", "SPacketOpenWindow");
|
||||
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x32, "OpenSignEditor", "SPacketSignEditorOpen");
|
||||
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x33, "Ping");
|
||||
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x34, "PongResponse");
|
||||
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x35, "AutoRecipe", "SPacketPlaceGhostRecipe");
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x36, "Abilities", "SPacketPlayerAbilities");
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x37, "PlayerChat", "Chat", "SPacketChat");
|
||||
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x38, "PlayerCombatEnd");
|
||||
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerCombatEnter");
|
||||
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerCombatKill");
|
||||
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerInfoRemove");
|
||||
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerInfoUpdate", "PlayerInfo");
|
||||
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x3D, "LookAt", "SPacketPlayerPosLook");
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x3E, "Position");
|
||||
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x3F, "Recipes", "SPacketRecipeBook");
|
||||
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x40, "EntityDestroy", "SPacketDestroyEntities");
|
||||
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x41, "RemoveEntityEffect", "SPacketRemoveEntityEffect");
|
||||
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x42, "ResetScorePacket");
|
||||
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x43, "ResourcePackPopPacket");
|
||||
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x44, "ResourcePackPushPacket");
|
||||
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x45, "Respawn", "SPacketRespawn");
|
||||
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x46, "EntityHeadRotation", "SPacketEntityHeadLook");
|
||||
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x47, "MultiBlockChange", "SPacketMultiBlockChange");
|
||||
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x48, "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
|
||||
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x49, "ServerData");
|
||||
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x4A, "SetActionBarText");
|
||||
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x4B, "SetBorderCenter");
|
||||
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x4C, "SetBorderLerpSize");
|
||||
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x4D, "SetBorderSize");
|
||||
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x4E, "SetBorderWarningDelay");
|
||||
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4F, "SetBorderWarningDistance");
|
||||
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x50, "Camera", "SPacketCamera");
|
||||
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x51, "HeldItemSlot", "SPacketHeldItemChange");
|
||||
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x52, "ViewCentre");
|
||||
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x53, "ViewDistance");
|
||||
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x54, "SpawnPosition", "SPacketSpawnPosition");
|
||||
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x55, "ScoreboardDisplayObjective", "SPacketDisplayObjective");
|
||||
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x56, "EntityMetadata", "SPacketEntityMetadata");
|
||||
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x57, "AttachEntity", "SPacketEntityAttach");
|
||||
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x58, "EntityVelocity", "SPacketEntityVelocity");
|
||||
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x59, "EntityEquipment", "SPacketEntityEquipment");
|
||||
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x5A, "Experience", "SPacketSetExperience");
|
||||
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x5B, "UpdateHealth", "SPacketUpdateHealth");
|
||||
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5C, "ScoreboardObjective", "SPacketScoreboardObjective");
|
||||
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x5D, "Mount", "SPacketSetPassengers");
|
||||
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x5E, "ScoreboardTeam", "SPacketTeams");
|
||||
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x5F, "ScoreboardScore", "SPacketUpdateScore");
|
||||
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x60, "SetSimulationDistance");
|
||||
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x61, "SetSubtitleText");
|
||||
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x62, "UpdateTime", "SPacketTimeUpdate");
|
||||
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x63, "SetTitleText");
|
||||
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x64, "SetTitlesAnimation");
|
||||
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x65, "EntitySound", "SPacketSoundEffect");
|
||||
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x66, "NamedSoundEffect");
|
||||
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x67, "StartConfiguration");
|
||||
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x68, "StopSound");
|
||||
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x69, "SystemChat");
|
||||
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x6A, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
|
||||
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x6B, "NBTQuery");
|
||||
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x6C, "Collect", "SPacketCollectItem");
|
||||
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x6D, "EntityTeleport", "SPacketEntityTeleport");
|
||||
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x6E, "TickingStatePacket");
|
||||
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x6F, "TickingStepPacket");
|
||||
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x70, "Advancements", "SPacketAdvancementInfo");
|
||||
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x71, "UpdateAttributes", "SPacketEntityProperties");
|
||||
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x72, "EntityEffect", "SPacketEntityEffect");
|
||||
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x73, "RecipeUpdate");
|
||||
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x74, "Tags");
|
||||
|
||||
// ---- Removed in 1.9
|
||||
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
|
||||
@@ -239,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}
|
||||
*/
|
||||
@@ -265,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}
|
||||
*/
|
||||
@@ -274,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
|
||||
*/
|
||||
@@ -390,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
|
||||
@@ -410,61 +447,75 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static class Client extends PacketTypeEnum {
|
||||
private static final Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "TeleportAccept", "CPacketConfirmTeleport");
|
||||
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "TileNBTQuery");
|
||||
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x02, "DifficultyChange");
|
||||
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x03, "ChatAck");
|
||||
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, "ChatCommand");
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x05, "Chat", "CPacketChatMessage");
|
||||
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x06, "ChatSessionUpdate");
|
||||
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x07, "ChunkBatchReceived");
|
||||
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x08, "ClientCommand", "CPacketClientStatus");
|
||||
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x09, "Settings", "CPacketClientSettings");
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0A, "TabComplete", "CPacketTabComplete");
|
||||
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0B, "ConfigurationAcknowledged");
|
||||
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x0C, "EnchantItem", "CPacketEnchantItem");
|
||||
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0D, "WindowClick", "CPacketClickWindow");
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0E, "CloseWindow", "CPacketCloseWindow");
|
||||
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x0F, "ContainerSlotStateChangedPacket");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x10, "CustomPayload", "CPacketCustomPayload");
|
||||
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x11, "BEdit");
|
||||
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x12, "EntityNBTQuery");
|
||||
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x13, "UseEntity", "CPacketUseEntity");
|
||||
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x14, "JigsawGenerate");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x15, "KeepAlive", "CPacketKeepAlive");
|
||||
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x16, "DifficultyLock");
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x17, "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
|
||||
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x18, "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
|
||||
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x19, "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
|
||||
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x1A, "Flying$d");
|
||||
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x1B, "VehicleMove", "CPacketVehicleMove");
|
||||
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x1C, "BoatMove", "CPacketSteerBoat");
|
||||
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x1D, "PickItem");
|
||||
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x1E, "PingRequest");
|
||||
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x1F, "AutoRecipe", "CPacketPlaceRecipe");
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x20, "Abilities", "CPacketPlayerAbilities");
|
||||
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x21, "BlockDig", "CPacketPlayerDigging");
|
||||
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x22, "EntityAction", "CPacketEntityAction");
|
||||
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x23, "SteerVehicle", "CPacketInput");
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x24, "Pong");
|
||||
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x25, "RecipeSettings");
|
||||
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x26, "RecipeDisplayed", "CPacketRecipeInfo");
|
||||
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x27, "ItemName");
|
||||
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x28, "ResourcePackStatus", "CPacketResourcePackStatus");
|
||||
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x29, "Advancements", "CPacketSeenAdvancements");
|
||||
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x2A, "TrSel");
|
||||
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x2B, "Beacon");
|
||||
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x2C, "HeldItemSlot", "CPacketHeldItemChange");
|
||||
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x2D, "SetCommandBlock");
|
||||
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x2E, "SetCommandMinecart");
|
||||
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, "SetCreativeSlot", "CPacketCreativeInventoryAction");
|
||||
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x30, "SetJigsaw");
|
||||
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x31, "Struct");
|
||||
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x32, "UpdateSign", "CPacketUpdateSign");
|
||||
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x33, "ArmAnimation", "CPacketAnimation");
|
||||
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x34, "Spectate", "CPacketSpectate");
|
||||
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x35, "UseItem", "CPacketPlayerTryUseItemOnBlock");
|
||||
public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x36, "BlockPlace", "CPacketPlayerTryUseItem");
|
||||
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "AcceptTeleportation", "TeleportAccept", "CPacketConfirmTeleport");
|
||||
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
|
||||
@@ -484,6 +535,24 @@ 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
|
||||
*/
|
||||
@Deprecated
|
||||
public static final PacketType BLOCK_PLACE = USE_ITEM_ON.clone();
|
||||
|
||||
private static final Client INSTANCE = new Client();
|
||||
|
||||
// Prevent accidental construction
|
||||
@@ -517,9 +586,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
private static final Sender SENDER = Sender.SERVER;
|
||||
|
||||
@ForceAsync
|
||||
public static final PacketType SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, "ServerInfo", "SPacketServerInfo");
|
||||
public static final PacketType SERVER_INFO = new PacketType(PROTOCOL, SENDER, 0x00, "StatusResponse", "ServerInfo", "SPacketServerInfo");
|
||||
@ForceAsync
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x01, "Pong", "SPacketPong");
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x01, "PongResponse", "Pong", "SPacketPong");
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to {@link #SERVER_INFO}
|
||||
@@ -548,9 +617,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static class Client extends PacketTypeEnum {
|
||||
private static final Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Start", "CPacketServerQuery");
|
||||
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "StatusRequest", "Start", "CPacketServerQuery");
|
||||
@ForceAsync
|
||||
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x01, "Ping", "CPacketPing");
|
||||
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x01, "PingRequest", "Ping", "CPacketPing");
|
||||
|
||||
private static final Client INSTANCE = new Client();
|
||||
|
||||
@@ -585,11 +654,12 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
private static final Sender SENDER = Sender.SERVER;
|
||||
|
||||
@ForceAsync
|
||||
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, "Disconnect", "SPacketDisconnect");
|
||||
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "EncryptionBegin", "SPacketEncryptionRequest");
|
||||
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "Success", "SPacketLoginSuccess");
|
||||
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, "SetCompression", "SPacketEnableCompression");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, "CustomPayload", "SPacketCustomPayload");
|
||||
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, "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");
|
||||
|
||||
private static final Server INSTANCE = new Server();
|
||||
|
||||
@@ -611,10 +681,11 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static class Client extends PacketTypeEnum {
|
||||
private static final Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Start", "CPacketLoginStart");
|
||||
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "EncryptionBegin", "CPacketEncryptionResponse");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x02, "CustomPayload", "CPacketCustomPayload");
|
||||
public static final PacketType START = new PacketType(PROTOCOL, SENDER, 0x00, "Hello", "Start", "CPacketLoginStart");
|
||||
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "Key", "EncryptionBegin", "CPacketEncryptionResponse");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x02, "CustomQueryAnswer", "CustomPayload", "CPacketCustomPayload");
|
||||
public static final PacketType LOGIN_ACK = new PacketType(PROTOCOL, SENDER, 0x03, "LoginAcknowledged");
|
||||
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x04, "CookieResponse");
|
||||
|
||||
private static final Client INSTANCE = new Client();
|
||||
|
||||
@@ -647,22 +718,42 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static class Server extends PacketTypeEnum {
|
||||
private static final Sender SENDER = Sender.SERVER;
|
||||
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x00, "CustomPayload");
|
||||
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x01, "Disconnect");
|
||||
public static final PacketType FINISH_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x02, "FinishConfiguration");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x03, "KeepAlive");
|
||||
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x04, "Ping");
|
||||
public static final PacketType REGISTRY_DATA = new PacketType(PROTOCOL, SENDER, 0x05, "RegistryData");
|
||||
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x06, "ResourcePackPopPacket");
|
||||
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x07, "ResourcePackPushPacket");
|
||||
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x08, "UpdateEnabledFeatures");
|
||||
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x09, "UpdateTags");
|
||||
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x00, "CookieRequest");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x01, "CustomPayload");
|
||||
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x02, "Disconnect");
|
||||
public static final PacketType FINISH_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x03, "FinishConfiguration");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x04, "KeepAlive");
|
||||
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x05, "Ping");
|
||||
public static final PacketType RESET_CHAT = new PacketType(PROTOCOL, SENDER, 0x06, "ResetChat");
|
||||
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 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
|
||||
*/
|
||||
@Deprecated
|
||||
public static final PacketType RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 255, "ResourcePack");
|
||||
|
||||
private static final Server INSTANCE = new Server();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Server() { super(); }
|
||||
|
||||
public static Server getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -672,11 +763,24 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
private static final Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType CLIENT_INFORMATION = new PacketType(PROTOCOL, SENDER, 0x00, "ClientInformation");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x01, "CustomPayload");
|
||||
public static final PacketType FINISH_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x02, "FinishConfiguration");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x03, "KeepAlive");
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x04, "Pong");
|
||||
public static final PacketType RESOURCE_PACK_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "ResourcePack");
|
||||
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x01, "CookieResponse");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x02, "CustomPayload");
|
||||
public static final PacketType FINISH_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x03, "FinishConfiguration");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x04, "KeepAlive");
|
||||
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();
|
||||
|
||||
// Prevent accidental construction
|
||||
private Client() { super(); }
|
||||
|
||||
public static Client getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,11 +794,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
STATUS("Status", "status"),
|
||||
LOGIN("Login", "login"),
|
||||
CONFIGURATION("Configuration", "configuration"),
|
||||
|
||||
/**
|
||||
* Only for packets removed in Minecraft 1.7.2
|
||||
*/
|
||||
LEGACY("", "");
|
||||
UNKNOWN("", "");
|
||||
|
||||
private final String packetName;
|
||||
private final String mojangName;
|
||||
@@ -710,14 +810,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
* @return The corresponding protocol.
|
||||
*/
|
||||
public static Protocol fromVanilla(Enum<?> vanilla) {
|
||||
if (vanilla == null) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
switch (vanilla.name()) {
|
||||
case "HANDSHAKING": return HANDSHAKING;
|
||||
case "PLAY": return PLAY;
|
||||
case "STATUS": return STATUS;
|
||||
case "LOGIN": return LOGIN;
|
||||
case "CONFIGURATION": return CONFIGURATION;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized vanilla enum " + vanilla);
|
||||
default: return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -824,7 +927,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
addPacketTypes(Status.Client.getInstance()).
|
||||
addPacketTypes(Status.Server.getInstance()).
|
||||
addPacketTypes(Login.Client.getInstance()).
|
||||
addPacketTypes(Login.Server.getInstance());
|
||||
addPacketTypes(Login.Server.getInstance()).
|
||||
addPacketTypes(Configuration.Client.getInstance()).
|
||||
addPacketTypes(Configuration.Server.getInstance());
|
||||
}
|
||||
return LOOKUP;
|
||||
}
|
||||
@@ -931,6 +1036,14 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatSimpleClassName(Protocol protocol, Sender sender, String name) {
|
||||
return "Packet" + protocol.getPacketName() + sender.getPacketName() + name;
|
||||
}
|
||||
|
||||
private static String formatSimpleMojangClassName(Protocol protocol, Sender sender, String name) {
|
||||
return sender.getMojangName() + name + "Packet";
|
||||
}
|
||||
|
||||
private static String formatMojangClassName(Protocol protocol, Sender sender, String name) {
|
||||
return "net.minecraft.network.protocol." + protocol.getMojangName() + "." + sender.getMojangName()
|
||||
+ name + "Packet";
|
||||
@@ -1001,7 +1114,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
return type;
|
||||
}
|
||||
|
||||
static Consumer<String> onDynamicCreate = x -> {};
|
||||
static BiConsumer<PacketType, String> onDynamicCreate = (type, className) -> {};
|
||||
static BiConsumer<PacketType, Integer> onIdMismatch = (type, newId) -> {};
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender, ID, and class for 1.8+
|
||||
@@ -1019,7 +1133,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
|
||||
// Check the map first
|
||||
String className = packetClass.getName();
|
||||
PacketType type = find(map, className);
|
||||
PacketType type = find(map, packetClass);
|
||||
if (type == null) {
|
||||
// Guess we don't support this packet :/
|
||||
type = new PacketType(protocol, sender, packetId, PROTOCOL_VERSION, className);
|
||||
@@ -1027,14 +1141,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
|
||||
// Many may be scheduled, but only the first will be executed
|
||||
scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString());
|
||||
onDynamicCreate.accept(className);
|
||||
onDynamicCreate.accept(type, className);
|
||||
} else if (packetId != type.getCurrentId()) {
|
||||
onIdMismatch.accept(type, packetId);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static PacketType find(Map<String, PacketType> map, String clazz) {
|
||||
PacketType ret = map.get(clazz);
|
||||
private static PacketType find(Map<String, PacketType> map, Class<?> packetClass) {
|
||||
String className = packetClass.getName();
|
||||
PacketType ret = map.get(className);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
@@ -1044,7 +1161,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
List<String> aliases = check.getClassNames();
|
||||
if (aliases.size() > 1) {
|
||||
for (String alias : aliases) {
|
||||
if (alias.equals(clazz)) {
|
||||
if (alias.equals(className) || alias.equals(packetClass.getSimpleName())) {
|
||||
// We have a match!
|
||||
return check;
|
||||
}
|
||||
@@ -1174,6 +1291,9 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
} else {
|
||||
classNames.add(formatClassName(protocol, sender, classname));
|
||||
classNames.add(formatMojangClassName(protocol, sender, classname));
|
||||
classNames.add(formatSimpleClassName(protocol, sender, classname));
|
||||
classNames.add(formatSimpleMojangClassName(protocol, sender, classname));
|
||||
classNames.add(classname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.google.common.collect.Multimap;
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketTypeLookup {
|
||||
|
||||
public static class ProtocolSenderLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = new IntegerMap<>();
|
||||
@@ -27,6 +28,8 @@ class PacketTypeLookup {
|
||||
public final IntegerMap<PacketType> STATUS_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> LOGIN_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> LOGIN_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> CONFIGURATION_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> CONFIGURATION_SERVER = new IntegerMap<>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
@@ -44,6 +47,8 @@ class PacketTypeLookup {
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
case CONFIGURATION:
|
||||
return sender == Sender.CLIENT ? CONFIGURATION_CLIENT : CONFIGURATION_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
@@ -87,11 +92,6 @@ class PacketTypeLookup {
|
||||
}
|
||||
}
|
||||
|
||||
// Packet IDs from 1.6.4 and below
|
||||
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<>();
|
||||
private final IntegerMap<PacketType> serverLookup = new IntegerMap<>();
|
||||
private final IntegerMap<PacketType> clientLookup = new IntegerMap<>();
|
||||
|
||||
// Packets for 1.7.2
|
||||
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
|
||||
|
||||
@@ -123,9 +123,11 @@ class PacketTypeLookup {
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
* @deprecated no longer works and will always return null
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketType getFromLegacy(int packetId) {
|
||||
return legacyLookup.get(packetId);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,20 +144,11 @@ class PacketTypeLookup {
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - which packet type to look for first.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
* @deprecated no longer works and will always return null
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId, Sender preference) {
|
||||
if (preference == Sender.CLIENT)
|
||||
return getFirst(packetId, clientLookup, serverLookup);
|
||||
else
|
||||
return getFirst(packetId, serverLookup, clientLookup);
|
||||
}
|
||||
|
||||
// Helper method for looking up in two sets
|
||||
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
|
||||
if (first.containsKey(packetId))
|
||||
return first.get(packetId);
|
||||
else
|
||||
return second.get(packetId);
|
||||
@Deprecated
|
||||
public PacketType getFromLegacy(int packetId, Sender preference) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +165,8 @@ class PacketTypeLookup {
|
||||
}
|
||||
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
|
||||
return classLookup.getMap(protocol, sender).get(name);
|
||||
Map<String, PacketType> map = classLookup.getMap(protocol, sender);
|
||||
return map.get(name);
|
||||
}
|
||||
|
||||
public ClassLookup getClassLookup() {
|
||||
|
||||
@@ -37,24 +37,23 @@ import com.google.common.collect.Iterables;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import com.comphenix.protocol.internal.PlatformProvider;
|
||||
|
||||
/**
|
||||
* The main entry point for ProtocolLib.
|
||||
*
|
||||
@@ -76,8 +75,6 @@ public class ProtocolLib extends JavaPlugin {
|
||||
|
||||
public static final ReportType REPORT_CANNOT_PARSE_MINECRAFT_VERSION = new ReportType(
|
||||
"Unable to retrieve current Minecraft version. Assuming %s");
|
||||
public static final ReportType REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS = new ReportType(
|
||||
"Unable to detect conflicting plugin versions.");
|
||||
public static final ReportType REPORT_CANNOT_REGISTER_COMMAND = new ReportType("Cannot register command %s: %s");
|
||||
|
||||
public static final ReportType REPORT_CANNOT_CREATE_TIMEOUT_TASK = new ReportType(
|
||||
@@ -166,7 +163,7 @@ public class ProtocolLib extends JavaPlugin {
|
||||
: new DefaultScheduler(this);
|
||||
|
||||
// Check for other versions
|
||||
this.checkConflictingVersions();
|
||||
scanForOtherProtocolLibJars();
|
||||
|
||||
// Handle unexpected Minecraft versions
|
||||
MinecraftVersion version = this.verifyMinecraftVersion();
|
||||
@@ -309,6 +306,14 @@ public class ProtocolLib extends JavaPlugin {
|
||||
logger.addHandler(this.redirectHandler);
|
||||
}
|
||||
|
||||
private void highlyVisibleError(String... lines) {
|
||||
Logger directLogging = Logger.getLogger("Minecraft");
|
||||
|
||||
for (String line : ChatExtensions.toFlowerBox(lines, "*", 3, 1)) {
|
||||
directLogging.severe(line);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
try {
|
||||
@@ -317,23 +322,14 @@ public class ProtocolLib extends JavaPlugin {
|
||||
|
||||
// Silly plugin reloaders!
|
||||
if (protocolManager == null) {
|
||||
Logger directLogging = Logger.getLogger("Minecraft");
|
||||
String[] message = new String[]{
|
||||
" ProtocolLib does not support plugin reloaders! ", " Please use the built-in reload command! "
|
||||
};
|
||||
|
||||
// Print as severe
|
||||
for (String line : ChatExtensions.toFlowerBox(message, "*", 3, 1)) {
|
||||
directLogging.severe(line);
|
||||
}
|
||||
|
||||
this.disablePlugin();
|
||||
highlyVisibleError(
|
||||
" ProtocolLib does not support plugin reloaders! ",
|
||||
" Please use the built-in reload command! "
|
||||
);
|
||||
disablePlugin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for incompatible plugins
|
||||
this.checkForIncompatibility(manager);
|
||||
|
||||
// Set up command handlers
|
||||
this.registerCommand(CommandProtocol.NAME, this.commandProtocol);
|
||||
this.registerCommand(CommandPacket.NAME, this.commandPacket);
|
||||
@@ -369,24 +365,6 @@ public class ProtocolLib extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkForIncompatibility(PluginManager manager) {
|
||||
for (String plugin : ProtocolLibrary.INCOMPATIBLE) {
|
||||
if (manager.getPlugin(plugin) != null) {
|
||||
// Special case for TagAPI and iTag
|
||||
if (plugin.equals("TagAPI")) {
|
||||
Plugin iTag = manager.getPlugin("iTag");
|
||||
if (iTag == null || iTag.getDescription().getVersion().startsWith("1.0")) {
|
||||
logger.severe("Detected incompatible plugin: TagAPI");
|
||||
}
|
||||
} else {
|
||||
logger.severe("Detected incompatible plugin: " + plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin authors: Notify me to remove these
|
||||
|
||||
// Used to check Minecraft version
|
||||
private MinecraftVersion verifyMinecraftVersion() {
|
||||
MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION);
|
||||
@@ -416,50 +394,47 @@ public class ProtocolLib extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConflictingVersions() {
|
||||
Pattern ourPlugin = Pattern.compile("ProtocolLib-(.*)\\.jar");
|
||||
MinecraftVersion currentVersion = new MinecraftVersion(this.getDescription().getVersion());
|
||||
MinecraftVersion newestVersion = null;
|
||||
|
||||
// Skip the file that contains this current instance however
|
||||
File loadedFile = this.getFile();
|
||||
|
||||
private void scanForOtherProtocolLibJars() {
|
||||
try {
|
||||
// Scan the plugin folder for newer versions of ProtocolLib
|
||||
// The plugin folder isn't always plugins/
|
||||
File loadedFile = this.getFile();
|
||||
File pluginFolder = this.getDataFolder().getParentFile();
|
||||
|
||||
File[] candidates = pluginFolder.listFiles();
|
||||
if (candidates != null) {
|
||||
for (File candidate : candidates) {
|
||||
if (candidate.isFile() && !candidate.equals(loadedFile)) {
|
||||
Matcher match = ourPlugin.matcher(candidate.getName());
|
||||
if (match.matches()) {
|
||||
MinecraftVersion version = new MinecraftVersion(match.group(1));
|
||||
|
||||
if (candidate.length() == 0) {
|
||||
// Delete and inform the user
|
||||
logger.info((candidate.delete() ? "Deleted " : "Could not delete ") + candidate);
|
||||
} else if (newestVersion == null || newestVersion.compareTo(version) < 0) {
|
||||
newestVersion = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (candidates == null) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// TODO This shows [ProtocolLib] and [ProtocolLibrary] in the message
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DETECT_CONFLICTING_PLUGINS).error(e));
|
||||
}
|
||||
|
||||
// See if the newest version is actually higher
|
||||
if (newestVersion != null && currentVersion.compareTo(newestVersion) < 0) {
|
||||
// We don't need to set internal classes or instances to NULL - that would break the other loaded plugin
|
||||
this.skipDisable = true;
|
||||
String ourName = loadedFile.getName();
|
||||
List<String> others = new ArrayList<>();
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
"Detected a newer version of ProtocolLib (%s) in plugin folder than the current (%s). Disabling.",
|
||||
newestVersion.getVersion(), currentVersion.getVersion()));
|
||||
for (File candidate : candidates) {
|
||||
if (!candidate.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String jarName = candidate.getName();
|
||||
if (jarName.equals(ourName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String jarNameLower = candidate.getName().toLowerCase();
|
||||
if (!jarNameLower.startsWith("protocollib") || !jarNameLower.endsWith(".jar")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
others.add(jarName);
|
||||
}
|
||||
|
||||
if (!others.isEmpty()) {
|
||||
highlyVisibleError(
|
||||
" Detected multiple ProtocolLib JAR files in the plugin directory! ",
|
||||
" You should remove all but one of them or there will likely be undesired behavior. ",
|
||||
" This JAR: " + loadedFile.getName(),
|
||||
" Other detected JARs: " + String.join(",", others)
|
||||
);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ProtocolLogger.debug("Failed to scan plugins directory for ProtocolLib jars", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,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));
|
||||
@@ -530,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;
|
||||
|
||||
@@ -561,15 +540,15 @@ public class ProtocolLib extends JavaPlugin {
|
||||
|
||||
// that reloading the server might break ProtocolLib / plugins depending on it
|
||||
if (Util.isCurrentlyReloading()) {
|
||||
logger.severe("╔══════════════════════════════════════════════════════════════════╗");
|
||||
logger.severe("║ WARNING ║");
|
||||
logger.severe("║ RELOADING THE SERVER WHILE PROTOCOL LIB IS ENABLED MIGHT ║");
|
||||
logger.severe("║ LEAD TO UNEXPECTED ERRORS! ║");
|
||||
logger.severe("║ ║");
|
||||
logger.severe("║ Consider to cleanly restart your server if you encounter ║");
|
||||
logger.severe("║ any issues related to Protocol Lib before opening an issue ║");
|
||||
logger.severe("║ on GitHub! ║");
|
||||
logger.severe("╚══════════════════════════════════════════════════════════════════╝");
|
||||
highlyVisibleError(
|
||||
" WARNING ",
|
||||
" RELOADING THE SERVER WHILE PROTOCOLLIB IS ENABLED MIGHT ",
|
||||
" LEAD TO UNEXPECTED ERRORS! ",
|
||||
" ",
|
||||
" Consider cleanly restarting your server if you encounter ",
|
||||
" any issues related to ProtocolLib before opening an issue ",
|
||||
" on GitHub! "
|
||||
);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
|
||||
@@ -17,9 +17,7 @@ package com.comphenix.protocol;
|
||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.scheduler.ProtocolScheduler;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import java.util.List;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
@@ -37,17 +35,12 @@ public class ProtocolLibrary {
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "1.20.4";
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "26.3";
|
||||
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) was released.
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (26.3) was released.
|
||||
*/
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-12-07";
|
||||
|
||||
/**
|
||||
* Plugins that are currently incompatible with ProtocolLib.
|
||||
*/
|
||||
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2026-09-15";
|
||||
|
||||
private static Plugin plugin;
|
||||
private static ProtocolConfig config;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.updater.Updater;
|
||||
|
||||
/**
|
||||
* Callback invoked on the main thread after an asynchronous update check completes.
|
||||
* <p>
|
||||
* Extracted as a top-level class (instead of an anonymous inner class) so that
|
||||
* {@code PaperPluginClassLoader} can always resolve its {@code .class} entry when
|
||||
* {@link org.bukkit.scheduler.BukkitScheduler#runTask} dispatches it back onto the
|
||||
* main server thread — the same pattern used by {@code SpigotUpdateRunnable}.
|
||||
*/
|
||||
final class UpdateNotifyRunnable implements Runnable {
|
||||
|
||||
private final CommandSender sender;
|
||||
private final boolean command;
|
||||
private final Updater updater;
|
||||
private final ProtocolConfig config;
|
||||
private final Plugin plugin;
|
||||
private final CommandProtocol owner;
|
||||
|
||||
UpdateNotifyRunnable(CommandSender sender, boolean command,
|
||||
Updater updater, ProtocolConfig config,
|
||||
Plugin plugin, CommandProtocol owner) {
|
||||
this.sender = sender;
|
||||
this.command = command;
|
||||
this.updater = updater;
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (command) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
String remoteVersion = updater.getRemoteVersion();
|
||||
if (remoteVersion != null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
|
||||
}
|
||||
} else if (updater.shouldNotify() || config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
}
|
||||
|
||||
updater.removeListener(this);
|
||||
owner.updateFinished();
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,6 @@
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
@@ -31,16 +25,22 @@ import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.scheduler.ProtocolScheduler;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Represents a filter manager for asynchronous packets.
|
||||
* <p>
|
||||
@@ -50,8 +50,8 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
private OutboundPacketListenerSet outboundTimeoutListeners;
|
||||
private InboundPacketListenerSet inboundTimeoutListeners;
|
||||
private Set<PacketListener> timeoutListeners;
|
||||
|
||||
private PacketProcessingQueue serverProcessingQueue;
|
||||
@@ -84,11 +84,11 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
*/
|
||||
public AsyncFilterManager(ErrorReporter reporter, ProtocolScheduler scheduler) {
|
||||
// Initialize timeout listeners
|
||||
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||
this.outboundTimeoutListeners = new OutboundPacketListenerSet(null, reporter);
|
||||
this.inboundTimeoutListeners = new InboundPacketListenerSet(null, reporter);
|
||||
this.timeoutListeners = ConcurrentHashMap.newKeySet();
|
||||
|
||||
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
|
||||
this.playerSendingHandler = new PlayerSendingHandler(outboundTimeoutListeners, inboundTimeoutListeners);
|
||||
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.playerSendingHandler.initializeScheduler();
|
||||
@@ -130,9 +130,9 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
if (!ListeningWhitelist.isEmpty(sending))
|
||||
serverTimeoutListeners.addListener(listener, sending);
|
||||
outboundTimeoutListeners.addListener(listener);
|
||||
if (!ListeningWhitelist.isEmpty(receiving))
|
||||
serverTimeoutListeners.addListener(listener, receiving);
|
||||
inboundTimeoutListeners.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,9 +145,8 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder();
|
||||
|
||||
// Add every asynchronous packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler :
|
||||
Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
|
||||
builder.add(handler.getListener().getAsyncListener());
|
||||
for (AsyncListenerHandler handler : Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
|
||||
builder.add(handler.getAsyncListener());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
@@ -203,13 +202,9 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Do it in the opposite order
|
||||
if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 ||
|
||||
clientTimeoutListeners.removeListener(listener, receiving).size() > 0) {
|
||||
timeoutListeners.remove(listener);
|
||||
if (timeoutListeners.remove(listener)) {
|
||||
outboundTimeoutListeners.removeListener(listener);
|
||||
inboundTimeoutListeners.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,9 +228,9 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
return null;
|
||||
|
||||
for (PacketType type : search.getTypes()) {
|
||||
for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) {
|
||||
if (element.getListener().getAsyncListener() == target) {
|
||||
return element.getListener();
|
||||
for (AsyncListenerHandler element : queue.get(type)) {
|
||||
if (element.getAsyncListener() == target) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,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
|
||||
@@ -290,12 +285,12 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
}
|
||||
|
||||
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||
for (AsyncListenerHandler listener : ImmutableList.copyOf(processingQueue.values())) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener.getListener());
|
||||
if (Objects.equal(listener.getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,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);
|
||||
@@ -328,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,16 +339,15 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
@Override
|
||||
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
|
||||
return list != null && list.size() > 0;
|
||||
return getProcessingQueue(packet).contains(packet.getPacketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,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
|
||||
@@ -388,8 +380,8 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
playerSendingHandler.cleanupAll();
|
||||
timeoutListeners.clear();
|
||||
|
||||
serverTimeoutListeners = null;
|
||||
clientTimeoutListeners = null;
|
||||
outboundTimeoutListeners = null;
|
||||
inboundTimeoutListeners = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,18 +409,18 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
// Now, get the next non-cancelled listener
|
||||
if (!marker.hasExpired()) {
|
||||
for (; marker.getListenerTraversal().hasNext(); ) {
|
||||
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
||||
|
||||
AsyncListenerHandler handler = marker.getListenerTraversal().next();
|
||||
|
||||
if (!handler.isCancelled()) {
|
||||
marker.incrementProcessingDelay();
|
||||
handler.enqueuePacket(packet);
|
||||
marker.incrementProcessingDelay();
|
||||
handler.enqueuePacket(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There are no more listeners - queue the packet for transmission
|
||||
signalFreeProcessingSlot(packet);
|
||||
signalFreeProcessingSlot(packet, onMainThread);
|
||||
|
||||
PacketSendingQueue queue = getSendingQueue(packet, false);
|
||||
|
||||
@@ -467,11 +459,18 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet has finished processing.
|
||||
* Signal that a packet has finished processing. Tries to process further packets
|
||||
* if a processing slot is still free.
|
||||
* @param packet - packet to signal.
|
||||
* @param onMainThread whether or not this method was run by the main thread.
|
||||
*/
|
||||
public void signalFreeProcessingSlot(PacketEvent packet) {
|
||||
getProcessingQueue(packet).signalProcessingDone();
|
||||
public void signalFreeProcessingSlot(PacketEvent packet, boolean onMainThread) {
|
||||
PacketProcessingQueue queue = getProcessingQueue(packet);
|
||||
// mark slot as done
|
||||
queue.signalProcessingDone();
|
||||
|
||||
// start processing next slot if possible
|
||||
queue.signalBeginProcessing(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,9 +34,8 @@ import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.scheduler.Task;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||
import com.comphenix.protocol.timing.TimedTracker;
|
||||
import com.comphenix.protocol.timing.TimingListenerType;
|
||||
import com.comphenix.protocol.timing.TimingTrackerManager;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
@@ -100,9 +99,6 @@ public class AsyncListenerHandler {
|
||||
// Warn plugins that the async listener handler must be started
|
||||
private Task warningTask;
|
||||
|
||||
// Timing manager
|
||||
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||
|
||||
/**
|
||||
* Construct a manager for an asynchronous packet handler.
|
||||
* @param mainThread - the main game thread.
|
||||
@@ -326,9 +322,9 @@ public class AsyncListenerHandler {
|
||||
|
||||
scheduleAsync(() -> delegateCopy.apply(listenerLoop));
|
||||
}
|
||||
|
||||
|
||||
private void scheduleAsync(Runnable runnable) {
|
||||
filterManager.getScheduler().runTaskAsync(runnable);
|
||||
filterManager.getScheduler().runTaskAsync(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -542,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.");
|
||||
@@ -600,33 +596,17 @@ public class AsyncListenerHandler {
|
||||
marker.setListenerHandler(this);
|
||||
marker.setWorkerID(workerID);
|
||||
|
||||
// We're not THAT worried about performance here
|
||||
if (timedManager.isTiming()) {
|
||||
// Retrieve the tracker to use
|
||||
TimedTracker tracker = timedManager.getTracker(listener,
|
||||
packet.isServerPacket() ? ListenerType.ASYNC_SERVER_SIDE : ListenerType.ASYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
|
||||
// And we're done
|
||||
tracker.endTracking(token, packet.getPacketType());
|
||||
|
||||
} else {
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
}
|
||||
TimingTrackerManager.get(listener, packet.isServerPacket() ? TimingListenerType.ASYNC_OUTBOUND : TimingListenerType.ASYNC_INBOUND)
|
||||
.track(packet.getPacketType(), () -> {
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
listener.onPacketReceiving(packet);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
|
||||
|
||||
@@ -23,15 +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.PrioritizedListener;
|
||||
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;
|
||||
@@ -65,12 +68,12 @@ 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.
|
||||
*/
|
||||
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||
private transient Iterator<AsyncListenerHandler> listenerTraversal;
|
||||
|
||||
// Timeout handling
|
||||
private long initialTime;
|
||||
@@ -87,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;
|
||||
@@ -110,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;
|
||||
@@ -181,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,7 +368,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
* Retrieve iterator for the next listener in line.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
Iterator<AsyncListenerHandler> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
|
||||
@@ -374,7 +376,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
* Set the iterator for the next listener.
|
||||
* @param listenerTraversal - the new async packet listener iterator.
|
||||
*/
|
||||
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||
void setListenerTraversal(Iterator<AsyncListenerHandler> listenerTraversal) {
|
||||
this.listenerTraversal = listenerTraversal;
|
||||
}
|
||||
|
||||
@@ -384,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.concurrent.PacketTypeMultiMap;
|
||||
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.comphenix.protocol.injector.PrioritizedListener;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.MinMaxPriorityQueue;
|
||||
|
||||
|
||||
@@ -37,7 +41,7 @@ import com.google.common.collect.MinMaxPriorityQueue;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||
class PacketProcessingQueue {
|
||||
public static final ReportType REPORT_GUAVA_CORRUPT_MISSING =
|
||||
new ReportType("Guava is either missing or corrupt. Reverting to PriorityQueue.");
|
||||
|
||||
@@ -66,6 +70,16 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
||||
// Packets for sending
|
||||
private PlayerSendingHandler sendingHandler;
|
||||
|
||||
private final PacketTypeMultiMap<AsyncListenerHandler> map = new PacketTypeMultiMap<>();
|
||||
|
||||
public void addListener(AsyncListenerHandler listener, ListeningWhitelist whitelist) {
|
||||
map.put(whitelist, listener);
|
||||
}
|
||||
|
||||
public List<PacketType> removeListener(AsyncListenerHandler listener, ListeningWhitelist whitelist) {
|
||||
return map.remove(whitelist, listener);
|
||||
}
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
|
||||
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||
}
|
||||
@@ -131,17 +145,17 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
||||
if (holder != null) {
|
||||
PacketEvent packet = holder.getEvent();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
|
||||
Iterable<AsyncListenerHandler> list = map.get(packet.getPacketType());
|
||||
|
||||
marker.incrementProcessingDelay();
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||
Iterator<AsyncListenerHandler> iterator = list.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
marker.setListenerTraversal(iterator);
|
||||
iterator.next().getListener().enqueuePacket(packet);
|
||||
iterator.next().enqueuePacket(packet);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -178,17 +192,31 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
||||
public int getMaximumConcurrency() {
|
||||
return maximumConcurrency;
|
||||
}
|
||||
|
||||
public boolean contains(PacketType packetType) {
|
||||
return map.contains(packetType);
|
||||
}
|
||||
|
||||
public Iterable<AsyncListenerHandler> get(PacketType packetType) {
|
||||
return map.get(packetType);
|
||||
}
|
||||
|
||||
public ImmutableSet<PacketType> keySet() {
|
||||
return map.getPacketTypes();
|
||||
}
|
||||
|
||||
public Iterable<AsyncListenerHandler> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||
if (handler != null) {
|
||||
handler.getListener().cancel();
|
||||
}
|
||||
for (AsyncListenerHandler handler : ImmutableList.copyOf(map.values())) {
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
// Remove the rest, just in case
|
||||
clearListeners();
|
||||
map.clear();
|
||||
|
||||
// Remove every packet in the queue
|
||||
processingQueue.clear();
|
||||
|
||||
@@ -24,27 +24,26 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.ConcurrentPlayerMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Contains every sending queue for every player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class PlayerSendingHandler {
|
||||
private final ErrorReporter reporter;
|
||||
private final ConcurrentMap<Player, QueueContainer> playerSendingQueues;
|
||||
|
||||
// Timeout listeners
|
||||
private final SortedPacketListenerList serverTimeoutListeners;
|
||||
private final SortedPacketListenerList clientTimeoutListeners;
|
||||
private final OutboundPacketListenerSet outboundTimeoutListeners;
|
||||
private final InboundPacketListenerSet inboundTimeoutListeners;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
@@ -58,37 +57,37 @@ class PlayerSendingHandler {
|
||||
* @author Kristian
|
||||
*/
|
||||
private class QueueContainer {
|
||||
private final PacketSendingQueue serverQueue;
|
||||
private final PacketSendingQueue clientQueue;
|
||||
private final PacketSendingQueue outboundQueue;
|
||||
private final PacketSendingQueue inboundQueue;
|
||||
|
||||
public QueueContainer() {
|
||||
// Server packets can be sent concurrently
|
||||
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||
outboundQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
serverTimeoutListeners.invokePacketSending(reporter, event);
|
||||
outboundTimeoutListeners.invoke(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Client packets must be synchronized
|
||||
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||
inboundQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
clientTimeoutListeners.invokePacketSending(reporter, event);
|
||||
inboundTimeoutListeners.invoke(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketSendingQueue getServerQueue() {
|
||||
return serverQueue;
|
||||
public PacketSendingQueue getOutboundQueue() {
|
||||
return outboundQueue;
|
||||
}
|
||||
|
||||
public PacketSendingQueue getClientQueue() {
|
||||
return clientQueue;
|
||||
public PacketSendingQueue getInboundQueue() {
|
||||
return inboundQueue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +97,9 @@ class PlayerSendingHandler {
|
||||
* @param serverTimeoutListeners - set of server timeout listeners.
|
||||
* @param clientTimeoutListeners - set of client timeout listeners.
|
||||
*/
|
||||
public PlayerSendingHandler(ErrorReporter reporter,
|
||||
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
|
||||
|
||||
this.reporter = reporter;
|
||||
this.serverTimeoutListeners = serverTimeoutListeners;
|
||||
this.clientTimeoutListeners = clientTimeoutListeners;
|
||||
public PlayerSendingHandler(OutboundPacketListenerSet serverTimeoutListeners, InboundPacketListenerSet clientTimeoutListeners) {
|
||||
this.outboundTimeoutListeners = serverTimeoutListeners;
|
||||
this.inboundTimeoutListeners = clientTimeoutListeners;
|
||||
|
||||
// Initialize storage of queues
|
||||
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
|
||||
@@ -138,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;
|
||||
@@ -154,7 +154,7 @@ class PlayerSendingHandler {
|
||||
|
||||
// Check for NULL again
|
||||
if (queues != null)
|
||||
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
|
||||
return packet.isServerPacket() ? queues.getOutboundQueue() : queues.getInboundQueue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@@ -165,8 +165,8 @@ class PlayerSendingHandler {
|
||||
public void sendAllPackets() {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queues : playerSendingQueues.values()) {
|
||||
queues.getClientQueue().cleanupAll();
|
||||
queues.getServerQueue().cleanupAll();
|
||||
queues.getInboundQueue().cleanupAll();
|
||||
queues.getOutboundQueue().cleanupAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ class PlayerSendingHandler {
|
||||
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().signalPacketUpdate(types, synchronusOK);
|
||||
queue.getOutboundQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ class PlayerSendingHandler {
|
||||
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().signalPacketUpdate(types, synchronusOK);
|
||||
queue.getInboundQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +202,7 @@ class PlayerSendingHandler {
|
||||
*/
|
||||
public void trySendServerPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().trySendPackets(onMainThread);
|
||||
queue.getOutboundQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ class PlayerSendingHandler {
|
||||
*/
|
||||
public void trySendClientPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().trySendPackets(onMainThread);
|
||||
queue.getInboundQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ class PlayerSendingHandler {
|
||||
List<PacketSendingQueue> result = new ArrayList<>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getServerQueue());
|
||||
result.add(queue.getOutboundQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ class PlayerSendingHandler {
|
||||
List<PacketSendingQueue> result = new ArrayList<>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getClientQueue());
|
||||
result.add(queue.getInboundQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* A thread-safe implementation of a listener multimap.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class AbstractConcurrentListenerMultimap<T> {
|
||||
|
||||
// The core of our map
|
||||
private final ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<T>>> mapListeners;
|
||||
|
||||
protected AbstractConcurrentListenerMultimap() {
|
||||
this.mapListeners = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet receivers.
|
||||
*
|
||||
* @param listener - listener with a list of packets to receive notifications for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(T listener, ListeningWhitelist whitelist) {
|
||||
final PrioritizedListener<T> prioritized = new PrioritizedListener<>(listener, whitelist.getPriority());
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
this.addListener(type, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notification list
|
||||
private void addListener(PacketType type, PrioritizedListener<T> listener) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preserve iterator order,
|
||||
// which is an essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener<T>> value = new SortedCopyOnWriteArray<>();
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree on which to use
|
||||
list = this.mapListeners.putIfAbsent(type, value);
|
||||
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
*
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<PacketType> removeListener(T listener, ListeningWhitelist whitelist) {
|
||||
List<PacketType> removedPackets = new ArrayList<>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
|
||||
|
||||
if(list == null || list.isEmpty()) continue;
|
||||
|
||||
// Remove any listeners
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener<T>(listener, whitelist.getPriority()));
|
||||
|
||||
if (list.isEmpty()) {
|
||||
this.mapListeners.remove(type);
|
||||
removedPackets.add(type);
|
||||
}
|
||||
// Move on to the next
|
||||
}
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||
* <p>
|
||||
* The returned list is thread-safe and doesn't require synchronization.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
public Collection<PrioritizedListener<T>> getListener(PacketType type) {
|
||||
return this.mapListeners.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every listener.
|
||||
*
|
||||
* @return Every listener.
|
||||
*/
|
||||
public Iterable<PrioritizedListener<T>> values() {
|
||||
return Iterables.concat(this.mapListeners.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every registered packet type:
|
||||
*
|
||||
* @return Registered packet type.
|
||||
*/
|
||||
public Set<PacketType> keySet() {
|
||||
return this.mapListeners.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
this.mapListeners.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,494 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Represents a generic store of intervals and associated values. No two intervals
|
||||
* can overlap in this representation.
|
||||
* <p>
|
||||
* Note that this implementation is not thread safe.
|
||||
*
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TKey> - type of the key. Must implement Comparable.
|
||||
* @param <TValue> - type of the value to associate.
|
||||
*/
|
||||
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
|
||||
|
||||
protected enum State {
|
||||
OPEN,
|
||||
CLOSE,
|
||||
BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range and a value in this interval tree.
|
||||
*/
|
||||
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
||||
private final EndPoint left;
|
||||
private final EndPoint right;
|
||||
|
||||
Entry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalAccessError("left cannot be NUll");
|
||||
if (right == null)
|
||||
throw new IllegalAccessError("right cannot be NUll");
|
||||
if (left.key.compareTo(right.key) > 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
|
||||
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<TKey> getKey() {
|
||||
return Range.closed(left.key, right.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue getValue() {
|
||||
return left.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue setValue(TValue value) {
|
||||
TValue old = left.value;
|
||||
|
||||
// Set both end points
|
||||
left.value = value;
|
||||
right.value = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick equality check
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof AbstractIntervalTree.Entry) {
|
||||
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
|
||||
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
|
||||
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(left.key, right.key, left.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single end point (open, close or both) of a range.
|
||||
*/
|
||||
protected class EndPoint {
|
||||
|
||||
// Whether or not the end-point is opening a range, closing a range or both.
|
||||
public State state;
|
||||
|
||||
// The value this range contains
|
||||
public TValue value;
|
||||
|
||||
// The key of this end point
|
||||
public TKey key;
|
||||
|
||||
public EndPoint(State state, TKey key, TValue value) {
|
||||
this.state = state;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// To quickly look up ranges we'll index them by endpoints
|
||||
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
|
||||
return remove(lowerBound, upperBound, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
||||
checkBounds(lowerBound, upperBound);
|
||||
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
||||
|
||||
EndPoint first = getNextEndPoint(lowerBound, true);
|
||||
EndPoint last = getPreviousEndPoint(upperBound, true);
|
||||
|
||||
// Used while resizing intervals
|
||||
EndPoint previous = null;
|
||||
EndPoint next = null;
|
||||
|
||||
Set<Entry> resized = new HashSet<>();
|
||||
Set<Entry> removed = new HashSet<>();
|
||||
|
||||
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
||||
if (first != null && first.state == State.CLOSE) {
|
||||
previous = getPreviousEndPoint(first.key, false);
|
||||
|
||||
// Add the interval back
|
||||
if (previous != null) {
|
||||
removed.add(getEntry(previous, first));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closing element too.
|
||||
if (last != null && last.state == State.OPEN) {
|
||||
next = getNextEndPoint(last.key, false);
|
||||
|
||||
if (next != null) {
|
||||
removed.add(getEntry(last, next));
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove both ranges
|
||||
removeEntrySafely(previous, first);
|
||||
removeEntrySafely(last, next);
|
||||
|
||||
// Add new resized intervals
|
||||
if (preserveDifference) {
|
||||
if (previous != null) {
|
||||
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
|
||||
}
|
||||
if (next != null) {
|
||||
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the removed entries too
|
||||
getEntries(removed, range);
|
||||
invokeEntryRemoved(removed);
|
||||
|
||||
if (preserveDifference) {
|
||||
invokeEntryAdded(resized);
|
||||
}
|
||||
|
||||
// Remove the range as well
|
||||
range.clear();
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entry from a given set of end points.
|
||||
* @param left - leftmost end point.
|
||||
* @param right - rightmost end point.
|
||||
* @return The associated entry.
|
||||
*/
|
||||
protected Entry getEntry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalArgumentException("left endpoint cannot be NULL.");
|
||||
if (right == null)
|
||||
throw new IllegalArgumentException("right endpoint cannot be NULL.");
|
||||
|
||||
// Make sure the order is correct
|
||||
if (right.key.compareTo(left.key) < 0) {
|
||||
return getEntry(right, left);
|
||||
} else {
|
||||
return new Entry(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeEntrySafely(EndPoint left, EndPoint right) {
|
||||
if (left != null && right != null) {
|
||||
bounds.remove(left.key);
|
||||
bounds.remove(right.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a given end point
|
||||
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
|
||||
EndPoint endPoint = bounds.get(key);
|
||||
|
||||
if (endPoint != null) {
|
||||
endPoint.state = State.BOTH;
|
||||
} else {
|
||||
endPoint = new EndPoint(state, key, value);
|
||||
bounds.put(key, endPoint);
|
||||
}
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval of keys with a certain value. Any previous
|
||||
* association will be overwritten in the given interval.
|
||||
* <p>
|
||||
* Overlapping intervals are not permitted. A key can only be associated with a single value.
|
||||
*
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset this range.
|
||||
*/
|
||||
public void put(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
|
||||
remove(lowerBound, upperBound, true);
|
||||
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval without performing any interval checks.
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset the range.
|
||||
*/
|
||||
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// OK. Add the end points now
|
||||
if (value != null) {
|
||||
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
||||
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
||||
|
||||
return new Entry(left, right);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the validity of the given interval.
|
||||
* @param lowerBound - lower bound (inclusive).
|
||||
* @param upperBound - upper bound (inclusive).
|
||||
*/
|
||||
private void checkBounds(TKey lowerBound, TKey upperBound) {
|
||||
if (lowerBound == null)
|
||||
throw new IllegalAccessError("lowerbound cannot be NULL.");
|
||||
if (upperBound == null)
|
||||
throw new IllegalAccessError("upperBound cannot be NULL.");
|
||||
if (upperBound.compareTo(lowerBound) < 0)
|
||||
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given key is within an interval.
|
||||
* @param key - key to check.
|
||||
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(TKey key) {
|
||||
return getEndPoint(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates over every range in this interval tree.
|
||||
* @return Number of ranges.
|
||||
*/
|
||||
public Set<Entry> entrySet() {
|
||||
// Don't mind the Java noise
|
||||
Set<Entry> result = new HashSet<>();
|
||||
getEntries(result, bounds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every interval.
|
||||
*/
|
||||
public void clear() {
|
||||
if (!bounds.isEmpty()) {
|
||||
remove(bounds.firstKey(), bounds.lastKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map of end points into a set of entries.
|
||||
* @param destination - set of entries.
|
||||
* @param map - a map of end points.
|
||||
*/
|
||||
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
||||
Map.Entry<TKey, EndPoint> last = null;
|
||||
|
||||
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
|
||||
switch (entry.getValue().state) {
|
||||
case BOTH:
|
||||
EndPoint point = entry.getValue();
|
||||
destination.add(new Entry(point, point));
|
||||
break;
|
||||
case CLOSE:
|
||||
if (last != null) {
|
||||
destination.add(new Entry(last.getValue(), entry.getValue()));
|
||||
}
|
||||
break;
|
||||
case OPEN:
|
||||
// We don't know the full range yet
|
||||
last = entry;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Illegal open/close state detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts every range from the given tree into the current tree.
|
||||
* @param other - the other tree to read from.
|
||||
*/
|
||||
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
||||
// Naively copy every range.
|
||||
for (Entry entry : other.entrySet()) {
|
||||
put(entry.left.key, entry.right.key, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
|
||||
* @param key - the level to read for.
|
||||
* @return The correct amount of experience, or NULL if nothing was recorded.
|
||||
*/
|
||||
public TValue get(TKey key) {
|
||||
EndPoint point = getEndPoint(key);
|
||||
|
||||
if (point != null)
|
||||
return point.value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the left-most end-point associated with this key.
|
||||
* @param key - key to search for.
|
||||
* @return The end point found, or NULL.
|
||||
*/
|
||||
protected EndPoint getEndPoint(TKey key) {
|
||||
EndPoint ends = bounds.get(key);
|
||||
|
||||
if (ends != null) {
|
||||
// Always return the end point to the left
|
||||
if (ends.state == State.CLOSE) {
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
|
||||
return left != null ? left.getValue() : null;
|
||||
|
||||
} else {
|
||||
return ends;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We need to determine if the point intersects with a range
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
|
||||
|
||||
// We only need to check to the left
|
||||
if (left != null && left.getValue().state == State.OPEN) {
|
||||
return left.getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The previous end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
|
||||
|
||||
if (previous != null)
|
||||
return previous.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The next end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
|
||||
|
||||
if (next != null)
|
||||
return next.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Entry added) {
|
||||
if (added != null) {
|
||||
onEntryAdded(added);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Set<Entry> added) {
|
||||
for (Entry entry : added) {
|
||||
onEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryRemoved(Set<Entry> removed) {
|
||||
for (Entry entry : removed) {
|
||||
onEntryRemoved(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners for added or removed entries
|
||||
/**
|
||||
* Invoked when an entry is added.
|
||||
* @param added - the entry that was added.
|
||||
*/
|
||||
protected void onEntryAdded(Entry added) { }
|
||||
|
||||
/**
|
||||
* Invoked when an entry is removed.
|
||||
* @param removed - the removed entry.
|
||||
*/
|
||||
protected void onEntryRemoved(Entry removed) { }
|
||||
|
||||
// Helpers for decrementing or incrementing key values
|
||||
/**
|
||||
* Decrement the given key by one unit.
|
||||
* @param key - the key that should be decremented.
|
||||
* @return The new decremented key.
|
||||
*/
|
||||
protected abstract TKey decrementKey(TKey key);
|
||||
|
||||
/**
|
||||
* Increment the given key by one unit.
|
||||
* @param key - the key that should be incremented.
|
||||
* @return The new incremented key.
|
||||
*/
|
||||
protected abstract TKey incrementKey(TKey key);
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalCause;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A map that supports blocking on read operations. Null keys are not supported.
|
||||
* <p>
|
||||
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
|
||||
* <p>
|
||||
* @author Kristian
|
||||
*
|
||||
* @param <TKey> - type of the key.
|
||||
* @param <TValue> - type of the value.
|
||||
*/
|
||||
public class BlockingHashMap<TKey, TValue> {
|
||||
// Map of values
|
||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||
|
||||
// Map of locked objects
|
||||
private final ConcurrentMap<TKey, Object> locks;
|
||||
|
||||
/**
|
||||
* Retrieve a cache loader that will always throw an exception.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return An invalid cache loader.
|
||||
*/
|
||||
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
|
||||
return new CacheLoader<TKey, TValue>() {
|
||||
@Override
|
||||
public TValue load(TKey key) throws Exception {
|
||||
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
*/
|
||||
public BlockingHashMap() {
|
||||
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<TKey, TValue>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||
// Clean up locks too
|
||||
if (entry.getCause() != RemovalCause.REPLACED) {
|
||||
locks.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
|
||||
|
||||
// Normal concurrent hash map
|
||||
locks = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return The created map.
|
||||
*/
|
||||
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
|
||||
return new BlockingHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @return The value to which the specified key is mapped.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
lock.wait();
|
||||
value = backingMap.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return get(key, timeout, unit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* <p>
|
||||
* If timeout is zero, this method will return immediately if it can't find an socket injector.
|
||||
*
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||
if (timeout < 0)
|
||||
throw new IllegalArgumentException("Timeout cannot be less than zero.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null && timeout > 0) {
|
||||
final Object lock = getLock(key);
|
||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||
|
||||
// Don't exceed the timeout
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
try {
|
||||
long remainingTime = stopTimeNS - System.nanoTime();
|
||||
|
||||
if (remainingTime > 0) {
|
||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||
value = backingMap.get(key);
|
||||
} else {
|
||||
// Timeout elapsed
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// This is fairly dangerous - but we might HAVE to block the thread
|
||||
if (!ignoreInterrupted)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a given key with the given value.
|
||||
* <p>
|
||||
* Wakes up any blocking getters on this specific key.
|
||||
*
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value.
|
||||
* @return The previously associated value.
|
||||
*/
|
||||
public TValue put(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.put(key, value);
|
||||
final Object lock = getLock(key);
|
||||
|
||||
// Inform our readers about this change
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If and only if a key is not present in the map will it be associated with the given value.
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value to associate.
|
||||
* @return The previous value this key has been associated with.
|
||||
*/
|
||||
public TValue putIfAbsent(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.putIfAbsent(key, value);
|
||||
|
||||
// No need to unlock readers if we haven't changed anything
|
||||
if (previous == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return backingMap.size();
|
||||
}
|
||||
|
||||
public Collection<TValue> values() {
|
||||
return backingMap.values();
|
||||
}
|
||||
|
||||
public Set<TKey> keys() {
|
||||
return backingMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically retrieve the lock associated with a given key.
|
||||
* @param key - the current key.
|
||||
* @return An asssociated lock.
|
||||
*/
|
||||
private Object getLock(TKey key) {
|
||||
Object lock = locks.get(key);
|
||||
|
||||
if (lock == null) {
|
||||
Object created = new Object();
|
||||
|
||||
// Do this atomically
|
||||
lock = locks.putIfAbsent(key, created);
|
||||
|
||||
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
|
||||
if (lock == null) {
|
||||
lock = created;
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,8 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
|
||||
Preconditions.checkNotNull(player, "player cannot be null");
|
||||
|
||||
Object key = keyMethod.apply(player);
|
||||
|
||||
Preconditions.checkState(key != null, "Unable to derive a key for the player.");
|
||||
|
||||
keyLookup.put(key, player);
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An implicitly sorted array list that preserves insertion order and maintains duplicates.
|
||||
*
|
||||
* @param <T> - type of the elements in the list.
|
||||
*/
|
||||
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
|
||||
|
||||
// Prevent reordering
|
||||
private volatile List<T> list;
|
||||
|
||||
/**
|
||||
* Construct an empty sorted array.
|
||||
*/
|
||||
public SortedCopyOnWriteArray() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list. The elements will be automatically sorted.
|
||||
*
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped) {
|
||||
this.list = new ArrayList<>(wrapped);
|
||||
Collections.sort(this.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given element in the proper location.
|
||||
*
|
||||
* @param value - element to insert.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean add(T value) {
|
||||
// We use NULL as a special marker, so we don't allow it
|
||||
if (value == null) throw new IllegalArgumentException("value cannot be NULL");
|
||||
|
||||
List<T> copy = new ArrayList<>();
|
||||
boolean inserted = false;
|
||||
|
||||
for (T element : this.list) {
|
||||
// If the value is now greater than the current element, it should be placed right before it
|
||||
if (!inserted && value.compareTo(element) < 0) {
|
||||
copy.add(value);
|
||||
inserted = true;
|
||||
}
|
||||
copy.add(element);
|
||||
}
|
||||
|
||||
// Don't forget to add it
|
||||
if (!inserted) {
|
||||
copy.add(value);
|
||||
}
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> values) {
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
|
||||
if (values.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
|
||||
// Insert the new content and sort it
|
||||
copy.addAll(values);
|
||||
Collections.sort(copy);
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a new list with every element except the one given.
|
||||
* <p>
|
||||
* Objects will be compared using the given objects equals() method.
|
||||
*
|
||||
* @param value - value to remove.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean remove(Object value) {
|
||||
List<T> copy = new ArrayList<>();
|
||||
boolean result = false;
|
||||
|
||||
// Note that there's not much to be gained from using BinarySearch, as we
|
||||
// have to copy (and thus read) the entire list regardless.
|
||||
|
||||
// Copy every element except the one given to us.
|
||||
for (T element : this.list) {
|
||||
if (!Objects.equal(value, element)) {
|
||||
copy.add(element);
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.list = copy;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
|
||||
if (values.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
copy.removeAll(values);
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
|
||||
if (values.isEmpty()) return false;
|
||||
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
copy.removeAll(values);
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a copy of every element except the one with the given index.
|
||||
*
|
||||
* @param index - index of the element to remove.
|
||||
*/
|
||||
public synchronized void remove(int index) {
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
|
||||
copy.remove(index);
|
||||
this.list = copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an element by index.
|
||||
*
|
||||
* @param index - index of element to retrieve.
|
||||
* @return The element at the given location.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return this.list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the list.
|
||||
*
|
||||
* @return Size of the list.
|
||||
*/
|
||||
public int size() {
|
||||
return this.list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an iterator over the elements in the given list. Warning: No not attempt to remove elements using the
|
||||
* iterator.
|
||||
*/
|
||||
public Iterator<T> iterator() {
|
||||
return Iterables.unmodifiableIterable(this.list).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object value) {
|
||||
return this.list.contains(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
return this.list.containsAll(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return this.list.toArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return this.list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.list.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.comphenix.protocol.concurrent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
* Manages the association between packet types and their corresponding
|
||||
* listeners.
|
||||
* <p>
|
||||
* This class is thread-safe for modifications. All read methods work on a
|
||||
* lock-free, best-effort principle, ensuring fast access.
|
||||
* </p>
|
||||
*/
|
||||
public class PacketTypeListenerSet {
|
||||
|
||||
// Map to store packet types and their associated listeners
|
||||
private final Map<PacketType, Set<PacketListener>> typeMap = new HashMap<>();
|
||||
// Set to store packet classes of packet types that have listeners
|
||||
private final Set<Class<?>> classSet = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Adds a listener for a specific packet type.
|
||||
*
|
||||
* @param packetType the packet type
|
||||
* @param listener the listener to add
|
||||
* @return {@code true} if the listener and packetType was added to the set;
|
||||
* {@code false} otherwise
|
||||
* @throws NullPointerException if the packetType or listener is null
|
||||
*/
|
||||
public synchronized boolean add(PacketType packetType, PacketListener listener) {
|
||||
Objects.requireNonNull(packetType, "packetType cannot be null");
|
||||
Objects.requireNonNull(listener, "listener cannot be null");
|
||||
|
||||
Set<PacketListener> listenerSet = this.typeMap.computeIfAbsent(packetType, key -> new HashSet<>());
|
||||
if (!listenerSet.add(listener)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we can always add the packet class here as long as the listener got added
|
||||
this.classSet.add(packetType.getPacketClass());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener for a specific packet type.
|
||||
*
|
||||
* @param packetType the packet type
|
||||
* @param listener the listener to remove
|
||||
* @return {@code true} if the set contained the specified listener;
|
||||
* {@code false} otherwise
|
||||
* @throws NullPointerException if the packetType or listener is null
|
||||
*/
|
||||
public synchronized boolean remove(PacketType packetType, PacketListener listener) {
|
||||
Objects.requireNonNull(packetType, "packetType cannot be null");
|
||||
Objects.requireNonNull(listener, "listener cannot be null");
|
||||
|
||||
Set<PacketListener> listenerSet = this.typeMap.get(packetType);
|
||||
if (listenerSet == null) {
|
||||
// this should never happen so better check for it during unit tests
|
||||
assert !classSet.contains(packetType.getPacketClass());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!listenerSet.remove(listener)) {
|
||||
// return since the listenerSet didn't change
|
||||
return false;
|
||||
}
|
||||
|
||||
// packet type has no listeners remove type
|
||||
if (listenerSet.isEmpty()) {
|
||||
this.typeMap.remove(packetType);
|
||||
this.classSet.remove(packetType.getPacketClass());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any listeners for a specific packet type.
|
||||
*
|
||||
* @param packetType the packet type
|
||||
* @return true if there are listeners for the packet type, false otherwise
|
||||
*/
|
||||
public boolean contains(PacketType packetType) {
|
||||
return this.typeMap.containsKey(packetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any listeners for a specific packet class.
|
||||
*
|
||||
* @param packetClass the packet class
|
||||
* @return true if there are listeners for the packet class, false otherwise
|
||||
*/
|
||||
public boolean contains(Class<?> packetClass) {
|
||||
return this.classSet.contains(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the packet types that have listeners.
|
||||
*
|
||||
* @return a set of packet types that have listeners
|
||||
*/
|
||||
public ImmutableSet<PacketType> values() {
|
||||
return ImmutableSet.copyOf(this.typeMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all listeners and their associated packet types.
|
||||
*/
|
||||
public void clear() {
|
||||
this.typeMap.clear();
|
||||
this.classSet.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package com.comphenix.protocol.concurrent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* A map-like data structure that associates {@link PacketType}s with sets of
|
||||
* values. The values are stored in a {@link SortedCopyOnWriteSet}, which
|
||||
* ensures that the elements are kept in a sorted order based on the
|
||||
* {@link ListenerPriority} of the {@link ListeningWhitelist}, while maintaining
|
||||
* their insertion order for elements with equal priorities.
|
||||
* <p>
|
||||
* This class is thread-safe for modifications and guarantees a
|
||||
* modification-free iteration of associated values per packet type. All read
|
||||
* methods work on a lock-free, best-effort principle, ensuring fast access.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of elements maintained by this map
|
||||
*/
|
||||
public class PacketTypeMultiMap<T> {
|
||||
|
||||
private final Map<PacketType, SortedCopyOnWriteSet<T, PriorityHolder>> typeMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a value to the map, associating it with the {@link PacketType}s
|
||||
* contained in the specified {@link ListeningWhitelist}. If the value is
|
||||
* already present in the set (as determined by {@code equals}), it will not be
|
||||
* added again.
|
||||
*
|
||||
* @param key the whitelist containing the packet types to associate the value
|
||||
* with
|
||||
* @param value the value to be added
|
||||
* @throws NullPointerException if the key or value is null
|
||||
*/
|
||||
public synchronized void put(ListeningWhitelist key, T value) {
|
||||
Objects.requireNonNull(key, "key cannot be null");
|
||||
Objects.requireNonNull(value, "value cannot be null");
|
||||
|
||||
for (PacketType packetType : key.getTypes()) {
|
||||
this.typeMap.computeIfAbsent(packetType, type -> new SortedCopyOnWriteSet<>()).add(value,
|
||||
new PriorityHolder(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value from the map, disassociating it from the {@link PacketType}s
|
||||
* contained in the specified {@link ListeningWhitelist}. If the value is not
|
||||
* present, the map remains unchanged.
|
||||
*
|
||||
* @param key the whitelist containing the packet types to disassociate the
|
||||
* value from
|
||||
* @param value the value to be removed
|
||||
* @return a list of packet types that got removed because they don't have any
|
||||
* associated values anymore
|
||||
* @throws NullPointerException if the key or value is null
|
||||
*/
|
||||
public synchronized List<PacketType> remove(ListeningWhitelist key, T value) {
|
||||
Objects.requireNonNull(key, "key cannot be null");
|
||||
Objects.requireNonNull(value, "value cannot be null");
|
||||
|
||||
List<PacketType> removedTypes = new ArrayList<>();
|
||||
|
||||
for (PacketType packetType : key.getTypes()) {
|
||||
SortedCopyOnWriteSet<T, PriorityHolder> entrySet = this.typeMap.get(packetType);
|
||||
if (entrySet == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// we shouldn't have empty entrySets
|
||||
assert !entrySet.isEmpty();
|
||||
|
||||
// continue if value wasn't removed
|
||||
if (!entrySet.remove(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove packet type without entries
|
||||
if (entrySet.isEmpty()) {
|
||||
this.typeMap.remove(packetType);
|
||||
removedTypes.add(packetType);
|
||||
}
|
||||
}
|
||||
|
||||
return removedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable set of all {@link PacketType}s currently in the map.
|
||||
*
|
||||
* @return an immutable set of packet types
|
||||
*/
|
||||
public ImmutableSet<PacketType> getPacketTypes() {
|
||||
return ImmutableSet.copyOf(this.typeMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specified {@link PacketType} is contained in the map.
|
||||
*
|
||||
* @param packetType the packet type to check for
|
||||
* @return {@code true} if the packet type is contained in the map,
|
||||
* {@code false} otherwise
|
||||
*/
|
||||
public boolean contains(PacketType packetType) {
|
||||
return this.typeMap.containsKey(packetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable of values associated with a specified {@link PacketType}.
|
||||
* If no values are associated with the packet type, an empty iterator is
|
||||
* returned.
|
||||
*
|
||||
* @param packetType the packet type to retrieve values for
|
||||
* @return an iterable of values associated with the packet type
|
||||
*/
|
||||
public Iterable<T> get(PacketType packetType) {
|
||||
return () -> {
|
||||
SortedCopyOnWriteSet<T, PriorityHolder> entrySet = this.typeMap.get(packetType);
|
||||
|
||||
if (entrySet != null) {
|
||||
return entrySet.iterator();
|
||||
}
|
||||
|
||||
return Collections.emptyIterator();
|
||||
};
|
||||
}
|
||||
|
||||
public Iterable<T> values() {
|
||||
return Iterables.concat(this.typeMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all entries from the map.
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
this.typeMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder for priority information used to order elements within the
|
||||
* {@link SortedCopyOnWriteSet}.
|
||||
*/
|
||||
private static class PriorityHolder implements Comparable<PriorityHolder> {
|
||||
|
||||
private final ListenerPriority priority;
|
||||
|
||||
public PriorityHolder(ListeningWhitelist key) {
|
||||
this.priority = key.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PriorityHolder other) {
|
||||
return Integer.compare(this.priority.getSlot(), other.priority.getSlot());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.comphenix.protocol.concurrent;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A collection that stores elements in a sorted order based on a provided
|
||||
* {@link Comparable}, while ensuring element equality is checked using their
|
||||
* {@code equals} method.
|
||||
* <p>
|
||||
* This class uses a copy-on-write strategy for updates, ensuring that iteration
|
||||
* over the collection is safe for concurrent use, even though the collection
|
||||
* itself is not thread-safe for modifications.
|
||||
* <p>
|
||||
* Elements are inserted into the set in a position determined by their natural
|
||||
* ordering. If multiple elements have comparables that are considered equal
|
||||
* (i.e., {@code compareTo} returns zero), they will maintain their insertion
|
||||
* order. If an element is already present in the set (as determined by
|
||||
* {@code equals}), it will not be added again.
|
||||
*
|
||||
* @param <E> the type of elements maintained by this set
|
||||
* @param <C> the type of the comparable used for ordering the elements
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SortedCopyOnWriteSet<E, C extends Comparable<C>> implements Iterable<E> {
|
||||
|
||||
private volatile Entry<E, C>[] array = new Entry[0];
|
||||
|
||||
/**
|
||||
* Adds the specified element to this set in a sorted order based on the
|
||||
* provided {@code Comparable}. The element will be inserted before the first
|
||||
* position that is strictly greater than the element. This ensures that
|
||||
* elements maintain their insertion order when their comparables are considered
|
||||
* equal (i.e., when {@code compareTo} returns zero).
|
||||
* <p>
|
||||
* If the set already contains the element (as determined by {@code equals}),
|
||||
* the element is not added again.
|
||||
* </p>
|
||||
*
|
||||
* @param element the element to be added
|
||||
* @param comparable the comparable used to determine the element's position in
|
||||
* the sorted order
|
||||
* @return {@code true} if the element was added to the set; {@code false} if
|
||||
* the set already contained the element
|
||||
* @throws NullPointerException if the specified element is null
|
||||
*/
|
||||
public boolean add(E element, C comparable) {
|
||||
Objects.requireNonNull(element, "element cannot be null");
|
||||
|
||||
// create new entry
|
||||
Entry<E, C> entry = new Entry<>(element, comparable);
|
||||
|
||||
// Find correct insert index for element by compareTo, also use same loop to
|
||||
// scan for duplicate elements
|
||||
int insertIndex = -1;
|
||||
for (int index = 0; index < array.length; index++) {
|
||||
if (insertIndex == -1 && entry.compareTo(array[index]) < 0) {
|
||||
insertIndex = index;
|
||||
}
|
||||
|
||||
// array already contains element, return false
|
||||
if (array[index].is(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// insert at end of array
|
||||
if (insertIndex == -1) {
|
||||
insertIndex = array.length;
|
||||
}
|
||||
|
||||
// create a new array of size N+1
|
||||
Entry<E, C>[] newArray = new Entry[array.length + 1];
|
||||
|
||||
// copy the old array to the new array and insert the new element
|
||||
System.arraycopy(array, 0, newArray, 0, insertIndex);
|
||||
newArray[insertIndex] = entry;
|
||||
System.arraycopy(array, insertIndex, newArray, insertIndex + 1, array.length - insertIndex);
|
||||
|
||||
// copy new array to field
|
||||
array = newArray;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified element from this set if it is present.
|
||||
*
|
||||
* @param element the element to be removed
|
||||
* @return {@code true} if the set contained the specified element;
|
||||
* {@code false} otherwise
|
||||
* @throws NullPointerException if the specified element is null
|
||||
*/
|
||||
public boolean remove(E element) {
|
||||
Objects.requireNonNull(element, "element cannot be null");
|
||||
|
||||
// find the element in array
|
||||
int removeIndex = -1;
|
||||
for (int index = 0; index < array.length; index++) {
|
||||
if (array[index].is(element)) {
|
||||
removeIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// can't find element, return false
|
||||
if (removeIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// create a new array of size N-1
|
||||
Entry<E, C>[] newArray = new Entry[array.length - 1];
|
||||
|
||||
// copy the elements from the old array to the new array, excluding removed
|
||||
// element
|
||||
System.arraycopy(array, 0, newArray, 0, removeIndex);
|
||||
System.arraycopy(array, removeIndex + 1, newArray, removeIndex, array.length - removeIndex - 1);
|
||||
|
||||
// copy new array to field
|
||||
array = newArray;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this set contains no elements.
|
||||
*
|
||||
* @return {@code true} if this set contains no elements
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return this.array.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the elements in this set. The elements are returned
|
||||
* in natural order.
|
||||
*
|
||||
* @return an iterator over the elements in this set
|
||||
*/
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new ElementIterator(this.array);
|
||||
}
|
||||
|
||||
private class ElementIterator implements Iterator<E> {
|
||||
|
||||
private final Entry<E, C>[] array;
|
||||
private int cursor = 0;
|
||||
|
||||
public ElementIterator(Entry<E, C>[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.cursor < this.array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
if (this.cursor >= this.array.length) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
int index = this.cursor++;
|
||||
return this.array[index].getElement();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Entry<E, C extends Comparable<C>> implements Comparable<Entry<E, C>> {
|
||||
|
||||
private E element;
|
||||
private C comperable;
|
||||
|
||||
public Entry(E element, C comperable) {
|
||||
this.element = element;
|
||||
this.comperable = comperable;
|
||||
}
|
||||
|
||||
public E getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry<E, C> other) {
|
||||
return this.comperable.compareTo(other.comperable);
|
||||
}
|
||||
|
||||
public boolean is(E element) {
|
||||
return this.element.equals(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolConfig;
|
||||
import com.comphenix.protocol.ProtocolLib;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.collections.ExpireHashMap;
|
||||
@@ -41,11 +45,6 @@ import com.comphenix.protocol.reflect.PrettyPrinter;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang.builder.ToStringStyle;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
* Internal class used to handle exceptions.
|
||||
*
|
||||
@@ -479,7 +478,7 @@ public class DetailedErrorReporter implements ErrorReporter {
|
||||
} catch (LinkageError ex) {
|
||||
// Apache is probably missing
|
||||
apacheCommonsMissing = true;
|
||||
} catch (ThreadDeath | OutOfMemoryError e) {
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (Throwable ex) {
|
||||
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.utility.StreamSerializer;
|
||||
import com.comphenix.protocol.wrappers.*;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.lang.reflect.Array;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Sound;
|
||||
@@ -25,10 +26,52 @@ import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Array;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.utility.StreamSerializer;
|
||||
import com.comphenix.protocol.wrappers.BlockPosition;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
|
||||
import com.comphenix.protocol.wrappers.Converters;
|
||||
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
|
||||
import com.comphenix.protocol.wrappers.Either;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers;
|
||||
import com.comphenix.protocol.wrappers.MinecraftKey;
|
||||
import com.comphenix.protocol.wrappers.MovingObjectPositionBlock;
|
||||
import com.comphenix.protocol.wrappers.MultiBlockChangeInfo;
|
||||
import com.comphenix.protocol.wrappers.Pair;
|
||||
import com.comphenix.protocol.wrappers.PlayerInfoData;
|
||||
import com.comphenix.protocol.wrappers.WrappedAttribute;
|
||||
import com.comphenix.protocol.wrappers.WrappedBlockData;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataValue;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedLevelChunkData;
|
||||
import com.comphenix.protocol.wrappers.WrappedMessageSignature;
|
||||
import com.comphenix.protocol.wrappers.WrappedNumberFormat;
|
||||
import com.comphenix.protocol.wrappers.WrappedParticle;
|
||||
import com.comphenix.protocol.wrappers.WrappedPositionMoveRotation;
|
||||
import com.comphenix.protocol.wrappers.WrappedPositionPath;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import com.comphenix.protocol.wrappers.WrappedRegistrable;
|
||||
import com.comphenix.protocol.wrappers.WrappedRegistry;
|
||||
import com.comphenix.protocol.wrappers.WrappedRemoteChatSessionData;
|
||||
import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
|
||||
import com.comphenix.protocol.wrappers.WrappedServerPing;
|
||||
import com.comphenix.protocol.wrappers.WrappedStatistic;
|
||||
import com.comphenix.protocol.wrappers.WrappedTeamParameters;
|
||||
import com.comphenix.protocol.wrappers.WrappedVecDelta;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
public abstract class AbstractStructure {
|
||||
protected transient Object handle;
|
||||
@@ -557,9 +600,7 @@ public abstract class AbstractStructure {
|
||||
*/
|
||||
public StructureModifier<List<PlayerInfoData>> getPlayerInfoDataLists() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(PlayerInfoData.getConverter()));
|
||||
return getLists(PlayerInfoData.getConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -769,6 +810,10 @@ public abstract class AbstractStructure {
|
||||
* @return A modifier for MobEffectList fields.
|
||||
*/
|
||||
public StructureModifier<PotionEffectType> getEffectTypes() {
|
||||
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
|
||||
return getHolders(MinecraftReflection.getMobEffectListClass(), BukkitConverters.getEffectTypeConverter());
|
||||
}
|
||||
|
||||
// Convert to and from Bukkit
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getMobEffectListClass(),
|
||||
@@ -793,11 +838,20 @@ public abstract class AbstractStructure {
|
||||
* @return A modifier for Holder fields
|
||||
* @param <T> Bukkit type
|
||||
*/
|
||||
public <T> StructureModifier<T> getHolders(Class<?> genericType,
|
||||
EquivalentConverter<T> converter) {
|
||||
public <T> StructureModifier<T> getHolders(Class<?> genericType, EquivalentConverter<T> converter) {
|
||||
Preconditions.checkNotNull(genericType, "genericType cannot be null");
|
||||
Preconditions.checkNotNull(converter, "converter cannot be null");
|
||||
|
||||
Class<?> holderClass = MinecraftReflection.getHolderClass();
|
||||
|
||||
WrappedRegistry registry = WrappedRegistry.getRegistry(genericType);
|
||||
if (registry == null) {
|
||||
throw new IllegalArgumentException("No registry found for " + genericType);
|
||||
}
|
||||
|
||||
return structureModifier.withParamType(
|
||||
MinecraftReflection.getHolderClass(),
|
||||
Converters.holder(converter, WrappedRegistry.getRegistry(genericType)),
|
||||
holderClass,
|
||||
Converters.ignoreNull(Converters.holder(converter, registry)),
|
||||
genericType
|
||||
);
|
||||
}
|
||||
@@ -857,6 +911,55 @@ public abstract class AbstractStructure {
|
||||
EnumWrappers.getChatTypeConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the DisplaySlot enum in 1.20.2.
|
||||
* @return A modifier for DisplaySlot enum fields.
|
||||
*/
|
||||
public StructureModifier<EnumWrappers.DisplaySlot> getDisplaySlots() {
|
||||
return structureModifier.withType(
|
||||
EnumWrappers.getDisplaySlotClass(),
|
||||
EnumWrappers.getDisplaySlotConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the RenderType enum.
|
||||
* @return A modifier for RenderType enum fields.
|
||||
*/
|
||||
public StructureModifier<EnumWrappers.RenderType> getRenderTypes() {
|
||||
return structureModifier.withType(
|
||||
EnumWrappers.getRenderTypeClass(),
|
||||
EnumWrappers.getRenderTypeConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the ChatFormatting enum.
|
||||
* @return A modifier for ChatFormatting enum fields.
|
||||
*/
|
||||
public StructureModifier<EnumWrappers.ChatFormatting> getChatFormattings() {
|
||||
return structureModifier.withType(
|
||||
EnumWrappers.getChatFormattingClass(),
|
||||
EnumWrappers.getChatFormattingConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for optional team parameters in 1.17+.
|
||||
* @return A modifier for optional team parameters fields.
|
||||
*/
|
||||
public StructureModifier<Optional<WrappedTeamParameters>> getOptionalTeamParameters() {
|
||||
return getOptionals(BukkitConverters.getWrappedTeamParametersConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the NumberFormat class in 1.20.4+.
|
||||
* @return A modifier for NumberFormat fields.
|
||||
*/
|
||||
public StructureModifier<WrappedNumberFormat> getNumberFormats() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getNumberFormatClass().orElse(null),
|
||||
BukkitConverters.getWrappedNumberFormatConverter());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the MinecraftKey class.
|
||||
* @return A modifier for MinecraftKey fields.
|
||||
@@ -1063,15 +1166,52 @@ public abstract class AbstractStructure {
|
||||
}
|
||||
|
||||
/**
|
||||
* Since 26.3 the signature fields are optional, an absent signature is read and written as {@code null}.
|
||||
*
|
||||
* @return read/writer structure direct access to unsalted signature data for example in chat message (since 1.19.3)
|
||||
*/
|
||||
public StructureModifier<WrappedMessageSignature> getMessageSignatures() {
|
||||
if (MinecraftVersion.v26_3.atOrAbove()) {
|
||||
EquivalentConverter<WrappedMessageSignature> converter = BukkitConverters.getWrappedMessageSignatureConverter();
|
||||
return structureModifier.withParamType(
|
||||
Optional.class,
|
||||
new EquivalentConverter<WrappedMessageSignature>() {
|
||||
@Override
|
||||
public Object getGeneric(WrappedMessageSignature specific) {
|
||||
return Optional.ofNullable(converter.getGeneric(specific));
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedMessageSignature getSpecific(Object generic) {
|
||||
return generic == null ? null : ((Optional<?>) generic).map(converter::getSpecific).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedMessageSignature> getSpecificType() {
|
||||
return WrappedMessageSignature.class;
|
||||
}
|
||||
},
|
||||
MinecraftReflection.getMessageSignatureClass()
|
||||
);
|
||||
}
|
||||
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getMessageSignatureClass(),
|
||||
BukkitConverters.getWrappedMessageSignatureConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the ClientIntent enum used in Handshake/SetProtocol packet
|
||||
* @return A modifier for Protocol enum fields.
|
||||
*/
|
||||
public StructureModifier<EnumWrappers.ClientIntent> getClientIntents() {
|
||||
// Convert to and from the wrapper
|
||||
return structureModifier.withType(
|
||||
EnumWrappers.getClientIntentClass(),
|
||||
EnumWrappers.getClientIntentConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param leftConverter converter for left values
|
||||
* @param rightConverter converter for right values
|
||||
@@ -1184,6 +1324,48 @@ public abstract class AbstractStructure {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for {@link WrappedPositionMoveRotation} (available since Minecraft 1.21.2).
|
||||
* <p>
|
||||
* This is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC} (before 26.3).
|
||||
*
|
||||
* @return A modifier for PositionMoveRotation fields.
|
||||
*/
|
||||
public StructureModifier<WrappedPositionMoveRotation> getPositionMoveRotation() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getPositionMoveRotationClass(),
|
||||
WrappedPositionMoveRotation.getConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for {@link WrappedVecDelta} (available since Minecraft 26.3).
|
||||
* <p>
|
||||
* This is used in {@code REL_ENTITY_MOVE} and {@code REL_ENTITY_MOVE_LOOK}.
|
||||
*
|
||||
* @return A modifier for VecDelta fields.
|
||||
*/
|
||||
public StructureModifier<WrappedVecDelta> getVecDeltas() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getVecDeltaClass().orElse(void.class),
|
||||
WrappedVecDelta.getConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for {@link WrappedPositionPath} (available since Minecraft 26.3).
|
||||
* <p>
|
||||
* This is used in {@code ENTITY_POSITION_SYNC}.
|
||||
*
|
||||
* @return A modifier for PositionPath fields.
|
||||
*/
|
||||
public StructureModifier<WrappedPositionPath> getPositionPaths() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getPositionPathClass().orElse(void.class),
|
||||
WrappedPositionPath.getConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an equivalent converter for ItemStack arrays.
|
||||
* @author Kristian
|
||||
|
||||
@@ -13,6 +13,7 @@ public enum ListenerOptions {
|
||||
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
|
||||
* known to be transmitted during login.
|
||||
*/
|
||||
@Deprecated
|
||||
DISABLE_GAMEPHASE_DETECTION,
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
@@ -90,20 +95,6 @@ public class ListeningWhitelist {
|
||||
return new Builder(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given enum.
|
||||
*
|
||||
* @param options - the options to copy, or NULL to indicate the empty set.
|
||||
* @return A copy of the enum set.
|
||||
*/
|
||||
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
|
||||
if (options != null && !options.isEmpty()) {
|
||||
return EnumSet.copyOf(options);
|
||||
} else {
|
||||
return EnumSet.noneOf(enumClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given set.
|
||||
*
|
||||
@@ -311,6 +302,7 @@ public class ListeningWhitelist {
|
||||
* @param gamePhase - the gamephase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder gamePhase(GamePhase gamePhase) {
|
||||
this.gamePhase = gamePhase;
|
||||
return this;
|
||||
@@ -321,6 +313,7 @@ public class ListeningWhitelist {
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder gamePhaseBoth() {
|
||||
return gamePhase(GamePhase.BOTH);
|
||||
}
|
||||
|
||||
@@ -232,13 +232,13 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifications of received packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
|
||||
throw new IllegalStateException("Override onPacketSending to get notifications of sent packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,9 +31,11 @@ import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
@@ -56,10 +58,11 @@ import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.Converters;
|
||||
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft packet indirectly.
|
||||
@@ -78,7 +81,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
|
||||
// Used to clone packets
|
||||
private static final AggregateCloner DEEP_CLONER = AggregateCloner
|
||||
.newBuilder()
|
||||
.instanceProvider(StructureCache::newPacket)
|
||||
.instanceProvider(StructureCache::newInstance)
|
||||
.andThen(BukkitCloner.class)
|
||||
.andThen(ImmutableDetector.class)
|
||||
.andThen(JavaOptionalCloner.class)
|
||||
@@ -89,7 +92,7 @@ public class PacketContainer extends AbstractStructure implements Serializable {
|
||||
|
||||
private static final AggregateCloner SHALLOW_CLONER = AggregateCloner
|
||||
.newBuilder()
|
||||
.instanceProvider(StructureCache::newPacket)
|
||||
.instanceProvider(StructureCache::newInstance)
|
||||
.andThen(param -> {
|
||||
if (param == null)
|
||||
throw new IllegalArgumentException("Cannot be NULL.");
|
||||
@@ -345,6 +348,11 @@ public class PacketContainer extends AbstractStructure implements Serializable {
|
||||
}
|
||||
|
||||
Function<Object, Object> deserializer = PACKET_DESERIALIZER_METHODS.computeIfAbsent(packetType, type -> {
|
||||
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(type.getPacketClass());
|
||||
if (streamCodec != null) {
|
||||
return streamCodec::decode;
|
||||
}
|
||||
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
// best guess - a constructor which takes a buffer as the only argument
|
||||
ConstructorAccessor bufferConstructor = Accessors.getConstructorAccessorOrNull(
|
||||
@@ -392,7 +400,14 @@ public class PacketContainer extends AbstractStructure implements Serializable {
|
||||
}
|
||||
|
||||
Object targetBuffer = MinecraftReflection.createPacketDataSerializer(0);
|
||||
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
|
||||
|
||||
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(type.getPacketClass());
|
||||
if (streamCodec != null) {
|
||||
streamCodec.encode(targetBuffer, handle);
|
||||
} else {
|
||||
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
|
||||
}
|
||||
|
||||
return targetBuffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -17,10 +17,27 @@
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.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;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
import com.comphenix.protocol.utility.Util;
|
||||
|
||||
import net.bytebuddy.description.ByteCodeElement;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
@@ -35,33 +52,25 @@ import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.BanEntry;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Statistic;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Represents a player object that can be serialized by Java.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||
class SerializedOfflinePlayer implements Serializable {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
@@ -115,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;
|
||||
}
|
||||
@@ -145,15 +150,13 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getRespawnLocation() {
|
||||
return null;
|
||||
}
|
||||
public Location getRespawnLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getLocation() {
|
||||
return null;
|
||||
}
|
||||
public Location getLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO do we need to implement this?
|
||||
|
||||
@@ -214,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;
|
||||
}
|
||||
@@ -276,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;
|
||||
}
|
||||
@@ -323,7 +315,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
try {
|
||||
// Try to get the real player underneath
|
||||
@@ -342,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) {
|
||||
@@ -352,6 +343,38 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private OfflinePlayer createOfflinePlayerProxy() {
|
||||
return (OfflinePlayer) Proxy.newProxyInstance(
|
||||
OfflinePlayer.class.getClassLoader(),
|
||||
new Class<?>[]{OfflinePlayer.class},
|
||||
(proxy, method, arguments) -> {
|
||||
if (method.getDeclaringClass() == Object.class) {
|
||||
return switch (method.getName()) {
|
||||
case "equals" -> proxy == arguments[0];
|
||||
case "hashCode" -> System.identityHashCode(proxy);
|
||||
case "toString" -> "SerializedOfflinePlayer[" + this.name + "]";
|
||||
default -> throw new UnsupportedOperationException("Unsupported Object method " + method);
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
Method target = SerializedOfflinePlayer.class.getMethod(
|
||||
method.getName(),
|
||||
method.getParameterTypes());
|
||||
return target.invoke(this, arguments);
|
||||
} catch (NoSuchMethodException exception) {
|
||||
if (method.isDefault()) {
|
||||
return InvocationHandler.invokeDefault(proxy, method, arguments);
|
||||
}
|
||||
throw new UnsupportedOperationException(
|
||||
"The method " + method.getName() + " is not supported for offline players.",
|
||||
exception);
|
||||
} catch (InvocationTargetException exception) {
|
||||
throw exception.getCause();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Constructor<? extends Player> setupProxyPlayerConstructor() {
|
||||
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
|
||||
final String[] methodNames = new String[offlinePlayerMethods.length];
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.comphenix.protocol.injector;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
@Deprecated
|
||||
public enum GamePhase {
|
||||
/**
|
||||
* Only listen for packets sent or received before a player has logged in.
|
||||
|
||||
+9
-13
@@ -25,29 +25,25 @@ import com.comphenix.protocol.events.PacketEvent;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ListenerInvoker {
|
||||
public interface ListenerManager {
|
||||
|
||||
boolean hasInboundListener(PacketType packetType);
|
||||
|
||||
boolean hasOutboundListener(PacketType packetType);
|
||||
|
||||
boolean hasMainThreadListener(PacketType packetType);
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
*
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
void invokePacketReceiving(PacketEvent event);
|
||||
void invokeInboundPacketListeners(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
*
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
void invokePacketSending(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve the associated type of a packet.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return The packet type.
|
||||
* @deprecated use {@link com.comphenix.protocol.injector.packet.PacketRegistry#getPacketType(PacketType.Protocol, Class)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
PacketType getPacketType(Object packet);
|
||||
void invokeOutboundPacketListeners(PacketEvent event);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
@@ -7,7 +9,6 @@ import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketPostListener;
|
||||
import com.comphenix.protocol.events.ScheduledPacket;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* Represents a processor for network markers.
|
||||
@@ -43,7 +44,7 @@ public class NetworkProcessor {
|
||||
for (PacketPostListener listener : marker.getPostListeners()) {
|
||||
try {
|
||||
listener.onPostEvent(event);
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
|
||||
|
||||
@@ -172,8 +172,6 @@ public class PacketConstructor {
|
||||
result = unwrapper.unwrapItem(values[i]);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
lastException = e;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.netty.channel.Channel;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -50,16 +23,39 @@ import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.concurrent.PacketTypeListenerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
|
||||
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.PacketListenerSet;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public class PacketFilterManager implements ListenerManager, InternalManager {
|
||||
|
||||
// plugin verifier reports
|
||||
private static final ReportType PLUGIN_VERIFIER_ERROR = new ReportType("Plugin verifier error: %s");
|
||||
private static final ReportType INVALID_PLUGIN_VERIFY = new ReportType("Plugin %s does not %s on ProtocolLib");
|
||||
|
||||
// listener registration reports
|
||||
private static final ReportType UNSUPPORTED_PACKET = new ReportType(
|
||||
"Plugin %s tried to register listener for unknown packet %s [direction: from %s]");
|
||||
|
||||
// bukkit references
|
||||
private final Plugin plugin;
|
||||
private final Server server;
|
||||
@@ -72,15 +68,14 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
private final PluginVerifier pluginVerifier;
|
||||
|
||||
// packet listeners
|
||||
private final SortedPacketListenerList inboundListeners;
|
||||
private final SortedPacketListenerList outboundListeners;
|
||||
private final PacketTypeListenerSet mainThreadPacketTypes;
|
||||
private final InboundPacketListenerSet inboundListeners;
|
||||
private final OutboundPacketListenerSet outboundListeners;
|
||||
|
||||
// only for api lookups
|
||||
private final Set<PacketListener> registeredListeners;
|
||||
|
||||
// injectors
|
||||
private final PacketInjector packetInjector;
|
||||
private final PlayerInjectionHandler playerInjectionHandler;
|
||||
private final NetworkManagerInjector networkManagerInjector;
|
||||
|
||||
// status of this manager
|
||||
@@ -104,17 +99,15 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
// packet listeners
|
||||
this.registeredListeners = new HashSet<>();
|
||||
this.inboundListeners = new SortedPacketListenerList();
|
||||
this.outboundListeners = new SortedPacketListenerList();
|
||||
this.mainThreadPacketTypes = new PacketTypeListenerSet();
|
||||
this.inboundListeners = new InboundPacketListenerSet(mainThreadPacketTypes, this.reporter);
|
||||
this.outboundListeners = new OutboundPacketListenerSet(mainThreadPacketTypes, this.reporter);
|
||||
|
||||
// injectors
|
||||
this.networkManagerInjector = new NetworkManagerInjector(
|
||||
builder.getLibrary(),
|
||||
builder.getServer(),
|
||||
this,
|
||||
builder.getReporter());
|
||||
this.packetInjector = this.networkManagerInjector.getPacketInjector();
|
||||
this.playerInjectionHandler = this.networkManagerInjector.getPlayerInjectionHandler();
|
||||
|
||||
// ensure that all packet types are loaded and synced
|
||||
PacketRegistry.getClientPacketTypes();
|
||||
@@ -122,7 +115,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
// hook into all connected players
|
||||
for (Player player : this.server.getOnlinePlayers()) {
|
||||
this.playerInjectionHandler.injectPlayer(player, ConflictStrategy.BAIL_OUT);
|
||||
this.networkManagerInjector.getInjector(player).inject();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,29 +152,20 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
|
||||
if (!this.closed) {
|
||||
// if we skip the packet events later when actually writing into the pipeline we at least notify all
|
||||
// monitor listeners before doing so - they will not be able to change the event tho
|
||||
if (!filters) {
|
||||
// ensure we are on the main thread if any listener requires that
|
||||
if (this.playerInjectionHandler.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
|
||||
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false);
|
||||
this.outboundListeners.invokePacketSending(this.reporter, event, ListenerPriority.MONITOR);
|
||||
|
||||
// update the marker of the event without accidentally constructing it
|
||||
marker = NetworkMarker.getNetworkMarker(event);
|
||||
}
|
||||
|
||||
// process outbound
|
||||
this.playerInjectionHandler.sendServerPacket(receiver, packet, marker, filters);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,13 +176,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
@Override
|
||||
public void sendWirePacket(Player receiver, WirePacket packet) {
|
||||
if (!this.closed) {
|
||||
// special case - we just throw the wire packet down the pipeline without processing it
|
||||
Channel outboundChannel = this.playerInjectionHandler.getChannel(receiver);
|
||||
if (outboundChannel == null) {
|
||||
throw new IllegalArgumentException("Unable to obtain connection of player " + receiver);
|
||||
}
|
||||
|
||||
outboundChannel.writeAndFlush(packet);
|
||||
this.networkManagerInjector.getInjector(receiver).sendWirePacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,38 +192,45 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
@Override
|
||||
public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) {
|
||||
if (!this.closed) {
|
||||
// make sure we are on the main thread if any listener of the packet needs it
|
||||
if (this.playerInjectionHandler.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 = this.packetInjector.packetReceived(packet, sender);
|
||||
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.invokePacketRecieving(this.reporter, event, ListenerPriority.MONITOR);
|
||||
}
|
||||
|
||||
// post to the player inject, reset our cancel state change
|
||||
this.playerInjectionHandler.receiveClientPacket(sender, nmsPacket);
|
||||
this.inboundListeners.invoke(event, ListenerPriority.MONITOR);
|
||||
});
|
||||
}
|
||||
|
||||
// post to the player inject, reset our cancel state change
|
||||
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion(Player player) {
|
||||
return this.playerInjectionHandler.getProtocolVersion(player);
|
||||
return this.networkManagerInjector.getInjector(player).getProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -320,28 +305,20 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
if (outbound != null && outbound.isEnabled()) {
|
||||
// verification
|
||||
this.verifyWhitelist(listener, outbound);
|
||||
this.playerInjectionHandler.checkListener(listener);
|
||||
|
||||
// registration
|
||||
this.registeredListeners.add(listener);
|
||||
this.outboundListeners.addListener(listener, outbound);
|
||||
|
||||
// let the injectors know about the change as well
|
||||
this.registerPacketListenerInInjectors(listener, outbound.getTypes());
|
||||
this.outboundListeners.addListener(listener);
|
||||
}
|
||||
|
||||
// register as inbound listener if anything outbound is handled
|
||||
if (inbound != null && inbound.isEnabled()) {
|
||||
// verification
|
||||
this.verifyWhitelist(listener, inbound);
|
||||
this.playerInjectionHandler.checkListener(listener);
|
||||
|
||||
// registration
|
||||
this.registeredListeners.add(listener);
|
||||
this.inboundListeners.addListener(listener, inbound);
|
||||
|
||||
// let the injectors know about the change as well
|
||||
this.registerPacketListenerInInjectors(listener, inbound.getTypes());
|
||||
this.inboundListeners.addListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,18 +331,12 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
// remove outbound listeners (if any)
|
||||
if (outbound != null && outbound.isEnabled()) {
|
||||
Collection<PacketType> removed = this.outboundListeners.removeListener(listener, outbound);
|
||||
if (!removed.isEmpty()) {
|
||||
this.unregisterPacketListenerInInjectors(removed);
|
||||
}
|
||||
this.outboundListeners.removeListener(listener);
|
||||
}
|
||||
|
||||
// remove inbound listeners (if any)
|
||||
if (inbound != null && inbound.isEnabled()) {
|
||||
Collection<PacketType> removed = this.inboundListeners.removeListener(listener, inbound);
|
||||
if (!removed.isEmpty()) {
|
||||
this.unregisterPacketListenerInInjectors(removed);
|
||||
}
|
||||
this.inboundListeners.removeListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,12 +387,12 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getSendingFilterTypes() {
|
||||
return Collections.unmodifiableSet(this.playerInjectionHandler.getSendingFilters());
|
||||
return Collections.unmodifiableSet(this.outboundListeners.getPacketTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getReceivingFilterTypes() {
|
||||
return Collections.unmodifiableSet(this.packetInjector.getPacketHandlers());
|
||||
return Collections.unmodifiableSet(this.inboundListeners.getPacketTypes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -455,23 +426,28 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
this.injected = true;
|
||||
this.networkManagerInjector.inject();
|
||||
|
||||
// Paper disables reconfiguration when any PlayerLoginEvent listener is registered.
|
||||
if (!this.networkManagerInjector.isUsingPaperChannelInitializer()) {
|
||||
manager.registerEvents(new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void handleLogin(PlayerLoginEvent event) {
|
||||
networkManagerInjector.getInjector(event.getPlayer()).inject();
|
||||
}
|
||||
}, plugin);
|
||||
}
|
||||
|
||||
// all listeners we need, this is a bit messy, but it makes the job correctly
|
||||
manager.registerEvents(new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void handleLogin(PlayerLoginEvent event) {
|
||||
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void handleJoin(PlayerJoinEvent event) {
|
||||
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
|
||||
networkManagerInjector.getInjector(event.getPlayer()).inject();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void handleQuit(PlayerQuitEvent event) {
|
||||
PacketFilterManager.this.asyncFilterManager.removePlayer(event.getPlayer());
|
||||
PacketFilterManager.this.playerInjectionHandler.handleDisconnect(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@@ -494,11 +470,14 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
|
||||
// uninject all clutter
|
||||
this.networkManagerInjector.close();
|
||||
this.playerInjectionHandler.close();
|
||||
|
||||
// clear listener collections
|
||||
this.mainThreadPacketTypes.clear();
|
||||
this.inboundListeners.clear();
|
||||
this.outboundListeners.clear();
|
||||
|
||||
// cleanup
|
||||
this.registeredListeners.clear();
|
||||
this.packetInjector.cleanupAll();
|
||||
this.asyncFilterManager.cleanupAll();
|
||||
}
|
||||
}
|
||||
@@ -514,46 +493,56 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokePacketReceiving(PacketEvent event) {
|
||||
public boolean hasInboundListener(PacketType packetType) {
|
||||
return this.inboundListeners.containsPacketType(packetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasOutboundListener(PacketType packetType) {
|
||||
return this.outboundListeners.containsPacketType(packetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType packetType) {
|
||||
return this.mainThreadPacketTypes.contains(packetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeInboundPacketListeners(PacketEvent event) {
|
||||
if (!this.closed) {
|
||||
this.postPacketToListeners(this.inboundListeners, event, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokePacketSending(PacketEvent event) {
|
||||
public void invokeOutboundPacketListeners(PacketEvent event) {
|
||||
if (!this.closed) {
|
||||
this.postPacketToListeners(this.outboundListeners, event, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType getPacketType(Object packet) {
|
||||
if (!MinecraftReflection.isPacketClass(packet)) {
|
||||
throw new IllegalArgumentException("Given packet is not a minecraft packet instance");
|
||||
}
|
||||
|
||||
PacketType type = PacketRegistry.getPacketType(packet.getClass());
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unable to associate given packet " + packet + " with a registered packet!");
|
||||
|
||||
private boolean requiresMainThread(PacketContainer packet) {
|
||||
return this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread();
|
||||
}
|
||||
|
||||
private void postPacketToListeners(SortedPacketListenerList listeners, PacketEvent event, boolean outbound) {
|
||||
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
|
||||
if (outbound) {
|
||||
listeners.invokePacketSending(this.reporter, event);
|
||||
} else {
|
||||
listeners.invokePacketRecieving(this.reporter, event);
|
||||
}
|
||||
listeners.invoke(event);
|
||||
|
||||
// check if we need to post the packet to the async handler
|
||||
if (!event.isCancelled() && event.getAsyncMarker() != null && !event.getAsyncMarker().isAsyncCancelled()) {
|
||||
@@ -587,39 +576,4 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPacketListenerInInjectors(PacketListener listener, Collection<PacketType> packetTypes) {
|
||||
for (PacketType packetType : packetTypes) {
|
||||
// check the packet direction
|
||||
if (packetType.getSender() == Sender.SERVER) {
|
||||
// check if the packet is registered on the server side
|
||||
if (PacketRegistry.getServerPacketTypes().contains(packetType)) {
|
||||
this.playerInjectionHandler.addPacketHandler(packetType, listener.getSendingWhitelist().getOptions());
|
||||
} else {
|
||||
this.reporter.reportWarning(this, Report.newBuilder(UNSUPPORTED_PACKET)
|
||||
.messageParam(PacketAdapter.getPluginName(listener), packetType, packetType.getSender())
|
||||
.build());
|
||||
}
|
||||
} else if (packetType.getSender() == Sender.CLIENT) {
|
||||
// check if the packet is registered on the client side
|
||||
if (PacketRegistry.getClientPacketTypes().contains(packetType)) {
|
||||
this.packetInjector.addPacketHandler(packetType, listener.getReceivingWhitelist().getOptions());
|
||||
} else {
|
||||
this.reporter.reportWarning(this, Report.newBuilder(UNSUPPORTED_PACKET)
|
||||
.messageParam(PacketAdapter.getPluginName(listener), packetType, packetType.getSender())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterPacketListenerInInjectors(Collection<PacketType> packetTypes) {
|
||||
for (PacketType packetType : packetTypes) {
|
||||
if (packetType.getSender() == Sender.SERVER) {
|
||||
this.playerInjectionHandler.removePacketHandler(packetType);
|
||||
} else if (packetType.getSender() == Sender.CLIENT) {
|
||||
this.packetInjector.removePacketHandler(packetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||
import com.comphenix.protocol.timing.TimedTracker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Registry of synchronous packet listeners.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||
// The current listener manager
|
||||
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||
|
||||
public SortedPacketListenerList() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// The returned list is thread-safe
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener of the given priority.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
* @param priorityFilter - the required priority for a listener to be invoked.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// The returned list is thread-safe
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular receiving listener.
|
||||
* @param reporter - the error reporter.
|
||||
* @param event - the related packet event.
|
||||
* @param element - the listener to invoke.
|
||||
*/
|
||||
private void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
element.getListener().onPacketReceiving(event);
|
||||
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
|
||||
invokePacketSending(reporter, event, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener of the given priority.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
* @param priorityFilter - the priority for a listener to be invoked. If null is provided, every registered listener will be invoked
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, @Nullable ListenerPriority priorityFilter) {
|
||||
invokeUnpackedPacketSending(reporter, event, priorityFilter);
|
||||
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) {
|
||||
if(subPacket == null) {
|
||||
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "Failed to invoke packet event " + (priorityFilter == null ? "" : ("with priority " + priorityFilter)) + " in bundle because bundle contains null packet: " + packets, new Throwable());
|
||||
continue;
|
||||
}
|
||||
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(), event.getPlayer());
|
||||
invokeUnpackedPacketSending(reporter, subPacketEvent, priorityFilter);
|
||||
|
||||
if (!subPacketEvent.isCancelled()) {
|
||||
// if the packet event has been cancelled, the packet will be removed from the bundle
|
||||
PacketContainer packet = subPacketEvent.getPacket();
|
||||
if(packet == null) {
|
||||
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet container returned for " + subPacketEvent, new Throwable());
|
||||
} else if(packet.getHandle() == null) {
|
||||
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet handle returned for " + subPacketEvent, new Throwable());
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeUnpackedPacketSending(ErrorReporter reporter, PacketEvent event, @org.jetbrains.annotations.Nullable ListenerPriority priorityFilter) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (priorityFilter == null || element.getPriority() == priorityFilter) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeSendingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (priorityFilter == null || element.getPriority() == priorityFilter) {
|
||||
invokeSendingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular sending listener.
|
||||
* @param reporter - the error reporter.
|
||||
* @param event - the related packet event.
|
||||
* @param element - the listener to invoke.
|
||||
*/
|
||||
private void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
element.getListener().onPacketSending(event);
|
||||
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,26 +17,41 @@
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.injector.packet.KnownPacketData;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
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.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;
|
||||
import com.comphenix.protocol.reflect.instances.InstanceCreator;
|
||||
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 com.comphenix.protocol.utility.ZeroBuffer;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
@@ -50,57 +65,184 @@ import net.bytebuddy.matcher.ElementMatchers;
|
||||
public class StructureCache {
|
||||
|
||||
// Structure modifiers
|
||||
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, Optional<Supplier<Object>>> CACHED_INSTANCE_CREATORS = new ConcurrentHashMap<>();
|
||||
private static final Map<PacketType, StructureModifier<Object>> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
// packet data serializer which always returns an empty nbt tag compound
|
||||
private static final Object TRICK_INIT_LOCK = new Object();
|
||||
private static boolean TRICK_TRIED = false;
|
||||
|
||||
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
|
||||
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
|
||||
private static Function<ByteBuf, Object> TRICKED_DATA_SERIALIZER_BASE;
|
||||
private static Supplier<Object> TRICKED_DATA_SERIALIZER_JSON;
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to {@link #newInstance(Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static Object newPacket(Class<?> packetClass) {
|
||||
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
|
||||
// prefer construction via PacketDataSerializer constructor on 1.17 and above
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
|
||||
clazz,
|
||||
MinecraftReflection.getPacketDataSerializerClass());
|
||||
if (serializerAccessor != null) {
|
||||
// check if the method is possible
|
||||
if (tryInitTrickDataSerializer()) {
|
||||
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) {
|
||||
return creatorFor(clazz).isPresent();
|
||||
}
|
||||
|
||||
public static Object newInstance(Class<?> clazz) {
|
||||
Optional<Supplier<Object>> creator = creatorFor(clazz);
|
||||
if (!creator.isPresent()) {
|
||||
throw new IllegalArgumentException("Cannot create instance of " + clazz);
|
||||
}
|
||||
return creator.get().get();
|
||||
}
|
||||
|
||||
static Supplier<Object> determineBestCreator(Class<?> 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());
|
||||
|
||||
// Preserve the existing packet singleton behavior.
|
||||
if (packetClass && singletons.size() == 1) {
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(singletons.get(0));
|
||||
try {
|
||||
accessor.get(null);
|
||||
return () -> accessor.get(null);
|
||||
} 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 {
|
||||
InstanceCreator creator = InstanceCreator.forClass(clazz);
|
||||
if (creator.get() != null) {
|
||||
return creator;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
try {
|
||||
// first try with the base accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_BASE.apply(new ZeroBuffer());
|
||||
streamCodec.decode(serializer); // throwaway instance, for testing
|
||||
|
||||
// method is working
|
||||
return () -> streamCodec.decode(serializer);
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
byte[] data;
|
||||
if (clazz.equals(PacketType.Play.Server.MAP_CHUNK.getPacketClass())) {
|
||||
data = KnownPacketData.MAP_CHUNK;
|
||||
} else if (clazz.equals(PacketType.Play.Server.SCOREBOARD_OBJECTIVE.getPacketClass())) {
|
||||
data = KnownPacketData.SCOREBOARD_OBJECTIVE;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_BASE.apply(Unpooled.copiedBuffer(data));
|
||||
streamCodec.decode(serializer);
|
||||
|
||||
return () -> {
|
||||
((ByteBuf) serializer).readerIndex(0);
|
||||
return streamCodec.decode(serializer);
|
||||
};
|
||||
} catch (Exception ignored1) {
|
||||
try {
|
||||
// try with the json accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_JSON.get();
|
||||
streamCodec.decode(serializer); // throwaway instance, for testing
|
||||
|
||||
// method is working
|
||||
return () -> streamCodec.decode(serializer);
|
||||
} catch (Exception ignored2) {
|
||||
// shrug, fall back to default behaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prefer construction via PacketDataSerializer constructor on 1.17 and above
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
|
||||
clazz,
|
||||
MinecraftReflection.getPacketDataSerializerClass());
|
||||
if (serializerAccessor != null) {
|
||||
// check if the method is possible
|
||||
if (tryInitTrickDataSerializer()) {
|
||||
try {
|
||||
// first try with the base accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_BASE.apply(new ZeroBuffer());
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
// first try with the base accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
|
||||
// try with the json accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_JSON.get();
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception exception) {
|
||||
try {
|
||||
// try with the json accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_JSON.invoke(new ZeroBuffer());
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception ignored) {
|
||||
// shrug, fall back to default behaviour
|
||||
}
|
||||
} catch (Exception ignored1) {
|
||||
// shrug, fall back to default behaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try via DefaultInstances as fallback
|
||||
return () -> {
|
||||
Object packetInstance = DefaultInstances.DEFAULT.create(clazz);
|
||||
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + clazz);
|
||||
return packetInstance;
|
||||
};
|
||||
});
|
||||
return packetConstructor.get();
|
||||
Object instance = DefaultInstances.DEFAULT.create(clazz);
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return () -> DefaultInstances.DEFAULT.create(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,39 +298,62 @@ public class StructureCache {
|
||||
*/
|
||||
public static Object newNullDataSerializer() {
|
||||
tryInitTrickDataSerializer();
|
||||
return TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
|
||||
return TRICKED_DATA_SERIALIZER_BASE.apply(new ZeroBuffer());
|
||||
}
|
||||
|
||||
static void initTrickDataSerializer() {
|
||||
// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
|
||||
Object textCompound = WrappedChatComponent.fromText("").getHandle();
|
||||
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();
|
||||
// base builder which intercepts a few methods
|
||||
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
|
||||
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
|
||||
.method(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())
|
||||
.and(ElementMatchers.returns(ElementMatchers.isSubTypeOf(MinecraftReflection.getNBTBaseClass()))))
|
||||
.intercept(FixedValue.value(compound))
|
||||
.method(ElementMatchers.returns(MinecraftReflection.getIChatBaseComponentClass()))
|
||||
.intercept(FixedValue.value(textCompound))
|
||||
.method(ElementMatchers.returns(PublicKey.class).and(ElementMatchers.takesNoArguments()))
|
||||
.intercept(FixedValue.nullValue());
|
||||
Class<?> serializerBase = baseBuilder.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
TRICKED_DATA_SERIALIZER_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
|
||||
static void initTrickDataSerializer() {
|
||||
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();
|
||||
|
||||
// extended builder which intercepts the read string method as well
|
||||
Class<?> withStringIntercept = baseBuilder
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
|
||||
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
|
||||
.intercept(FixedValue.value("{}"))
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
TRICKED_DATA_SERIALIZER_JSON = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
|
||||
}
|
||||
// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
|
||||
Object textCompound = WrappedChatComponent.fromText("").getHandle();
|
||||
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();
|
||||
|
||||
// base builder which intercepts a few methods
|
||||
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
|
||||
.createSubclass(registryByteBuf.orElse(MinecraftReflection.getPacketDataSerializerClass()))
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
|
||||
.method(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())
|
||||
.and(ElementMatchers.returns(ElementMatchers.isSubTypeOf(MinecraftReflection.getNBTBaseClass()))))
|
||||
.intercept(FixedValue.value(compound))
|
||||
.method(ElementMatchers.returns(MinecraftReflection.getIChatBaseComponentClass()))
|
||||
.intercept(FixedValue.value(textCompound))
|
||||
.method(ElementMatchers.returns(PublicKey.class).and(ElementMatchers.takesNoArguments()))
|
||||
.intercept(FixedValue.nullValue());
|
||||
Class<?> serializerBase = baseBuilder.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
|
||||
if (registryByteBuf.isPresent()) {
|
||||
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(serializerBase, true).getConstructor(FuzzyMethodContract.newBuilder()
|
||||
.parameterExactType(ByteBuf.class)
|
||||
.parameterExactType(MinecraftReflection.getRegistryAccessClass())
|
||||
.build()));
|
||||
TRICKED_DATA_SERIALIZER_BASE = (buf) -> accessor.invoke(buf, MinecraftRegistryAccess.get());
|
||||
} else {
|
||||
ConstructorAccessor accessor = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
|
||||
TRICKED_DATA_SERIALIZER_BASE = accessor::invoke;
|
||||
}
|
||||
|
||||
//xtended builder which intercepts the read string method as well
|
||||
Class<?> withStringIntercept = baseBuilder
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
|
||||
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
|
||||
.intercept(FixedValue.value("{}"))
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
|
||||
if (registryByteBuf.isPresent()) {
|
||||
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(withStringIntercept).getConstructor(FuzzyMethodContract.newBuilder()
|
||||
.parameterDerivedOf(ByteBuf.class)
|
||||
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
|
||||
.build()));
|
||||
TRICKED_DATA_SERIALIZER_JSON = () -> accessor.invoke(new ZeroBuffer(), MinecraftRegistryAccess.get());
|
||||
} else {
|
||||
ConstructorAccessor accessor = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
|
||||
TRICKED_DATA_SERIALIZER_JSON = () -> accessor.invoke(new ZeroBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet data serializer sub-class if needed to allow the fixed read of a NbtTagCompound because of a
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.comphenix.protocol.injector.collection;
|
||||
|
||||
import com.comphenix.protocol.concurrent.PacketTypeListenerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
|
||||
public class InboundPacketListenerSet extends PacketListenerSet {
|
||||
|
||||
public InboundPacketListenerSet(PacketTypeListenerSet mainThreadPacketTypes, ErrorReporter errorReporter) {
|
||||
super(mainThreadPacketTypes, errorReporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListeningWhitelist getListeningWhitelist(PacketListener packetListener) {
|
||||
return packetListener.getReceivingWhitelist();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeListener(PacketEvent event, PacketListener listener) {
|
||||
try {
|
||||
event.setReadOnly(listener.getReceivingWhitelist().getPriority() == ListenerPriority.MONITOR);
|
||||
listener.onPacketReceiving(event);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
errorReporter.reportMinimal(listener.getPlugin(), "onPacketReceiving(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.comphenix.protocol.injector.collection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrent.PacketTypeListenerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
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 {
|
||||
|
||||
public OutboundPacketListenerSet(PacketTypeListenerSet mainThreadPacketTypes, ErrorReporter errorReporter) {
|
||||
super(mainThreadPacketTypes, errorReporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListeningWhitelist getListeningWhitelist(PacketListener packetListener) {
|
||||
return packetListener.getSendingWhitelist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(PacketEvent event, @Nullable ListenerPriority priorityFilter) {
|
||||
super.invoke(event, priorityFilter);
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Server.BUNDLE && !event.isCancelled()) {
|
||||
// unpack the bundle and invoke for each packet in the bundle
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean changed = packet == null || packet.getHandle() != subPacket.getHandle();
|
||||
if (outPackets == null && changed) {
|
||||
outPackets = new ArrayList<>(packets.size());
|
||||
outPackets.addAll(packets.subList(0, index));
|
||||
}
|
||||
if (outPackets != null && packet != null) {
|
||||
outPackets.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invokeListener(PacketEvent event, PacketListener listener) {
|
||||
try {
|
||||
event.setReadOnly(listener.getSendingWhitelist().getPriority() == ListenerPriority.MONITOR);
|
||||
listener.onPacketSending(event);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
errorReporter.reportMinimal(listener.getPlugin(), "onPacketSending(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.comphenix.protocol.injector.collection;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrent.PacketTypeListenerSet;
|
||||
import com.comphenix.protocol.concurrent.PacketTypeMultiMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.timing.TimingListenerType;
|
||||
import com.comphenix.protocol.timing.TimingTrackerManager;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public abstract class PacketListenerSet {
|
||||
|
||||
private static final ReportType REPORT_UNSUPPORTED_PACKET = new ReportType(
|
||||
"Plugin %s tried to register listener for unknown packet %s [direction: from %s]");
|
||||
private static final ReportType REPORT_NULL_PACKET = new ReportType(
|
||||
"Plugin %s tried to set a packet or packet handle to null [type: %s, direction: %s]");
|
||||
|
||||
protected final PacketTypeMultiMap<PacketListener> map = new PacketTypeMultiMap<>();
|
||||
|
||||
protected final PacketTypeListenerSet mainThreadPacketTypes;
|
||||
protected final ErrorReporter errorReporter;
|
||||
|
||||
public PacketListenerSet(PacketTypeListenerSet mainThreadPacketTypes, ErrorReporter errorReporter) {
|
||||
this.mainThreadPacketTypes = mainThreadPacketTypes;
|
||||
this.errorReporter = errorReporter;
|
||||
}
|
||||
|
||||
protected abstract ListeningWhitelist getListeningWhitelist(PacketListener packetListener);
|
||||
|
||||
public void addListener(PacketListener packetListener) {
|
||||
ListeningWhitelist listeningWhitelist = getListeningWhitelist(packetListener);
|
||||
|
||||
for (Iterator<PacketType> packet = listeningWhitelist.getTypes().iterator(); packet.hasNext();) {
|
||||
PacketType packetType = packet.next();
|
||||
|
||||
Set<PacketType> supportedPacketTypes = (packetType.getSender() == Sender.SERVER)
|
||||
? PacketRegistry.getServerPacketTypes()
|
||||
: PacketRegistry.getClientPacketTypes();
|
||||
|
||||
if (!supportedPacketTypes.contains(packetType)) {
|
||||
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_UNSUPPORTED_PACKET)
|
||||
.messageParam(PacketAdapter.getPluginName(packetListener), packetType, packetType.getSender())
|
||||
.build());
|
||||
|
||||
// remove unknown packet types
|
||||
packet.remove();
|
||||
}
|
||||
}
|
||||
|
||||
this.map.put(listeningWhitelist, packetListener);
|
||||
|
||||
Set<ListenerOptions> options = listeningWhitelist.getOptions();
|
||||
for (PacketType packetType : listeningWhitelist.getTypes()) {
|
||||
if (this.mainThreadPacketTypes != null && !packetType.isAsyncForced()) {
|
||||
boolean isOutboundSync = packetType.getSender() == Sender.SERVER
|
||||
&& !options.contains(ListenerOptions.ASYNC);
|
||||
boolean isInboundSync = packetType.getSender() == Sender.CLIENT
|
||||
&& options.contains(ListenerOptions.SYNC);
|
||||
if (isOutboundSync || isInboundSync) {
|
||||
this.mainThreadPacketTypes.add(packetType, packetListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(PacketListener packetListener) {
|
||||
ListeningWhitelist listeningWhitelist = getListeningWhitelist(packetListener);
|
||||
this.map.remove(listeningWhitelist, packetListener);
|
||||
|
||||
if (this.mainThreadPacketTypes != null) {
|
||||
for (PacketType packetType : listeningWhitelist.getTypes()) {
|
||||
this.mainThreadPacketTypes.remove(packetType, packetListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean containsPacketType(PacketType packetType) {
|
||||
return this.map.contains(packetType);
|
||||
}
|
||||
|
||||
public final ImmutableSet<PacketType> getPacketTypes() {
|
||||
return this.map.getPacketTypes();
|
||||
}
|
||||
|
||||
public void invoke(PacketEvent event) {
|
||||
this.invoke(event, null);
|
||||
}
|
||||
|
||||
public void invoke(PacketEvent event, @Nullable ListenerPriority priorityFilter) {
|
||||
Iterable<PacketListener> listeners = this.map.get(event.getPacketType());
|
||||
|
||||
for (PacketListener listener : listeners) {
|
||||
ListeningWhitelist listeningWhitelist = listener.getReceivingWhitelist();
|
||||
if (priorityFilter != null && listeningWhitelist.getPriority() != priorityFilter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PacketContainer originalPacket = event.getPacket();
|
||||
if (originalPacket == null || originalPacket.getHandle() == null) {
|
||||
// ignore null packets, they are evil and shouldn't exist
|
||||
break;
|
||||
}
|
||||
|
||||
// invoke packet listener
|
||||
TimingTrackerManager
|
||||
.get(listener, event.isServerPacket() ? TimingListenerType.SYNC_OUTBOUND : TimingListenerType.SYNC_INBOUND)
|
||||
.track(event.getPacketType(), () -> invokeListener(event, listener));
|
||||
|
||||
// check for new null packets
|
||||
PacketContainer newPacket = event.getPacket();
|
||||
if (newPacket == null || newPacket.getHandle() == null) {
|
||||
errorReporter.reportWarning(this, Report.newBuilder(REPORT_NULL_PACKET)
|
||||
.messageParam(PacketAdapter.getPluginName(listener), originalPacket.getType(), originalPacket.getType().getSender())
|
||||
.build());
|
||||
// reset packet to previous packet
|
||||
event.setPacket(originalPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void invokeListener(PacketEvent event, PacketListener listener);
|
||||
|
||||
public void clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.comphenix.protocol.injector.netty;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
/**
|
||||
* Represents a listener for received or sent packets.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ChannelListener {
|
||||
|
||||
/**
|
||||
* Invoked when a packet is being sent to the client.
|
||||
* <p>
|
||||
* This is invoked on the main thread.
|
||||
*
|
||||
* @param injector - the channel injector.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
|
||||
*/
|
||||
PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
|
||||
|
||||
/**
|
||||
* Invoked when a packet is being received from a client.
|
||||
* <p>
|
||||
* This is invoked on an asynchronous worker thread.
|
||||
*
|
||||
* @param injector - the channel injector.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the associated network marker, if any.
|
||||
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
|
||||
*/
|
||||
PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
|
||||
|
||||
/**
|
||||
* Determine if there is a packet listener for the given packet.
|
||||
*
|
||||
* @param packetClass - the packet class to check.
|
||||
* @return TRUE if there is such a listener, FALSE otherwise.
|
||||
*/
|
||||
boolean hasListener(Class<?> packetClass);
|
||||
|
||||
/**
|
||||
* Determine if there is a server packet listener that must be executed on the main thread.
|
||||
*
|
||||
* @param packetClass - the packet class to check.
|
||||
* @return TRUE if there is, FALSE otherwise.
|
||||
*/
|
||||
boolean hasMainThreadListener(Class<?> packetClass);
|
||||
|
||||
boolean hasMainThreadListener(PacketType type);
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
*
|
||||
* @return The error reporter.
|
||||
*/
|
||||
ErrorReporter getReporter();
|
||||
|
||||
/**
|
||||
* Determine if debug mode is enabled.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
boolean isDebug();
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.comphenix.protocol.injector.netty;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents an injected client connection.
|
||||
@@ -12,6 +16,8 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public interface Injector {
|
||||
|
||||
SocketAddress getAddress();
|
||||
|
||||
/**
|
||||
* Retrieve the current protocol version of the player.
|
||||
*
|
||||
@@ -23,12 +29,8 @@ public interface Injector {
|
||||
* Inject the current channel.
|
||||
* <p>
|
||||
* Note that only active channels can be injected.
|
||||
*
|
||||
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
|
||||
*/
|
||||
boolean inject();
|
||||
|
||||
void uninject();
|
||||
void inject();
|
||||
|
||||
/**
|
||||
* Close the current injector.
|
||||
@@ -42,20 +44,13 @@ public interface Injector {
|
||||
* @param marker - the network marker.
|
||||
* @param filtered - whether or not the packet is filtered.
|
||||
*/
|
||||
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
|
||||
void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered);
|
||||
|
||||
void receiveClientPacket(Object packet);
|
||||
void readServerboundPacket(Object packet);
|
||||
|
||||
/**
|
||||
* Retrieve the current protocol state.
|
||||
*
|
||||
* @return The current protocol.
|
||||
* @deprecated use {@link #getCurrentProtocol(PacketType.Sender)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default Protocol getCurrentProtocol() {
|
||||
return this.getCurrentProtocol(PacketType.Sender.SERVER);
|
||||
}
|
||||
void sendWirePacket(WirePacket packet);
|
||||
|
||||
void disconnect(String message);
|
||||
|
||||
/**
|
||||
* Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different
|
||||
@@ -66,22 +61,6 @@ public interface Injector {
|
||||
*/
|
||||
Protocol getCurrentProtocol(PacketType.Sender sender);
|
||||
|
||||
/**
|
||||
* Retrieve the network marker associated with a given packet.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return The network marker.
|
||||
*/
|
||||
NetworkMarker getMarker(Object packet);
|
||||
|
||||
/**
|
||||
* Associate a given network marker with a specific packet.
|
||||
*
|
||||
* @param packet - the NMS packet.
|
||||
* @param marker - the associated marker.
|
||||
*/
|
||||
void saveMarker(Object packet, NetworkMarker marker);
|
||||
|
||||
/**
|
||||
* Retrieve the current player or temporary player associated with the injector.
|
||||
*
|
||||
@@ -89,6 +68,24 @@ public interface Injector {
|
||||
*/
|
||||
Player getPlayer();
|
||||
|
||||
/**
|
||||
* Retrieve the player name captured during login.
|
||||
*
|
||||
* @return The player name, or NULL if it is not known yet.
|
||||
*/
|
||||
default String getPlayerName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the player UUID captured during login.
|
||||
*
|
||||
* @return The player UUID, or NULL if it is not available.
|
||||
*/
|
||||
default UUID getPlayerUniqueId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current player instance.
|
||||
*
|
||||
@@ -96,7 +93,7 @@ public interface Injector {
|
||||
*/
|
||||
void setPlayer(Player player);
|
||||
|
||||
void disconnect(String message);
|
||||
boolean isConnected();
|
||||
|
||||
/**
|
||||
* Determine if the channel has already been injected.
|
||||
|
||||
@@ -19,10 +19,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.utility.StreamSerializer;
|
||||
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
@@ -81,6 +84,11 @@ public class WirePacket {
|
||||
public static byte[] bytesFromPacket(PacketContainer packet) {
|
||||
checkNotNull(packet, "packet cannot be null!");
|
||||
|
||||
// packets are written by their stream codec since 1.20.5
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
return bytesFromStreamCodec(packet);
|
||||
}
|
||||
|
||||
ByteBuf buffer = PacketContainer.createPacketBuffer();
|
||||
ByteBuf store = PacketContainer.createPacketBuffer();
|
||||
|
||||
@@ -107,6 +115,44 @@ public class WirePacket {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte[] bytesFromStreamCodec(PacketContainer packet) {
|
||||
// Spigot keeps the data of unknown custom payloads in a buffer and the codec reads from it,
|
||||
// so the reader index is restored to keep the packet writable after this call
|
||||
ByteBuf payloadData = getCustomPayloadData(packet);
|
||||
int readerIndex = payloadData != null ? payloadData.readerIndex() : 0;
|
||||
|
||||
try {
|
||||
ByteBuf buffer = (ByteBuf) packet.serializeToBuffer();
|
||||
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
|
||||
} finally {
|
||||
if (payloadData != null) {
|
||||
payloadData.readerIndex(readerIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf getCustomPayloadData(PacketContainer packet) {
|
||||
PacketType type = packet.getType();
|
||||
if (type != PacketType.Play.Server.CUSTOM_PAYLOAD
|
||||
&& type != PacketType.Play.Client.CUSTOM_PAYLOAD
|
||||
&& type != PacketType.Configuration.Server.CUSTOM_PAYLOAD
|
||||
&& type != PacketType.Configuration.Client.CUSTOM_PAYLOAD) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object payload = packet.getModifier()
|
||||
.withType(CustomPacketPayloadWrapper.getCustomPacketPayloadClass())
|
||||
.readSafely(0);
|
||||
if (payload == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StructureModifier<ByteBuf> buffers = new StructureModifier<>(payload.getClass())
|
||||
.withTarget(payload)
|
||||
.withType(ByteBuf.class);
|
||||
return buffers.readSafely(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WirePacket from an existing Minecraft packet
|
||||
*
|
||||
@@ -118,6 +164,10 @@ public class WirePacket {
|
||||
checkNotNull(packet, "packet cannot be null!");
|
||||
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
|
||||
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
return fromPacket(PacketContainer.fromPacket(packet));
|
||||
}
|
||||
|
||||
ByteBuf buffer = PacketContainer.createPacketBuffer();
|
||||
|
||||
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
|
||||
|
||||
+87
-11
@@ -1,18 +1,23 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
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 io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final class ChannelProtocolUtil {
|
||||
@@ -28,7 +33,10 @@ final class ChannelProtocolUtil {
|
||||
.build());
|
||||
|
||||
BiFunction<Channel, PacketType.Sender, Object> baseResolver = null;
|
||||
if (attributeKeys.size() == 1) {
|
||||
if (attributeKeys.isEmpty()) {
|
||||
// since 1.20.5 the protocol is stored as final field in de-/encoder
|
||||
baseResolver = new Post1_20_5WrappedResolver();
|
||||
} else if (attributeKeys.size() == 1) {
|
||||
// if there is only one attribute key we can assume it's the correct one (1.8 - 1.20.1)
|
||||
Object protocolKey = Accessors.getFieldAccessor(attributeKeys.get(0)).get(null);
|
||||
baseResolver = new Pre1_20_2DirectResolver((AttributeKey<Object>) protocolKey);
|
||||
@@ -68,7 +76,7 @@ final class ChannelProtocolUtil {
|
||||
}
|
||||
|
||||
// decorate the base resolver by wrapping its return value into our packet type value
|
||||
PROTOCOL_RESOLVER = baseResolver.andThen(nmsProtocol -> PacketType.Protocol.fromVanilla((Enum<?>) nmsProtocol));
|
||||
PROTOCOL_RESOLVER = baseResolver.andThen(protocol -> PacketType.Protocol.fromVanilla((Enum<?>) protocol));
|
||||
}
|
||||
|
||||
private static final class Pre1_20_2DirectResolver implements BiFunction<Channel, PacketType.Sender, Object> {
|
||||
@@ -130,4 +138,72 @@ final class ChannelProtocolUtil {
|
||||
return this.protocolAccessor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since 1.20.5 the protocol is stored as final field in de-/encoder
|
||||
*/
|
||||
private static final class Post1_20_5WrappedResolver implements BiFunction<Channel, PacketType.Sender, Object> {
|
||||
|
||||
// lazy initialized when needed
|
||||
private Function<Object, Object> serverProtocolAccessor;
|
||||
private Function<Object, Object> clientProtocolAccessor;
|
||||
|
||||
@Override
|
||||
public Object apply(Channel channel, PacketType.Sender sender) {
|
||||
Class<? extends ChannelHandler> handlerClass = this.getHandlerClass(sender)
|
||||
.asSubclass(ChannelHandler.class);
|
||||
|
||||
ChannelHandlerContext handlerContext = channel.pipeline().context(handlerClass);
|
||||
if (handlerContext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Function<Object, Object> protocolAccessor = this.getProtocolAccessor(handlerClass, sender);
|
||||
return protocolAccessor.apply(handlerContext.handler());
|
||||
}
|
||||
|
||||
private Function<Object, Object> getProtocolAccessor(Class<?> codecHandler, PacketType.Sender sender) {
|
||||
switch (sender) {
|
||||
case SERVER:
|
||||
if (this.serverProtocolAccessor == null) {
|
||||
this.serverProtocolAccessor = getProtocolAccessor(codecHandler);
|
||||
}
|
||||
return this.serverProtocolAccessor;
|
||||
case CLIENT:
|
||||
if (this.clientProtocolAccessor == null) {
|
||||
this.clientProtocolAccessor = getProtocolAccessor(codecHandler);
|
||||
}
|
||||
return this.clientProtocolAccessor;
|
||||
default:
|
||||
throw new IllegalArgumentException("Illegal packet sender " + sender.name());
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> getHandlerClass(PacketType.Sender sender) {
|
||||
switch (sender) {
|
||||
case SERVER:
|
||||
return MinecraftReflection.getMinecraftClass("network.PacketEncoder");
|
||||
case CLIENT:
|
||||
return MinecraftReflection.getMinecraftClass("network.PacketDecoder");
|
||||
default:
|
||||
throw new IllegalArgumentException("Illegal packet sender " + sender.name());
|
||||
}
|
||||
}
|
||||
|
||||
private Function<Object, Object> getProtocolAccessor(Class<?> codecHandler) {
|
||||
Class<?> protocolInfoClass = MinecraftReflection.getProtocolInfoClass();
|
||||
|
||||
MethodAccessor protocolAccessor = Accessors.getMethodAccessor(FuzzyReflection
|
||||
.fromClass(protocolInfoClass).getMethodByReturnTypeAndParameters("id",
|
||||
MinecraftReflection.getEnumProtocolClass(), new Class[0]));
|
||||
|
||||
FieldAccessor protocolInfoAccessor = Accessors.getFieldAccessor(codecHandler, protocolInfoClass, true);
|
||||
|
||||
// get ProtocolInfo from handler and get EnumProtocol of ProtocolInfo
|
||||
return (handler) -> {
|
||||
Object protocolInfo = protocolInfoAccessor.get(handler);
|
||||
return protocolAccessor.invoke(protocolInfo);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import org.bukkit.entity.Player;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
|
||||
final class EmptyInjector implements Injector {
|
||||
|
||||
@@ -16,18 +21,21 @@ final class EmptyInjector implements Injector {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getAddress() {
|
||||
if (this.player != null) {
|
||||
return this.player.getAddress();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninject() {
|
||||
public void inject() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,11 +43,19 @@ final class EmptyInjector implements Injector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveClientPacket(Object packet) {
|
||||
public void readServerboundPacket(Object packet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendWirePacket(WirePacket packet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,27 +63,29 @@ final class EmptyInjector implements Injector {
|
||||
return Protocol.HANDSHAKING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkMarker getMarker(Object packet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerName() {
|
||||
return this.player != null ? this.player.getName() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getPlayerUniqueId() {
|
||||
return this.player != null ? this.player.getUniqueId() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
public boolean isConnected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+28
-19
@@ -1,44 +1,53 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final NettyChannelInjector injector;
|
||||
private final ChannelListener channelListener;
|
||||
|
||||
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
|
||||
public InboundPacketInterceptor(NettyChannelInjector injector) {
|
||||
this.injector = injector;
|
||||
this.channelListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
Class<?> messageClass = msg.getClass();
|
||||
if (this.shouldInterceptMessage(messageClass)) {
|
||||
// process the login if the packet is one before posting the packet to any handler to provide "real" data
|
||||
// the method invocation will do nothing if the packet is not a login packet
|
||||
this.injector.tryProcessLogin(msg);
|
||||
|
||||
// check if there are any listeners bound for the packet - if not just post the packet down the pipeline
|
||||
if (!this.channelListener.hasListener(messageClass)) {
|
||||
if (MinecraftReflection.isPacketClass(msg)) {
|
||||
// try get packet type
|
||||
PacketType.Protocol protocol = this.injector.getInboundProtocol();
|
||||
if (protocol == Protocol.UNKNOWN) {
|
||||
ProtocolLogger.debug("skipping unknown inbound protocol for {0}", msg.getClass());
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// call all inbound listeners
|
||||
this.injector.processInboundPacket(ctx, msg, messageClass);
|
||||
PacketType packetType = PacketRegistry.getPacketType(protocol, msg.getClass());
|
||||
if (packetType == null) {
|
||||
ProtocolLogger.debug("skipping unknown inbound packet type for {0}", msg.getClass());
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if there are any listeners bound for the packet - if not just send the
|
||||
// packet down the pipeline
|
||||
if (!this.injector.hasInboundListener(packetType)) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// don't invoke next handler and transfer packet to injector for further processing
|
||||
this.injector.processInbound(ctx, new PacketContainer(packetType, msg));
|
||||
} else {
|
||||
// just pass the message down the pipeline
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldInterceptMessage(Class<?> messageClass) {
|
||||
// only intercept minecraft packets and no garbage from other stuff in the channel
|
||||
return MinecraftReflection.getPacketClass().isAssignableFrom(messageClass);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
public class InboundProtocolReader extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final NettyChannelInjector injector;
|
||||
|
||||
private PacketType.Protocol protocol = null;
|
||||
|
||||
public InboundProtocolReader(NettyChannelInjector injector) {
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
public PacketType.Protocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
this.protocol = this.injector.getCurrentProtocol(PacketType.Sender.CLIENT);
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
@@ -14,21 +14,25 @@
|
||||
*/
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.ListenerManager;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.temporary.MinimalInjector;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftFields;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
|
||||
/**
|
||||
* Represents an injector factory.
|
||||
@@ -39,24 +43,23 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public class InjectionFactory {
|
||||
|
||||
// This should work as long as the injectors are, uh, injected
|
||||
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
|
||||
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
|
||||
|
||||
// bukkit stuff
|
||||
private final Plugin plugin;
|
||||
private final Server server;
|
||||
|
||||
// protocol lib stuff
|
||||
private final ErrorReporter errorReporter;
|
||||
private final ListenerManager listenerManager;
|
||||
|
||||
// state of the factory
|
||||
private boolean closed;
|
||||
|
||||
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
|
||||
public InjectionFactory(Plugin plugin, ErrorReporter errorReporter, ListenerManager listenerManager) {
|
||||
this.plugin = plugin;
|
||||
this.server = server;
|
||||
this.errorReporter = errorReporter;
|
||||
this.listenerManager = listenerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,11 +75,10 @@ public class InjectionFactory {
|
||||
* Construct or retrieve a channel injector from an existing Bukkit player.
|
||||
*
|
||||
* @param player - the existing Bukkit player.
|
||||
* @param listener - the listener.
|
||||
* @return A new injector, an existing injector associated with this player, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromPlayer(Player player, ChannelListener listener) {
|
||||
public Injector fromPlayer(Player player) {
|
||||
if (this.closed) {
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
@@ -114,10 +116,9 @@ public class InjectionFactory {
|
||||
// construct a new injector as it seems like we have none yet
|
||||
injector = new NettyChannelInjector(
|
||||
player,
|
||||
this.server,
|
||||
networkManager,
|
||||
channel,
|
||||
listener,
|
||||
this.listenerManager,
|
||||
this,
|
||||
this.errorReporter);
|
||||
this.cacheInjector(player, injector);
|
||||
@@ -155,31 +156,27 @@ public class InjectionFactory {
|
||||
* Construct a new channel injector for the given channel.
|
||||
*
|
||||
* @param channel - the channel.
|
||||
* @param listener - the listener.
|
||||
* @param playerFactory - a temporary player creator.
|
||||
* @return The channel injector, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
|
||||
public Injector fromChannel(Channel channel) {
|
||||
if (this.closed) {
|
||||
return EmptyInjector.WITHOUT_PLAYER;
|
||||
}
|
||||
|
||||
Object netManager = this.findNetworkManager(channel);
|
||||
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
|
||||
Object networkManager = this.findNetworkManager(channel);
|
||||
Player temporaryPlayer = TemporaryPlayerFactory.createTemporaryPlayer();
|
||||
|
||||
NettyChannelInjector injector = new NettyChannelInjector(
|
||||
temporaryPlayer,
|
||||
this.server,
|
||||
netManager,
|
||||
networkManager,
|
||||
channel,
|
||||
listener,
|
||||
this.listenerManager,
|
||||
this,
|
||||
this.errorReporter);
|
||||
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
|
||||
|
||||
// Initialize temporary player
|
||||
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
|
||||
TemporaryPlayerFactory.setInjectorForPlayer(temporaryPlayer, injector);
|
||||
return injector;
|
||||
}
|
||||
|
||||
@@ -235,9 +232,9 @@ public class InjectionFactory {
|
||||
* @return The associated injector, or NULL if this is a Bukkit player.
|
||||
*/
|
||||
private NettyChannelInjector getTemporaryInjector(Player player) {
|
||||
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
if (injector instanceof NettyChannelMinimalInjector) {
|
||||
return ((NettyChannelMinimalInjector) injector).getInjector();
|
||||
Injector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
if (injector instanceof NettyChannelInjector) {
|
||||
return (NettyChannelInjector) injector;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -250,11 +247,12 @@ public class InjectionFactory {
|
||||
* @return The network manager.
|
||||
*/
|
||||
private Object findNetworkManager(Channel channel) {
|
||||
// Find the network manager
|
||||
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
|
||||
MinecraftReflection.getNetworkManagerClass());
|
||||
if (networkManager != null) {
|
||||
return networkManager;
|
||||
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
|
||||
|
||||
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
|
||||
if (networkManagerClass.isAssignableFrom(entry.getValue().getClass())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
|
||||
|
||||
+373
-327
@@ -2,94 +2,93 @@ package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerManager;
|
||||
import com.comphenix.protocol.injector.NetworkProcessor;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.utility.*;
|
||||
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NettyChannelInjector implements Injector {
|
||||
|
||||
// an accessor used when we're unable to retrieve the actual packet field in an outbound packet send
|
||||
private static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
|
||||
@Override
|
||||
public Object get(Object instance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Object instance, Object value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field getField() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private static final String INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
|
||||
private static final String INBOUND_INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
|
||||
private static final String INBOUND_PROTOCOL_GETTER_NAME = "protocol_lib_inbound_protocol_getter";
|
||||
private static final String WIRE_PACKET_ENCODER_NAME = "protocol_lib_wire_packet_encoder";
|
||||
|
||||
// all registered channel handlers to easier make sure we unregister them all from the pipeline
|
||||
private static final String[] PROTOCOL_LIB_HANDLERS = new String[]{
|
||||
WIRE_PACKET_ENCODER_NAME, INTERCEPTOR_NAME
|
||||
private static final String[] NETTY_HANDLER_NAMES = new String[]{
|
||||
WIRE_PACKET_ENCODER_NAME, INBOUND_INTERCEPTOR_NAME, INBOUND_PROTOCOL_GETTER_NAME
|
||||
};
|
||||
|
||||
private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
|
||||
private static final ReportType REPORT_CANNOT_SEND_WRITE_PACKET = new ReportType("Unable to send wire packet %s to %s");
|
||||
private static final ReportType REPORT_CANNOT_READ_PACKET = new ReportType("Unable to read packet %s for %s");
|
||||
private static final ReportType REPORT_CANNOT_DISCONNECT = new ReportType("Unable to disconnect %s for %s");
|
||||
|
||||
private static final WirePacketEncoder WIRE_PACKET_ENCODER = new WirePacketEncoder();
|
||||
private static final Map<Class<?>, FieldAccessor> PACKET_ACCESSORS = new ConcurrentHashMap<>(16, 0.9f);
|
||||
|
||||
private static final Class<?> LOGIN_PACKET_START_CLASS = PacketType.Login.Client.START.getPacketClass();
|
||||
private static final Class<?> PACKET_PROTOCOL_CLASS = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass();
|
||||
|
||||
// use random attribute name because they need to be unique and would throw on reload
|
||||
private static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey());
|
||||
private static final AttributeKey<NettyChannelInjector> INJECTOR = AttributeKey.valueOf(getRandomKey());
|
||||
|
||||
// lazy initialized fields, if we don't need them we don't bother about them
|
||||
private static FieldAccessor LOGIN_PROFILE_ACCESSOR;
|
||||
private static FieldAccessor PROTOCOL_VERSION_ACCESSOR;
|
||||
static NettyChannelInjector findInjector(Channel channel) {
|
||||
return channel.attr(INJECTOR).get();
|
||||
}
|
||||
|
||||
// bukkit stuff
|
||||
private final Server server;
|
||||
private static String getRandomKey() {
|
||||
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
|
||||
}
|
||||
|
||||
// protocol lib stuff we need
|
||||
private final ErrorReporter errorReporter;
|
||||
private final InjectionFactory injectionFactory;
|
||||
private final ListenerManager listenerManager;
|
||||
private final NetworkProcessor networkProcessor;
|
||||
private final PacketListenerInvoker listenerInvoker;
|
||||
|
||||
// references
|
||||
private final Object networkManager;
|
||||
private final Channel wrappedChannel;
|
||||
private final ChannelListener channelListener;
|
||||
private final InjectionFactory injectionFactory;
|
||||
private final Channel channel;
|
||||
|
||||
private final FieldAccessor channelField;
|
||||
|
||||
@@ -99,45 +98,49 @@ public class NettyChannelInjector implements Injector {
|
||||
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
|
||||
|
||||
// status of this injector
|
||||
private volatile boolean closed = false;
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
private volatile boolean injected = false;
|
||||
|
||||
// information about the player belonging to this injector
|
||||
private String playerName;
|
||||
private Player resolvedPlayer;
|
||||
private UUID playerUniqueId;
|
||||
private Player player;
|
||||
|
||||
// lazy initialized fields, if we don't need them we don't bother about them
|
||||
private Object playerConnection;
|
||||
private volatile InboundProtocolReader inboundProtocolReader;
|
||||
|
||||
public NettyChannelInjector(
|
||||
Player player,
|
||||
Server server,
|
||||
Object netManager,
|
||||
Object networkManager,
|
||||
Channel channel,
|
||||
ChannelListener listener,
|
||||
ListenerManager listenerManager,
|
||||
InjectionFactory injector,
|
||||
ErrorReporter errorReporter
|
||||
) {
|
||||
// bukkit stuff
|
||||
this.server = server;
|
||||
this.resolvedPlayer = player;
|
||||
this.player = player;
|
||||
if (player != null && !(player instanceof TemporaryPlayer)) {
|
||||
this.playerName = player.getName();
|
||||
this.playerUniqueId = player.getUniqueId();
|
||||
}
|
||||
|
||||
// protocol lib stuff
|
||||
this.errorReporter = errorReporter;
|
||||
this.networkProcessor = new NetworkProcessor(errorReporter);
|
||||
this.listenerInvoker = new PacketListenerInvoker(networkManager);
|
||||
|
||||
// references
|
||||
this.networkManager = netManager;
|
||||
this.wrappedChannel = channel;
|
||||
this.channelListener = listener;
|
||||
this.networkManager = networkManager;
|
||||
this.channel = channel;
|
||||
this.listenerManager = listenerManager;
|
||||
this.injectionFactory = injector;
|
||||
|
||||
// register us into the channel
|
||||
this.wrappedChannel.attr(INJECTOR).set(this);
|
||||
this.channel.attr(INJECTOR).set(this);
|
||||
|
||||
// read the channel field from the network manager given to this method
|
||||
// we re-read this field every time as plugins/spigot forks might give us different network manager types
|
||||
Field channelField = FuzzyReflection.fromObject(netManager, true).getField(FuzzyFieldContract.newBuilder()
|
||||
Field channelField = FuzzyReflection.fromObject(networkManager, true).getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(Channel.class)
|
||||
.banModifier(Modifier.STATIC)
|
||||
.build());
|
||||
@@ -146,112 +149,110 @@ public class NettyChannelInjector implements Injector {
|
||||
// hook here into the close future to be 100% sure that this injector gets closed when the channel we wrap gets closed
|
||||
// normally we listen to the disconnect event, but there is a very small period of time, between the login and actual
|
||||
// join that is not covered by the disconnect event and may lead to unexpected injector states...
|
||||
this.wrappedChannel.closeFuture().addListener(future -> this.close());
|
||||
this.channel.closeFuture().addListener(future -> this.close());
|
||||
}
|
||||
|
||||
static NettyChannelInjector findInjector(Channel channel) {
|
||||
return channel.attr(INJECTOR).get();
|
||||
}
|
||||
|
||||
static Object findChannelHandler(Channel channel, Class<?> type) {
|
||||
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
|
||||
if (type.isAssignableFrom(entry.getValue().getClass())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getRandomKey() {
|
||||
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
|
||||
}
|
||||
|
||||
private static boolean hasProtocolLibHandler(Channel channel) {
|
||||
for (String handler : PROTOCOL_LIB_HANDLERS) {
|
||||
if (channel.pipeline().get(handler) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@Override
|
||||
public SocketAddress getAddress() {
|
||||
return this.channel.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
Integer protocolVersion = this.wrappedChannel.attr(PROTOCOL_VERSION).get();
|
||||
Integer protocolVersion = this.channel.attr(PROTOCOL_VERSION).get();
|
||||
return protocolVersion == null ? MinecraftProtocolVersion.getCurrentVersion() : protocolVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inject() {
|
||||
// we only do this on the channel event loop to prevent blocking the main server thread
|
||||
// and to be sure that the netty pipeline view we get is up-to-date
|
||||
if (this.wrappedChannel.eventLoop().inEventLoop()) {
|
||||
// ensure that we should actually inject into the channel
|
||||
if (this.closed || this.wrappedChannel instanceof ByteBuddyGenerated || !this.wrappedChannel.isActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check here if we need to rewrite the channel field and do so
|
||||
// minecraft overrides the channel field when the channel actually becomes active, so we need to ensure that our
|
||||
// proxied channel is always on that field - therefore this rewrite is event before we check if we're already
|
||||
// injected into the channel
|
||||
this.rewriteChannelField();
|
||||
|
||||
// check if we already injected into the channel
|
||||
if (hasProtocolLibHandler(this.wrappedChannel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// inject our handlers
|
||||
this.wrappedChannel.pipeline().addAfter("encoder", WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER);
|
||||
this.wrappedChannel.pipeline().addAfter(
|
||||
"decoder",
|
||||
INTERCEPTOR_NAME,
|
||||
new InboundPacketInterceptor(this, this.channelListener));
|
||||
|
||||
this.injected = true;
|
||||
return true;
|
||||
} else {
|
||||
// re-run in event loop, return false as we cannot be sure if the injection actually worked
|
||||
this.ensureInEventLoop(this::inject);
|
||||
return false;
|
||||
public void inject() {
|
||||
// ensure we are in the channel's event loop to be "thread-safe" within netty
|
||||
// and our own code
|
||||
if (!this.channel.eventLoop().inEventLoop()) {
|
||||
channel.eventLoop().execute(this::inject);
|
||||
return;
|
||||
}
|
||||
|
||||
// don't need to inject if injector or channel got closed
|
||||
if (this.closed.get() || !this.channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// wrap channel inside the NetworkMananger to proxy write calls to our
|
||||
// processOutbound method, this way we try to minimize the amount of thread
|
||||
// jumps from event loop to minecraft main thread in order to process packets in
|
||||
// main thread
|
||||
Object networkManangerChannel = this.channelField.get(this.networkManager);
|
||||
if (!(networkManangerChannel instanceof NettyChannelProxy)) {
|
||||
EventLoop proxyEventLoop = new NettyEventLoopProxy(this.channel.eventLoop(), this);
|
||||
Channel proxyChannel = new NettyChannelProxy(this.channel, proxyEventLoop, this);
|
||||
this.channelField.set(this.networkManager, proxyChannel);
|
||||
}
|
||||
|
||||
ChannelPipeline pipeline = this.channel.pipeline();
|
||||
|
||||
// since 1.20.5 the en-/decoder is renamed to out-/inbound_config when the
|
||||
// channel is waiting for the next protocol phase (after terminal packet)
|
||||
String encoderName = pipeline.get("outbound_config") != null
|
||||
? "outbound_config"
|
||||
: "encoder";
|
||||
String decoderName = pipeline.get("inbound_config") != null
|
||||
? "inbound_config"
|
||||
: "decoder";
|
||||
|
||||
// try to add wire packet encoder
|
||||
if (pipeline.context(WIRE_PACKET_ENCODER_NAME) == null) {
|
||||
pipeline.addAfter(encoderName, WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER);
|
||||
}
|
||||
|
||||
// try to add protocol reader, this is necessary because the en-/decoder will
|
||||
// remove or reconfigure the protocol for terminal since 1.20.2+
|
||||
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove() && pipeline.context(INBOUND_PROTOCOL_GETTER_NAME) == null) {
|
||||
this.inboundProtocolReader = new InboundProtocolReader(this);
|
||||
pipeline.addBefore(decoderName, INBOUND_PROTOCOL_GETTER_NAME, this.inboundProtocolReader);
|
||||
}
|
||||
|
||||
// try to add inbound packet interceptor
|
||||
if (pipeline.context(INBOUND_INTERCEPTOR_NAME) == null) {
|
||||
pipeline.addAfter(decoderName, INBOUND_INTERCEPTOR_NAME, new InboundPacketInterceptor(this));
|
||||
}
|
||||
|
||||
// mark injector as injected
|
||||
this.injected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninject() {
|
||||
// ensure that we injected into the channel before trying to remove anything from it
|
||||
if (this.injected) {
|
||||
// uninject on the event loop to ensure the instant visibility of the change and prevent blocks of other threads
|
||||
if (this.wrappedChannel.eventLoop().inEventLoop()) {
|
||||
this.injected = false;
|
||||
private void uninject() {
|
||||
// ensure we are in the channel's event loop to be "thread-safe" within netty
|
||||
// and our own code
|
||||
if (!this.channel.eventLoop().inEventLoop()) {
|
||||
channel.eventLoop().execute(this::uninject);
|
||||
return;
|
||||
}
|
||||
|
||||
// remove known references to us
|
||||
this.wrappedChannel.attr(INJECTOR).remove();
|
||||
this.channelField.set(this.networkManager, this.wrappedChannel);
|
||||
// replace wrapped channel in NetworkManager with original channel
|
||||
this.channelField.set(this.networkManager, this.channel);
|
||||
|
||||
for (String handler : PROTOCOL_LIB_HANDLERS) {
|
||||
try {
|
||||
this.wrappedChannel.pipeline().remove(handler);
|
||||
} catch (NoSuchElementException ignored) {
|
||||
// ignore that one, probably an edge case
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.ensureInEventLoop(this::uninject);
|
||||
// remove all ProtocolLib netty handler
|
||||
ChannelPipeline pipeline = this.channel.pipeline();
|
||||
for (String handlerName : NETTY_HANDLER_NAMES) {
|
||||
if (pipeline.context(handlerName) != null) {
|
||||
pipeline.remove(handlerName);
|
||||
}
|
||||
}
|
||||
|
||||
// mark injector as uninjected
|
||||
this.injected = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// ensure that the injector wasn't close before
|
||||
if (!this.closed) {
|
||||
this.closed = true;
|
||||
|
||||
if (this.closed.compareAndSet(false, true)) {
|
||||
// remove all of our references from the channel
|
||||
this.uninject();
|
||||
|
||||
// remove any outgoing references
|
||||
this.channel.attr(INJECTOR).set(null);
|
||||
|
||||
// cleanup
|
||||
this.savedMarkers.clear();
|
||||
this.skippedPackets.clear();
|
||||
@@ -262,9 +263,9 @@ public class NettyChannelInjector implements Injector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
// do not send the packet if this injector was already closed / is not injected yet
|
||||
if (this.closed || !this.injected) {
|
||||
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
// ignore call if the injector is closed or not injected
|
||||
if (this.closed.get() || !this.injected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -274,17 +275,12 @@ public class NettyChannelInjector implements Injector {
|
||||
}
|
||||
|
||||
// save the given packet marker and send the packet
|
||||
this.saveMarker(packet, marker);
|
||||
if (marker != null) {
|
||||
this.savedMarkers.put(packet, marker);
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.resolvedPlayer instanceof ByteBuddyGenerated) {
|
||||
MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet);
|
||||
} else {
|
||||
// ensure that the player is properly connected before sending
|
||||
Object playerConnection = this.getPlayerConnection();
|
||||
if (playerConnection != null) {
|
||||
MinecraftMethods.getSendPacketMethod().invoke(playerConnection, packet);
|
||||
}
|
||||
}
|
||||
this.listenerInvoker.send(packet);
|
||||
} catch (Exception exception) {
|
||||
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET)
|
||||
.messageParam(packet, this.playerName)
|
||||
@@ -294,82 +290,110 @@ public class NettyChannelInjector implements Injector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveClientPacket(Object packet) {
|
||||
// do not do that if we're not injected or this injector was closed
|
||||
if (this.closed || !this.injected) {
|
||||
public void readServerboundPacket(Object packet) {
|
||||
// ignore call if the injector is closed or not injected
|
||||
if (this.closed.get() || !this.injected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ensureInEventLoop(() -> {
|
||||
try {
|
||||
this.channel.pipeline().context(INBOUND_INTERCEPTOR_NAME).fireChannelRead(packet);
|
||||
} catch (Exception exception) {
|
||||
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_PACKET)
|
||||
.messageParam(packet, this.playerName)
|
||||
.error(exception)
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendWirePacket(WirePacket packet) {
|
||||
// ignore call if the injector is closed or not injected
|
||||
if (this.closed.get() || !this.injected) {
|
||||
return;
|
||||
}
|
||||
|
||||
Runnable receiveAction = () -> {
|
||||
this.ensureInEventLoop(() -> {
|
||||
try {
|
||||
// try to invoke the method, this should normally not fail
|
||||
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet);
|
||||
this.channel.writeAndFlush(packet);
|
||||
} catch (Exception exception) {
|
||||
// 99% the user gave wrong information to the server
|
||||
this.errorReporter.reportMinimal(this.injectionFactory.getPlugin(), "receiveClientPacket", exception);
|
||||
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_WRITE_PACKET)
|
||||
.messageParam(packet, this.playerName)
|
||||
.error(exception)
|
||||
.build());
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// execute the action on the event loop rather than any thread which we should potentially not block
|
||||
if (this.wrappedChannel.eventLoop().inEventLoop()) {
|
||||
receiveAction.run();
|
||||
} else {
|
||||
this.ensureInEventLoop(receiveAction);
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
// ignore call if the injector is closed or not injected
|
||||
if (this.closed.get() || !this.injected) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.listenerInvoker.disconnect(message);
|
||||
} catch (Exception exception) {
|
||||
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DISCONNECT)
|
||||
.messageParam(this.playerName, message)
|
||||
.error(exception)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getCurrentProtocol(PacketType.Sender sender) {
|
||||
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.wrappedChannel, sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkMarker getMarker(Object packet) {
|
||||
return this.savedMarkers.get(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||
if (marker != null && !this.closed) {
|
||||
this.savedMarkers.put(packet, marker);
|
||||
}
|
||||
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.channel, sender);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
// if the player was already resolved there is no need to do further lookups
|
||||
if (this.resolvedPlayer != null) {
|
||||
return this.resolvedPlayer;
|
||||
if (this.player != null && !(this.player instanceof TemporaryPlayer)) {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
// check if the name of the player is already known to the injector
|
||||
if (this.playerName != null) {
|
||||
this.resolvedPlayer = this.server.getPlayerExact(this.playerName);
|
||||
Player bukkitPlayer = Bukkit.getPlayerExact(this.playerName);
|
||||
if (bukkitPlayer != null) {
|
||||
this.player = bukkitPlayer;
|
||||
this.injectionFactory.cacheInjector(this.player, this);
|
||||
}
|
||||
}
|
||||
|
||||
// either we resolved it or we didn't...
|
||||
return this.resolvedPlayer;
|
||||
return this.player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerName() {
|
||||
return this.playerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getPlayerUniqueId() {
|
||||
return this.playerUniqueId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayer(Player player) {
|
||||
this.resolvedPlayer = player;
|
||||
this.injectionFactory.invalidate(this.player, this.playerName);
|
||||
|
||||
this.player = player;
|
||||
this.playerName = player.getName();
|
||||
this.playerUniqueId = player.getUniqueId();
|
||||
|
||||
this.injectionFactory.cacheInjector(player, this);
|
||||
this.injectionFactory.cacheInjector(player.getName(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
// we're still during pre-login, just close the connection
|
||||
if (this.playerConnection == null || this.resolvedPlayer instanceof ByteBuddyGenerated) {
|
||||
this.wrappedChannel.disconnect();
|
||||
} else {
|
||||
try {
|
||||
// try to call the disconnect method on the player
|
||||
MinecraftMethods.getDisconnectMethod(this.playerConnection.getClass()).invoke(this.playerConnection, message);
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalArgumentException("Unable to disconnect the current injector", exception);
|
||||
}
|
||||
}
|
||||
public boolean isConnected() {
|
||||
return this.channel.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -379,138 +403,118 @@ public class NettyChannelInjector implements Injector {
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return this.closed;
|
||||
return this.closed.get();
|
||||
}
|
||||
|
||||
void tryProcessLogin(Object packet) {
|
||||
// check if the given packet is a login packet
|
||||
if (LOGIN_PACKET_START_CLASS != null && LOGIN_PACKET_START_CLASS.equals(packet.getClass())) {
|
||||
PacketType.Protocol getInboundProtocol() {
|
||||
if (this.inboundProtocolReader != null) {
|
||||
return this.inboundProtocolReader.getProtocol();
|
||||
}
|
||||
return getCurrentProtocol(PacketType.Sender.CLIENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any plugin registed listeners or built-in listeners for the
|
||||
* given packet type exist. See {@link #processInbound(ChannelHandlerContext, PacketContainer)}
|
||||
*
|
||||
* @param type the packet type
|
||||
* @return true if the packet type has listeners; otherwise false
|
||||
*/
|
||||
boolean hasInboundListener(PacketType type) {
|
||||
// always return true for types of the built-in listener (see #processInbound)
|
||||
return type == PacketType.Handshake.Client.SET_PROTOCOL ||
|
||||
type == PacketType.Login.Client.START ||
|
||||
// check for plugin registed listener
|
||||
this.listenerManager.hasInboundListener(type);
|
||||
}
|
||||
|
||||
void processInbound(ChannelHandlerContext ctx, PacketContainer packet) {
|
||||
// process set protocol packets for the protocol version
|
||||
if (packet.getType() == PacketType.Handshake.Client.SET_PROTOCOL) {
|
||||
Integer protocolVersion = packet.getIntegers().readSafely(0);
|
||||
if (protocolVersion != null) {
|
||||
this.channel.attr(PROTOCOL_VERSION).set(protocolVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// process login packets for the player name
|
||||
if (packet.getType() == PacketType.Login.Client.START) {
|
||||
String username;
|
||||
UUID uniqueId;
|
||||
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
|
||||
// 1.19 removed the profile from the packet and now sends the plain username directly
|
||||
// ensure that the game profile accessor is available
|
||||
if (LOGIN_PROFILE_ACCESSOR == null) {
|
||||
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(LOGIN_PACKET_START_CLASS, String.class, true);
|
||||
}
|
||||
// 1.19 replaced the gameprofile with username and uuid field
|
||||
username = packet.getStrings().readSafely(0);
|
||||
uniqueId = readLoginUniqueId(packet);
|
||||
} else {
|
||||
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
|
||||
username = profile != null ? profile.getName() : null;
|
||||
uniqueId = profile != null ? profile.getUUID() : null;
|
||||
}
|
||||
|
||||
// get the username from the packet
|
||||
String username = (String) LOGIN_PROFILE_ACCESSOR.get(packet);
|
||||
this.playerUniqueId = uniqueId;
|
||||
|
||||
// cache the injector and the player name
|
||||
if (username != null) {
|
||||
this.playerName = username;
|
||||
this.injectionFactory.cacheInjector(username, this);
|
||||
} else {
|
||||
// ensure that the game profile accessor is available
|
||||
if (LOGIN_PROFILE_ACCESSOR == null) {
|
||||
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(
|
||||
LOGIN_PACKET_START_CLASS,
|
||||
MinecraftReflection.getGameProfileClass(),
|
||||
true);
|
||||
}
|
||||
|
||||
// the client only sends the name but the server wraps it into a GameProfile, so here we are
|
||||
WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_PROFILE_ACCESSOR.get(packet));
|
||||
|
||||
// cache the injector and the player name
|
||||
this.playerName = profile.getName();
|
||||
this.injectionFactory.cacheInjector(profile.getName(), this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// protocol version begin
|
||||
if (PACKET_PROTOCOL_CLASS != null && PACKET_PROTOCOL_CLASS.equals(packet.getClass())) {
|
||||
// ensure the protocol version accessor is available
|
||||
if (PROTOCOL_VERSION_ACCESSOR == null) {
|
||||
try {
|
||||
Field ver = FuzzyReflection.fromClass(PACKET_PROTOCOL_CLASS, true).getField(FuzzyFieldContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.typeExact(int.class)
|
||||
.build());
|
||||
PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
// unable to resolve that field, continue no-op
|
||||
PROTOCOL_VERSION_ACCESSOR = NO_OP_ACCESSOR;
|
||||
}
|
||||
}
|
||||
|
||||
// read the protocol version from the field if available
|
||||
if (PROTOCOL_VERSION_ACCESSOR != NO_OP_ACCESSOR) {
|
||||
int protocolVersion = (int) PROTOCOL_VERSION_ACCESSOR.get(packet);
|
||||
this.wrappedChannel.attr(PROTOCOL_VERSION).set(protocolVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rewriteChannelField() {
|
||||
// check if we need to rewrite the channel or if the channel is already correct (prevent wrapping a wrapped channel)
|
||||
Object currentChannel = this.channelField.get(this.networkManager);
|
||||
if (currentChannel instanceof NettyChannelProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
// the field is not correct, rewrite now to our handler
|
||||
Channel ch = new NettyChannelProxy(this.wrappedChannel, new NettyEventLoopProxy(this.wrappedChannel.eventLoop(), this) {
|
||||
@Override
|
||||
protected Runnable doProxyRunnable(Runnable original) {
|
||||
return NettyChannelInjector.this.processOutbound(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> Callable<T> doProxyCallable(Callable<T> original) {
|
||||
return NettyChannelInjector.this.processOutbound(original);
|
||||
}
|
||||
}, this);
|
||||
this.channelField.set(this.networkManager, ch);
|
||||
}
|
||||
|
||||
private void ensureInEventLoop(Runnable runnable) {
|
||||
this.ensureInEventLoop(this.wrappedChannel.eventLoop(), runnable);
|
||||
}
|
||||
|
||||
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
|
||||
if (eventLoop.inEventLoop()) {
|
||||
runnable.run();
|
||||
// invoke plugin listeners
|
||||
if (this.listenerManager.hasInboundListener(packet.getType())) {
|
||||
this.processInboundInternal(ctx, packet);
|
||||
} else {
|
||||
eventLoop.execute(runnable);
|
||||
ctx.fireChannelRead(packet.getHandle());
|
||||
}
|
||||
}
|
||||
|
||||
void processInboundPacket(ChannelHandlerContext ctx, Object packet, Class<?> packetClass) {
|
||||
if (this.channelListener.hasMainThreadListener(packetClass) && !this.server.isPrimaryThread()) {
|
||||
// not on the main thread but we are required to be - re-schedule the packet on the main thread
|
||||
ProtocolLibrary.getScheduler().runTask(
|
||||
() -> this.processInboundPacket(ctx, packet, packetClass));
|
||||
private static UUID readLoginUniqueId(PacketContainer packet) {
|
||||
UUID uniqueId = packet.getUUIDs().readSafely(0);
|
||||
if (uniqueId != null) {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
StructureModifier<Optional> optionals = packet.getModifier().withType(Optional.class);
|
||||
for (int i = 0; i < optionals.size(); i++) {
|
||||
Optional<?> optional = optionals.readSafely(i);
|
||||
if (optional != null && optional.orElse(null) instanceof UUID optionalUniqueId) {
|
||||
return optionalUniqueId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processInboundInternal(ChannelHandlerContext ctx, PacketContainer packetContainer) {
|
||||
if (this.listenerManager.hasMainThreadListener(packetContainer.getType()) && !Bukkit.isPrimaryThread()) {
|
||||
// not on the main thread but we are required to be reschedule the packet on the
|
||||
// main thread
|
||||
ProtocolLibrary.getScheduler().runTask(() -> this.processInboundInternal(ctx, packetContainer));
|
||||
return;
|
||||
}
|
||||
|
||||
// call packet handlers, a null result indicates that we shouldn't change anything
|
||||
PacketEvent interceptionResult = this.channelListener.onPacketReceiving(this, packet, null);
|
||||
if (interceptionResult == null) {
|
||||
// create event and invoke listeners
|
||||
PacketEvent event = PacketEvent.fromClient(this, packetContainer, this.player);
|
||||
this.listenerManager.invokeInboundPacketListeners(event);
|
||||
|
||||
// get packet of event
|
||||
Object packet = event.getPacket().getHandle();
|
||||
|
||||
// fire the intercepted packet down the pipeline if it wasn't cancelled and isn't null
|
||||
if (!event.isCancelled() && packet != null) {
|
||||
this.ensureInEventLoop(ctx.channel().eventLoop(), () -> ctx.fireChannelRead(packet));
|
||||
return;
|
||||
}
|
||||
|
||||
// fire the intercepted packet down the pipeline if it wasn't cancelled
|
||||
if (!interceptionResult.isCancelled()) {
|
||||
this.ensureInEventLoop(
|
||||
ctx.channel().eventLoop(),
|
||||
() -> ctx.fireChannelRead(interceptionResult.getPacket().getHandle()));
|
||||
|
||||
// check if there were any post events added the packet after we fired it down the pipeline
|
||||
// we use this way as we don't want to construct a new network manager accidentally
|
||||
NetworkMarker marker = NetworkMarker.getNetworkMarker(interceptionResult);
|
||||
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
||||
if (marker != null) {
|
||||
this.networkProcessor.invokePostEvent(interceptionResult, marker);
|
||||
this.networkProcessor.invokePostEvent(event, marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<T> T processOutbound(T action) {
|
||||
// get the accessor to the packet field
|
||||
// if we are unable to look up the accessor then just return the runnable, probably nothing of our business
|
||||
// try to get packet field accessor or return on failure
|
||||
FieldAccessor packetAccessor = this.lookupPacketAccessor(action);
|
||||
if (packetAccessor == NO_OP_ACCESSOR) {
|
||||
if (packetAccessor == FieldAccessor.NO_OP_ACCESSOR) {
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -535,30 +539,42 @@ public class NettyChannelInjector implements Injector {
|
||||
return action;
|
||||
}
|
||||
|
||||
// no listener and no marker - no magic :)
|
||||
if (!this.channelListener.hasListener(packet.getClass()) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
|
||||
PacketType.Protocol protocol = this.getCurrentProtocol(PacketType.Sender.SERVER);
|
||||
if (protocol == Protocol.UNKNOWN) {
|
||||
ProtocolLogger.debug("skipping unknown outbound protocol for {0}", packet.getClass());
|
||||
return action;
|
||||
}
|
||||
|
||||
PacketType packetType = PacketRegistry.getPacketType(protocol, packet.getClass());
|
||||
if (packetType == null) {
|
||||
ProtocolLogger.debug("skipping unknown outbound packet type for {0}", packet.getClass());
|
||||
return action;
|
||||
}
|
||||
|
||||
// no listener and no marker - no magic :)
|
||||
if (!this.listenerManager.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
|
||||
return action;
|
||||
}
|
||||
|
||||
PacketContainer packetContainer = new PacketContainer(packetType, packet);
|
||||
|
||||
// ensure that we are on the main thread if we need to
|
||||
if (this.channelListener.hasMainThreadListener(packet.getClass()) && !this.server.isPrimaryThread()) {
|
||||
if (!Bukkit.isPrimaryThread() && hasMainThreadListener(this.listenerManager, protocol, packetContainer)) {
|
||||
// not on the main thread but we are required to be - re-schedule the packet on the main thread
|
||||
ProtocolLibrary.getScheduler().runTask(
|
||||
() -> this.sendServerPacket(packet, null, true));
|
||||
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, marker, true));
|
||||
return null;
|
||||
}
|
||||
|
||||
// call all listeners which are listening to the outbound packet, if any
|
||||
// null indicates that no listener was affected by the packet, meaning that we can directly send the original packet
|
||||
PacketEvent event = this.channelListener.onPacketSending(this, packet, marker);
|
||||
if (event == null) {
|
||||
return action;
|
||||
}
|
||||
// create event and invoke listeners
|
||||
PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player);
|
||||
this.listenerManager.invokeOutboundPacketListeners(event);
|
||||
|
||||
// get packet of event
|
||||
Object interceptedPacket = event.getPacket().getHandle();
|
||||
|
||||
// if the event wasn't cancelled by this action we must recheck if the packet changed during the method call
|
||||
if (!event.isCancelled()) {
|
||||
if (!event.isCancelled() && interceptedPacket != null) {
|
||||
// rewrite the packet in the given action if the packet was changed during the event call
|
||||
Object interceptedPacket = event.getPacket().getHandle();
|
||||
if (interceptedPacket != packet) {
|
||||
packetAccessor.set(action, interceptedPacket);
|
||||
}
|
||||
@@ -579,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
|
||||
@@ -608,37 +662,29 @@ public class NettyChannelInjector implements Injector {
|
||||
}
|
||||
|
||||
private FieldAccessor lookupPacketAccessor(Object action) {
|
||||
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> {
|
||||
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), key -> {
|
||||
try {
|
||||
// find the field
|
||||
Field packetField = FuzzyReflection.fromClass(clazz, true).getField(FuzzyFieldContract
|
||||
.newBuilder()
|
||||
Field packetField = FuzzyReflection.fromClass(key, true).getField(
|
||||
FuzzyFieldContract.newBuilder()
|
||||
.typeSuperOf(MinecraftReflection.getPacketClass())
|
||||
.build());
|
||||
return Accessors.getFieldAccessor(packetField);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
// no such field found :(
|
||||
return NO_OP_ACCESSOR;
|
||||
// return noop accessor because computeIfAbsent interprets null as a missing key
|
||||
return FieldAccessor.NO_OP_ACCESSOR;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Object getPlayerConnection() {
|
||||
// resolve the player connection if needed
|
||||
if (this.playerConnection == null) {
|
||||
Player target = this.getPlayer();
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.playerConnection = MinecraftFields.getPlayerConnection(target);
|
||||
}
|
||||
|
||||
// cannot be null at this point
|
||||
return this.playerConnection;
|
||||
private void ensureInEventLoop(Runnable runnable) {
|
||||
this.ensureInEventLoop(this.channel.eventLoop(), runnable);
|
||||
}
|
||||
|
||||
public Channel getWrappedChannel() {
|
||||
return this.wrappedChannel;
|
||||
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
|
||||
if (eventLoop.inEventLoop()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
eventLoop.execute(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.injector.temporary.MinimalInjector;
|
||||
import java.net.SocketAddress;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
final class NettyChannelMinimalInjector implements MinimalInjector {
|
||||
|
||||
private final NettyChannelInjector injector;
|
||||
|
||||
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getAddress() {
|
||||
return this.injector.getWrappedChannel().remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
this.injector.disconnect(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
this.injector.sendServerPacket(packet, marker, filtered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return this.injector.getPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return this.injector.getWrappedChannel().isActive();
|
||||
}
|
||||
|
||||
public NettyChannelInjector getInjector() {
|
||||
return this.injector;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelConfig;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelId;
|
||||
import io.netty.channel.ChannelMetadata;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelProgressivePromise;
|
||||
@@ -33,6 +34,11 @@ final class NettyChannelProxy implements Channel {
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelId id() {
|
||||
return this.delegate.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
return this.eventLoop;
|
||||
@@ -238,6 +244,11 @@ final class NettyChannelProxy implements Channel {
|
||||
return this.delegate.attr(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean hasAttr(AttributeKey<T> attributeKey) {
|
||||
return this.delegate.hasAttr(attributeKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Channel o) {
|
||||
return this.delegate.compareTo(o);
|
||||
|
||||
+31
-33
@@ -1,15 +1,5 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ProgressivePromise;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.concurrent.ScheduledFuture;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -20,11 +10,24 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.netty.channel.AbstractEventLoop;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.util.concurrent.AbstractEventExecutor;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ProgressivePromise;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
import io.netty.util.concurrent.ScheduledFuture;
|
||||
|
||||
/**
|
||||
* An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which
|
||||
* schedule something on the event loop to methods which decide what should happen to the scheduled task.
|
||||
*/
|
||||
abstract class NettyEventLoopProxy implements EventLoop {
|
||||
final class NettyEventLoopProxy extends AbstractEventLoop {
|
||||
|
||||
private static final Callable<?> EMPTY_CALLABLE = () -> null;
|
||||
private static final Runnable EMPTY_RUNNABLE = () -> {
|
||||
@@ -40,7 +43,7 @@ abstract class NettyEventLoopProxy implements EventLoop {
|
||||
|
||||
private Runnable proxyRunnable(Runnable original) {
|
||||
// execute the proxy and check if we need to do anything
|
||||
Runnable proxied = this.doProxyRunnable(original);
|
||||
Runnable proxied = this.injector.processOutbound(original);
|
||||
if (proxied != null && proxied == original) {
|
||||
// was not changed, we need to mark the packet as processed manually
|
||||
return () -> {
|
||||
@@ -55,7 +58,7 @@ abstract class NettyEventLoopProxy implements EventLoop {
|
||||
|
||||
private <T> Callable<T> proxyCallable(Callable<T> original) {
|
||||
// execute the proxy and check if we need to do anything
|
||||
Callable<T> proxied = this.doProxyCallable(original);
|
||||
Callable<T> proxied = this.injector.processOutbound(original);
|
||||
if (proxied != null && proxied == original) {
|
||||
// was not changed, we need to mark the packet as processed manually
|
||||
return () -> {
|
||||
@@ -68,26 +71,6 @@ abstract class NettyEventLoopProxy implements EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies the given runnable. The returned runnable will be executed instead of the original. If this method returns
|
||||
* null a no-op runnable will be scheduled instead, preventing the original action from happening.
|
||||
*
|
||||
* @param original the runnable to proxy.
|
||||
* @return the runnable to execute instead, null to execute no action.
|
||||
*/
|
||||
protected abstract Runnable doProxyRunnable(Runnable original);
|
||||
|
||||
/**
|
||||
* Proxies the given callable. The returned callable will be executed instead of the original. If this method returns
|
||||
* null a callable which always returns null will be scheduled instead, preventing the original action from
|
||||
* happening.
|
||||
*
|
||||
* @param original the callable to proxy.
|
||||
* @param <T> the return type of the original callable.
|
||||
* @return the callable to execute instead of the original, null to use a no-op callable instead.
|
||||
*/
|
||||
protected abstract <T> Callable<T> doProxyCallable(Callable<T> original);
|
||||
|
||||
@Override
|
||||
public EventLoopGroup parent() {
|
||||
return this.delegate.parent();
|
||||
@@ -255,6 +238,15 @@ abstract class NettyEventLoopProxy implements EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
// ShreddedPaper uses this method
|
||||
@Override
|
||||
public void lazyExecute(Runnable command) {
|
||||
Runnable proxied = this.proxyRunnable(command);
|
||||
if (proxied != null) {
|
||||
((AbstractEventExecutor)this.delegate).lazyExecute(proxied);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super EventExecutor> action) {
|
||||
this.delegate.forEach(action);
|
||||
@@ -270,6 +262,12 @@ abstract class NettyEventLoopProxy implements EventLoop {
|
||||
return this.delegate.register(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(ChannelPromise channelPromise) {
|
||||
return this.delegate.register(channelPromise);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public ChannelFuture register(Channel channel, ChannelPromise promise) {
|
||||
return this.delegate.register(channel, promise);
|
||||
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package com.comphenix.protocol.injector.netty.channel;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
|
||||
/**
|
||||
* This class facilitates the invocation of methods on the current packet listener.
|
||||
* It attempts to execute the <code>send</code>, and <code>disconnect</code> methods, upon failure
|
||||
* (either due to the absence of the method or the packet listener being of an incorrect type),
|
||||
* it delegates the call to the network manager.
|
||||
*
|
||||
* <p>Supported packet listener types include CONFIGURATION and PLAY. If the packet
|
||||
* listener does not match these types, or if the required method is missing, the
|
||||
* operation falls back to similar methods available in the network manager.
|
||||
*
|
||||
* <p>It is important to note that this class does not handle exceptions internally.
|
||||
* Instead, it propagates them to the caller. During the initialization phase, the
|
||||
* class will throw an exception if the necessary methods in the network manager are
|
||||
* not available, ensuring that these dependencies are addressed early in the runtime.
|
||||
*/
|
||||
public class PacketListenerInvoker {
|
||||
|
||||
private static final Class<?> PACKET_LISTENER_CLASS = MinecraftReflection.getMinecraftClass("network.PacketListener", "PacketListener");
|
||||
private static final Class<?> GAME_PACKET_LISTENER_CLASS = MinecraftReflection.getPlayerConnectionClass();
|
||||
private static final Class<?> COMMON_PACKET_LISTENER_CLASS = MinecraftReflection.getNullableNMS("server.network.ServerCommonPacketListenerImpl");
|
||||
private static final Class<?> PREFERRED_PACKET_LISTENER_CLASS = COMMON_PACKET_LISTENER_CLASS != null ? COMMON_PACKET_LISTENER_CLASS : GAME_PACKET_LISTENER_CLASS;
|
||||
|
||||
private static final MethodAccessor PACKET_LISTENER_SEND = getPacketListenerSend();
|
||||
private static final MethodAccessor PACKET_LISTENER_DISCONNECT = getPacketListenerDisconnect();
|
||||
private static final boolean DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT = doesPacketListenerDisconnectUseComponent();
|
||||
|
||||
private static final MethodAccessor NETWORK_MANAGER_SEND = getNetworkManagerSend();
|
||||
private static final MethodAccessor NETWORK_MANAGER_DISCONNECT = getNetworkManagerDisconnect();
|
||||
private static final MethodAccessor NETWORK_MANAGER_PACKET_LISTENER = getNetworkManagerPacketListener();
|
||||
|
||||
public static void ensureStaticInitializedWithoutError() {
|
||||
}
|
||||
|
||||
private static MethodAccessor getPacketListenerSend() {
|
||||
FuzzyReflection packetListener = FuzzyReflection.fromClass(PREFERRED_PACKET_LISTENER_CLASS);
|
||||
|
||||
List<Method> send = packetListener.getMethodList(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.parameterCount(1)
|
||||
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
|
||||
.build());
|
||||
|
||||
if (send.isEmpty()) {
|
||||
ProtocolLogger.debug("Can't get packet listener send method");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Accessors.getMethodAccessor(send.get(0));
|
||||
}
|
||||
|
||||
private static MethodAccessor getPacketListenerDisconnect() {
|
||||
FuzzyReflection packetListener = FuzzyReflection.fromClass(PREFERRED_PACKET_LISTENER_CLASS);
|
||||
|
||||
List<Method> disconnect = packetListener.getMethodList(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.parameterCount(1)
|
||||
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
|
||||
.build());
|
||||
|
||||
if (disconnect.isEmpty()) {
|
||||
disconnect = packetListener.getMethodList(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.nameRegex("disconnect.*")
|
||||
.parameterCount(1)
|
||||
.parameterExactType(String.class, 0)
|
||||
.build());
|
||||
}
|
||||
|
||||
if (disconnect.isEmpty()) {
|
||||
ProtocolLogger.debug("Can't get packet listener disconnect method");
|
||||
return null;
|
||||
}
|
||||
|
||||
return Accessors.getMethodAccessor(disconnect.get(0));
|
||||
}
|
||||
|
||||
private static boolean doesPacketListenerDisconnectUseComponent() {
|
||||
if (PACKET_LISTENER_DISCONNECT != null) {
|
||||
Parameter reason = PACKET_LISTENER_DISCONNECT.getMethod().getParameters()[0];
|
||||
return MinecraftReflection.isIChatBaseComponent(reason.getClass());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static MethodAccessor getNetworkManagerSend() {
|
||||
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
|
||||
|
||||
Method send = networkManager.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.parameterCount(1)
|
||||
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
|
||||
.build());
|
||||
|
||||
return Accessors.getMethodAccessor(send);
|
||||
}
|
||||
|
||||
private static MethodAccessor getNetworkManagerDisconnect() {
|
||||
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
|
||||
|
||||
Method disconnect = networkManager.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.parameterCount(1)
|
||||
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
|
||||
.build());
|
||||
|
||||
return Accessors.getMethodAccessor(disconnect);
|
||||
}
|
||||
|
||||
private static MethodAccessor getNetworkManagerPacketListener() {
|
||||
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
|
||||
|
||||
Method packetListener = networkManager.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeExact(PACKET_LISTENER_CLASS)
|
||||
.parameterCount(0)
|
||||
.build());
|
||||
|
||||
return Accessors.getMethodAccessor(packetListener);
|
||||
}
|
||||
|
||||
private final Object networkManager;
|
||||
private final AtomicReference<Object> packetListener = new AtomicReference<>(null);
|
||||
|
||||
PacketListenerInvoker(Object networkManager) {
|
||||
if (!MinecraftReflection.is(MinecraftReflection.getNetworkManagerClass(), networkManager)) {
|
||||
throw new IllegalArgumentException("Given NetworkManager isn't an isntance of NetworkManager");
|
||||
}
|
||||
this.networkManager = networkManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current packet listener associated with this network manager.
|
||||
*
|
||||
* <p>This method ensures thread-safety and returns the packet listener only if it is an
|
||||
* instance of the preferred class. If the packet listener has changed or does not match
|
||||
* the preferred class, it returns {@code null}.
|
||||
*
|
||||
* @return the current packet listener if it meets the required criteria, otherwise {@code null}.
|
||||
*/
|
||||
private Object getPacketListener() {
|
||||
// Retrieve the current packet listener from the network manager using reflection.
|
||||
Object packetListener = NETWORK_MANAGER_PACKET_LISTENER.invoke(this.networkManager);
|
||||
|
||||
// Perform a thread-safe check to see if the packet listener has changed since the last retrieval.
|
||||
if (!this.packetListener.compareAndSet(packetListener, packetListener)) {
|
||||
// If the packet listener has changed, attempt to update the cached listener to the new instance,
|
||||
// or invalidate the cached object if it does not match the preferred type.
|
||||
if (PREFERRED_PACKET_LISTENER_CLASS.isInstance(packetListener)) {
|
||||
this.packetListener.set(packetListener);
|
||||
} else {
|
||||
this.packetListener.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the currently cached packet listener, which may be null if it does not match the preferred type.
|
||||
return this.packetListener.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet using the current packet listener if available and valid; otherwise,
|
||||
* falls back to the network manager.
|
||||
*
|
||||
* @param packet The packet to be sent.
|
||||
*/
|
||||
public void send(Object packet) {
|
||||
Object packetListener = this.getPacketListener();
|
||||
if (PACKET_LISTENER_SEND != null && packetListener != null) {
|
||||
PACKET_LISTENER_SEND.invoke(packetListener, packet);
|
||||
} else {
|
||||
NETWORK_MANAGER_SEND.invoke(this.networkManager, packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the player using the current packet listener if available and valid; otherwise,
|
||||
* falls back to the network manager.
|
||||
*
|
||||
* @param reason The reason for the disconnection.
|
||||
*/
|
||||
public void disconnect(String reason) {
|
||||
Object packetListener = this.getPacketListener();
|
||||
boolean hasPacketListener = PACKET_LISTENER_DISCONNECT != null && packetListener != null;
|
||||
|
||||
Object wrapped = reason;
|
||||
if (!hasPacketListener || DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT) {
|
||||
wrapped = WrappedChatComponent.fromText(reason).getHandle();
|
||||
}
|
||||
|
||||
if (hasPacketListener) {
|
||||
PACKET_LISTENER_DISCONNECT.invoke(packetListener, wrapped);
|
||||
} else {
|
||||
NETWORK_MANAGER_DISCONNECT.invoke(this.networkManager, wrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-14
@@ -1,9 +1,10 @@
|
||||
package com.comphenix.protocol.injector.netty.manager;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
@@ -11,18 +12,15 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
|
||||
|
||||
private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
|
||||
|
||||
private final InjectionFactory factory;
|
||||
private final NetworkManagerInjector listener;
|
||||
private final TemporaryPlayerFactory playerFactory;
|
||||
private final ErrorReporter errorReporter;
|
||||
private final InjectionFactory injectionFactory;
|
||||
|
||||
public InjectionChannelInboundHandler(
|
||||
InjectionFactory factory,
|
||||
NetworkManagerInjector listener,
|
||||
TemporaryPlayerFactory playerFactory
|
||||
ErrorReporter errorReporter,
|
||||
InjectionFactory injectionFactory
|
||||
) {
|
||||
this.factory = factory;
|
||||
this.listener = listener;
|
||||
this.playerFactory = playerFactory;
|
||||
this.errorReporter = errorReporter;
|
||||
this.injectionFactory = injectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,11 +33,11 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
|
||||
// We're first checking if the factory is still open, just might be a delay between accepting the connection
|
||||
// (which adds this handler to the pipeline) and the actual channelActive call. If the injector is closed at
|
||||
// that point we might accidentally trigger class loads which result in exceptions.
|
||||
if (!this.factory.isClosed()) {
|
||||
if (!this.injectionFactory.isClosed()) {
|
||||
try {
|
||||
this.factory.fromChannel(ctx.channel(), this.listener, this.playerFactory).inject();
|
||||
this.injectionFactory.fromChannel(ctx.channel()).inject();
|
||||
} catch (Exception exception) {
|
||||
this.listener.getReporter().reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
|
||||
this.errorReporter.reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
|
||||
.messageParam(ctx.channel())
|
||||
.error(exception)
|
||||
.build());
|
||||
@@ -52,7 +50,7 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
|
||||
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
// we do it this way to prevent the lookup overheat
|
||||
// we do it this way to prevent the lookup overhead
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ final class InjectionChannelInitializer extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isSharable() {
|
||||
// we do it this way to prevent the lookup overheat
|
||||
// we do it this way to prevent the lookup overhead
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
final class ListeningList implements List<Object> {
|
||||
|
||||
private final List<Object> original;
|
||||
|
||||
+57
-127
@@ -7,21 +7,15 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.ListenerManager;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.internal.PlatformProvider;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
@@ -29,136 +23,49 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.Pair;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class NetworkManagerInjector implements ChannelListener {
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
|
||||
public class NetworkManagerInjector {
|
||||
|
||||
private static final String INBOUND_INJECT_HANDLER_NAME = "protocol_lib_inbound_inject";
|
||||
private static final TemporaryPlayerFactory PLAYER_FACTORY = new TemporaryPlayerFactory();
|
||||
|
||||
private final PacketTypeSet inboundListeners = new PacketTypeSet();
|
||||
private final PacketTypeSet outboundListeners = new PacketTypeSet();
|
||||
private final PacketTypeSet mainThreadListeners = new PacketTypeSet();
|
||||
|
||||
// all list fields which we've overridden and need to revert to a non-proxying list afterwards
|
||||
private final Set<Pair<Object, FieldAccessor>> overriddenLists = new HashSet<>();
|
||||
|
||||
private final ErrorReporter errorReporter;
|
||||
private final ListenerInvoker listenerInvoker;
|
||||
private final InjectionFactory injectionFactory;
|
||||
|
||||
// injectors based on this "global" injector
|
||||
private final PacketInjector packetInjector;
|
||||
private final PlayerInjectionHandler playerInjectionHandler;
|
||||
|
||||
// netty handler
|
||||
private final InjectionChannelInboundHandler injectionHandler;
|
||||
private final InjectionChannelInitializer pipelineInjectorHandler;
|
||||
|
||||
private boolean debug = false;
|
||||
private Runnable paperChannelInitializerCleanup;
|
||||
|
||||
// status of this injector
|
||||
private boolean closed = false;
|
||||
private boolean injected = false;
|
||||
|
||||
public NetworkManagerInjector(Plugin plugin, Server server, ListenerInvoker listenerInvoker, ErrorReporter reporter) {
|
||||
public NetworkManagerInjector(Plugin plugin, ListenerManager listenerManager, ErrorReporter reporter) {
|
||||
this.errorReporter = reporter;
|
||||
this.listenerInvoker = listenerInvoker;
|
||||
this.injectionFactory = new InjectionFactory(plugin, server, reporter);
|
||||
this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager);
|
||||
|
||||
// hooking netty handlers
|
||||
InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler(
|
||||
this.injectionFactory,
|
||||
this,
|
||||
PLAYER_FACTORY);
|
||||
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
|
||||
|
||||
// other injectors
|
||||
this.playerInjectionHandler = new NetworkManagerPlayerInjector(
|
||||
this.outboundListeners,
|
||||
this,
|
||||
this.injectionFactory,
|
||||
this.mainThreadListeners);
|
||||
this.packetInjector = new NetworkManagerPacketInjector(
|
||||
this.inboundListeners,
|
||||
this.listenerInvoker,
|
||||
this,
|
||||
this.mainThreadListeners);
|
||||
this.injectionHandler = new InjectionChannelInboundHandler(
|
||||
this.errorReporter,
|
||||
this.injectionFactory);
|
||||
this.pipelineInjectorHandler = new InjectionChannelInitializer(
|
||||
INBOUND_INJECT_HANDLER_NAME,
|
||||
this.injectionHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker) {
|
||||
// check if we need to intercept the packet
|
||||
Class<?> packetClass = packet.getClass();
|
||||
if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || outboundListeners.contains(packetClass)) {
|
||||
// wrap packet and construct the event
|
||||
PacketType.Protocol currentProtocol = injector.getCurrentProtocol(PacketType.Sender.SERVER);
|
||||
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(currentProtocol, packetClass), packet);
|
||||
PacketEvent packetEvent = PacketEvent.fromServer(this, container, marker, injector.getPlayer());
|
||||
|
||||
// post to all listeners, then return the packet event we constructed
|
||||
this.listenerInvoker.invokePacketSending(packetEvent);
|
||||
return packetEvent;
|
||||
}
|
||||
|
||||
// no listener so there is no change we need to apply
|
||||
return null;
|
||||
public Injector getInjector(Player player) {
|
||||
return this.injectionFactory.fromPlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker) {
|
||||
// check if we need to intercept the packet
|
||||
Class<?> packetClass = packet.getClass();
|
||||
if (marker != null || inboundListeners.contains(packetClass)) {
|
||||
// wrap the packet and construct the event
|
||||
PacketType.Protocol currentProtocol = injector.getCurrentProtocol(PacketType.Sender.CLIENT);
|
||||
PacketType packetType = PacketRegistry.getPacketType(currentProtocol, packetClass);
|
||||
|
||||
// if packet type could not be found, fallback to HANDSHAKING protocol
|
||||
// temporary workaround for https://github.com/dmulloy2/ProtocolLib/issues/2601
|
||||
if (packetType == null) {
|
||||
packetType = PacketRegistry.getPacketType(PacketType.Protocol.HANDSHAKING, packetClass);
|
||||
}
|
||||
|
||||
PacketContainer container = new PacketContainer(packetType, packet);
|
||||
PacketEvent packetEvent = PacketEvent.fromClient(this, container, marker, injector.getPlayer());
|
||||
|
||||
// post to all listeners, then return the packet event we constructed
|
||||
this.listenerInvoker.invokePacketReceiving(packetEvent);
|
||||
return packetEvent;
|
||||
}
|
||||
|
||||
// no listener so there is no change we need to apply
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasListener(Class<?> packetClass) {
|
||||
return this.outboundListeners.contains(packetClass) || this.inboundListeners.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(Class<?> packetClass) {
|
||||
return this.mainThreadListeners.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType type) {
|
||||
return this.mainThreadListeners.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getReporter() {
|
||||
return this.errorReporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return this.debug;
|
||||
}
|
||||
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
public boolean isUsingPaperChannelInitializer() {
|
||||
return this.paperChannelInitializerCleanup != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -167,6 +74,13 @@ public class NetworkManagerInjector implements ChannelListener {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformProvider platformProvider = PlatformProvider.get();
|
||||
if (platformProvider.hasEarlyChannelInitialization()) {
|
||||
this.paperChannelInitializerCleanup = platformProvider.registerChannelInitializer(this::injectChannel);
|
||||
this.injected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// get all "server connections" defined in the minecraft server class
|
||||
FuzzyReflection server = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
|
||||
List<Method> serverConnectionGetter = server.getMethodList(FuzzyMethodContract.newBuilder()
|
||||
@@ -229,6 +143,23 @@ public class NetworkManagerInjector implements ChannelListener {
|
||||
this.injected = true;
|
||||
}
|
||||
|
||||
private void injectChannel(Channel channel) {
|
||||
addInjectionHandler(channel, MinecraftReflection.getNetworkManagerClass(), this.injectionHandler);
|
||||
}
|
||||
|
||||
static void addInjectionHandler(Channel channel, Class<?> networkManagerClass, ChannelHandler injectionHandler) {
|
||||
for (String handlerName : channel.pipeline().names()) {
|
||||
ChannelHandler handler = channel.pipeline().get(handlerName);
|
||||
if (networkManagerClass.isInstance(handler)) {
|
||||
channel.pipeline().addBefore(handlerName, INBOUND_INJECT_HANDLER_NAME, injectionHandler);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the existing behavior for forks with an unexpected pipeline layout.
|
||||
channel.pipeline().addLast(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this.closed || !this.injected) {
|
||||
return;
|
||||
@@ -257,14 +188,13 @@ public class NetworkManagerInjector implements ChannelListener {
|
||||
|
||||
// clear up
|
||||
this.overriddenLists.clear();
|
||||
this.injectionFactory.close();
|
||||
}
|
||||
|
||||
public PacketInjector getPacketInjector() {
|
||||
return this.packetInjector;
|
||||
}
|
||||
|
||||
public PlayerInjectionHandler getPlayerInjectionHandler() {
|
||||
return this.playerInjectionHandler;
|
||||
try {
|
||||
if (this.paperChannelInitializerCleanup != null) {
|
||||
this.paperChannelInitializerCleanup.run();
|
||||
}
|
||||
} finally {
|
||||
this.paperChannelInitializerCleanup = null;
|
||||
this.injectionFactory.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package com.comphenix.protocol.injector.netty.manager;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.packet.AbstractPacketInjector;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
final class NetworkManagerPacketInjector extends AbstractPacketInjector {
|
||||
|
||||
private final ListenerInvoker invoker;
|
||||
private final ChannelListener channelListener;
|
||||
private final PacketTypeSet mainThreadListeners;
|
||||
|
||||
public NetworkManagerPacketInjector(
|
||||
PacketTypeSet inboundFilters,
|
||||
ListenerInvoker invoker,
|
||||
ChannelListener listener,
|
||||
PacketTypeSet mainThreadListeners
|
||||
) {
|
||||
super(inboundFilters);
|
||||
|
||||
this.invoker = invoker;
|
||||
this.channelListener = listener;
|
||||
this.mainThreadListeners = mainThreadListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
if (!type.isAsyncForced() && options != null && options.contains(ListenerOptions.SYNC)) {
|
||||
this.mainThreadListeners.addType(type);
|
||||
}
|
||||
|
||||
return super.addPacketHandler(type, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removePacketHandler(PacketType type) {
|
||||
this.mainThreadListeners.removeType(type);
|
||||
return super.removePacketHandler(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent packetReceived(PacketContainer packet, Player client) {
|
||||
PacketEvent event = PacketEvent.fromClient(this.channelListener, packet, null, client);
|
||||
this.invoker.invokePacketReceiving(event);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType type) {
|
||||
return this.mainThreadListeners.contains(type);
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
package com.comphenix.protocol.injector.netty.manager;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.netty.ChannelListener;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
|
||||
import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector;
|
||||
import com.comphenix.protocol.injector.player.AbstractPlayerInjectionHandler;
|
||||
import io.netty.channel.Channel;
|
||||
import java.util.Set;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
final class NetworkManagerPlayerInjector extends AbstractPlayerInjectionHandler {
|
||||
|
||||
private final ChannelListener listener;
|
||||
private final InjectionFactory injectionFactory;
|
||||
private final PacketTypeSet mainThreadListeners;
|
||||
|
||||
public NetworkManagerPlayerInjector(
|
||||
PacketTypeSet outboundListener,
|
||||
ChannelListener listener,
|
||||
InjectionFactory injectionFactory,
|
||||
PacketTypeSet mainThreadListeners
|
||||
) {
|
||||
super(outboundListener);
|
||||
|
||||
this.listener = listener;
|
||||
this.injectionFactory = injectionFactory;
|
||||
this.mainThreadListeners = mainThreadListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion(Player player) {
|
||||
return this.injectionFactory.fromPlayer(player, this.listener).getProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectPlayer(Player player, ConflictStrategy strategy) {
|
||||
this.injectionFactory.fromPlayer(player, this.listener).inject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDisconnect(Player player) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
this.injectionFactory.fromPlayer(player, this.listener).uninject();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
|
||||
this.injectionFactory.fromPlayer(receiver, this.listener).sendServerPacket(packet.getHandle(), marker, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveClientPacket(Player player, Object mcPacket) {
|
||||
this.injectionFactory.fromPlayer(player, this.listener).receiveClientPacket(mcPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePlayer(Player player) {
|
||||
this.injectionFactory.fromPlayer(player, this.listener).inject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(PacketType type) {
|
||||
return this.mainThreadListeners.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel getChannel(Player player) {
|
||||
Injector injector = this.injectionFactory.fromPlayer(player, this.listener);
|
||||
if (injector instanceof NettyChannelInjector) {
|
||||
return ((NettyChannelInjector) injector).getWrappedChannel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
if (!type.isAsyncForced() && (options == null || !options.contains(ListenerOptions.ASYNC))) {
|
||||
this.mainThreadListeners.addType(type);
|
||||
}
|
||||
|
||||
super.addPacketHandler(type, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketHandler(PacketType type) {
|
||||
this.mainThreadListeners.removeType(type);
|
||||
super.removePacketHandler(type);
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class AbstractPacketInjector implements PacketInjector {
|
||||
|
||||
private final PacketTypeSet inboundFilters;
|
||||
|
||||
public AbstractPacketInjector(PacketTypeSet inboundFilters) {
|
||||
this.inboundFilters = inboundFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
this.inboundFilters.addType(type);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removePacketHandler(PacketType type) {
|
||||
this.inboundFilters.removeType(type);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPacketHandler(PacketType type) {
|
||||
return this.inboundFilters.contains(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getPacketHandlers() {
|
||||
return this.inboundFilters.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
this.inboundFilters.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
public final class KnownPacketData {
|
||||
public static final byte[] SCOREBOARD_OBJECTIVE = {
|
||||
4, 84, 101, 115, 116, 1
|
||||
};
|
||||
|
||||
public static final byte[] MAP_CHUNK = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00
|
||||
};
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents an incoming packet injector.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketInjector {
|
||||
|
||||
/**
|
||||
* Start intercepting packets with the given packet type.
|
||||
*
|
||||
* @param type - the type of the packets to start intercepting.
|
||||
* @param options - any listener options.
|
||||
* @return TRUE if we didn't already intercept these packets, FALSE otherwise.
|
||||
*/
|
||||
boolean addPacketHandler(PacketType type, Set<ListenerOptions> options);
|
||||
|
||||
/**
|
||||
* Stop intercepting packets with the given packet type.
|
||||
*
|
||||
* @param type - the type of the packets to stop intercepting.
|
||||
* @return TRUE if we successfuly stopped intercepting a given packet ID, FALSE otherwise.
|
||||
*/
|
||||
boolean removePacketHandler(PacketType type);
|
||||
|
||||
/**
|
||||
* Determine if packets with the given packet type is being intercepted.
|
||||
*
|
||||
* @param type - the packet type to lookup.
|
||||
* @return TRUE if we do, FALSE otherwise.
|
||||
*/
|
||||
boolean hasPacketHandler(PacketType type);
|
||||
|
||||
/**
|
||||
* Retrieve every intercepted packet type.
|
||||
*
|
||||
* @return Every intercepted packet type.
|
||||
*/
|
||||
Set<PacketType> getPacketHandlers();
|
||||
|
||||
/**
|
||||
* Let the packet listeners process the given packet.
|
||||
*
|
||||
* @param packet - a packet to process.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The resulting packet event.
|
||||
*/
|
||||
PacketEvent packetReceived(PacketContainer packet, Player client);
|
||||
|
||||
/**
|
||||
* Determine if we have packet listeners with the given type that must be executed on the main thread.
|
||||
*
|
||||
* @param type - the packet type.
|
||||
* @return TRUE if we do, FALSE otherwise.
|
||||
*/
|
||||
boolean hasMainThreadListener(PacketType type);
|
||||
|
||||
/**
|
||||
* Perform any necessary cleanup before unloading ProtocolLib.
|
||||
*/
|
||||
void cleanupAll();
|
||||
}
|
||||
@@ -19,12 +19,23 @@ package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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.fuzzy.FuzzyClassContract;
|
||||
@@ -32,43 +43,52 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
|
||||
|
||||
/**
|
||||
* Static packet registry in Minecraft.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketRegistry {
|
||||
// Whether or not the registry has been initialized
|
||||
private static volatile boolean INITIALIZED = false;
|
||||
|
||||
static void reset() {
|
||||
synchronized (registryLock) {
|
||||
INITIALIZED = false;
|
||||
}
|
||||
}
|
||||
static void reset() {
|
||||
synchronized (registryLock) {
|
||||
INITIALIZED = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a register we are currently building.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
protected static class Register {
|
||||
public static class Register {
|
||||
// The main lookup table
|
||||
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
|
||||
|
||||
final Map<Class<?>, PacketType> classToType = new ConcurrentHashMap<>();
|
||||
final Map<Class<?>, WrappedStreamCodec> classToCodec = new ConcurrentHashMap<>();
|
||||
final Map<PacketType.Protocol, Map<Class<?>, PacketType>> protocolClassToType = new ConcurrentHashMap<>();
|
||||
|
||||
volatile Set<PacketType> serverPackets = new HashSet<>();
|
||||
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);
|
||||
@@ -83,6 +103,7 @@ public class PacketRegistry {
|
||||
|
||||
/**
|
||||
* Determine if the current register is outdated.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isOutdated() {
|
||||
@@ -128,7 +149,8 @@ public class PacketRegistry {
|
||||
modifier = new StructureModifier<>(protocol.getClass().getSuperclass());
|
||||
}
|
||||
|
||||
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol).withType(Map.class);
|
||||
StructureModifier<Map<Object, Map<Integer, Class<?>>>> maps = modifier.withTarget(protocol)
|
||||
.withType(Map.class);
|
||||
for (Map.Entry<Object, Map<Integer, Class<?>>> entry : maps.read(0).entrySet()) {
|
||||
String direction = entry.getKey().toString();
|
||||
if (direction.contains("CLIENTBOUND")) { // Sent by Server
|
||||
@@ -163,7 +185,7 @@ public class PacketRegistry {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static synchronized Register createNewRegister() {
|
||||
private static synchronized Register createRegisterV1_15_0() {
|
||||
Object[] protocols = ENUM_PROTOCOL.getEnumConstants();
|
||||
|
||||
// ID to Packet class maps
|
||||
@@ -180,11 +202,8 @@ public class PacketRegistry {
|
||||
for (Object protocol : protocols) {
|
||||
if (mainMapField == null) {
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true);
|
||||
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.typeDerivedOf(Map.class)
|
||||
.build());
|
||||
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL).typeDerivedOf(Map.class).build());
|
||||
mainMapField.setAccessible(true);
|
||||
}
|
||||
|
||||
@@ -202,28 +221,22 @@ public class PacketRegistry {
|
||||
Class<?> packetHolderClass = holder.getClass();
|
||||
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
|
||||
FuzzyReflection holderFuzzy = FuzzyReflection.fromClass(packetHolderClass, true);
|
||||
holderClassField = holderFuzzy.getField(FuzzyFieldContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.typeMatches(FuzzyClassContract.newBuilder()
|
||||
.method(FuzzyMethodContract.newBuilder()
|
||||
.returnTypeExact(MinecraftReflection.getPacketClass())
|
||||
.parameterCount(2)
|
||||
.parameterExactType(int.class, 0)
|
||||
.parameterExactType(MinecraftReflection.getPacketDataSerializerClass(), 1)
|
||||
.build())
|
||||
.build())
|
||||
.build());
|
||||
holderClassField = holderFuzzy
|
||||
.getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.typeMatches(FuzzyClassContract.newBuilder().method(FuzzyMethodContract
|
||||
.newBuilder().returnTypeExact(MinecraftReflection.getPacketClass())
|
||||
.parameterCount(2).parameterExactType(int.class, 0).parameterExactType(
|
||||
MinecraftReflection.getPacketDataSerializerClass(), 1)
|
||||
.build()).build())
|
||||
.build());
|
||||
holderClassField.setAccessible(true);
|
||||
packetHolderClass = holderClassField.getType();
|
||||
}
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(packetHolderClass, true);
|
||||
packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.typeDerivedOf(Map.class)
|
||||
.build());
|
||||
packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL).typeDerivedOf(Map.class).build());
|
||||
packetMapField.setAccessible(true);
|
||||
}
|
||||
|
||||
@@ -255,13 +268,13 @@ public class PacketRegistry {
|
||||
// Maps we have to occasionally check have changed
|
||||
// TODO: Find equivalent in Object2IntMap
|
||||
|
||||
/* for (Object map : serverMaps.values()) {
|
||||
result.containers.add(new MapContainer(map));
|
||||
}
|
||||
|
||||
for (Object map : clientMaps.values()) {
|
||||
result.containers.add(new MapContainer(map));
|
||||
} */
|
||||
/*
|
||||
* for (Object map : serverMaps.values()) { result.containers.add(new
|
||||
* MapContainer(map)); }
|
||||
*
|
||||
* for (Object map : clientMaps.values()) { result.containers.add(new
|
||||
* MapContainer(map)); }
|
||||
*/
|
||||
|
||||
for (Object protocol : protocols) {
|
||||
Enum<?> enumProtocol = (Enum<?>) protocol;
|
||||
@@ -279,9 +292,15 @@ public class PacketRegistry {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static synchronized Register createRegisterV1_20_5() {
|
||||
final Register result = new Register();
|
||||
ProtocolRegistry_1_20_5.fillRegister(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses a key->value map to value->key
|
||||
* Non-deterministic behavior when multiple keys are mapped to the same value
|
||||
* Reverses a key->value map to value->key Non-deterministic behavior when
|
||||
* multiple keys are mapped to the same value
|
||||
*/
|
||||
private static <K, V> Map<V, K> reverse(Map<K, V> map) {
|
||||
Map<V, K> newMap = new HashMap<>(map.size());
|
||||
@@ -291,7 +310,8 @@ public class PacketRegistry {
|
||||
return newMap;
|
||||
}
|
||||
|
||||
protected static void associatePackets(Register register, Map<Integer, Class<?>> lookup, PacketType.Protocol protocol, Sender sender) {
|
||||
protected static void associatePackets(Register register, Map<Integer, Class<?>> lookup,
|
||||
PacketType.Protocol protocol, Sender sender) {
|
||||
for (Map.Entry<Integer, Class<?>> entry : lookup.entrySet()) {
|
||||
int packetId = entry.getKey();
|
||||
Class<?> packetClass = entry.getValue();
|
||||
@@ -299,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);
|
||||
}
|
||||
@@ -330,8 +350,10 @@ public class PacketRegistry {
|
||||
return;
|
||||
}
|
||||
|
||||
if (MinecraftVersion.BEE_UPDATE.atOrAbove()) {
|
||||
REGISTER = createNewRegister();
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
REGISTER = createRegisterV1_20_5();
|
||||
} else if (MinecraftVersion.BEE_UPDATE.atOrAbove()) {
|
||||
REGISTER = createRegisterV1_15_0();
|
||||
} else {
|
||||
REGISTER = createOldRegister();
|
||||
}
|
||||
@@ -340,8 +362,21 @@ public class PacketRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped stream codec to de-/serialize the given packet class
|
||||
*
|
||||
* @param packetClass - the packet class
|
||||
* @return wrapped stream codec if existing, otherwise <code>null</code>
|
||||
*/
|
||||
@Nullable
|
||||
public static WrappedStreamCodec getStreamCodec(Class<?> packetClass) {
|
||||
initialize();
|
||||
return REGISTER.classToCodec.get(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet type is supported on the current server.
|
||||
*
|
||||
* @param type - the type to check.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
@@ -352,6 +387,7 @@ public class PacketRegistry {
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported server packet type.
|
||||
*
|
||||
* @return Every server packet type.
|
||||
*/
|
||||
public static Set<PacketType> getServerPacketTypes() {
|
||||
@@ -360,9 +396,10 @@ public class PacketRegistry {
|
||||
|
||||
return Collections.unmodifiableSet(REGISTER.serverPackets);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve every known and supported server packet type.
|
||||
*
|
||||
* @return Every server packet type.
|
||||
*/
|
||||
public static Set<PacketType> getClientPacketTypes() {
|
||||
@@ -375,22 +412,40 @@ 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;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param type - the packet type.
|
||||
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
||||
* @param type - the packet type.
|
||||
* @param forceVanilla - whether or not to look for vanilla classes, not
|
||||
* injected classes.
|
||||
* @return The associated class.
|
||||
* @deprecated forceVanilla no longer has any effect
|
||||
*/
|
||||
@@ -405,7 +460,7 @@ public class PacketRegistry {
|
||||
// Try the lookup first (may be null, so check contains)
|
||||
Optional<Class<?>> res = REGISTER.typeToClass.get(type);
|
||||
if (res != null) {
|
||||
if(res.isPresent() && MinecraftReflection.isBundleDelimiter(res.get())) {
|
||||
if (res.isPresent() && MinecraftReflection.isBundleDelimiter(res.get())) {
|
||||
return MinecraftReflection.getPackedBundlePacketClass();
|
||||
}
|
||||
return res;
|
||||
@@ -414,21 +469,25 @@ public class PacketRegistry {
|
||||
// Then try looking up the class names
|
||||
Class<?> clazz = searchForPacket(type.getClassNames());
|
||||
if (clazz != null) {
|
||||
// we'd like for it to be associated correctly from the get-go; this is OK on older versions though
|
||||
ProtocolLogger.warnAbove(type.getCurrentVersion(), "Updating associated class for {0} to {1}", type.name(), clazz);
|
||||
// we'd like for it to be associated correctly from the get-go; this is OK on
|
||||
// older versions though
|
||||
ProtocolLogger.warnAbove(type.getCurrentVersion(), "Updating associated class for {0} to {1}", type.name(),
|
||||
clazz);
|
||||
}
|
||||
|
||||
// cache it for next time
|
||||
associate(type, clazz);
|
||||
if(clazz != null && MinecraftReflection.isBundleDelimiter(clazz)) {
|
||||
clazz = MinecraftReflection.getPackedBundlePacketClass().orElseThrow(() -> new IllegalStateException("Packet bundle class not found."));
|
||||
if (clazz != null && MinecraftReflection.isBundleDelimiter(clazz)) {
|
||||
clazz = MinecraftReflection.getPackedBundlePacketClass()
|
||||
.orElseThrow(() -> new IllegalStateException("Packet bundle class not found."));
|
||||
}
|
||||
return Optional.ofNullable(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the packet class associated with a given type. First attempts to read from the
|
||||
* type-to-class mapping, and tries
|
||||
* Get the packet class associated with a given type. First attempts to read
|
||||
* from the type-to-class mapping, and tries
|
||||
*
|
||||
* @param type the packet type
|
||||
* @return The associated class
|
||||
*/
|
||||
@@ -439,9 +498,11 @@ public class PacketRegistry {
|
||||
|
||||
/**
|
||||
* Retrieve the packet type of a given packet.
|
||||
*
|
||||
* @param packet - the class of the packet.
|
||||
* @return The packet type, or NULL if not found.
|
||||
* @deprecated major issues due to packets with shared classes being registered in multiple states.
|
||||
* @deprecated major issues due to packets with shared classes being registered
|
||||
* in multiple states.
|
||||
*/
|
||||
@Deprecated
|
||||
public static PacketType getPacketType(Class<?> packet) {
|
||||
@@ -450,16 +511,18 @@ public class PacketRegistry {
|
||||
if (MinecraftReflection.isBundlePacket(packet)) {
|
||||
return PacketType.Play.Server.BUNDLE;
|
||||
}
|
||||
|
||||
|
||||
return REGISTER.classToType.get(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated packet type for a packet class in the given protocol state.
|
||||
* Retrieve the associated packet type for a packet class in the given protocol
|
||||
* state.
|
||||
*
|
||||
* @param protocol the protocol state to retrieve the packet from.
|
||||
* @param packet the class identifying the packet type.
|
||||
* @return the packet type associated with the given class in the given protocol state, or null if not found.
|
||||
* @param packet the class identifying the packet type.
|
||||
* @return the packet type associated with the given class in the given protocol
|
||||
* state, or null if not found.
|
||||
*/
|
||||
public static PacketType getPacketType(PacketType.Protocol protocol, Class<?> packet) {
|
||||
initialize();
|
||||
@@ -467,12 +530,22 @@ public class PacketRegistry {
|
||||
return PacketType.Play.Server.BUNDLE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reverts https://github.com/dmulloy2/ProtocolLib/pull/2568 for server versions 1.8 to 1.20.1.
|
||||
*
|
||||
* Since packet classes are not shared for these versions,
|
||||
* the protocol state is not needed to determine the packet type from class.
|
||||
*/
|
||||
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
|
||||
return getPacketType(packet);
|
||||
}
|
||||
Map<Class<?>, PacketType> classToTypesForProtocol = REGISTER.protocolClassToType.get(protocol);
|
||||
return classToTypesForProtocol == null ? null : classToTypesForProtocol.get(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type of a given packet.
|
||||
*
|
||||
* @param packet - the class of the packet.
|
||||
* @param sender - the sender of the packet, or NULL.
|
||||
* @return The packet type, or NULL if not found.
|
||||
@@ -482,4 +555,4 @@ public class PacketRegistry {
|
||||
public static PacketType getPacketType(Class<?> packet, Sender sender) {
|
||||
return getPacketType(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+102
@@ -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));
|
||||
}
|
||||
}
|
||||
+150
@@ -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) {
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class AbstractPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
|
||||
private final PacketTypeSet sendingFilters;
|
||||
|
||||
public AbstractPlayerInjectionHandler(PacketTypeSet sendingFilters) {
|
||||
this.sendingFilters = sendingFilters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
this.sendingFilters.addType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketHandler(PacketType type) {
|
||||
this.sendingFilters.removeType(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getSendingFilters() {
|
||||
return this.sendingFilters.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.sendingFilters.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReceivePackets() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
// They're all fine!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkListener(Set<PacketListener> listeners) {
|
||||
// Yes, really
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import io.netty.channel.Channel;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public interface PlayerInjectionHandler {
|
||||
|
||||
/**
|
||||
* Retrieve the protocol version of the given player.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @return The protocol version, or {@link Integer#MIN_VALUE}.
|
||||
*/
|
||||
int getProtocolVersion(Player player);
|
||||
|
||||
/**
|
||||
* Add an underlying packet handler of the given type.
|
||||
*
|
||||
* @param type - packet type to register.
|
||||
* @param options - any specified listener options.
|
||||
*/
|
||||
void addPacketHandler(PacketType type, Set<ListenerOptions> options);
|
||||
|
||||
/**
|
||||
* Remove an underlying packet handler of this type.
|
||||
*
|
||||
* @param type - packet type to unregister.
|
||||
*/
|
||||
void removePacketHandler(PacketType type);
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This call will be ignored if there's no listener that can receive the given events.
|
||||
*
|
||||
* @param player - player to hook.
|
||||
* @param strategy - how to handle injection conflicts.
|
||||
*/
|
||||
void injectPlayer(Player player, ConflictStrategy strategy);
|
||||
|
||||
/**
|
||||
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||
*
|
||||
* @param player - player to process.
|
||||
*/
|
||||
void handleDisconnect(Player player);
|
||||
|
||||
/**
|
||||
* Uninject the given player.
|
||||
*
|
||||
* @param player - player to uninject.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
boolean uninjectPlayer(Player player);
|
||||
|
||||
/**
|
||||
* Send the given packet to the given receiver.
|
||||
*
|
||||
* @param receiver - the player receiver.
|
||||
* @param packet - the packet to send.
|
||||
* @param marker - network marker.
|
||||
* @param filters - whether or not to invoke the packet filters.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
|
||||
/**
|
||||
* Process a packet as if it were sent by the given player.
|
||||
*
|
||||
* @param player - the sender.
|
||||
* @param mcPacket - the packet to process.
|
||||
*/
|
||||
void receiveClientPacket(Player player, Object mcPacket);
|
||||
|
||||
/**
|
||||
* Ensure that packet readers are informed of this player reference.
|
||||
*
|
||||
* @param player - the player to update.
|
||||
*/
|
||||
void updatePlayer(Player player);
|
||||
|
||||
/**
|
||||
* Determine if the given listeners are valid.
|
||||
*
|
||||
* @param listeners - listeners to check.
|
||||
*/
|
||||
void checkListener(Set<PacketListener> listeners);
|
||||
|
||||
/**
|
||||
* Determine if a listener is valid or not.
|
||||
* <p>
|
||||
* If not, a warning will be printed to the console.
|
||||
*
|
||||
* @param listener - listener to check.
|
||||
*/
|
||||
void checkListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Retrieve the current list of registered sending listeners.
|
||||
*
|
||||
* @return List of the sending listeners's packet IDs.
|
||||
*/
|
||||
Set<PacketType> getSendingFilters();
|
||||
|
||||
/**
|
||||
* Whether or not this player injection handler can also receive packets.
|
||||
*
|
||||
* @return TRUE if it can, FALSE otherwise.
|
||||
*/
|
||||
boolean canReceivePackets();
|
||||
|
||||
/**
|
||||
* Close any lingering proxy injections.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Determine if we have packet listeners with the given type that must be executed on the main thread.
|
||||
*
|
||||
* @param type - the packet type.
|
||||
* @return TRUE if we do, FALSE otherwise.
|
||||
*/
|
||||
boolean hasMainThreadListener(PacketType type);
|
||||
|
||||
Channel getChannel(Player player);
|
||||
|
||||
/**
|
||||
* How to handle a previously existing player injection.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
enum ConflictStrategy {
|
||||
/**
|
||||
* Override it.
|
||||
*/
|
||||
OVERRIDE,
|
||||
|
||||
/**
|
||||
* Immediately exit.
|
||||
*/
|
||||
BAIL_OUT;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.comphenix.protocol.injector.temporary;
|
||||
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import java.net.SocketAddress;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Represents an injector that only gives access to a player's socket.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface MinimalInjector {
|
||||
|
||||
/**
|
||||
* Retrieve the associated address of this player.
|
||||
*
|
||||
* @return The associated address.
|
||||
*/
|
||||
SocketAddress getAddress();
|
||||
|
||||
/**
|
||||
* Attempt to disconnect the current client.
|
||||
*
|
||||
* @param message - the message to display.
|
||||
*/
|
||||
void disconnect(String message);
|
||||
|
||||
/**
|
||||
* Send a packet to the client.
|
||||
*
|
||||
* @param packet - server packet to send.
|
||||
* @param marker - the network marker.
|
||||
* @param filtered - whether or not the packet will be filtered by our listeners.
|
||||
*/
|
||||
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
|
||||
|
||||
/**
|
||||
* Retrieve the hooked player.
|
||||
*
|
||||
* @return The hooked player.
|
||||
*/
|
||||
Player getPlayer();
|
||||
|
||||
/**
|
||||
* Determines if the player is currently connected.
|
||||
*
|
||||
* @return true if the player is connected.
|
||||
*/
|
||||
boolean isConnected();
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.comphenix.protocol.injector.temporary;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
|
||||
/**
|
||||
* A temporary player created by ProtocolLib when a true player instance does not exist.
|
||||
* <p>
|
||||
@@ -8,15 +12,17 @@ package com.comphenix.protocol.injector.temporary;
|
||||
*/
|
||||
public class TemporaryPlayer {
|
||||
|
||||
private volatile MinimalInjector injector;
|
||||
protected volatile Injector injector;
|
||||
|
||||
MinimalInjector getInjector() {
|
||||
public Injector getInjector() {
|
||||
return this.injector;
|
||||
}
|
||||
|
||||
void setInjector(MinimalInjector injector) {
|
||||
if (injector == null) {
|
||||
throw new IllegalArgumentException("Injector cannot be NULL.");
|
||||
void setInjector(Injector injector) {
|
||||
Objects.requireNonNull(injector, "injector can't be null");
|
||||
|
||||
if (this.injector != null) {
|
||||
throw new IllegalStateException("Can't redefine injector for temporary player");
|
||||
}
|
||||
|
||||
this.injector = injector;
|
||||
|
||||
+35
-41
@@ -17,17 +17,21 @@
|
||||
|
||||
package com.comphenix.protocol.injector.temporary;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
|
||||
import net.bytebuddy.description.ByteCodeElement;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
@@ -36,8 +40,6 @@ import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.implementation.bind.annotation.This;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Create fake player instances that represents pre-authenticated clients.
|
||||
@@ -46,13 +48,15 @@ public class TemporaryPlayerFactory {
|
||||
|
||||
private static final Constructor<? extends Player> PLAYER_CONSTRUCTOR = setupProxyPlayerConstructor();
|
||||
|
||||
private TemporaryPlayerFactory() {}
|
||||
|
||||
/**
|
||||
* Retrieve the injector from a given player if it contains one.
|
||||
*
|
||||
* @param player - the player that may contain a reference to a player injector.
|
||||
* @return The referenced player injector, or NULL if none can be found.
|
||||
*/
|
||||
public static MinimalInjector getInjectorFromPlayer(Player player) {
|
||||
public static Injector getInjectorFromPlayer(Player player) {
|
||||
if (player instanceof TemporaryPlayer) {
|
||||
return ((TemporaryPlayer) player).getInjector();
|
||||
}
|
||||
@@ -65,7 +69,7 @@ public class TemporaryPlayerFactory {
|
||||
* @param player - the player to update.
|
||||
* @param injector - the injector to store.
|
||||
*/
|
||||
public static void setInjectorInPlayer(Player player, MinimalInjector injector) {
|
||||
public static void setInjectorForPlayer(Player player, Injector injector) {
|
||||
((TemporaryPlayer) player).setInjector(injector);
|
||||
}
|
||||
|
||||
@@ -76,11 +80,10 @@ public class TemporaryPlayerFactory {
|
||||
public Object delegate(
|
||||
@This Object obj,
|
||||
@Origin Method method,
|
||||
@FieldValue("server") Server server,
|
||||
@FieldValue("injector") Injector injector,
|
||||
@AllArguments Object... args
|
||||
) throws Throwable {
|
||||
String methodName = method.getName();
|
||||
MinimalInjector injector = ((TemporaryPlayer) obj).getInjector();
|
||||
|
||||
if (injector == null) {
|
||||
throw new IllegalStateException("Unable to find injector.");
|
||||
@@ -92,7 +95,7 @@ public class TemporaryPlayerFactory {
|
||||
} else if (methodName.equals("getAddress")) {
|
||||
return injector.getAddress();
|
||||
} else if (methodName.equals("getServer")) {
|
||||
return server;
|
||||
return Bukkit.getServer();
|
||||
}
|
||||
|
||||
// Handle send message methods
|
||||
@@ -130,7 +133,16 @@ public class TemporaryPlayerFactory {
|
||||
if (methodName.equals("isOnline")) {
|
||||
return injector.isConnected();
|
||||
} else if (methodName.equals("getName")) {
|
||||
String playerName = injector.getPlayerName();
|
||||
if (playerName != null) {
|
||||
return playerName;
|
||||
}
|
||||
return "UNKNOWN[" + injector.getAddress() + "]";
|
||||
} else if (methodName.equals("getUniqueId")) {
|
||||
UUID playerUniqueId = injector.getPlayerUniqueId();
|
||||
if (playerUniqueId != null) {
|
||||
return playerUniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore all other methods
|
||||
@@ -144,21 +156,15 @@ public class TemporaryPlayerFactory {
|
||||
|
||||
try {
|
||||
final Constructor<?> constructor = ByteBuddyFactory.getInstance()
|
||||
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
|
||||
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
|
||||
.name(TemporaryPlayerFactory.class.getPackage().getName() + ".TemporaryPlayerInvocationHandler")
|
||||
.implement(Player.class)
|
||||
|
||||
.defineField("server", Server.class, Visibility.PRIVATE)
|
||||
.defineConstructor(Visibility.PUBLIC)
|
||||
.withParameters(Server.class)
|
||||
.intercept(MethodCall.invoke(TemporaryPlayer.class.getDeclaredConstructor())
|
||||
.andThen(FieldAccessor.ofField("server").setsArgumentAt(0)))
|
||||
.method(callbackFilter)
|
||||
.intercept(implementation)
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
|
||||
.getLoaded()
|
||||
.getDeclaredConstructor(Server.class);
|
||||
.getDeclaredConstructor();
|
||||
return (Constructor<? extends Player>) constructor;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Failed to find Temporary Player constructor!", e);
|
||||
@@ -172,9 +178,9 @@ public class TemporaryPlayerFactory {
|
||||
* @param message - a message.
|
||||
* @return Always NULL.
|
||||
*/
|
||||
private static Object sendMessage(MinimalInjector injector, String message) {
|
||||
private static Object sendMessage(Injector injector, String message) {
|
||||
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
|
||||
injector.sendServerPacket(packet.getHandle(), null, false);
|
||||
injector.sendClientboundPacket(packet.getHandle(), null, false);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -188,36 +194,24 @@ public class TemporaryPlayerFactory {
|
||||
* <li>getPlayer()</li>
|
||||
* <li>getAddress()</li>
|
||||
* <li>getServer()</li>
|
||||
* <li>getName()</li>
|
||||
* <li>getUniqueId(), when supplied by the login protocol</li>
|
||||
* <li>chat(String)</li>
|
||||
* <li>sendMessage(String)</li>
|
||||
* <li>sendMessage(String[])</li>
|
||||
* <li>kickPlayer(String)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note that a temporary player has not yet been assigned a name, and thus cannot be
|
||||
* uniquely identified. Use the address instead.
|
||||
* Before the login start packet is received, the player has no profile information
|
||||
* and can only be identified by its address.
|
||||
*
|
||||
* @param server - the current server.
|
||||
* @return A temporary player instance.
|
||||
*/
|
||||
public Player createTemporaryPlayer(final Server server) {
|
||||
public static Player createTemporaryPlayer() {
|
||||
try {
|
||||
return PLAYER_CONSTRUCTOR.newInstance(server);
|
||||
return PLAYER_CONSTRUCTOR.newInstance();
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
throw new IllegalStateException("Unable to create temporary player", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a temporary player with the given associated socket injector.
|
||||
*
|
||||
* @param server - the parent server.
|
||||
* @param injector - the referenced socket injector.
|
||||
* @return The temporary player.
|
||||
*/
|
||||
public Player createTemporaryPlayer(Server server, MinimalInjector injector) {
|
||||
Player temporary = this.createTemporaryPlayer(server);
|
||||
((TemporaryPlayer) temporary).setInjector(injector);
|
||||
return temporary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.comphenix.protocol.internal;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLib;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public interface PlatformProvider {
|
||||
|
||||
static PlatformProvider get() {
|
||||
return Holder.INSTANCE;
|
||||
}
|
||||
|
||||
boolean hasEarlyChannelInitialization();
|
||||
|
||||
Runnable registerChannelInitializer(Consumer<Channel> channelInitializer);
|
||||
|
||||
void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor);
|
||||
|
||||
final class Holder {
|
||||
|
||||
private static final PlatformProvider INSTANCE = ServiceLoader.load(
|
||||
PlatformProvider.class,
|
||||
PlatformProvider.class.getClassLoader())
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No ProtocolLib platform provider is available"));
|
||||
|
||||
private Holder() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,6 @@
|
||||
|
||||
package com.comphenix.protocol.reflect;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.instances.BannedGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -43,6 +34,17 @@ import java.util.Optional;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.instances.BannedGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.InstanceProvider;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
/**
|
||||
* Provides list-oriented access to the fields of a Minecraft packet.
|
||||
* <p>
|
||||
@@ -88,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;
|
||||
@@ -142,8 +146,9 @@ public class StructureModifier<T> {
|
||||
|
||||
for (FieldAccessor accessor : fields) {
|
||||
Field field = accessor.getField();
|
||||
if (!field.getType().isPrimitive() && !Modifier.isFinal(field.getModifiers())) {
|
||||
if (DEFAULT_GENERATOR.hasDefault(field.getType())) {
|
||||
if (!field.getType().isPrimitive()) {// && !Modifier.isFinal(field.getModifiers())) {
|
||||
// if (DEFAULT_GENERATOR.hasDefault(field.getType())) {
|
||||
if (StructureCache.canCreateInstance(field.getType())) {
|
||||
requireDefaults.put(accessor, currentFieldIndex);
|
||||
}
|
||||
}
|
||||
@@ -198,6 +203,7 @@ public class StructureModifier<T> {
|
||||
other.defaultFields,
|
||||
other.converter,
|
||||
other.subtypeCache);
|
||||
this.paramTypeCache = other.paramTypeCache;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,13 +401,12 @@ public class StructureModifier<T> {
|
||||
// They must be null or messages will be blank
|
||||
Field field = accessor.getField();
|
||||
if (field.getType().getCanonicalName().equals("net.md_5.bungee.api.chat.BaseComponent[]")) {
|
||||
accessor.set(this.target, null);
|
||||
accessor.set(target, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the default value and write the field
|
||||
Object defaultValue = DEFAULT_GENERATOR.getDefault(field.getType());
|
||||
accessor.set(this.target, defaultValue);
|
||||
Object value = StructureCache.newInstance(field.getType());
|
||||
accessor.set(target, value);
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -529,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<>();
|
||||
@@ -569,7 +584,11 @@ public class StructureModifier<T> {
|
||||
|
||||
// Cache structure modifiers
|
||||
result = this.withFieldType(fieldType, fields, defaults);
|
||||
this.subtypeCache.put(fieldType, result);
|
||||
if (paramTypeKey == null) {
|
||||
this.subtypeCache.put(fieldType, result);
|
||||
} else {
|
||||
this.paramTypeCache.put(paramTypeKey, result);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the target too
|
||||
|
||||
@@ -4,6 +4,21 @@ import java.lang.reflect.Constructor;
|
||||
|
||||
public interface ConstructorAccessor {
|
||||
|
||||
/**
|
||||
* NoOp Accessor, does what is says: nothing.
|
||||
*/
|
||||
static final ConstructorAccessor NO_OP_ACCESSOR = new ConstructorAccessor() {
|
||||
@Override
|
||||
public Object invoke(Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Constructor<?> getConstructor() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke the underlying constructor.
|
||||
*
|
||||
|
||||
Loaded 100 of 243 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user