Compare commits

..
91 Commits
Author SHA1 Message Date
Dan Mulloy 1aeb002afe Update version to 5.4.0 for release 2025-08-06 20:25:03 -05:00
Dan Mulloy 79d44f0d6e Minor build fixes 2025-08-06 20:06:30 -05:00
Dan Mulloy 9bde9b4231 Update group in Gradle example 2025-08-06 11:58:30 -05:00
Dan Mulloy 714252df6f Publish to Maven Central 2025-08-06 11:04:00 -05:00
Leviaria e56bcf624d Added support for Minecraft 1.21.8 (#3523) 2025-07-27 19:30:42 -05:00
Diogo Correia 596e5e4246 fix: incorrect version gating on WrappedTeamParamters (#3496) 2025-06-27 19:22:06 -05:00
Diogo Correia f3f895995a fix: initialization error of ChunkData wrapper on MC 1.21.5+ (#3495) 2025-06-27 12:23:21 -05:00
Diogo Correia 67e7375b2f fix: initialization error for WrappedChatComponent on 1.20.4-1.21.5 (#3493) 2025-06-27 10:17:03 -05:00
Pasqual Koschmieder 9417eee444 drop unnecessary minecraft import from EnumWrappers (#3476) 2025-06-19 12:57:42 -05:00
Dan Mulloy a432fae4dd 1.21.6 update (#3475) 2025-06-18 15:33:20 -05:00
JavaFruit 8a5e59d4f2 fix: re-add string overloads for nametagVisibility and collisionRule in WrappedTeamParameters.Builder (#3445) 2025-05-23 20:03:53 +02:00
Maurice Eisenblätter 90a386b743 Fix some 1.21.5 related issues (unit tests) (#3432) 2025-05-09 19:07:58 -05:00
Pasqual Koschmieder a80762feed update dependencies for java 24 support (#3425) 2025-05-06 14:07:29 -05:00
Brokkonaut a936183b76 Fix WrappedList - NBT lists may now be heterogeneous (#3394) 2025-04-22 19:21:43 -05:00
Pasqual Koschmieder 804aa5df11 fix: data watcher serializer initialization (#3384) 2025-03-31 09:46:08 -05:00
Maurice Eisenblätter 1e73df8600 Initial support for 1.21.5 (#3381) 2025-03-29 17:26:16 -05:00
Ryan 4314a55866 fix: Show helpful message when no supported updater loads (#3358) 2025-03-25 19:37:11 -05:00
Diogo Correia 2c0d632dc2 fix: listOrder not preserved when converting PlayerInfoData (#3347)
The field `listOrder` was always set to zero in the wrapper instead of
being read from the NMS object. This commit fixes that, while adding the
respective constructor and getter.
2025-02-04 01:11:03 +00:00
Pasqual Koschmieder fd5d8c576e fix/improve publishing (#3350) 2025-02-03 19:03:37 -06:00
Diogo Correia 0256953056 fix: unable to convert PlayerInfoData if gamemode is null (#3334) 2024-12-30 19:30:26 -06:00
Dan Mulloy 24262d3f7d Update link to wiki.vg 2024-12-09 14:41:23 -06:00
Brokkonaut 7a85663ded Update to 1.21.4 (#3319) 2024-12-09 14:17:17 -06:00
Dan Mulloy bcff6c6719 Update to 1.21.3 2024-11-08 11:03:51 -06:00
Dan Mulloy 3155e76d06 Mojang Mappings 2024-10-27 21:33:15 -05:00
Dan Mulloy 6845acd89d Update to 1.21.3 2024-10-26 15:21:23 -05:00
Dan Mulloy 3442d3fb8f Update version to 5.3.0 for release 2024-10-20 17:12:58 -05:00
Pasqual Koschmieder 1e3fa2d36e deps: update bytebuddy and shadow (#3247) 2024-09-24 21:53:32 +02:00
JustRed23 c9ee2f16e2 Fix serializing and deserializing of ItemStack on 1.21 (#3217) 2024-09-23 12:30:34 +00:00
Dan Mulloy 7f615378e4 Compile against 1.21.1 2024-09-22 21:41:32 -05:00
SzczurekYT d636be81e7 Fix ConcurrentModificationException when shutting down async listeners (#3169) 2024-09-23 00:11:17 +00:00
Bjorn Beishline 3cb261f466 Fix ConcurrentModificationException in PacketListenerSet (#3241) 2024-09-23 00:04:12 +00:00
Maurice Eisenblätter 7338183ed6 Fix Github actions & unit tests (#3244)
* chore: update github actions

* test: fix initialization

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

Fixes #3120
2024-07-22 21:56:48 -05:00
Dan Mulloy f227b17f5f fix: test instantiation 2024-07-22 21:52:43 -05:00
Scott Hiett 4aa344bab2 Fix WrappedTeamParameters.Builder incorrectly using displayName for prefix and suffix (#3050) 2024-06-27 03:12:12 +00:00
Maurice Eisenblätter da65c9d3c1 Improve/cleanup injector (part 2) (#3042)
* fix(#3024): cache noop accessor on ctor lookup failure
* fix(#3023): add client intent enum wrapper
2024-06-25 21:07:16 -05:00
Maurice Eisenblätter 47bcd53fd0 Improve/cleanup injector (#2992) 2024-06-16 20:42:46 -05:00
Dan Mulloy 2cad996252 fix: gracefully fail for unknown protocols
Fixes #3012
Fixes #3001
Fixes #2994
2024-06-14 14:00:59 -05:00
Dan Mulloy 46d40d752e fix: EnumProtocol Mojang mapping 2024-06-13 20:16:52 -05:00
Dan Mulloy a006b70c6f fix: JVM error when instantiating certain types 2024-06-13 19:49:24 -05:00
Dan Mulloy e726f6eb0d 1.21 2024-06-13 18:01:40 -05:00
Dan Mulloy 389233f599 Don't wrap attribute in holder < 1.20.5
Addresses #2998
2024-06-11 21:08:02 -05:00
Dan Mulloy 4051caca0b Another one
Addresses #2998
2024-06-10 12:07:29 -05:00
Dan Mulloy 914bc9aa7f Fix error on 1.20.4
Fixes #2998
2024-06-10 10:13:27 -05:00
Dan Mulloy 8272ecb33d Fix some more Mojang mappings 2024-06-09 14:53:38 -05:00
Dan Mulloy d13ce24507 Stop caching constructor parameters
Addresses #2990
2024-06-08 14:09:23 -05:00
Dan Mulloy b6a7d5f91d Fix some more Mojang mappings 2024-06-08 13:45:17 -05:00
Dan Mulloy f6b3af4221 Add helper methods for data watchers, fix wrapping
Addresses #2987
2024-06-08 12:04:44 -05:00
vytskalt 616431cc33 Add several scoreboard type wrappers (#2819) 2024-06-06 21:22:08 -05:00
Dan Mulloy 77ada09c11 Separate new and legacy data watcher impls 2024-06-06 21:04:26 -05:00
Maurice Eisenblätter 389314cc2f Fix dropping of outbound configuration packets if sync processed (#2982)
fixes #2980
2024-06-06 18:37:16 +00:00
Maurice Eisenblätter 56fb51e94f Improve injector listener tracking and fix terminal packets breaking injection (#2951) 2024-06-06 08:39:34 -05:00
Dan Mulloy 76432f9d36 Well, it works for Mojang mapped anyways 2024-06-05 22:46:20 -05:00
Dan Mulloy f0468e04d9 Fix data watchers and particles 2024-06-05 22:36:24 -05:00
Dan Mulloy 052e7a3dc0 Remove import 2024-06-04 22:41:48 -05:00
Dan Mulloy 9ae73d9e20 Fix creating most packets, fix more tests
Disable the rest for now
2024-06-04 22:35:23 -05:00
Diogo Correia 11f4810884 fix: WrappedRegistrable returning incorrect key (#2970) 2024-06-04 09:59:33 -05:00
Dan Mulloy fe51dea707 Remove unused imports 2024-06-03 21:02:04 -05:00
Dan Mulloy 4afbe845ff Fix attributes, a few enums, and some tests 2024-06-03 20:56:56 -05:00
Dan Mulloy 0daf389aff Fix spaces 2024-06-03 14:15:10 -05:00
Dan Mulloy 5c19f647fa Rework duplicate check to work with Paper
Fixes #2963
2024-06-03 14:12:20 -05:00
iiAhmedYT e07f01ece8 Add Helper methods in AdventureComponentConverter for relocating plugins (#2934) 2024-06-02 22:35:22 -05:00
Brokkonaut 7d4458bb5f Added mojmap packet class names (#2940)
Fixes #2952
2024-06-02 22:34:09 -05:00
Günter Nejar Keller 41a0c9da41 Add more aliases for chat visibility (#2928)
Fixes #2926
2024-06-02 22:33:01 -05:00
LOOHP 720bb83234 Fixed Listening to Handshake Packets & ServerPingRecord (#2933)
Fixed Listening to Handshake Packets

Fixes #2647
2024-06-02 22:31:07 -05:00
Richy 1325ec6a98 Revert protocol-states being used for 1.8 - 1.20.1 packet class to packet type translation (#2961) 2024-06-02 22:27:49 -05:00
Maurice Eisenblätter b0c4b7fe45 Add support for 1.20.5/1.20.6 part 2 (#2910) 2024-05-06 19:02:15 +02:00
Maurice Eisenblätter a2f02652e9 Fix possible deadlock in async packet processing (#2545)
fix: start processing further packets if a packet is done
2024-05-06 11:27:16 +02:00
Dan Mulloy f9d3266777 WIP: Compile against 1.20.5 2024-04-27 22:46:28 -05:00
Maurice Eisenblätter 70e4812fde Initial Support for 1.20.5 (#2894) 2024-04-27 22:21:16 -05:00
Dan Mulloy d0c7382d7f Bump version to 5.2.1 2024-04-27 21:55:52 -05:00
Dan Mulloy e1255edb32 Fix build 2024-04-07 11:18:58 -05:00
Dan Mulloy 6f057b361b Bump version to 5.2.0 for release 2024-04-07 10:04:32 -05:00
Lukas Alt 80aa420099 Fixed setting Base64 favicon for 1.19.4 or later (#2533)
Fix redundant base64 encoding of favicon
2023-12-19 15:53:16 +00:00
Nick 564349c914 Fallback to the HANDSHAKING protocol if no packet type is found in the registry (fixes https://github.com/dmulloy2/ProtocolLib/issues/2601) 2023-12-16 11:12:57 -06:00
TrainmasterHD 0da27515a4 Finish 1.20.4 update (#2683)
* update PacketTypes

* add new enum values for EnumWrappers and add ScoreboardAction to known invalids as it was removed

* make MinecraftKey optional in AutoWrapperTest

* update adventure dependencies to 4.14.0

* fix typo in maximum minecraft version

* add chat component for disconnect packet when running clone test

* adjust chat components to new component structure
2023-12-16 08:47:22 -06:00
Richy 2448d8372e Cache if a class has a default instance (#2676) 2023-12-11 08:07:10 -06:00
Dan Mulloy 0eca2eebd2 Bump version to 5.2.0-SNAPSHOT 2023-12-09 16:09:29 -06:00
Dan Mulloy 8ba1dc1284 Start 1.20.4 update 2023-12-09 15:56:45 -06:00
Pasqual Koschmieder 80a097953f update dependencies & gradle (#2589) 2023-10-25 07:07:58 -05:00
Maurice Eisenblätter d4b4f50674 Fix async handler scheduler support for folia (#2531) 2023-10-24 20:10:35 -05:00
Pasqual Koschmieder a7aa31adc0 improve support for custom payloads in 1.20.2 (#2553) 2023-10-25 01:01:35 +00:00
Pasqual Koschmieder af33a2ab41 fix invalid packet types due to state mismatch when calling packet events (#2568) 2023-10-24 19:56:38 -05:00
Pasqual Koschmieder 03d7be13d0 Update for 1.20.2 (#2501) 2023-09-23 16:21:07 -05:00
Maurice Eisenblätter f0401acd2f Improve async packet processing logic (#2503)
* fix: enqueue async packet event after processing delay is zero
* fix: async packet processing
2023-08-27 10:11:37 -05:00
Fanfaryy 2686c9fec0 Fixed PING packet for 1.19.1 and 1.19.2 (#2518)
Changed PLAYER_CHAT_HEADER duplicated currentId due to bug on 1.19.1 and 1.19.2 versions with PING
2023-08-26 21:00:57 +00:00
Lukas Alt 8fc5e509ae Added factory method for initializing WrappedDataValue (#2523) 2023-08-26 15:49:11 -05:00
Manuel P 9e9b39a37d Fix PacketContainer serialization (#2479)
Fixes #2478
2023-08-05 19:06:09 +00:00
Tomescu Vlad-Costin e219103a25 Added a null check for the optional converter (#2485) 2023-08-05 19:02:41 +00:00
Dan Mulloy 98fbcc6585 Update version to 5.1.1 for development 2023-08-05 13:58:29 -05:00
211 changed files with 9796 additions and 5958 deletions

No files matched your search

+8 -6
View File
@@ -1,28 +1,30 @@
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: '21'
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
+5 -5
View File
@@ -20,17 +20,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '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
-1
View File
@@ -181,4 +181,3 @@ logs/
build/
logs/
.java-version
gradle.properties
+9 -15
View File
@@ -2,7 +2,7 @@
Certain tasks are impossible to perform with the standard Bukkit API, and may require
working with and even modifying Minecraft directly. A common technique is to modify incoming
and outgoing [packets](https://wiki.vg/Protocol), or inject custom packets into the
and outgoing packets, or inject custom packets into the
stream. This is quite cumbersome to do, however, and most implementations will break
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
@@ -12,9 +12,10 @@ lead to more subtle bugs.
### Resources
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
* [Spigot Page](https://spigotmc.org/resources/protocollib.1997/)
* [Releases](https://github.com/dmulloy2/ProtocolLib/releases)
* [JavaDoc](https://dmulloy2.net/ProtocolLib/javadoc/)
* [Protocol Wiki](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol)
### Compilation
@@ -42,18 +43,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.0.0</version>
<version>5.4.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -63,11 +57,11 @@ 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.0.0'
compileOnly 'net.dmulloy2:ProtocolLib:5.4.0'
}
```
-114
View File
@@ -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.1.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.3'
compileOnly 'org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.20-R0.1-SNAPSHOT'
compileOnly 'io.netty:netty-all:4.0.23.Final'
compileOnly 'net.kyori:adventure-text-serializer-gson:4.13.0'
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation 'org.mockito:mockito-inline:4.11.0'
testImplementation 'io.netty:netty-common:4.1.77.Final'
testImplementation 'io.netty:netty-transport:4.1.77.Final'
testImplementation 'org.spigotmc:spigot:1.20-R0.1-SNAPSHOT'
testImplementation 'net.kyori:adventure-text-serializer-gson:4.13.0'
testImplementation 'net.kyori:adventure-text-serializer-plain:4.13.1'
}
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'
}
+169
View File
@@ -0,0 +1,169 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import io.github.patrick.gradle.remapper.RemapTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
`maven-publish`
`signing`
id("com.gradleup.shadow") version "9.0.0-beta13"
id("io.github.patrick.remapper") version "1.4.2"
id("com.vanniktech.maven.publish") version "0.34.0"
}
group = "net.dmulloy2"
description = "Provides access to the Minecraft protocol"
val mcVersion = "1.21.8"
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val buildNumber = System.getenv("BUILD_NUMBER") ?: ""
val isJenkins = buildNumber.isNotEmpty()
repositories {
if (!isJenkins) {
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.17.5")
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT:remapped-mojang")
compileOnly("io.netty:netty-all:4.0.23.Final")
/*
* TODO(fix): once you update kyori:adventure-text-serializer-gson please uncomment the TODO in
* com.comphenix.protocol.wrappers.WrappedComponentStyleTest if the following issue got fixed:
* https://github.com/KyoriPowered/adventure/issues/1194
*/
compileOnly("net.kyori:adventure-text-serializer-gson:4.21.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
testImplementation("org.mockito:mockito-core:5.6.0")
testImplementation("io.netty:netty-common:4.1.97.Final")
testImplementation("io.netty:netty-transport:4.1.97.Final")
testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT:remapped-mojang")
testImplementation("net.kyori:adventure-text-serializer-gson:4.14.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.14.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks {
processResources {
val fullVersion = if (isSnapshot && isJenkins) "${version}-${buildNumber}" else version
eachFile {
expand("version" to fullVersion)
}
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
shadowJar {
dependencies {
include(dependency("net.bytebuddy:byte-buddy:.*"))
}
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes(
"paperweight-mappings-namespace" to "mojang"
)
}
archiveFileName = "ProtocolLib.jar"
}
remap {
dependsOn("shadowJar")
inputTask.set(getByName<ShadowJar>("shadowJar"))
version.set(mcVersion)
action.set(RemapTask.Action.MOJANG_TO_SPIGOT)
}
assemble {
dependsOn("remap")
}
javadoc {
options.encoding = "UTF-8"
}
compileJava {
options.encoding = "UTF-8"
}
}
mavenPublishing {
publishToMavenCentral()
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")
}
}
}
+6 -2
View File
@@ -1,2 +1,6 @@
nexusUsername=""
nexusPassword=""
version=5.4.0
mavenCentralUsername=
mavenCentralPassword=
signing.keyId=
signing.password=
signing.secretKeyRingFile=
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+9 -8
View File
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# 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
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_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" \
@@ -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;
@@ -143,15 +144,14 @@ class CommandProtocol extends CommandBase {
}
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 +161,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>
@@ -29,7 +35,7 @@ import org.bukkit.Bukkit;
public class PacketType implements Serializable, Cloneable, Comparable<PacketType> {
// Increment whenever the type changes
private static final long serialVersionUID = 1L;
/**
* Represents an unknown packet ID.
*/
@@ -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,119 +107,140 @@ 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 NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 0x03, "NamedEntitySpawn", "SPacketSpawnPlayer");
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x04, "Animation", "SPacketAnimation");
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x05, "Statistic", "SPacketStatistics");
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "BlockChangedAck");
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x07, "BlockBreakAnimation", "SPacketBlockBreakAnim");
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x08, "TileEntityData", "SPacketUpdateTileEntity");
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x09, "BlockAction", "SPacketBlockAction");
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x0A, "BlockChange", "SPacketBlockChange");
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0B, "Boss", "SPacketUpdateBossInfo");
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0C, "ServerDifficulty", "SPacketServerDifficulty");
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, "TabComplete", "SPacketTabComplete");
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, "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x12, "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x13, "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x14, "SetSlot", "SPacketSetSlot");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x15, "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x16, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x17, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x18, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x19, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1A, "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1B, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1C, "EntityStatus", "SPacketEntityStatus");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x1D, "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x1E, "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x1F, "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x20, "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x21, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x22, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x23, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x24, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x25, "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x26, "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x27, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x28, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x29, "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2A, "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2B, "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2C, "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x2D, "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x30, "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x31, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x32, "Ping");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x33, "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x34, "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x35, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x36, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x37, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x38, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x3B, "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x3C, "Position");
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x3D, "Recipes", "SPacketRecipeBook");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x3E, "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x3F, "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 0x40, "ResourcePackSend", "SPacketResourcePackSend");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x41, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x42, "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x43, "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x44, "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x45, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x46, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x47, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x48, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x49, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x4A, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4B, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x4C, "Camera", "SPacketCamera");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x4D, "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x4E, "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x4F, "ViewDistance");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x50, "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x51, "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x52, "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x53, "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x54, "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x55, "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x56, "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x57, "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x58, "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x59, "Mount", "SPacketSetPassengers");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x5A, "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x5B, "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5C, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5D, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x5E, "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x60, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x61, "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x62, "NamedSoundEffect");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x63, "StopSound");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x64, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x65, "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x66, "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x67, "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x68, "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x69, "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x6A, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x6B, "UpdateEnabledFeatures");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x6C, "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x6D, "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x6E, "Tags");
// ---- Removed in 1.9
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetData", "WindowData", "SPacketWindowProperty");
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x15, "CookieRequest");
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x16, "Cooldown", "SetCooldown", "SPacketCooldown");
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x17, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x18, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x19, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1B, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1C, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1D, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1E, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x1F, "EntityPositionSync");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x20, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x22, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x23, "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x24, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x25, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x26, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x27, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x28, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x29, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x2A, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x2B, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x2C, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2D, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x30, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x31, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x32, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x33, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x34, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x35, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x36, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x37, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x38, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3D, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3E, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3F, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x41, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x42, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x43, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x44, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x45, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x46, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x47, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x48, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x49, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x4A, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x4B, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x4C, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x4D, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x4E, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x4F, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x50, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x51, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x52, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x53, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x54, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x55, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x56, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x57, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x58, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x59, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x5A, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5B, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x5C, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x5D, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x5E, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x60, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x61, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x62, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x63, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x64, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x65, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x66, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x67, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x68, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x69, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x6A, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x6B, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x6C, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x6D, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x6E, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x6F, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x70, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x71, "StoreCookie");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x72, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x73, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x74, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x75, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x76, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x77, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x78, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x79, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x7A, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x7B, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x7C, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x7D, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x7E, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x7F, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x80, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x81, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x82, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x83, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x84, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x85, "ShowDialog");
/**
* @deprecated Removed in 1.9
@@ -233,8 +260,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}
*/
@@ -259,8 +284,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}
*/
@@ -268,8 +291,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
*/
@@ -346,25 +367,56 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x0C, "ChatPreview");
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 240, "ChatPreview");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType PLAYER_CHAT_HEADER = new PacketType(PROTOCOL, SENDER, 0x32, "PlayerChatHeader");
public static final PacketType PLAYER_CHAT_HEADER = new PacketType(PROTOCOL, SENDER, 239, "PlayerChatHeader");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType SET_DISPLAY_CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x4E, "SetDisplayChatPreview");
public static final PacketType SET_DISPLAY_CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 238, "SetDisplayChatPreview");
/**
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CUSTOM_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x16, "CustomSoundEffect", "SPacketCustomSound");
public static final PacketType CUSTOM_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 237, "CustomSoundEffect", "SPacketCustomSound");
/**
* @deprecated Removed in 1.20.2: moved to configuration phase packets
*/
@Deprecated
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 236, "UpdateEnabledFeatures");
/**
* @deprecated Removed in 1.20.2
*/
@Deprecated
public static final PacketType NAMED_ENTITY_SPAWN = new PacketType(PROTOCOL, SENDER, 235, "NamedEntitySpawn", "SPacketSpawnPlayer");
/**
* @deprecated Removed in 1.20.4: replaced with new packets for removing and sending resource packs
*/
@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();
@@ -386,57 +438,72 @@ 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 CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x07, "ClientCommand", "CPacketClientStatus");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x08, "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x09, "TabComplete", "CPacketTabComplete");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x0A, "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0B, "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0C, "CloseWindow", "CPacketCloseWindow");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x0D, "CustomPayload", "CPacketCustomPayload");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x0E, "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x0F, "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x10, "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x11, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x12, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x13, "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x14, "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x15, "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x16, "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x17, "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x18, "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x19, "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x1A, "PickItem");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x1B, "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x1C, "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x1D, "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x1E, "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x1F, "SteerVehicle", "CPacketInput");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x20, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x21, "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x22, "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x23, "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x24, "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x25, "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x26, "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x27, "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x28, "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x29, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x2A, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x2B, "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x2C, "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x2D, "Struct");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x2E, "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2F, "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x30, "Spectate", "CPacketSpectate");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x31, "UseItem", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType BLOCK_PLACE = new PacketType(PROTOCOL, SENDER, 0x32, "BlockPlace", "CPacketPlayerTryUseItem");
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "AcceptTeleportation", "TeleportAccept", "CPacketConfirmTeleport");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x02, "SelectBundleItem");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x03, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeGameMode");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x06, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x08, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x09, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0A, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0B, "ClientCommand", "CPacketClientStatus");
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientTickEnd");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0E, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0F, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x10, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x14, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x15, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x17, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x18, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x19, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1A, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1B, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1C, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1D, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x21, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x23, "PickItemFromBlock");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromEntity", "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x25, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x26, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x27, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerLoaded");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2C, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2D, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x2E, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x2F, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x30, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x31, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x32, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x33, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x34, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x35, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x36, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x37, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x38, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x39, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3A, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3B, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x3C, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x3D, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType TEST_INSTANCE_BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x3E, "TestInstanceBlockAction");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x3F, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x40, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x41, "CustomClickAction");
/**
* @deprecated Removed in 1.17
@@ -454,7 +521,13 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @deprecated Removed in 1.19.3
*/
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 0x06, "ChatPreview");
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 253, "ChatPreview");
/**
* @deprecated Renamed to USE_ITEM_ON
*/
@Deprecated
public static final PacketType BLOCK_PLACE = USE_ITEM_ON.clone();
private static final Client INSTANCE = new Client();
@@ -489,9 +562,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}
@@ -520,9 +593,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();
@@ -557,11 +630,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();
@@ -583,9 +657,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();
@@ -605,6 +681,82 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
}
}
/**
* Packets sent and received during client configuration.
* @author Pasqual Koschmieder
*/
public static class Configuration {
static final Protocol PROTOCOL = Protocol.CONFIGURATION;
/**
* Outgoing packets.
*/
public static class Server extends PacketTypeEnum {
private static final Sender SENDER = Sender.SERVER;
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 STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0A, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0B, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0C, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x0F, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x10, "ServerLinks");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x11, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x12, "ShowDialog");
/**
* @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;
}
}
/**
* Incoming packets.
*/
public static class Client extends PacketTypeEnum {
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType CLIENT_INFORMATION = new PacketType(PROTOCOL, SENDER, 0x00, "ClientInformation");
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");
private static final Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(); }
public static Client getInstance() {
return INSTANCE;
}
}
}
/**
* Represents the different protocol or connection states.
* @author Kristian
@@ -614,11 +766,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
PLAY("Play", "game"),
STATUS("Status", "status"),
LOGIN("Login", "login"),
/**
* Only for packets removed in Minecraft 1.7.2
*/
LEGACY("", "");
CONFIGURATION("Configuration", "configuration"),
UNKNOWN("", "");
private final String packetName;
private final String mojangName;
@@ -634,13 +783,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;
default:
throw new IllegalArgumentException("Unrecognized vanilla enum " + vanilla);
case "CONFIGURATION": return CONFIGURATION;
default: return UNKNOWN;
}
}
@@ -731,7 +884,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private boolean forceAsync;
private boolean dynamic;
private int hashCode;
private transient int hashCode;
/**
* Retrieve the current packet/legacy lookup.
@@ -747,7 +900,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;
}
@@ -854,6 +1009,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";
@@ -924,7 +1087,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+
@@ -942,7 +1106,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);
@@ -950,14 +1114,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;
}
@@ -967,7 +1134,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;
}
@@ -981,8 +1148,11 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
/**
* Lookup a packet type from a packet class.
* @param packetClass - the packet class.
* @return The corresponding packet type, or NULL if not found.
* @return The corresponding packet type, never null.
* @throws IllegalArgumentException if the given packet class is not a registered packet.
* @deprecated since 1.20.2 there are packet classes that are shared between protocol states, therefore the result can be invalid.
*/
@Deprecated
public static PacketType fromClass(Class<?> packetClass) {
PacketType type = PacketRegistry.getPacketType(packetClass);
@@ -1086,7 +1256,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
this.sender = Preconditions.checkNotNull(sender, "sender cannot be NULL");
this.currentId = currentId;
this.version = version;
this.classNames = new ArrayList<>();
for (String classname : names) {
if (isMcpPacketName(classname)) { // Minecraft MCP packets
@@ -1094,6 +1264,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);
}
@@ -60,7 +65,9 @@ class PacketTypeLookup {
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<>();
public final Map<String, PacketType> CONFIGURATION_CLIENT = new ConcurrentHashMap<>();
public final Map<String, PacketType> CONFIGURATION_SERVER = new ConcurrentHashMap<>();
/**
* Retrieve the correct integer map for a specific protocol and sender.
* @param protocol - the protocol.
@@ -77,17 +84,14 @@ 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);
}
}
}
// 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();
@@ -119,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;
}
/**
@@ -138,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;
}
/**
@@ -168,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,21 +37,19 @@ 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;
@@ -76,8 +74,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 +162,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 +305,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 +321,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 +364,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 +393,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);
}
}
@@ -530,6 +504,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 +546,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.1";
public static final String MAXIMUM_MINECRAFT_VERSION = "1.21.8";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.1) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.21.8) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-06-12";
/**
* Plugins that are currently incompatible with ProtocolLib.
*/
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
public static final String MINECRAFT_LAST_RELEASE_DATE = "2025-07-17";
private static Plugin plugin;
private static ProtocolConfig config;
@@ -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,21 @@ 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.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 +49,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 +83,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 +129,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 +144,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 +201,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 +227,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;
}
}
}
@@ -290,12 +284,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);
}
}
}
@@ -346,8 +340,7 @@ 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());
}
/**
@@ -388,8 +381,8 @@ public class AsyncFilterManager implements AsynchronousManager {
playerSendingHandler.cleanupAll();
timeoutListeners.clear();
serverTimeoutListeners = null;
clientTimeoutListeners = null;
outboundTimeoutListeners = null;
inboundTimeoutListeners = null;
}
@Override
@@ -413,6 +406,23 @@ public class AsyncFilterManager implements AsynchronousManager {
// Only send if the packet is ready
if (marker.decrementProcessingDelay() == 0) {
// Now, get the next non-cancelled listener
if (!marker.hasExpired()) {
for (; marker.getListenerTraversal().hasNext(); ) {
AsyncListenerHandler handler = marker.getListenerTraversal().next();
if (!handler.isCancelled()) {
marker.incrementProcessingDelay();
handler.enqueuePacket(packet);
return;
}
}
}
// There are no more listeners - queue the packet for transmission
signalFreeProcessingSlot(packet, onMainThread);
PacketSendingQueue queue = getSendingQueue(packet, false);
// No need to create a new queue if the player has logged out
@@ -450,11 +460,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);
}
/**
@@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
@@ -32,14 +34,11 @@ 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;
import org.bukkit.plugin.Plugin;
/**
* Represents a handler for an asynchronous event.
* <p>
@@ -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) {
listener.getPlugin().getServer().getScheduler().runTaskAsynchronously(listener.getPlugin(), runnable);
filterManager.getScheduler().runTaskAsync(runnable);
}
/**
@@ -600,53 +596,22 @@ 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);
}
// Now, get the next non-cancelled listener
if (!marker.hasExpired()) {
for (; marker.getListenerTraversal().hasNext(); ) {
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
if (!handler.isCancelled()) {
handler.enqueuePacket(packet);
return;
}
}
}
// There are no more listeners - queue the packet for transmission
filterManager.signalFreeProcessingSlot(packet);
// Note that listeners can opt to delay the packet transmission
filterManager.signalPacketTransmission(packet);
}
@@ -31,7 +31,6 @@ import com.comphenix.protocol.PacketType;
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.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -70,7 +69,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* Current list of async packet listeners.
*/
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
private transient Iterator<AsyncListenerHandler> listenerTraversal;
// Timeout handling
private long initialTime;
@@ -366,7 +365,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 +373,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;
}
@@ -17,18 +17,20 @@
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.ImmutableSet;
import com.google.common.collect.MinMaxPriorityQueue;
@@ -37,7 +39,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 +68,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 +143,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 +190,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 : 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();
@@ -154,7 +150,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 +161,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 +175,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 +187,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 +198,7 @@ class PlayerSendingHandler {
*/
public void trySendServerPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
queue.getServerQueue().trySendPackets(onMainThread);
queue.getOutboundQueue().trySendPackets(onMainThread);
}
}
@@ -212,7 +208,7 @@ class PlayerSendingHandler {
*/
public void trySendClientPackets(boolean onMainThread) {
for (QueueContainer queue : playerSendingQueues.values()) {
queue.getClientQueue().trySendPackets(onMainThread);
queue.getInboundQueue().trySendPackets(onMainThread);
}
}
@@ -224,7 +220,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 +232,7 @@ class PlayerSendingHandler {
List<PacketSendingQueue> result = new ArrayList<>();
for (QueueContainer queue : playerSendingQueues.values())
result.add(queue.getClientQueue());
result.add(queue.getInboundQueue());
return result;
}
@@ -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;
}
}
@@ -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,22 +1,17 @@
package com.comphenix.protocol.events;
import javax.annotation.Nonnull;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.*;
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 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 javax.annotation.Nonnull;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
@@ -31,6 +26,50 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
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.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.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;
protected transient StructureModifier<Object> structureModifier;
@@ -410,7 +449,7 @@ public abstract class AbstractStructure {
}
/**
* Retrieves a read/write structure for collections of watchable objects.
* Retrieves a read/write structure for collections of watchable objects before Minecraft 1.19.3.
* <p>
* This modifier will automatically marshal between the visible WrappedWatchableObject and the
* internal Minecraft WatchableObject.
@@ -424,7 +463,7 @@ public abstract class AbstractStructure {
}
/**
* Retrieves a read/write structure for collections of data values.
* Retrieves a read/write structure for collections of data values for Minecraft 1.19.3 or later.
* @return A modifier for data values.
*/
public StructureModifier<List<WrappedDataValue>> getDataValueCollectionModifier() {
@@ -770,6 +809,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(),
@@ -794,11 +837,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
);
}
@@ -858,6 +910,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.
@@ -868,6 +969,16 @@ public abstract class AbstractStructure {
MinecraftKey.getConverter());
}
/**
* Retrieve a read/write structure for custom packet payloads (available since Minecraft 1.20.2).
* @return A modifier for CustomPacketPayloads fields.
*/
public StructureModifier<CustomPacketPayloadWrapper> getCustomPacketPayloads() {
return structureModifier.withType(
CustomPacketPayloadWrapper.getCustomPacketPayloadClass(),
CustomPacketPayloadWrapper.getConverter());
}
/**
* Retrieve a read/write structure for dimension IDs in 1.13.1+
* @return A modifier for dimension IDs
@@ -1063,6 +1174,17 @@ public abstract class AbstractStructure {
);
}
/**
* 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
@@ -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);
}
@@ -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;
}
@@ -17,10 +17,25 @@
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.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;
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,22 +50,18 @@ 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 java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
/**
* Represents a player object that can be serialized by Java.
@@ -141,6 +152,16 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return lastSeen;
}
@Override
public Location getRespawnLocation() {
return null;
}
@Override
public Location getLocation() {
return null;
}
// TODO do we need to implement this?
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException {
@@ -240,6 +261,24 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return banned;
}
@Nullable
public BanEntry<PlayerProfile> ban(@Nullable String s, @Nullable Date date, @Nullable String s1) {
setBanned(true);
return null;
}
@Nullable
public BanEntry<PlayerProfile> ban(@Nullable String s, @Nullable Instant instant, @Nullable String s1) {
setBanned(true);
return null;
}
@Nullable
public BanEntry<PlayerProfile> ban(@Nullable String s, @Nullable Duration duration, @Nullable String s1) {
setBanned(true);
return null;
}
public void setBanned(boolean banned) {
this.banned = banned;
}
@@ -22,6 +22,7 @@ package com.comphenix.protocol.injector;
*
* @author Kristian
*/
@Deprecated
public enum GamePhase {
/**
* Only listen for packets sent or received before a player has logged in.
@@ -25,27 +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.
*/
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,38 @@ 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.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 +67,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 +98,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 +114,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();
}
}
@@ -164,7 +156,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
// 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()) {
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
NetworkMarker copy = marker; // okay fine
ProtocolLibrary.getScheduler().scheduleSyncDelayedTask(
() -> this.sendServerPacket(receiver, packet, copy, false), 1L);
@@ -173,14 +165,14 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
// 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);
this.outboundListeners.invoke(event, ListenerPriority.MONITOR);
// update the marker of the event without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event);
}
// process outbound
this.playerInjectionHandler.sendServerPacket(receiver, packet, marker, filters);
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
}
}
@@ -192,13 +184,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);
}
}
@@ -216,7 +202,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
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()) {
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
ProtocolLibrary.getScheduler().runTask(
() -> this.receiveClientPacket(sender, packet, marker, filters));
return;
@@ -226,7 +212,8 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
// check to which listeners we need to post the packet
if (filters) {
// post to all listeners
PacketEvent event = this.packetInjector.packetReceived(packet, sender);
PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender);
this.invokeInboundPacketListeners(event);
if (event.isCancelled()) {
return;
}
@@ -235,17 +222,17 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
nmsPacket = event.getPacket().getHandle();
} else {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
this.inboundListeners.invokePacketRecieving(this.reporter, event, ListenerPriority.MONITOR);
this.inboundListeners.invoke(event, ListenerPriority.MONITOR);
}
// post to the player inject, reset our cancel state change
this.playerInjectionHandler.receiveClientPacket(sender, nmsPacket);
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 +307,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 +333,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 +389,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
@@ -460,18 +433,17 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@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 +466,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,34 +489,35 @@ 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 void postPacketToListeners(SortedPacketListenerList listeners, PacketEvent event, boolean outbound) {
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)) {
@@ -549,11 +525,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
}
// 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 +559,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,40 @@
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.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 +64,147 @@ 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);
}
public static boolean canCreateInstance(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, x ->
Optional.ofNullable(determineBestCreator(clazz)));
return creator.isPresent();
}
public static Object newInstance(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, x ->
Optional.ofNullable(determineBestCreator(clazz)));
if (!creator.isPresent()) {
throw new IllegalArgumentException("Cannot create instance of " + clazz);
}
return creator.get().get();
}
static Supplier<Object> determineBestCreator(Class<?> clazz) {
// certain packets are singletons which can't really be created
if (MinecraftReflection.isPacketClass(clazz)) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(clazz, false);
List<Field> singletons = fuzzy.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(clazz)
.requireModifier(Modifier.STATIC)
.requireModifier(Modifier.PUBLIC)
.build());
if (singletons.size() == 1) {
FieldAccessor accessor = Accessors.getFieldAccessor(singletons.get(0));
try {
accessor.get(null);
return () -> accessor.get(null);
} catch (Exception ignored) {
}
}
}
try {
InstanceCreator creator = InstanceCreator.forClass(clazz);
if (creator.get() != null) {
return creator;
}
} catch (Exception ignored) {
}
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(clazz);
// 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);
}
/**
@@ -118,7 +222,9 @@ public class StructureCache {
*
* @param packetType - packet type.
* @return A structure modifier.
* @deprecated use {@link #getStructure(PacketType)} instead.
*/
@Deprecated
public static StructureModifier<Object> getStructure(Class<?> packetType) {
// Get the ID from the class
PacketType type = PacketRegistry.getPacketType(packetType);
@@ -154,7 +260,61 @@ 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() {
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();
// 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()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(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());
}
}
/**
@@ -172,43 +332,13 @@ public class StructureCache {
}
try {
// 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.returns(MinecraftReflection.getNBTCompoundClass())
.and(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())))
.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);
// 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);
// worked
initTrickDataSerializer();
return true;
} catch (Exception ignored) {
} finally {
TRICK_TRIED = true;
}
// didn't work
return false;
}
}
@@ -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());
}
}
}
@@ -0,0 +1,82 @@
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;
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
Iterable<PacketContainer> packets = event.getPacket().getPacketBundles().read(0);
List<PacketContainer> outPackets = new ArrayList<>();
for (PacketContainer subPacket : packets) {
// ignore null packets as the will throw an error in the packet encoder
if (subPacket == null) {
continue;
}
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(),
event.getPlayer());
super.invoke(subPacketEvent, priorityFilter);
// if the packet has been cancelled, the packet will not be add to the bundle
if (subPacketEvent.isCancelled()) {
continue;
}
PacketContainer packet = subPacketEvent.getPacket();
if (packet == null || packet.getHandle() == null) {
// super.invoke() should prevent us from getting new null packet so we just ignore it here
continue;
} else {
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);
}
}
}
@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,8 +1,12 @@
package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
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.
@@ -11,6 +15,8 @@ import org.bukkit.entity.Player;
*/
public interface Injector {
SocketAddress getAddress();
/**
* Retrieve the current protocol version of the player.
*
@@ -22,12 +28,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.
@@ -41,32 +43,22 @@ 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);
void sendWirePacket(WirePacket packet);
void disconnect(String message);
/**
* Retrieve the current protocol state.
* Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different
* protocol states.
*
* @param sender the side for which the state should be resolved.
* @return The current protocol.
*/
Protocol getCurrentProtocol();
/**
* 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);
Protocol getCurrentProtocol(PacketType.Sender sender);
/**
* Retrieve the current player or temporary player associated with the injector.
@@ -82,7 +74,7 @@ public interface Injector {
*/
void setPlayer(Player player);
void disconnect(String message);
boolean isConnected();
/**
* Determine if the channel has already been injected.
@@ -0,0 +1,209 @@
package com.comphenix.protocol.injector.netty.channel;
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 {
public static final BiFunction<Channel, PacketType.Sender, PacketType.Protocol> PROTOCOL_RESOLVER;
static {
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
List<Field> attributeKeys = FuzzyReflection.fromClass(networkManagerClass, true).getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(AttributeKey.class)
.requireModifier(Modifier.STATIC)
.declaringClassExactType(networkManagerClass)
.build());
BiFunction<Channel, PacketType.Sender, Object> baseResolver = null;
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);
} else if (attributeKeys.size() > 1) {
// most likely 1.20.2+: 1 protocol key per protocol direction
AttributeKey<Object> serverBoundKey = null;
AttributeKey<Object> clientBoundKey = null;
for (Field keyField : attributeKeys) {
AttributeKey<Object> key = (AttributeKey<Object>) Accessors.getFieldAccessor(keyField).get(null);
if (key.name().equals("protocol")) {
// legacy (pre 1.20.2 name) - fall back to the old behaviour
baseResolver = new Pre1_20_2DirectResolver(key);
break;
}
if (key.name().contains("protocol")) {
// one of the two protocol keys for 1.20.2
if (key.name().contains("server")) {
serverBoundKey = key;
} else {
clientBoundKey = key;
}
}
}
if (baseResolver == null) {
if ((serverBoundKey == null || clientBoundKey == null)) {
// neither pre 1.20.2 key nor 1.20.2+ keys are available
throw new ExceptionInInitializerError("Unable to resolve protocol state attribute keys");
} else {
baseResolver = new Post1_20_2WrappedResolver(serverBoundKey, clientBoundKey);
}
}
} else {
throw new ExceptionInInitializerError("Unable to resolve protocol state attribute key(s)");
}
// decorate the base resolver by wrapping its return value into our packet type value
PROTOCOL_RESOLVER = baseResolver.andThen(protocol -> PacketType.Protocol.fromVanilla((Enum<?>) protocol));
}
private static final class Pre1_20_2DirectResolver implements BiFunction<Channel, PacketType.Sender, Object> {
private final AttributeKey<Object> attributeKey;
public Pre1_20_2DirectResolver(AttributeKey<Object> attributeKey) {
this.attributeKey = attributeKey;
}
@Override
public Object apply(Channel channel, PacketType.Sender sender) {
return channel.attr(this.attributeKey).get();
}
}
private static final class Post1_20_2WrappedResolver implements BiFunction<Channel, PacketType.Sender, Object> {
private final AttributeKey<Object> serverBoundKey;
private final AttributeKey<Object> clientBoundKey;
// lazy initialized when needed
private FieldAccessor protocolAccessor;
public Post1_20_2WrappedResolver(AttributeKey<Object> serverBoundKey, AttributeKey<Object> clientBoundKey) {
this.serverBoundKey = serverBoundKey;
this.clientBoundKey = clientBoundKey;
}
@Override
public Object apply(Channel channel, PacketType.Sender sender) {
AttributeKey<Object> key = this.getKeyForSender(sender);
Object codecData = channel.attr(key).get();
if (codecData == null) {
return null;
}
FieldAccessor protocolAccessor = this.getProtocolAccessor(codecData.getClass());
return protocolAccessor.get(codecData);
}
private AttributeKey<Object> getKeyForSender(PacketType.Sender sender) {
switch (sender) {
case SERVER:
return this.clientBoundKey;
case CLIENT:
return this.serverBoundKey;
default:
throw new IllegalArgumentException("Illegal packet sender " + sender.name());
}
}
private FieldAccessor getProtocolAccessor(Class<?> codecClass) {
if (this.protocolAccessor == null) {
Class<?> enumProtocolClass = MinecraftReflection.getEnumProtocolClass();
this.protocolAccessor = Accessors.getFieldAccessor(codecClass, enumProtocolClass, true);
}
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,9 +1,14 @@
package com.comphenix.protocol.injector.netty.channel;
import java.net.SocketAddress;
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 {
@@ -15,18 +20,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
@@ -34,27 +42,26 @@ 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 Protocol getCurrentProtocol() {
public void sendWirePacket(WirePacket packet) {
}
@Override
public void disconnect(String message) {
}
@Override
public Protocol getCurrentProtocol(PacketType.Sender sender) {
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;
@@ -66,7 +73,8 @@ final class EmptyInjector implements Injector {
}
@Override
public void disconnect(String message) {
public boolean isConnected() {
return false;
}
@Override
@@ -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);
}
}
@@ -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);
@@ -2,96 +2,89 @@ package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
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.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.*;
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;
@@ -101,46 +94,44 @@ 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 Player player;
// lazy initialized fields, if we don't need them we don't bother about them
private Object playerConnection;
private FieldAccessor protocolAccessor;
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;
// 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());
@@ -149,112 +140,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).remove();
// cleanup
this.savedMarkers.clear();
this.skippedPackets.clear();
@@ -265,9 +254,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;
}
@@ -277,17 +266,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)
@@ -297,91 +281,99 @@ 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 {
// try to invoke the method, this should normally not fail
this.listenerInvoker.read(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() {
// ensure that the accessor to the protocol field is available
if (this.protocolAccessor == null) {
this.protocolAccessor = Accessors.getFieldAccessor(
this.networkManager.getClass(),
MinecraftReflection.getEnumProtocolClass(),
true);
}
Object nmsProtocol = this.protocolAccessor.get(this.networkManager);
return Protocol.fromVanilla((Enum<?>) nmsProtocol);
}
@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);
}
public Protocol getCurrentProtocol(PacketType.Sender sender) {
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) {
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);
this.player = Bukkit.getPlayerExact(this.playerName);
if (this.player != null) {
this.injectionFactory.cacheInjector(this.player, this);
}
}
// either we resolved it or we didn't...
return this.resolvedPlayer;
return this.player;
}
@Override
public void setPlayer(Player player) {
this.resolvedPlayer = player;
this.injectionFactory.invalidate(this.player, this.playerName);
this.player = player;
this.playerName = player.getName();
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
@@ -391,138 +383,97 @@ 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;
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);
} else {
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
username = profile != null ? profile.getName() : null;
}
// get the username from the packet
String username = (String) LOGIN_PROFILE_ACCESSOR.get(packet);
// 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 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;
}
@@ -547,30 +498,41 @@ public class NettyChannelInjector implements Injector {
return action;
}
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.channelListener.hasListener(packet.getClass()) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
if (!this.listenerManager.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
return action;
}
// ensure that we are on the main thread if we need to
if (this.channelListener.hasMainThreadListener(packet.getClass()) && !this.server.isPrimaryThread()) {
if (this.listenerManager.hasMainThreadListener(packetType) && !Bukkit.isPrimaryThread()) {
// 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, null, 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
PacketContainer packetContainer = new PacketContainer(packetType, packet);
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);
}
@@ -620,37 +582,29 @@ public class NettyChannelInjector implements Injector {
}
private FieldAccessor lookupPacketAccessor(Object action) {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), key -> {
try {
// find the field
Field packetField = FuzzyReflection.fromClass(clazz, true).getField(FuzzyFieldContract
.newBuilder()
Field packetField = FuzzyReflection.fromClass(key, true).getField(
FuzzyFieldContract.newBuilder()
.typeSuperOf(MinecraftReflection.getPacketClass())
.build());
return Accessors.getFieldAccessor(packetField);
} catch (IllegalArgumentException exception) {
// no such field found :(
return NO_OP_ACCESSOR;
// return noop accessor because computeIfAbsent interprets null as a missing key
return FieldAccessor.NO_OP_ACCESSOR;
}
});
}
private Object getPlayerConnection() {
// resolve the player connection if needed
if (this.playerConnection == null) {
Player target = this.getPlayer();
if (target == null) {
return null;
}
this.playerConnection = MinecraftFields.getPlayerConnection(target);
}
// cannot be null at this point
return this.playerConnection;
private void ensureInEventLoop(Runnable runnable) {
this.ensureInEventLoop(this.channel.eventLoop(), runnable);
}
public Channel getWrappedChannel() {
return this.wrappedChannel;
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
} else {
eventLoop.execute(runnable);
}
}
}
@@ -1,44 +0,0 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
final class NettyChannelMinimalInjector implements MinimalInjector {
private final NettyChannelInjector injector;
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
this.injector = injector;
}
@Override
public SocketAddress getAddress() {
return this.injector.getWrappedChannel().remoteAddress();
}
@Override
public void disconnect(String message) {
this.injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
this.injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return this.injector.getPlayer();
}
@Override
public boolean isConnected() {
return this.injector.getWrappedChannel().isActive();
}
public NettyChannelInjector getInjector() {
return this.injector;
}
}
@@ -1,5 +1,15 @@
package com.comphenix.protocol.injector.netty.channel;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
@@ -10,21 +20,12 @@ 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;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
* 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 implements EventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
@@ -40,7 +41,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 +56,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 +69,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();
@@ -0,0 +1,237 @@
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;
import io.netty.channel.ChannelHandlerContext;
/**
* This class facilitates the invocation of methods on the current packet listener.
* It attempts to execute the <code>send</code>, <code>read</code>, and <code>disconnect</code>
* methods and, upon failure (either due to the absence of the method or the packet
* listener being of an incorrect type), it delegates the call to the network manager.
*
* <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_READ = getNetworkManagerRead();
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 getNetworkManagerRead() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true);
Method read = networkManager
.getMethodByParameters("read", ChannelHandlerContext.class, MinecraftReflection.getPacketClass());
return Accessors.getMethodAccessor(read);
}
private static MethodAccessor getNetworkManagerDisconnect() {
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
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);
}
}
/**
* Reads a packet directly using the network manager.
*
* @param packet The packet to be read.
*/
public void read(Object packet) {
NETWORK_MANAGER_READ.invoke(this.networkManager, null, packet);
}
/**
* Disconnects the player using the current packet listener if available and valid; otherwise,
* falls back to the network manager.
*
* @param reason The reason for the disconnection.
*/
public void disconnect(String reason) {
Object packetListener = this.getPacketListener();
boolean hasPacketListener = PACKET_LISTENER_DISCONNECT != null && packetListener != null;
Object wrapped = reason;
if (!hasPacketListener || DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT) {
wrapped = WrappedChatComponent.fromText(reason).getHandle();
}
if (hasPacketListener) {
PACKET_LISTENER_DISCONNECT.invoke(packetListener, wrapped);
} else {
NETWORK_MANAGER_DISCONNECT.invoke(this.networkManager, wrapped);
}
}
}
@@ -1,9 +1,10 @@
package com.comphenix.protocol.injector.netty.manager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -11,18 +12,15 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private final InjectionFactory factory;
private final NetworkManagerInjector listener;
private final TemporaryPlayerFactory playerFactory;
private final ErrorReporter errorReporter;
private final InjectionFactory injectionFactory;
public InjectionChannelInboundHandler(
InjectionFactory factory,
NetworkManagerInjector listener,
TemporaryPlayerFactory playerFactory
ErrorReporter errorReporter,
InjectionFactory injectionFactory
) {
this.factory = factory;
this.listener = listener;
this.playerFactory = playerFactory;
this.errorReporter = errorReporter;
this.injectionFactory = injectionFactory;
}
@Override
@@ -35,11 +33,11 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
// We're first checking if the factory is still open, just might be a delay between accepting the connection
// (which adds this handler to the pipeline) and the actual channelActive call. If the injector is closed at
// that point we might accidentally trigger class loads which result in exceptions.
if (!this.factory.isClosed()) {
if (!this.injectionFactory.isClosed()) {
try {
this.factory.fromChannel(ctx.channel(), this.listener, this.playerFactory).inject();
this.injectionFactory.fromChannel(ctx.channel()).inject();
} catch (Exception exception) {
this.listener.getReporter().reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
this.errorReporter.reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
.messageParam(ctx.channel())
.error(exception)
.build());
@@ -52,7 +50,7 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overheat
// we do it this way to prevent the lookup overhead
return true;
}
}
@@ -28,7 +28,7 @@ final class InjectionChannelInitializer extends ChannelInboundHandlerAdapter {
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overheat
// we do it this way to prevent the lookup overhead
return true;
}
}
@@ -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;
@@ -7,149 +7,54 @@ 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.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.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.Util;
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.ChannelFuture;
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 InjectionChannelInitializer pipelineInjectorHandler;
private boolean debug = false;
// 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.errorReporter,
this.injectionFactory);
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);
}
@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
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(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;
}
@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
PacketContainer container = new PacketContainer(PacketRegistry.getPacketType(packetClass), 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 Injector getInjector(Player player) {
return this.injectionFactory.fromPlayer(player);
}
@SuppressWarnings("unchecked")
@@ -238,7 +143,6 @@ public class NetworkManagerInjector implements ChannelListener {
// just reset to the list we wrapped originally
ListeningList ourList = (ListeningList) currentFieldValue;
List<Object> original = ourList.getOriginal();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (original) {
// revert the injection from all values of the list
ourList.unProcessAll();
@@ -251,12 +155,4 @@ public class NetworkManagerInjector implements ChannelListener {
this.overriddenLists.clear();
this.injectionFactory.close();
}
public PacketInjector getPacketInjector() {
return this.packetInjector;
}
public PlayerInjectionHandler getPlayerInjectionHandler() {
return this.playerInjectionHandler;
}
}
@@ -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);
}
}
@@ -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,44 +19,77 @@ 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;
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;
}
}
/**
* 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<?> packetClass, Sender sender, WrappedStreamCodec codec) {
typeToClass.put(type, Optional.of(packetClass));
classToType.put(packetClass, type);
if (codec != null) {
classToCodec.put(packetClass, codec);
}
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(packetClass, type);
public void registerPacket(PacketType type, Class<?> clazz, Sender sender) {
typeToClass.put(type, Optional.of(clazz));
classToType.put(clazz, type);
if (sender == Sender.CLIENT) {
clientPackets.add(type);
} else {
@@ -70,6 +103,7 @@ public class PacketRegistry {
/**
* Determine if the current register is outdated.
*
* @return TRUE if it is, FALSE otherwise.
*/
public boolean isOutdated() {
@@ -115,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
@@ -150,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
@@ -158,18 +193,17 @@ public class PacketRegistry {
final Map<Object, Map<Class<?>, Integer>> clientMaps = new LinkedHashMap<>();
Register result = new Register();
Field mainMapField = null;
Field packetMapField = null;
Field holderClassField = null; // only 1.20.2+
// Iterate through the protocols
for (Object protocol : protocols) {
if (mainMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(protocol.getClass(), true);
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
mainMapField = fuzzy.getField(FuzzyFieldContract.newBuilder().banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL).typeDerivedOf(Map.class).build());
mainMapField.setAccessible(true);
}
@@ -184,19 +218,40 @@ public class PacketRegistry {
for (Map.Entry<Object, Object> entry : directionMap.entrySet()) {
Object holder = entry.getValue();
if (packetMapField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(holder.getClass(), true);
packetMapField = fuzzy.getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.requireModifier(Modifier.FINAL)
.typeDerivedOf(Map.class)
.build());
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.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.setAccessible(true);
}
Map<Class<?>, Integer> packetMap;
Object holderInstance = holder;
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
try {
holderInstance = holderClassField.get(holder);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
}
Map<Class<?>, Integer> packetMap;
try {
packetMap = (Map<Class<?>, Integer>) packetMapField.get(holder);
packetMap = (Map<Class<?>, Integer>) packetMapField.get(holderInstance);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to access packet map", ex);
}
@@ -213,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;
@@ -237,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());
@@ -249,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();
@@ -257,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);
}
@@ -278,7 +340,7 @@ public class PacketRegistry {
/**
* Initializes the packet registry.
*/
private static void initialize() {
static void initialize() {
if (INITIALIZED) {
return;
}
@@ -288,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();
}
@@ -298,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.
*/
@@ -310,6 +387,7 @@ public class PacketRegistry {
/**
* Retrieve every known and supported server packet type.
*
* @return Every server packet type.
*/
public static Set<PacketType> getServerPacketTypes() {
@@ -318,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() {
@@ -338,7 +417,8 @@ public class PacketRegistry {
&& !Modifier.isAbstract(clazz.getModifiers())) {
return clazz;
}
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
}
return null;
@@ -347,8 +427,9 @@ public class PacketRegistry {
/**
* 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
*/
@@ -363,7 +444,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;
@@ -372,21 +453,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
*/
@@ -397,21 +482,54 @@ 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
public static PacketType getPacketType(Class<?> packet) {
initialize();
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.
*
* @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.
*/
public static PacketType getPacketType(PacketType.Protocol protocol, Class<?> packet) {
initialize();
if (MinecraftReflection.isBundlePacket(packet)) {
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.
@@ -421,4 +539,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);
}
});
}
}
@@ -0,0 +1,102 @@
package com.comphenix.protocol.injector.packet.internal;
import java.util.Objects;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AbstractWrapper;
/**
* Class only works for version 1.20.5+
*/
public class ProtocolInfoWrapper extends AbstractWrapper {
private static final @NotNull Class<?> SIMPLE_UNBOUND_PROTOCOL_CLASS;
private static final @NotNull MethodAccessor BIND_SIMPLE_ACCESSOR;
private static final @Nullable Class<?> UNBOUND_PROTOCOL_CLASS;
private static final @Nullable MethodAccessor BIND_CONTEXT_ACCESSOR;
private static final @NotNull MethodAccessor ID_ACCESSOR;
private static final @NotNull MethodAccessor FLOW_ACCESSOR;
private static final @NotNull MethodAccessor CODEC_ACCESSOR;
static {
SIMPLE_UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getMinecraftClass(
"network.protocol.SimpleUnboundProtocol" /* 1.21.5+ */,
"network.protocol.UnboundProtocol" /* 1.21.8+ Alternative */,
"network.ProtocolInfo$a" /* Spigot Mappings */,
"network.ProtocolInfo$Unbound" /* Mojang Mappings */);
BIND_SIMPLE_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(SIMPLE_UNBOUND_PROTOCOL_CLASS)
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class }));
UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getNullableNMS("network.protocol.UnboundProtocol");
if (UNBOUND_PROTOCOL_CLASS != null) {
BIND_CONTEXT_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(UNBOUND_PROTOCOL_CLASS)
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class, Object.class }));
} else {
BIND_CONTEXT_ACCESSOR = null;
}
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(MinecraftReflection.getProtocolInfoClass());
ID_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
FLOW_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("flow", MinecraftReflection.getPacketFlowClass(), new Class[0]));
CODEC_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
.getMethodByReturnTypeAndParameters("codec", MinecraftReflection.getStreamCodecClass(), new Class[0]));
}
public static boolean isUnboundProtocol(Object protocol) {
return SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol) || (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol));
}
public static ProtocolInfoWrapper fromUnbound(Object protocol, Object context) {
// bind to identity function in-order to map directly to FriendlyByteBuf instead of ByteBuf
if (SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
Object protocolInfo = BIND_SIMPLE_ACCESSOR.invoke(protocol, Function.identity());
return new ProtocolInfoWrapper(protocolInfo);
}
if (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
Object protocolInfo = BIND_CONTEXT_ACCESSOR.invoke(protocol, Function.identity(), Objects.requireNonNull(context));
return new ProtocolInfoWrapper(protocolInfo);
}
throw new RuntimeException("Unknown protocol implementation: " + protocol);
}
private ProtocolInfoWrapper(Object handle) {
super(MinecraftReflection.getProtocolInfoClass());
setHandle(handle);
}
public PacketType.Protocol id() {
Object protocol = ID_ACCESSOR.invoke(this.handle);
return PacketType.Protocol.fromVanilla((Enum<?>) protocol);
}
public PacketType.Sender flow() {
String packetFlow = FLOW_ACCESSOR.invoke(this.handle).toString();
if (packetFlow.contains("CLIENTBOUND")) {
return PacketType.Sender.SERVER;
} else if (packetFlow.contains("SERVERBOUND")) {
return PacketType.Sender.CLIENT;
}
throw new RuntimeException("Unknown packet flow: " + packetFlow);
}
public IdCodecWrapper codec() {
return new IdCodecWrapper(CODEC_ACCESSOR.invoke(this.handle));
}
}
@@ -0,0 +1,150 @@
package com.comphenix.protocol.injector.packet.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.packet.PacketRegistry.Register;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Class only works for version 1.20.5+
*/
public class ProtocolRegistry_1_20_5 {
private static final Class<?> PACKET_TYPE_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");;
/**
* List of all class containing PacketTypes
*/
public static final List<String> PACKET_TYPES_CLASS_NAMES = List.of(
"common.CommonPacketTypes",
"configuration.ConfigurationPacketTypes",
"cookie.CookiePacketTypes",
"game.GamePacketTypes",
"handshake.HandshakePacketTypes",
"login.LoginPacketTypes",
"ping.PingPacketTypes",
"status.StatusPacketTypes");
/**
* List of all class containing ProtocolInfos
*/
public static final List<Protocol> PROTOCOLS = List.of(
new Protocol("configuration.ConfigurationProtocols", null),
new Protocol("game.GameProtocols", ProtocolContexts::createGameProtocolContext),
new Protocol("handshake.HandshakeProtocols", null),
new Protocol("login.LoginProtocols", null),
new Protocol("status.StatusProtocols", null));
public static void fillRegister(Register register) {
final Map<Object, Class<?>> packetTypeMap = getPacketTypes();
for (Protocol protocol : PROTOCOLS) {
Class<?> protocolClass = MinecraftReflection.getNullableNMS("network.protocol." + protocol.className());
if (protocolClass == null) {
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocol);
continue;
}
for (Field field : protocolClass.getDeclaredFields()) {
try {
// ignore none static and final fields
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object fieldValue = field.get(null);
// Skip any field that isn't a unbound ProtocolInfo.
// We need to bind the ProtocolInfo ourself as we apply our own stream mapping
// inside the ProtocolInfoWrapper::fromUnbound method
if (!ProtocolInfoWrapper.isUnboundProtocol(fieldValue)) {
continue;
}
// create optional context object to bind protocol to
Object context = protocol.context() != null
? protocol.context().get()
: null;
// wrap bound protocol info for ease of use
ProtocolInfoWrapper protocolInfo = ProtocolInfoWrapper.fromUnbound(fieldValue, context);
PacketType.Protocol packetProtocol = protocolInfo.id();
PacketType.Sender packetSender = protocolInfo.flow();
// register each packet to Register instance
List<IdCodecWrapper.Entry> entries = protocolInfo.codec().getById();
for (int id = 0; id < entries.size(); id++) {
IdCodecWrapper.Entry entry = entries.get(id);
Class<?> packetClass = packetTypeMap.get(entry.type());
if (packetClass == null) {
throw new RuntimeException("packetType missing packet " + entry.type());
}
PacketType packetType = PacketType.fromCurrent(packetProtocol, packetSender, id, packetClass);
// TODO(fix): validate that every codec expects FriendlyByteBuf
register.registerPacket(packetType, packetClass, packetSender, entry.serializer());
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
/**
* Returns a PacketType to packet class lookup map
*/
private static Map<Object, Class<?>> getPacketTypes() {
Map<Object, Class<?>> packetTypeMap = new HashMap<>();
for (String packetTypesClassName : PACKET_TYPES_CLASS_NAMES) {
Class<?> packetTypesClass = MinecraftReflection.getNullableNMS("network.protocol." + packetTypesClassName);
if (packetTypesClass == null) {
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
continue;
}
// check every field for "static final PacketType<?>"
for (Field field : packetTypesClass.getDeclaredFields()) {
try {
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
continue;
}
Object packetType = field.get(null);
if (!PACKET_TYPE_CLASS.isInstance(packetType)) {
continue;
}
// retrieve the generic type T of the PacketType<T> field
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (packet instanceof Class<?>) {
packetTypeMap.put(packetType, (Class<?>) packet);
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
return packetTypeMap;
}
private record Protocol(@NotNull String className, @Nullable Supplier<Object> context) {
}
}
@@ -1,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;
@@ -17,17 +17,20 @@
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 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 +39,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 +47,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 +68,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 +79,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 +94,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
@@ -144,21 +146,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 +168,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;
@@ -197,27 +193,13 @@ public class TemporaryPlayerFactory {
* Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead.
*
* @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;
}
}
@@ -17,11 +17,6 @@
package com.comphenix.protocol.reflect;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
@@ -46,18 +41,6 @@ public class ObjectWriter {
* @return A structure modifier for the given type.
*/
private StructureModifier<Object> getModifier(Class<?> type) {
Class<?> packetClass = MinecraftReflection.getPacketClass();
// Handle subclasses of the packet class with our custom structure cache, if possible
if (!type.equals(packetClass) && packetClass.isAssignableFrom(type)) {
// might be a packet, but some packets are not registered (for example PacketPlayInFlying, only the subtypes are present)
PacketType packetType = PacketRegistry.getPacketType(type);
if (packetType != null) {
// packet is present, delegate to the cache
return StructureCache.getStructure(packetType);
}
}
// Create the structure modifier if we haven't already
StructureModifier<Object> modifier = CACHE.get(type);
if (modifier == null) {
@@ -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>
@@ -142,9 +144,9 @@ public class StructureModifier<T> {
for (FieldAccessor accessor : fields) {
Field field = accessor.getField();
if (!field.getType().isPrimitive() && !Modifier.isFinal(field.getModifiers())) {
Object defaultInstance = DEFAULT_GENERATOR.getDefault(field.getType());
if (defaultInstance != null) {
if (!field.getType().isPrimitive()) {// && !Modifier.isFinal(field.getModifiers())) {
// if (DEFAULT_GENERATOR.hasDefault(field.getType())) {
if (StructureCache.canCreateInstance(field.getType())) {
requireDefaults.put(accessor, currentFieldIndex);
}
}
@@ -396,13 +398,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;
@@ -4,6 +4,21 @@ import java.lang.reflect.Constructor;
public interface ConstructorAccessor {
/**
* NoOp Accessor, does what is says: nothing.
*/
static final ConstructorAccessor NO_OP_ACCESSOR = new ConstructorAccessor() {
@Override
public Object invoke(Object... args) {
return null;
}
@Override
public Constructor<?> getConstructor() {
return null;
}
};
/**
* Invoke the underlying constructor.
*
@@ -9,6 +9,25 @@ import java.lang.reflect.Field;
*/
public interface FieldAccessor {
/**
* NoOp Accessor, does what is says: nothing.
*/
static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
@Override
public Object get(Object instance) {
return null;
}
@Override
public void set(Object instance, Object value) {
}
@Override
public Field getField() {
return null;
}
};
/**
* Retrieve the value of a field for a particular instance.
*
@@ -72,7 +72,7 @@ public class BukkitCloner implements Cloner {
static {
fromManual(MinecraftReflection::getItemStackClass, source ->
MinecraftReflection.getMinecraftItemStack(MinecraftReflection.getBukkitItemStack(source).clone()));
fromWrapper(MinecraftReflection::getDataWatcherClass, WrappedDataWatcher::new);
fromManual(MinecraftReflection::getDataWatcherClass, source -> new WrappedDataWatcher(source).deepClone().getHandle());
fromConverter(MinecraftReflection::getBlockPositionClass, BlockPosition.getConverter());
fromWrapper(MinecraftReflection::getServerPingClass, WrappedServerPing::fromHandle);
fromConverter(MinecraftReflection::getMinecraftKeyClass, MinecraftKey.getConverter());
@@ -82,6 +82,8 @@ public class BukkitCloner implements Cloner {
fromWrapper(MinecraftReflection::getIChatBaseComponentClass, WrappedChatComponent::fromHandle);
fromWrapper(WrappedVillagerData::getNmsClass, WrappedVillagerData::fromHandle);
fromConverter(MinecraftReflection::getSectionPositionClass, BukkitConverters.getSectionPositionConverter());
fromManual(MinecraftReflection::getAttributeSnapshotClass, source ->
WrappedAttribute.fromHandle(source).shallowClone().getHandle());
try {
fromManual(ComponentConverter::getBaseComponentArrayClass, source ->
@@ -62,12 +62,12 @@ public class ImmutableDetector implements Cloner {
add(MinecraftReflection::getDataWatcherSerializerClass);
add(MinecraftReflection::getBlockClass);
add(MinecraftReflection::getItemClass);
add("sounds.SoundEffect", "sounds.SoundEvents", "SoundEffect");
add(MinecraftReflection::getSoundEffectClass);
if (MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
add(MinecraftReflection::getFluidTypeClass);
add(MinecraftReflection::getParticleTypeClass);
add("core.particles.Particle","core.particles.ParticleType", "Particle");
add(MinecraftReflection::getParticleClass);
}
if (MinecraftVersion.VILLAGE_UPDATE.atOrAbove()) {
@@ -29,6 +29,26 @@ public class FuzzyMatchers {
return (value, parent) -> value.isArray() && componentMatcher.isMatch(value.getComponentType(), parent);
}
public static AbstractFuzzyMatcher<Class<?>> except(Class<?> clazz) {
return (value, parent) -> !clazz.isAssignableFrom(value);
}
public static AbstractFuzzyMatcher<Class<?>> assignable(Class<?> clazz) {
return (value, parent) -> clazz.isAssignableFrom(value);
}
@SafeVarargs
public static AbstractFuzzyMatcher<Class<?>> and(AbstractFuzzyMatcher<Class<?>>... matchers) {
return (value, parent) -> {
for (AbstractFuzzyMatcher<Class<?>> matcher : matchers) {
if (!matcher.isMatch(value, parent)) {
return false;
}
}
return true;
};
}
/**
* Retrieve a fuzzy matcher that will match any class.
*
@@ -26,6 +26,8 @@ import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
/**
@@ -164,7 +166,19 @@ public class DefaultInstances implements InstanceProvider {
public <T> T getDefault(Class<T> type) {
return getDefaultInternal(type, registered, 0);
}
private final ThreadLocal<Map<Class<?>, Boolean>> cache = ThreadLocal.withInitial(WeakHashMap::new);
/**
* Determines if a given class has a default value.
*
* @param type - the class to check
* @return true if the class has a default value, false otherwise
*/
public boolean hasDefault(Class<?> type) {
return cache.get().computeIfAbsent(type, aClass -> getDefaultInternal(aClass, registered, 0) != null);
}
/**
* Retrieve the constructor with the fewest number of parameters.
* @param <T> Type
@@ -0,0 +1,155 @@
package com.comphenix.protocol.reflect.instances;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Supplier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
public final class InstanceCreator implements Supplier<Object> {
private static Map<Class<?>, Object> BANNED_PARAMETERS = new WeakHashMap<>();
static {
try {
BANNED_PARAMETERS.put(ByteBuffer.class, true);
BANNED_PARAMETERS.put(FloatBuffer.class, true);
} catch (Throwable ignored) {
}
}
private ConstructorAccessor constructor = null;
private MethodAccessor factoryMethod = null;
private Class<?>[] paramTypes = null;
private boolean failed = false;
private final Class<?> type;
private InstanceCreator(Class<?> type) {
this.type = type;
}
public static InstanceCreator forClass(Class<?> type) {
if (type == null) {
throw new IllegalArgumentException("Type cannot be null.");
}
return new InstanceCreator(type);
}
private Object createInstance(Class<?> clazz) {
try {
return DefaultInstances.DEFAULT.create(clazz);
} catch (Exception ignored) {
return null;
}
}
private Object[] createParams(Class<?>[] paramTypes) {
Object[] params = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
params[i] = createInstance(paramTypes[i]);
}
return params;
}
private boolean containsBannedParameter(Class<?>[] paramTypes) {
for (Class<?> paramType : paramTypes) {
if (BANNED_PARAMETERS.containsKey(paramType)) {
return true;
}
}
return false;
}
@Override
public Object get() {
Object[] params = paramTypes != null ? createParams(paramTypes) : null;
if (constructor != null) {
return constructor.invoke(params);
}
if (factoryMethod != null) {
return factoryMethod.invoke(null, params);
}
if (failed) {
return null;
}
Object result = null;
int minCount = Integer.MAX_VALUE;
for (Constructor<?> testCtor : type.getDeclaredConstructors()) {
Class<?>[] paramTypes = testCtor.getParameterTypes();
if (paramTypes.length > minCount) {
continue;
}
if (containsBannedParameter(paramTypes)) {
continue;
}
Object[] testParams = createParams(paramTypes);
try {
ConstructorAccessor testAccessor = Accessors.getConstructorAccessor(testCtor);
result = testAccessor.invoke(testParams);
minCount = paramTypes.length;
this.constructor = testAccessor;
this.paramTypes = paramTypes;
} catch (Exception ignored) {
}
}
if (result != null) {
return result;
}
minCount = Integer.MAX_VALUE;
for (Method testMethod : type.getDeclaredMethods()) {
int modifiers = testMethod.getModifiers();
if (!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
continue;
}
if (testMethod.getReturnType() != type) {
continue;
}
Class<?>[] paramTypes = testMethod.getParameterTypes();
if (paramTypes.length > minCount) {
continue;
}
if (containsBannedParameter(paramTypes)) {
continue;
}
Object[] testParams = createParams(paramTypes);
try {
MethodAccessor testAccessor = Accessors.getMethodAccessor(testMethod);
result = testAccessor.invoke(null, testParams);
minCount = paramTypes.length;
this.factoryMethod = testAccessor;
this.paramTypes = paramTypes;
} catch (Exception ignored) {
}
}
if (result == null) {
this.failed = true;
}
return result;
}
}
@@ -17,6 +17,7 @@
package com.comphenix.protocol.reflect.instances;
import java.util.Collection;
import javax.annotation.Nullable;
/**
@@ -24,6 +25,7 @@ import javax.annotation.Nullable;
*
* @author Kristian
*/
@FunctionalInterface
public interface InstanceProvider {
/**
* Create an instance given a type, if possible.
@@ -31,5 +33,5 @@ public interface InstanceProvider {
* @return The instance, or NULL if the type cannot be created.
* @throws NotConstructableException Thrown to indicate that this type cannot or should never be constructed.
*/
public abstract Object create(@Nullable Class<?> type);
Object create(@Nullable Class<?> type);
}
@@ -1,16 +1,18 @@
package com.comphenix.protocol.reflect.instances;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
public class MinecraftGenerator {
@@ -53,19 +55,25 @@ public class MinecraftGenerator {
}
}
return DEFAULT_ENTITY_TYPES;
} else if (type.isAssignableFrom(Map.class)) {
} else if (Map.class.isAssignableFrom(type)) {
ConstructorAccessor ctor = FAST_MAP_CONSTRUCTORS.computeIfAbsent(type, __ -> {
try {
String name = type.getCanonicalName();
if (name != null && name.contains("it.unimi.fastutils")) {
Class<?> clz = Class.forName(name.substring(name.length() - 3) + "OpenHashMap");
return Accessors.getConstructorAccessorOrNull(clz);
String name = type.getName();
if (name.contains("it.unimi.dsi.fastutil")) {
// convert interface maps to OpenHashMaps
if (!name.endsWith("OpenHashMap")) {
name = name.replace("Map", "OpenHashMap");
}
Class<?> hashMapClass = Class.forName(name);
return Accessors.getConstructorAccessorOrNull(hashMapClass);
}
} catch (Exception ignored) {
}
return null;
return ConstructorAccessor.NO_OP_ACCESSOR;
});
if (ctor != null) {
if (ctor != ConstructorAccessor.NO_OP_ACCESSOR) {
try {
return ctor.invoke();
} catch (Exception ignored) {
@@ -40,7 +40,7 @@ public class PrimitiveGenerator implements InstanceProvider {
/**
* Shared instance of this generator.
*/
public static PrimitiveGenerator INSTANCE = new PrimitiveGenerator();
public static final PrimitiveGenerator INSTANCE = new PrimitiveGenerator();
// Our default string value
private final String stringDefault;
@@ -29,4 +29,10 @@ public class DefaultScheduler implements ProtocolScheduler {
int taskId = scheduler.scheduleSyncDelayedTask(plugin, task, delay);
return taskId >= 0 ? new DefaultTask(scheduler, taskId) : null;
}
@Override
public Task runTaskAsync(Runnable task) {
int taskId = scheduler.runTaskAsynchronously(plugin, task).getTaskId();
return taskId >= 0 ? new DefaultTask(scheduler, taskId) : null;
}
}
@@ -1,7 +1,6 @@
package com.comphenix.protocol.scheduler;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
public class DefaultTask implements Task {
private final int taskId;
@@ -9,23 +9,32 @@ import org.bukkit.plugin.Plugin;
import java.util.function.Consumer;
public class FoliaScheduler implements ProtocolScheduler {
private final Object foliaScheduler;
private final Object foliaRegionScheduler;
private final MethodAccessor runAtFixedRate;
private final MethodAccessor runDelayed;
private final MethodAccessor execute;
private final MethodAccessor cancel;
private final Object foliaAsyncScheduler;
private final MethodAccessor executeAsync;
private final Plugin plugin;
public FoliaScheduler(Plugin plugin) {
this.plugin = plugin;
MethodAccessor getScheduler = Accessors.getMethodAccessor(Bukkit.getServer().getClass(), "getGlobalRegionScheduler");
this.foliaScheduler = getScheduler.invoke(Bukkit.getServer());
this.foliaRegionScheduler = getScheduler.invoke(Bukkit.getServer());
this.runAtFixedRate = Accessors.getMethodAccessor(foliaScheduler.getClass(), "runAtFixedRate", Plugin.class,
this.runAtFixedRate = Accessors.getMethodAccessor(foliaRegionScheduler.getClass(), "runAtFixedRate", Plugin.class,
Consumer.class, long.class, long.class);
this.execute = Accessors.getMethodAccessor(foliaScheduler.getClass(), "run", Plugin.class, Consumer.class);
this.runDelayed = Accessors.getMethodAccessor(foliaScheduler.getClass(), "runDelayed", Plugin.class, Consumer.class, long.class);
this.execute = Accessors.getMethodAccessor(foliaRegionScheduler.getClass(), "run", Plugin.class, Consumer.class);
this.runDelayed = Accessors.getMethodAccessor(foliaRegionScheduler.getClass(), "runDelayed", Plugin.class, Consumer.class, long.class);
MethodAccessor getAsyncScheduler = Accessors.getMethodAccessor(Bukkit.getServer().getClass(), "getAsyncScheduler");
foliaAsyncScheduler = getAsyncScheduler.invoke(Bukkit.getServer());
this.executeAsync = Accessors.getMethodAccessor(foliaAsyncScheduler.getClass(), "runNow", Plugin.class, Consumer.class);
Class<?> taskClass = MinecraftReflection.getLibraryClass("io.papermc.paper.threadedregions.scheduler.ScheduledTask");
this.cancel = Accessors.getMethodAccessor(taskClass, "cancel");
@@ -33,19 +42,25 @@ public class FoliaScheduler implements ProtocolScheduler {
@Override
public Task scheduleSyncRepeatingTask(Runnable task, long delay, long period) {
Object taskHandle = runAtFixedRate.invoke(foliaScheduler, plugin, (Consumer<Object>)(t -> task.run()), delay, period);
Object taskHandle = runAtFixedRate.invoke(foliaRegionScheduler, plugin, (Consumer<Object>)(t -> task.run()), delay, period);
return new FoliaTask(cancel, taskHandle);
}
@Override
public Task runTask(Runnable task) {
Object taskHandle = execute.invoke(foliaScheduler, plugin, (Consumer<Object>)(t -> task.run()));
Object taskHandle = execute.invoke(foliaRegionScheduler, plugin, (Consumer<Object>)(t -> task.run()));
return new FoliaTask(cancel, taskHandle);
}
@Override
public Task scheduleSyncDelayedTask(Runnable task, long delay) {
Object taskHandle = runDelayed.invoke(foliaScheduler, plugin, (Consumer<Object>)(t -> task.run()), delay);
Object taskHandle = runDelayed.invoke(foliaRegionScheduler, plugin, (Consumer<Object>)(t -> task.run()), delay);
return new FoliaTask(cancel, taskHandle);
}
@Override
public Task runTaskAsync(Runnable task) {
Object taskHandle = executeAsync.invoke(foliaAsyncScheduler, plugin, (Consumer<Object>)(t -> task.run()));
return new FoliaTask(cancel, taskHandle);
}
}
@@ -1,12 +1,11 @@
package com.comphenix.protocol.scheduler;
import com.comphenix.protocol.ProtocolLib;
import org.bukkit.plugin.Plugin;
public interface ProtocolScheduler {
Task scheduleSyncRepeatingTask(Runnable task, long delay, long period);
Task runTask(Runnable task);
Task scheduleSyncDelayedTask(Runnable task, long delay);
Task runTaskAsync(Runnable task);
}
@@ -1,125 +0,0 @@
package com.comphenix.protocol.timing;
import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
/**
* Represents an online algortihm of computing histograms over time.
* @author Kristian
*/
public class HistogramStream extends OnlineComputation {
/**
* Each bin in the histogram, indexed by time.
*/
protected List<StatisticsStream> bins;
/**
* The current statistics stream we are updating.
*/
protected StatisticsStream current;
/**
* The maximum number of observations in each bin.
*/
protected int binWidth;
/**
* The number of total observations.
*/
protected int count;
/**
* Construct a new histogram stream which splits up every observation in different bins, ordered by time.
* @param binWidth - maximum number of observations in each bin.
*/
public HistogramStream(int binWidth) {
this(new ArrayList<StatisticsStream>(), new StatisticsStream(), binWidth);
}
/**
* Construct a new copy of the given histogram.
* @param other - the histogram to copy.
*/
public HistogramStream(HistogramStream other) {
// Deep cloning
for (StatisticsStream stream : other.bins) {
StatisticsStream copy = stream.copy();
// Update current
if (stream == other.current)
this.current = copy;
this.bins.add(copy);
}
this.binWidth = other.binWidth;
}
/**
* Construct a new histogram stream.
* @param bins - list of bins.
* @param current - the current selected bin. This will be added to the list if it is not already present.
* @param binWidth - the desired number of observations in each bin.
*/
protected HistogramStream(List<StatisticsStream> bins, StatisticsStream current, int binWidth) {
if (binWidth < 1)
throw new IllegalArgumentException("binWidth cannot be less than 1");
this.bins = Preconditions.checkNotNull(bins, "bins cannot be NULL");
this.current = Preconditions.checkNotNull(current, "current cannot be NULL");
this.binWidth = binWidth;
if (!this.bins.contains(current)) {
this.bins.add(current);
}
}
@Override
public HistogramStream copy() {
return new HistogramStream(this);
}
/**
* Retrieve an immutable view of every bin in the histogram.
* @return Every bin in the histogram.
*/
public ImmutableList<StatisticsStream> getBins() {
return ImmutableList.copyOf(bins);
}
@Override
public void observe(double value) {
checkOverflow();
count++;
current.observe(value);
}
/**
* See if the current bin has overflowed. If so, construct a new bin and set it as the current.
*/
protected void checkOverflow() {
if (current.getCount() >= binWidth) {
bins.add(current = new StatisticsStream());
}
}
/**
* Retrieve the total statistics of every bin in the histogram.
* <p>
* This method is not thread safe.
* @return The total statistics.
*/
public StatisticsStream getTotal() {
StatisticsStream sum = null;
for (StatisticsStream stream : bins) {
sum = sum != null ? stream.add(sum) : stream;
}
return sum;
}
@Override
public int getCount() {
return count;
}
}
@@ -1,55 +0,0 @@
package com.comphenix.protocol.timing;
/**
* Represents an online computation.
*
* @author Kristian
*/
public abstract class OnlineComputation {
/**
* Retrieve a wrapper for another online computation that is synchronized.
*
* @param computation - the computation.
* @return The synchronized wrapper.
*/
public static OnlineComputation synchronizedComputation(final OnlineComputation computation) {
return new OnlineComputation() {
@Override
public synchronized void observe(double value) {
computation.observe(value);
}
@Override
public synchronized int getCount() {
return computation.getCount();
}
@Override
public synchronized OnlineComputation copy() {
return computation.copy();
}
};
}
/**
* Retrieve the number of observations.
*
* @return Number of observations.
*/
public abstract int getCount();
/**
* Observe a value.
*
* @param value - the observed value.
*/
public abstract void observe(double value);
/**
* Construct a copy of the current online computation.
*
* @return The new copy.
*/
public abstract OnlineComputation copy();
}
@@ -0,0 +1,32 @@
package com.comphenix.protocol.timing;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.comphenix.protocol.PacketType;
public class PluginTimingTracker implements TimingTracker {
private final Map<PacketType, StatisticsStream> statistics = new ConcurrentHashMap<>();
private volatile boolean hasReceivedData = false;
@Override
public void track(PacketType packetType, Runnable runnable) {
long startTime = System.nanoTime();
runnable.run();
long endTime = System.nanoTime();
this.statistics.computeIfAbsent(packetType, key -> new StatisticsStream())
.observe(endTime - startTime);
this.hasReceivedData = true;
}
public boolean hasReceivedData() {
return hasReceivedData;
}
public Map<PacketType, StatisticsStream> getStatistics() {
return statistics;
}
}
@@ -5,7 +5,7 @@ package com.comphenix.protocol.timing;
*
* @author Kristian
*/
public class StatisticsStream extends OnlineComputation {
public class StatisticsStream {
// This algorithm is due to Donald Knuth, as described in:
// Donald E. Knuth (1998). The Art of Computer Programming, volume 2:
// Seminumerical Algorithms, 3rd edn., p. 232. Boston: Addison-Wesley.
@@ -37,18 +37,12 @@ public class StatisticsStream extends OnlineComputation {
this.maximum = other.maximum;
}
@Override
public StatisticsStream copy() {
return new StatisticsStream(this);
}
/**
* Observe a value.
*
* @param value - the observed value.
*/
@Override
public void observe(double value) {
public synchronized void observe(double value) {
double delta = value - this.mean;
// As per Knuth
@@ -145,7 +139,6 @@ public class StatisticsStream extends OnlineComputation {
*
* @return Number of observations.
*/
@Override
public int getCount() {
return this.count;
}
@@ -1,184 +0,0 @@
package com.comphenix.protocol.timing;
import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.events.PacketListener;
import com.google.common.collect.ImmutableMap;
/**
* Represents a system for recording the time spent by each packet listener.
* @author Kristian
*/
public class TimedListenerManager {
public enum ListenerType {
ASYNC_SERVER_SIDE,
ASYNC_CLIENT_SIDE,
SYNC_SERVER_SIDE,
SYNC_CLIENT_SIDE;
}
// The shared manager
private final static TimedListenerManager INSTANCE = new TimedListenerManager();
// Running?
private final static AtomicBoolean timing = new AtomicBoolean();
// When it was started
private volatile Date started;
private volatile Date stopped;
// The map of time trackers
private final ConcurrentMap<String, ImmutableMap<ListenerType, TimedTracker>> map = new ConcurrentHashMap<>();
/**
* Retrieve the shared listener manager.
* <p>
* This should never change.
* @return The shared listener manager.
*/
public static TimedListenerManager getInstance() {
return INSTANCE;
}
/**
* Start timing listeners.
* @return TRUE if we started timing, FALSE if we are already timing listeners.
*/
public boolean startTiming() {
if (setTiming(true)) {
started = Calendar.getInstance().getTime();
return true;
}
return false;
}
/**s
* Stop timing listeners.
* @return TRUE if we stopped timing, FALSE otherwise.
*/
public boolean stopTiming() {
if (setTiming(false)) {
stopped = Calendar.getInstance().getTime();
return true;
}
return false;
}
/**
* Retrieve the time the listener was started.
* @return The time it was started, or NULL if they have never been started.
*/
public Date getStarted() {
return started;
}
/**
* Retrieve the time the time listeners was stopped.
* @return The time they were stopped, or NULL if not found.
*/
public Date getStopped() {
return stopped;
}
/**
* Set whether or not the timing manager is enabled.
* @param value - TRUE if it should be enabled, FALSE otherwise.
* @return TRUE if the value was changed, FALSE otherwise.
*/
private boolean setTiming(boolean value) {
return timing.compareAndSet(!value, value);
}
/**
* Determine if we are currently timing listeners.
* @return TRUE if we are, FALSE otherwise.
*/
public boolean isTiming() {
return timing.get();
}
/**
* Reset all packet gathering data.
*/
public void clear() {
map.clear();
}
/**
* Retrieve every tracked plugin.
* @return Every tracked plugin.
*/
public Set<String> getTrackedPlugins() {
return map.keySet();
}
/**
* Retrieve the timed tracker associated with the given plugin and listener type.
* @param plugin - the plugin.
* @param type - the listener type.
* @return The timed tracker.
*/
public TimedTracker getTracker(Plugin plugin, ListenerType type) {
return getTracker(plugin.getName(), type);
}
/**
* Retrieve the timed tracker associated with the given listener and listener type.
* @param listener - the listener.
* @param type - the listener type.
* @return The timed tracker.
*/
public TimedTracker getTracker(PacketListener listener, ListenerType type) {
return getTracker(listener.getPlugin().getName(), type);
}
/**
* Retrieve the timed tracker associated with the given plugin and listener type.
* @param pluginName - the plugin name.
* @param type - the listener type.
* @return The timed tracker.
*/
public TimedTracker getTracker(String pluginName, ListenerType type) {
return getTrackers(pluginName).get(type);
}
/**
* Retrieve the map of timed trackers for a specific plugin.
* @param pluginName - the plugin name.
* @return Map of timed trackers.
*/
private ImmutableMap<ListenerType, TimedTracker> getTrackers(String pluginName) {
ImmutableMap<ListenerType, TimedTracker> trackers = map.get(pluginName);
// Atomic pattern
if (trackers == null) {
ImmutableMap<ListenerType, TimedTracker> created = newTrackerMap();
trackers = map.putIfAbsent(pluginName, created);
// Success!
if (trackers == null) {
trackers = created;
}
}
return trackers;
}
/**
* Retrieve a new map of trackers for an unspecified plugin.
* @return A map of listeners and timed trackers.
*/
private ImmutableMap<ListenerType, TimedTracker> newTrackerMap() {
ImmutableMap.Builder<ListenerType, TimedTracker> builder = ImmutableMap.builder();
// Construct a map with every listener type
for (ListenerType type : ListenerType.values()) {
builder.put(type, new TimedTracker());
}
return builder.build();
}
}
@@ -1,72 +0,0 @@
package com.comphenix.protocol.timing;
import com.comphenix.protocol.PacketType;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Tracks the invocation time for a particular plugin against a list of packets.
*
* @author Kristian
*/
public class TimedTracker {
// Table of packets and invocations
private final AtomicInteger observations = new AtomicInteger();
private final Map<PacketType, StatisticsStream> packets = new HashMap<>();
/**
* Begin tracking an execution time.
*
* @return The current tracking token.
*/
public long beginTracking() {
return System.nanoTime();
}
/**
* Stop and record the execution time since the creation of the given tracking token.
*
* @param trackingToken - the tracking token.
* @param type - the packet type.
*/
public synchronized void endTracking(long trackingToken, PacketType type) {
StatisticsStream stream = this.packets.get(type);
// Lazily create a stream
if (stream == null) {
this.packets.put(type, stream = new StatisticsStream());
}
// Store this observation
stream.observe(System.nanoTime() - trackingToken);
this.observations.incrementAndGet();
}
/**
* Retrieve the total number of observations.
*
* @return Total number of observations.
*/
public int getObservations() {
return this.observations.get();
}
/**
* Retrieve an map (indexed by packet type) of all relevant statistics.
*
* @return The map of statistics.
*/
public synchronized Map<PacketType, StatisticsStream> getStatistics() {
final Map<PacketType, StatisticsStream> clone = new HashMap<>();
for (Entry<PacketType, StatisticsStream> entry : this.packets.entrySet()) {
clone.put(
entry.getKey(),
new StatisticsStream(entry.getValue())
);
}
return clone;
}
}
@@ -0,0 +1,6 @@
package com.comphenix.protocol.timing;
public enum TimingListenerType {
ASYNC_INBOUND, ASYNC_OUTBOUND, SYNC_INBOUND, SYNC_OUTBOUND;
}
@@ -1,20 +1,20 @@
package com.comphenix.protocol.timing;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import java.util.Map;
import java.util.TreeSet;
public class TimingReportGenerator {
import com.comphenix.protocol.PacketType;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
public class TimingReport {
private static final String NEWLINE = System.getProperty("line.separator");
private static final String META_STARTED = "Started: %s" + NEWLINE;
private static final String META_STOPPED = "Stopped: %s (after %s seconds)" + NEWLINE;
@@ -22,30 +22,39 @@ public class TimingReportGenerator {
private static final String LISTENER_HEADER = " TYPE: %s " + NEWLINE;
private static final String SEPERATION_LINE = " " + Strings.repeat("-", 139) + NEWLINE;
private static final String STATISTICS_HEADER =
" Protocol: Name: ID: Count: Min (ms): " +
" Protocol: Name: Count: Min (ms): " +
"Max (ms): Mean (ms): Std (ms): " + NEWLINE;
private static final String STATISTICS_ROW = " %-15s %-29s %-19s %-12d %-15.6f %-15.6f %-15.6f %.6f " + NEWLINE;
private static final String STATISTICS_ROW = " %-14s %-29s %-12d %-15.6f %-15.6f %-15.6f %.6f " + NEWLINE;
private static final String SUM_MAIN_THREAD = " => Time on main thread: %.6f ms" + NEWLINE;
public void saveTo(File destination, TimedListenerManager manager) throws IOException {
final Date started = manager.getStarted();
final Date stopped = manager.getStopped();
final long seconds = Math.abs((stopped.getTime() - started.getTime()) / 1000);
private final Date startTime;
private final Date stopTime;
private final ImmutableMap<String, ImmutableMap<TimingListenerType, PluginTimingTracker>> trackerMap;
try (BufferedWriter writer = Files.newWriter(destination, StandardCharsets.UTF_8)) {
public TimingReport(Date startTime, Date stopTime, ImmutableMap<String, ImmutableMap<TimingListenerType, PluginTimingTracker>> trackerMap) {
this.startTime = startTime;
this.stopTime = stopTime;
this.trackerMap = trackerMap;
}
public void saveTo(Path path) throws IOException {
final long seconds = Math.abs((stopTime.getTime() - startTime.getTime()) / 1000);
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
// Write some timing information
writer.write(String.format(META_STARTED, started));
writer.write(String.format(META_STOPPED, stopped, seconds));
writer.write(String.format(META_STARTED, startTime));
writer.write(String.format(META_STOPPED, stopTime, seconds));
writer.write(NEWLINE);
for (String plugin : manager.getTrackedPlugins()) {
writer.write(String.format(PLUGIN_HEADER, plugin));
for (Map.Entry<String, ImmutableMap<TimingListenerType, PluginTimingTracker>> pluginEntry : trackerMap.entrySet()) {
writer.write(String.format(PLUGIN_HEADER, pluginEntry.getKey()));
for (ListenerType type : ListenerType.values()) {
TimedTracker tracker = manager.getTracker(plugin, type);
for (Map.Entry<TimingListenerType, PluginTimingTracker> entry : pluginEntry.getValue().entrySet()) {
TimingListenerType type = entry.getKey();
PluginTimingTracker tracker = entry.getValue();
// We only care if it has any observations at all
if (tracker.getObservations() > 0) {
if (tracker.hasReceivedData()) {
writer.write(String.format(LISTENER_HEADER, type));
writer.write(SEPERATION_LINE);
@@ -59,7 +68,7 @@ public class TimingReportGenerator {
}
}
private void saveStatistics(Writer destination, TimedTracker tracker, ListenerType type) throws IOException {
private void saveStatistics(Writer destination, PluginTimingTracker tracker, TimingListenerType type) throws IOException {
Map<PacketType, StatisticsStream> streams = tracker.getStatistics();
StatisticsStream sum = new StatisticsStream();
int count = 0;
@@ -85,9 +94,9 @@ public class TimingReportGenerator {
printStatistic(destination, null, sum);
}
// These are executed on the main thread
if (type == ListenerType.SYNC_SERVER_SIDE) {
if (type == TimingListenerType.SYNC_OUTBOUND) {
destination.write(String.format(SUM_MAIN_THREAD,
toMilli(sum.getCount() * sum.getMean())
nanoToMillis(sum.getCount() * sum.getMean())
));
}
}
@@ -96,25 +105,20 @@ public class TimingReportGenerator {
destination.write(String.format(STATISTICS_ROW,
key != null ? key.getProtocol() : "SUM",
key != null ? key.name() : "-",
key != null ? getPacketId(key) : "-",
stream.getCount(),
toMilli(stream.getMinimum()),
toMilli(stream.getMaximum()),
toMilli(stream.getMean()),
toMilli(stream.getStandardDeviation())
nanoToMillis(stream.getMinimum()),
nanoToMillis(stream.getMaximum()),
nanoToMillis(stream.getMean()),
nanoToMillis(stream.getStandardDeviation())
));
}
private String getPacketId(PacketType type) {
return Strings.padStart(Integer.toString(type.getCurrentId()), 2, '0');
}
/**
* Convert a value in nanoseconds to milliseconds.
* @param value - the value.
* @return The value in milliseconds.
*/
private double toMilli(double value) {
private double nanoToMillis(double value) {
return value / 1000000.0;
}
}
@@ -0,0 +1,10 @@
package com.comphenix.protocol.timing;
import com.comphenix.protocol.PacketType;
public interface TimingTracker {
public static final TimingTracker EMPTY = (packetType, runnable) -> runnable.run();
void track(PacketType packetType, Runnable runnable);
}
@@ -0,0 +1,65 @@
package com.comphenix.protocol.timing;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import com.comphenix.protocol.events.PacketListener;
import com.google.common.collect.ImmutableMap;
public class TimingTrackerManager {
private final static AtomicBoolean IS_TRACKING = new AtomicBoolean();
private static volatile Date startTime;
private static volatile Date stopTime;
private static final Map<String, ImmutableMap<TimingListenerType, PluginTimingTracker>> TRACKER_MAP = new ConcurrentHashMap<>();
public static boolean startTracking() {
if (IS_TRACKING.compareAndSet(false, true)) {
startTime = Calendar.getInstance().getTime();
return true;
}
return false;
}
public static boolean isTracking() {
return IS_TRACKING.get();
}
public static boolean stopTracking() {
if (IS_TRACKING.compareAndSet(true, false)) {
stopTime = Calendar.getInstance().getTime();
return true;
}
return false;
}
public static TimingReport createReportAndReset() {
TimingReport report = new TimingReport(startTime, stopTime, ImmutableMap.copyOf(TRACKER_MAP));
TRACKER_MAP.clear();
return report;
}
public static TimingTracker get(PacketListener listener, TimingListenerType type) {
if (!IS_TRACKING.get()) {
return TimingTracker.EMPTY;
}
String plugin = listener.getPlugin().getName();
return TRACKER_MAP.computeIfAbsent(plugin, k -> newTrackerMap()).get(type);
}
private static ImmutableMap<TimingListenerType, PluginTimingTracker> newTrackerMap() {
ImmutableMap.Builder<TimingListenerType, PluginTimingTracker> builder = ImmutableMap.builder();
for (TimingListenerType type : TimingListenerType.values()) {
builder.put(type, new PluginTimingTracker());
}
return builder.build();
}
}
@@ -19,7 +19,6 @@ package com.comphenix.protocol.updater;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.utility.Closer;
import com.comphenix.protocol.utility.SchedulerUtil;
import org.bukkit.plugin.Plugin;
import java.io.BufferedReader;
Loaded 100 of 211 files, more files were not shown because too many files have changed in this diff. Show more