3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Merge branch 'dev/3.0.0' into dev/5.0.0

# Conflicts:
#	buildSrc/src/main/kotlin/com/velocitypowered/script/SetManifestImplVersionPlugin.kt
#	proxy/src/main/java/com/velocitypowered/proxy/connection/backend/VelocityServerConnection.java
#	proxy/src/main/java/com/velocitypowered/proxy/event/VelocityEventManager.java
#	proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java
#	proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java
Dieser Commit ist enthalten in:
Andrew Steinborn 2023-10-27 16:40:54 -04:00
Commit a037ab4956
89 geänderte Dateien mit 2869 neuen und 1134 gelöschten Zeilen

2
.gitignore vendored
Datei anzeigen

@ -83,6 +83,8 @@ gradle-app.setting
logs/ logs/
/velocity.toml /velocity.toml
/forwarding.secret /forwarding.secret
forwarding.secret
velocity.toml
server-icon.png server-icon.png
/bin/ /bin/
run/ run/

38
Jenkinsfile vendored
Datei anzeigen

@ -1,38 +0,0 @@
pipeline {
agent none
options {
disableConcurrentBuilds()
}
stages {
stage('Build') {
agent {
docker {
image 'velocitypowered/openjdk8-plus-git:slim'
args '-v gradle-cache:/root/.gradle:rw'
}
}
steps {
sh './gradlew build --no-daemon'
archiveArtifacts 'proxy/build/libs/*-all.jar,api/build/libs/*-all.jar'
}
}
stage('Deploy') {
when {
expression {
GIT_BRANCH = sh(returnStdout: true, script: 'git rev-parse --abbrev-ref HEAD').trim()
return GIT_BRANCH == 'master'
}
}
agent {
docker {
image 'velocitypowered/openjdk8-plus-git:slim'
args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw -v javadoc:/javadoc:rw'
}
}
steps {
sh 'export MAVEN_DEPLOYMENT=true; ./gradlew publish --no-daemon'
sh 'rsync -av --delete ./api/build/docs/javadoc/ /javadoc'
}
}
}
}

Datei anzeigen

@ -1,6 +1,7 @@
plugins { plugins {
`java-library` `java-library`
`maven-publish` `maven-publish`
id("velocity-publish")
} }
java { java {
@ -38,6 +39,8 @@ dependencies {
api("net.kyori:adventure-text-serializer-legacy") api("net.kyori:adventure-text-serializer-legacy")
api("net.kyori:adventure-text-serializer-plain") api("net.kyori:adventure-text-serializer-plain")
api("net.kyori:adventure-text-minimessage") api("net.kyori:adventure-text-minimessage")
api("net.kyori:adventure-text-logger-slf4j")
api("net.kyori:adventure-text-serializer-ansi")
api("org.slf4j:slf4j-api:$slf4jVersion") api("org.slf4j:slf4j-api:$slf4jVersion")
api("com.google.inject:guice:$guiceVersion") api("com.google.inject:guice:$guiceVersion")

Datei anzeigen

@ -47,7 +47,7 @@ public final class ServerPreConnectEvent implements
* *
* @param player the player who is connecting to a server * @param player the player who is connecting to a server
* @param originalServer the server the player was trying to connect to * @param originalServer the server the player was trying to connect to
* @param previousServer the server the player ís connected to * @param previousServer the server the player is connected to
*/ */
public ServerPreConnectEvent(Player player, RegisteredServer originalServer, public ServerPreConnectEvent(Player player, RegisteredServer originalServer,
@Nullable RegisteredServer previousServer) { @Nullable RegisteredServer previousServer) {

Datei anzeigen

@ -61,7 +61,9 @@ public enum ProtocolVersion {
MINECRAFT_1_19(759, "1.19"), MINECRAFT_1_19(759, "1.19"),
MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"), MINECRAFT_1_19_1(760, "1.19.1", "1.19.2"),
MINECRAFT_1_19_3(761, "1.19.3"), MINECRAFT_1_19_3(761, "1.19.3"),
MINECRAFT_1_19_4(762, "1.19.4"); MINECRAFT_1_19_4(762, "1.19.4"),
MINECRAFT_1_20(763, "1.20", "1.20.1"),
MINECRAFT_1_20_2(764, "1.20.2");
private static final int SNAPSHOT_BIT = 30; private static final int SNAPSHOT_BIT = 30;

Datei anzeigen

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018-2022 Velocity Contributors * Copyright (C) 2018-2023 Velocity Contributors
* *
* The Velocity API is licensed under the terms of the MIT License. For more details, * The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory. * reference the LICENSE file in the api top-level directory.
@ -7,6 +7,7 @@
package com.velocitypowered.api.permission; package com.velocitypowered.api.permission;
import java.util.Optional;
import net.kyori.adventure.util.TriState; import net.kyori.adventure.util.TriState;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -66,6 +67,21 @@ public enum Tristate {
return val ? TRUE : FALSE; return val ? TRUE : FALSE;
} }
/**
* Returns a {@link Tristate} from an {@link Optional}.
*
* <p>Unlike {@link #fromBoolean(boolean)}, this method returns {@link #UNDEFINED}
* if the value is empty.</p>
*
* @param val the optional boolean value
* @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value is empty,
* <code>true</code> or <code>false</code>, respectively.
*/
public static Tristate fromOptionalBoolean(Optional<Boolean> val) {
return val.map(Tristate::fromBoolean).orElse(UNDEFINED);
}
private final boolean booleanValue; private final boolean booleanValue;
Tristate(boolean booleanValue) { Tristate(boolean booleanValue) {

Datei anzeigen

@ -147,10 +147,17 @@ public interface Player extends
/** /**
* Clears the tab list header and footer for the player. * Clears the tab list header and footer for the player.
* *
* @deprecated Use {@link TabList#clearHeaderAndFooter()}. * @deprecated Use {@link Player#clearPlayerListHeaderAndFooter()}.
*/ */
@Deprecated @Deprecated
void clearHeaderAndFooter(); default void clearHeaderAndFooter() {
clearPlayerListHeaderAndFooter();
}
/**
* Clears the player list header and footer.
*/
void clearPlayerListHeaderAndFooter();
/** /**
* Returns the player's player list header. * Returns the player's player list header.

Datei anzeigen

@ -41,6 +41,12 @@ public interface ProxyServer extends Audience {
*/ */
void shutdown(); void shutdown();
/**
* Closes all listening endpoints for this server.
* This includes the main minecraft listener and query channel.
*/
void closeListeners();
/** /**
* Retrieves the player currently connected to this proxy by their Minecraft username. The search * Retrieves the player currently connected to this proxy by their Minecraft username. The search
* is case-insensitive. * is case-insensitive.

Datei anzeigen

@ -43,6 +43,28 @@ public interface TabList {
*/ */
void addEntry(TabListEntry entry); void addEntry(TabListEntry entry);
/**
* Adds a {@link Iterable} of {@link TabListEntry}'s to the {@link Player}'s tab list.
*
* @param entries to add to the tab list
*/
default void addEntries(Iterable<TabListEntry> entries) {
for (TabListEntry entry : entries) {
addEntry(entry);
}
}
/**
* Adds an array of {@link TabListEntry}'s to the {@link Player}'s tab list.
*
* @param entries to add to the tab list
*/
default void addEntries(TabListEntry... entries) {
for (TabListEntry entry : entries) {
addEntry(entry);
}
}
/** /**
* Removes the {@link TabListEntry} from the tab list with the {@link GameProfile} identified with * Removes the {@link TabListEntry} from the tab list with the {@link GameProfile} identified with
* the specified {@link UUID}. * the specified {@link UUID}.

17
build-logic/build.gradle.kts Normale Datei
Datei anzeigen

@ -0,0 +1,17 @@
plugins {
`kotlin-dsl`
alias(libs.plugins.spotless)
}
dependencies {
// this is OK as long as the same version catalog is used in the main build and build-logic
// see https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
implementation("com.diffplug.spotless:spotless-plugin-gradle:${libs.plugins.spotless.get().version}")
}
spotless {
kotlin {
licenseHeaderFile(rootProject.file("../HEADER.txt"))
}
}

Datei anzeigen

@ -1,3 +1,5 @@
@file:Suppress("UnstableApiUsage")
dependencyResolutionManagement { dependencyResolutionManagement {
repositories { repositories {
mavenCentral() mavenCentral()
@ -9,3 +11,5 @@ dependencyResolutionManagement {
} }
} }
} }
rootProject.name = "build-logic"

Datei anzeigen

@ -0,0 +1,6 @@
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
val Project.libs: LibrariesForLibs
get() = rootProject.extensions.getByType()

Datei anzeigen

@ -0,0 +1,10 @@
plugins {
checkstyle
}
extensions.configure<CheckstyleExtension> {
configFile = rootProject.file("config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
toolVersion = libs.checkstyle.get().version.toString()
}

Datei anzeigen

@ -0,0 +1,29 @@
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.withType
import java.io.ByteArrayOutputStream
val currentShortRevision = ByteArrayOutputStream().use {
exec {
executable = "git"
args = listOf("rev-parse", "HEAD")
standardOutput = it
}
it.toString().trim().substring(0, 8)
}
tasks.withType<Jar> {
manifest {
val buildNumber = System.getenv("BUILD_NUMBER")
val velocityHumanVersion: String =
if (project.version.toString().endsWith("-SNAPSHOT")) {
if (buildNumber == null) {
"${project.version} (git-$currentShortRevision)"
} else {
"${project.version} (git-$currentShortRevision-b$buildNumber)"
}
} else {
archiveVersion.get()
}
attributes["Implementation-Version"] = velocityHumanVersion
}
}

Datei anzeigen

@ -0,0 +1,33 @@
plugins {
java
`maven-publish`
}
extensions.configure<PublishingExtension> {
repositories {
maven {
credentials(PasswordCredentials::class.java)
name = "paper"
val base = "https://repo.papermc.io/repository/maven"
val releasesRepoUrl = "$base-releases/"
val snapshotsRepoUrl = "$base-snapshots/"
setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
}
}
publications {
create<MavenPublication>("maven") {
from(components["java"])
pom {
name.set("Velocity")
description.set("The modern, next-generation Minecraft server proxy")
url.set("https://papermc.io/software/velocity")
scm {
url.set("https://github.com/PaperMC/Velocity")
connection.set("scm:git:https://github.com/PaperMC/Velocity.git")
developerConnection.set("scm:git:https://github.com/PaperMC/Velocity.git")
}
}
}
}
}

Datei anzeigen

@ -0,0 +1,15 @@
import com.diffplug.gradle.spotless.SpotlessExtension
import com.diffplug.gradle.spotless.SpotlessPlugin
apply<SpotlessPlugin>()
extensions.configure<SpotlessExtension> {
java {
if (project.name == "velocity-api") {
licenseHeaderFile(file("HEADER.txt"))
} else {
licenseHeaderFile(rootProject.file("HEADER.txt"))
}
removeUnusedImports()
}
}

Datei anzeigen

@ -1,17 +1,14 @@
import com.velocitypowered.script.VelocityCheckstylePlugin
import com.velocitypowered.script.VelocityPublishPlugin
import com.velocitypowered.script.VelocitySpotlessPlugin
plugins { plugins {
`java-library` `java-library`
id("velocity-checkstyle") apply false
id("velocity-spotless") apply false
} }
subprojects { subprojects {
apply<JavaLibraryPlugin>() apply<JavaLibraryPlugin>()
apply<VelocityCheckstylePlugin>() apply(plugin = "velocity-checkstyle")
apply<VelocityPublishPlugin>() apply(plugin = "velocity-spotless")
apply<VelocitySpotlessPlugin>()
java { java {
toolchain { toolchain {
@ -19,12 +16,6 @@ subprojects {
} }
} }
repositories {
mavenCentral()
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // adventure
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies { dependencies {
testImplementation(rootProject.libs.junit) testImplementation(rootProject.libs.junit)
} }

Datei anzeigen

@ -1,37 +0,0 @@
plugins {
`kotlin-dsl`
checkstyle
alias(libs.plugins.indra.publishing)
alias(libs.plugins.spotless)
}
dependencies {
implementation("com.diffplug.spotless:spotless-plugin-gradle:${libs.plugins.spotless.get().version}")
}
gradlePlugin {
plugins {
register("set-manifest-impl-version") {
id = "set-manifest-impl-version"
implementationClass = "com.velocitypowered.script.SetManifestImplVersionPlugin"
}
register("velocity-checkstyle") {
id = "velocity-checkstyle"
implementationClass = "com.velocitypowered.script.VelocityCheckstylePlugin"
}
register("velocity-spotless") {
id = "velocity-spotless"
implementationClass = "com.velocitypowered.script.VelocitySpotlessPlugin"
}
register("velocity-publish") {
id = "velocity-publish"
implementationClass = "com.velocitypowered.script.VelocityPublishPlugin"
}
}
}
spotless {
kotlin {
licenseHeaderFile(project.rootProject.file("../HEADER.txt"))
}
}

Datei anzeigen

@ -1,54 +0,0 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.script
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.withType
import java.io.ByteArrayOutputStream
class SetManifestImplVersionPlugin : Plugin<Project> {
override fun apply(target: Project) = target.afterEvaluate { configure() }
private fun Project.configure() {
val currentShortRevision = ByteArrayOutputStream().use {
exec {
executable = "git"
args = listOf("rev-parse", "HEAD")
standardOutput = it
}
it.toString().trim().substring(0, 8)
}
tasks.withType<Jar> {
manifest {
val buildNumber = System.getenv("BUILD_NUMBER")
var velocityHumanVersion: String
if (project.version.toString().endsWith("-SNAPSHOT")) {
if (buildNumber != null) {
velocityHumanVersion = "${project.version} (git-$currentShortRevision-b$buildNumber)"
} else {
velocityHumanVersion = "${project.version} (git-$currentShortRevision)"
}
} else {
velocityHumanVersion = project.version.toString()
}
attributes["Implementation-Version"] = velocityHumanVersion
}
}
}
}

Datei anzeigen

@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.script
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.quality.CheckstyleExtension
import org.gradle.api.plugins.quality.CheckstylePlugin
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
class VelocityCheckstylePlugin : Plugin<Project> {
override fun apply(target: Project) = target.configure()
private fun Project.configure() {
apply<CheckstylePlugin>()
extensions.configure<CheckstyleExtension> {
configFile = project.rootProject.file("config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
toolVersion = "10.6.0"
}
}
}

Datei anzeigen

@ -1,72 +0,0 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.script
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.PasswordCredentials
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getByType
class VelocityPublishPlugin : Plugin<Project> {
override fun apply(target: Project) = target.afterEvaluate {
if (target.name != "velocity-proxy") {
configure()
}
}
private fun Project.configure() {
apply<JavaBasePlugin>()
apply<MavenPublishPlugin>()
extensions.configure<PublishingExtension> {
repositories {
maven {
credentials(PasswordCredentials::class.java)
name = "paper"
val base = "https://papermc.io/repo/repository/maven"
val releasesRepoUrl = "$base-releases/"
val snapshotsRepoUrl = "$base-snapshots/"
setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
}
}
publications {
create<MavenPublication>("maven") {
from(components["java"])
pom {
name.set("Velocity")
description.set("The modern, next-generation Minecraft server proxy")
url.set("https://www.velocitypowered.com")
scm {
url.set("https://github.com/PaperMC/Velocity")
connection.set("scm:git:https://github.com/PaperMC/Velocity.git")
developerConnection.set("scm:git:https://github.com/PaperMC/Velocity.git")
}
}
}
}
}
}
}

Datei anzeigen

@ -1,46 +0,0 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.script
import com.diffplug.gradle.spotless.SpotlessExtension
import com.diffplug.gradle.spotless.SpotlessPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import java.io.File
class VelocitySpotlessPlugin : Plugin<Project> {
override fun apply(target: Project) = target.configure()
private fun Project.configure() {
apply<SpotlessPlugin>()
extensions.configure<SpotlessExtension> {
java {
if (project.name == "velocity-api") {
licenseHeaderFile(project.file("HEADER.txt"))
} else {
licenseHeaderFile(project.rootProject.file("HEADER.txt"))
}
removeUnusedImports()
}
}
}
}

Datei anzeigen

@ -2,7 +2,7 @@
configurate = "3.7.3" configurate = "3.7.3"
flare = "2.0.1" flare = "2.0.1"
log4j = "2.20.0" log4j = "2.20.0"
netty = "4.1.90.Final" netty = "4.1.100.Final"
[plugins] [plugins]
indra-publishing = "net.kyori.indra.publishing:2.0.6" indra-publishing = "net.kyori.indra.publishing:2.0.6"
@ -10,13 +10,15 @@ shadow = "com.github.johnrengelman.shadow:8.1.0"
spotless = "com.diffplug.spotless:6.12.0" spotless = "com.diffplug.spotless:6.12.0"
[libraries] [libraries]
adventure-bom = "net.kyori:adventure-bom:4.13.1" adventure-bom = "net.kyori:adventure-bom:4.14.0"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.0" adventure-facet = "net.kyori:adventure-platform-facet:4.3.0"
asm = "org.ow2.asm:asm:9.5"
asynchttpclient = "org.asynchttpclient:async-http-client:2.12.3" asynchttpclient = "org.asynchttpclient:async-http-client:2.12.3"
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT" brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
bstats = "org.bstats:bstats-base:3.0.1" bstats = "org.bstats:bstats-base:3.0.1"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5" caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5"
checker-qual = "org.checkerframework:checker-qual:3.28.0" checker-qual = "org.checkerframework:checker-qual:3.28.0"
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
completablefutures = "com.spotify:completable-futures:0.3.5" completablefutures = "com.spotify:completable-futures:0.3.5"
configurate-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate" } configurate-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate" }
configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" } configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate" }
@ -28,13 +30,14 @@ flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref =
jline = "org.jline:jline-terminal-jansi:3.23.0" jline = "org.jline:jline-terminal-jansi:3.23.0"
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4" jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
junit = "org.junit.jupiter:junit-jupiter:5.9.0" junit = "org.junit.jupiter:junit-jupiter:5.9.0"
kyori-ansi = "net.kyori:ansi:1.0.3"
guava = "com.google.guava:guava:25.1-jre" guava = "com.google.guava:guava:25.1-jre"
gson = "com.google.code.gson:gson:2.10.1" gson = "com.google.code.gson:gson:2.10.1"
guice = "com.google.inject:guice:5.1.0" guice = "com.google.inject:guice:6.0.0"
lmbda = "org.lanternpowered:lmbda:2.0.0" lmbda = "org.lanternpowered:lmbda:2.0.0"
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } log4j-slf4j-impl = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" }
log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" } log4j-iostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" }
log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" }
mockito = "org.mockito:mockito-core:5.2.0" mockito = "org.mockito:mockito-core:5.2.0"
@ -44,7 +47,7 @@ netty-codec-http = { module = "io.netty:netty-codec-http", version.ref = "netty"
netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" } netty-handler = { module = "io.netty:netty-handler", version.ref = "netty" }
netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" } netty-transport-native-epoll = { module = "io.netty:netty-transport-native-epoll", version.ref = "netty" }
nightconfig = "com.electronwill.night-config:toml:3.6.6" nightconfig = "com.electronwill.night-config:toml:3.6.6"
slf4j = "org.slf4j:slf4j-api:1.7.30" slf4j = "org.slf4j:slf4j-api:2.0.7"
spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3" spotbugs-annotations = "com.github.spotbugs:spotbugs-annotations:4.7.3"
terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0" terminalconsoleappender = "net.minecrell:terminalconsoleappender:1.3.0"

Datei anzeigen

@ -1,6 +1,6 @@
plugins { plugins {
`java-library` `java-library`
`maven-publish` id("velocity-publish")
} }
dependencies { dependencies {

Datei anzeigen

@ -2,7 +2,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCach
plugins { plugins {
application application
`set-manifest-impl-version` id("velocity-init-manifest")
alias(libs.plugins.shadow) alias(libs.plugins.shadow)
} }
@ -93,6 +93,7 @@ dependencies {
implementation(project(":velocity-native")) implementation(project(":velocity-native"))
implementation(libs.bundles.log4j) implementation(libs.bundles.log4j)
implementation(libs.kyori.ansi)
implementation(libs.netty.codec) implementation(libs.netty.codec)
implementation(libs.netty.codec.haproxy) implementation(libs.netty.codec.haproxy)
implementation(libs.netty.codec.http) implementation(libs.netty.codec.http)
@ -114,6 +115,7 @@ dependencies {
implementation(libs.nightconfig) implementation(libs.nightconfig)
implementation(libs.bstats) implementation(libs.bstats)
implementation(libs.lmbda) implementation(libs.lmbda)
implementation(libs.asm)
implementation(libs.bundles.flare) implementation(libs.bundles.flare)
compileOnly(libs.spotbugs.annotations) compileOnly(libs.spotbugs.annotations)
testImplementation(libs.mockito) testImplementation(libs.mockito)

Datei anzeigen

@ -568,6 +568,11 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
shutdown(true); shutdown(true);
} }
@Override
public void closeListeners() {
this.cm.closeEndpoints(false);
}
public AsyncHttpClient getAsyncHttpClient() { public AsyncHttpClient getAsyncHttpClient() {
return cm.getHttpClient(); return cm.getHttpClient();
} }

Datei anzeigen

@ -36,6 +36,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler; import com.velocitypowered.proxy.connection.client.InitialLoginSessionHandler;
import com.velocitypowered.proxy.connection.client.StatusSessionHandler; import com.velocitypowered.proxy.connection.client.StatusSessionHandler;
import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.VelocityConnectionEvent; import com.velocitypowered.proxy.protocol.VelocityConnectionEvent;
@ -46,6 +47,7 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEnco
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -60,6 +62,9 @@ import io.netty.util.ReferenceCountUtil;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@ -78,7 +83,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
private final Channel channel; private final Channel channel;
private SocketAddress remoteAddress; private SocketAddress remoteAddress;
private StateRegistry state; private StateRegistry state;
private @Nullable MinecraftSessionHandler sessionHandler; private Map<StateRegistry, MinecraftSessionHandler> sessionHandlers;
private @Nullable MinecraftSessionHandler activeSessionHandler;
private ProtocolVersion protocolVersion; private ProtocolVersion protocolVersion;
private @Nullable MinecraftConnectionAssociation association; private @Nullable MinecraftConnectionAssociation association;
public final VelocityServer server; public final VelocityServer server;
@ -96,12 +102,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
this.remoteAddress = channel.remoteAddress(); this.remoteAddress = channel.remoteAddress();
this.server = server; this.server = server;
this.state = StateRegistry.HANDSHAKE; this.state = StateRegistry.HANDSHAKE;
this.sessionHandlers = new HashMap<>();
} }
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.connected(); activeSessionHandler.connected();
} }
if (association != null && server.getConfiguration().isLogPlayerConnections()) { if (association != null && server.getConfiguration().isLogPlayerConnections()) {
@ -111,12 +119,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.disconnected(); activeSessionHandler.disconnected();
} }
if (association != null && !knownDisconnect if (association != null && !knownDisconnect
&& !(sessionHandler instanceof StatusSessionHandler) && !(activeSessionHandler instanceof StatusSessionHandler)
&& server.getConfiguration().isLogPlayerConnections()) { && server.getConfiguration().isLogPlayerConnections()) {
logger.info("{} has disconnected", association); logger.info("{} has disconnected", association);
} }
@ -125,12 +133,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try { try {
if (sessionHandler == null) { if (activeSessionHandler == null) {
// No session handler available, do nothing // No session handler available, do nothing
return; return;
} }
if (sessionHandler.beforeHandle()) { if (activeSessionHandler.beforeHandle()) {
return; return;
} }
@ -140,15 +148,15 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (msg instanceof MinecraftPacket) { if (msg instanceof MinecraftPacket) {
MinecraftPacket pkt = (MinecraftPacket) msg; MinecraftPacket pkt = (MinecraftPacket) msg;
if (!pkt.handle(sessionHandler)) { if (!pkt.handle(activeSessionHandler)) {
sessionHandler.handleGeneric((MinecraftPacket) msg); activeSessionHandler.handleGeneric((MinecraftPacket) msg);
} }
} else if (msg instanceof HAProxyMessage) { } else if (msg instanceof HAProxyMessage) {
HAProxyMessage proxyMessage = (HAProxyMessage) msg; HAProxyMessage proxyMessage = (HAProxyMessage) msg;
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
proxyMessage.sourcePort()); proxyMessage.sourcePort());
} else if (msg instanceof ByteBuf) { } else if (msg instanceof ByteBuf) {
sessionHandler.handleUnknown((ByteBuf) msg); activeSessionHandler.handleUnknown((ByteBuf) msg);
} }
} finally { } finally {
ReferenceCountUtil.release(msg); ReferenceCountUtil.release(msg);
@ -157,20 +165,21 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.readCompleted(); activeSessionHandler.readCompleted();
} }
} }
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) { if (ctx.channel().isActive()) {
if (sessionHandler != null) { if (activeSessionHandler != null) {
try { try {
sessionHandler.exception(cause); activeSessionHandler.exception(cause);
} catch (Exception ex) { } catch (Exception ex) {
logger.error("{}: exception handling exception in {}", logger.error("{}: exception handling exception in {}",
(association != null ? association : channel.remoteAddress()), sessionHandler, cause); (association != null ? association : channel.remoteAddress()), activeSessionHandler,
cause);
} }
} }
@ -178,13 +187,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (cause instanceof ReadTimeoutException) { if (cause instanceof ReadTimeoutException) {
logger.error("{}: read timed out", association); logger.error("{}: read timed out", association);
} else { } else {
boolean frontlineHandler = sessionHandler instanceof InitialLoginSessionHandler boolean frontlineHandler = activeSessionHandler instanceof InitialLoginSessionHandler
|| sessionHandler instanceof HandshakeSessionHandler || activeSessionHandler instanceof HandshakeSessionHandler
|| sessionHandler instanceof StatusSessionHandler; || activeSessionHandler instanceof StatusSessionHandler;
boolean isQuietDecoderException = cause instanceof QuietDecoderException; boolean isQuietDecoderException = cause instanceof QuietDecoderException;
boolean willLog = !isQuietDecoderException && !frontlineHandler; boolean willLog = !isQuietDecoderException && !frontlineHandler;
if (willLog) { if (willLog) {
logger.error("{}: exception encountered in {}", association, sessionHandler, cause); logger.error("{}: exception encountered in {}", association, activeSessionHandler,
cause);
} else { } else {
knownDisconnect = true; knownDisconnect = true;
} }
@ -197,8 +207,8 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) { if (activeSessionHandler != null) {
sessionHandler.writabilityChanged(); activeSessionHandler.writabilityChanged();
} }
} }
@ -323,7 +333,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
/** /**
* Determines whether or not the channel should continue reading data automaticaly. * Determines whether or not the channel should continue reading data automatically.
* *
* @param autoReading whether or not we should read data automatically * @param autoReading whether or not we should read data automatically
*/ */
@ -341,10 +351,12 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
} }
// Ideally only used by the state switch
/** /**
* Changes the state of the Minecraft connection. * Sets the new state for the connection.
* *
* @param state the new state * @param state the state to use
*/ */
public void setState(StateRegistry state) { public void setState(StateRegistry state) {
ensureInEventLoop(); ensureInEventLoop();
@ -352,6 +364,25 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
this.state = state; this.state = state;
this.channel.pipeline().get(MinecraftEncoder.class).setState(state); this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
this.channel.pipeline().get(MinecraftDecoder.class).setState(state); this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
if (state == StateRegistry.CONFIG) {
// Activate the play packet queue
addPlayPacketQueueHandler();
} else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) {
// Remove the queue
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE);
}
}
/**
* Adds the play packet queue handler.
*/
public void addPlayPacketQueueHandler() {
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE,
new PlayPacketQueueHandler(this.protocolVersion,
channel.pipeline().get(MinecraftEncoder.class).getDirection()));
}
} }
public ProtocolVersion getProtocolVersion() { public ProtocolVersion getProtocolVersion() {
@ -382,32 +413,81 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
} }
public @Nullable MinecraftSessionHandler getSessionHandler() { public @Nullable MinecraftSessionHandler getActiveSessionHandler() {
return sessionHandler; return activeSessionHandler;
}
public @Nullable MinecraftSessionHandler getSessionHandlerForRegistry(StateRegistry registry) {
return this.sessionHandlers.getOrDefault(registry, null);
} }
/** /**
* Sets the session handler for this connection. * Sets the session handler for this connection.
* *
* @param registry the registry of the handler
* @param sessionHandler the handler to use * @param sessionHandler the handler to use
*/ */
public void setSessionHandler(MinecraftSessionHandler sessionHandler) { public void setActiveSessionHandler(StateRegistry registry,
MinecraftSessionHandler sessionHandler) {
Preconditions.checkNotNull(registry);
ensureInEventLoop(); ensureInEventLoop();
if (this.sessionHandler != null) { if (this.activeSessionHandler != null) {
this.sessionHandler.deactivated(); this.activeSessionHandler.deactivated();
} }
this.sessionHandler = sessionHandler; this.sessionHandlers.put(registry, sessionHandler);
this.activeSessionHandler = sessionHandler;
setState(registry);
sessionHandler.activated(); sessionHandler.activated();
} }
/**
* Switches the active session handler to the respective registry one.
*
* @param registry the registry of the handler
* @return true if successful and handler is present
*/
public boolean setActiveSessionHandler(StateRegistry registry) {
Preconditions.checkNotNull(registry);
ensureInEventLoop();
MinecraftSessionHandler handler = getSessionHandlerForRegistry(registry);
if (handler != null) {
boolean flag = true;
if (this.activeSessionHandler != null
&& (flag = !Objects.equals(handler, this.activeSessionHandler))) {
this.activeSessionHandler.deactivated();
}
this.activeSessionHandler = handler;
setState(registry);
if (flag) {
handler.activated();
}
}
return handler != null;
}
/**
* Adds a secondary session handler for this connection.
*
* @param registry the registry of the handler
* @param sessionHandler the handler to use
*/
public void addSessionHandler(StateRegistry registry, MinecraftSessionHandler sessionHandler) {
Preconditions.checkNotNull(registry);
Preconditions.checkArgument(registry != state, "Handler would overwrite handler");
ensureInEventLoop();
this.sessionHandlers.put(registry, sessionHandler);
}
private void ensureOpen() { private void ensureOpen() {
Preconditions.checkState(!isClosed(), "Connection is closed."); Preconditions.checkState(!isClosed(), "Connection is closed.");
} }
/** /**
* Sets the compression threshold on the connection. You are responsible for sending * Sets the compression threshold on the connection. You are responsible for sending {@link
* {@link com.velocitypowered.proxy.protocol.packet.SetCompression} beforehand. * com.velocitypowered.proxy.protocol.packet.SetCompression} beforehand.
* *
* @param threshold the compression threshold to use * @param threshold the compression threshold to use
*/ */
@ -497,5 +577,4 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
public void setType(ConnectionType connectionType) { public void setType(ConnectionType connectionType) {
this.connectionType = connectionType; this.connectionType = connectionType;
} }
} }

Datei anzeigen

@ -31,8 +31,10 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake; import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
import com.velocitypowered.proxy.protocol.packet.LegacyPing; import com.velocitypowered.proxy.protocol.packet.LegacyPing;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PingIdentify;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
@ -48,6 +50,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgement;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
@ -55,6 +58,11 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeatures;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -279,4 +287,36 @@ public interface MinecraftSessionHandler {
default boolean handle(UpsertPlayerInfo packet) { default boolean handle(UpsertPlayerInfo packet) {
return false; return false;
} }
default boolean handle(LoginAcknowledged packet) {
return false;
}
default boolean handle(ActiveFeatures packet) {
return false;
}
default boolean handle(FinishedUpdate packet) {
return false;
}
default boolean handle(RegistrySync packet) {
return false;
}
default boolean handle(TagsUpdate packet) {
return false;
}
default boolean handle(StartUpdate packet) {
return false;
}
default boolean handle(PingIdentify pingIdentify) {
return false;
}
default boolean handle(ChatAcknowledgement chatAcknowledgement) {
return false;
}
} }

Datei anzeigen

@ -39,8 +39,11 @@ import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
@ -51,6 +54,7 @@ import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.ServerData; import com.velocitypowered.proxy.protocol.packet.ServerData;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
@ -68,10 +72,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class); private static final Logger logger = LogManager.getLogger(BackendPlaySessionHandler.class);
private static final boolean BACKPRESSURE_LOG = Boolean private static final boolean BACKPRESSURE_LOG =
.getBoolean("velocity.log-server-backpressure"); Boolean.getBoolean("velocity.log-server-backpressure");
private static final int MAXIMUM_PACKETS_TO_FLUSH = Integer private static final int MAXIMUM_PACKETS_TO_FLUSH =
.getInteger("velocity.max-packets-per-flush", 8192); Integer.getInteger("velocity.max-packets-per-flush", 8192);
private final VelocityServer server; private final VelocityServer server;
private final VelocityServerConnection serverConn; private final VelocityServerConnection serverConn;
@ -86,7 +90,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
this.serverConn = serverConn; this.serverConn = serverConn;
this.playerConnection = serverConn.getPlayer().getConnection(); this.playerConnection = serverConn.getPlayer().getConnection();
MinecraftSessionHandler psh = playerConnection.getSessionHandler(); MinecraftSessionHandler psh = playerConnection.getActiveSessionHandler();
if (!(psh instanceof ClientPlaySessionHandler)) { if (!(psh instanceof ClientPlaySessionHandler)) {
throw new IllegalStateException( throw new IllegalStateException(
"Initializing BackendPlaySessionHandler with no backing client play session handler!"); "Initializing BackendPlaySessionHandler with no backing client play session handler!");
@ -101,12 +105,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
public void activated() { public void activated() {
serverConn.getServer().addPlayer(serverConn.getPlayer()); serverConn.getServer().addPlayer(serverConn.getPlayer());
if (server.getConfiguration().isBungeePluginChannelEnabled()) {
MinecraftConnection serverMc = serverConn.ensureConnected(); MinecraftConnection serverMc = serverConn.ensureConnected();
if (server.getConfiguration().isBungeePluginChannelEnabled()) {
serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(), serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(),
ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion())) ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion()))
)); ));
} }
} }
@Override @Override
@ -119,12 +124,28 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false; return false;
} }
@Override
public boolean handle(StartUpdate packet) {
MinecraftConnection smc = serverConn.ensureConnected();
smc.setAutoReading(false);
// Even when not auto reading messages are still decoded. Decode them with the correct state
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.CONFIG);
serverConn.getPlayer().switchToConfigState();
return true;
}
@Override @Override
public boolean handle(KeepAlive packet) { public boolean handle(KeepAlive packet) {
serverConn.getPendingPings().put(packet.getRandomId(), System.currentTimeMillis()); serverConn.getPendingPings().put(packet.getRandomId(), System.currentTimeMillis());
return false; // forwards on return false; // forwards on
} }
@Override
public boolean handle(ClientSettings packet) {
serverConn.ensureConnected().write(packet);
return true;
}
@Override @Override
public boolean handle(Disconnect packet) { public boolean handle(Disconnect packet) {
serverConn.disconnect(); serverConn.disconnect();
@ -198,13 +219,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
// We need to specially handle REGISTER and UNREGISTER packets. Later on, we'll write them to // Register and unregister packets are simply forwarded to the server as-is.
// the client. if (PluginMessageUtil.isRegister(packet) || PluginMessageUtil.isUnregister(packet)) {
if (PluginMessageUtil.isRegister(packet)) {
serverConn.getPlayer().getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
return false;
} else if (PluginMessageUtil.isUnregister(packet)) {
serverConn.getPlayer().getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
return false; return false;
} }
@ -226,17 +242,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
} }
byte[] copy = ByteBufUtil.getBytes(packet.content()); byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, copy);
copy); server.getEventManager().fire(event).thenAcceptAsync(pme -> {
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) { if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
PluginMessage copied = new PluginMessage(packet.getChannel(), PluginMessage copied = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
Unpooled.wrappedBuffer(copy));
playerConnection.write(copied); playerConnection.write(copied);
} }
}, playerConnection.eventLoop()) }, playerConnection.eventLoop()).exceptionally((ex) -> {
.exceptionally((ex) -> {
logger.error("Exception while handling plugin message {}", packet, ex); logger.error("Exception while handling plugin message {}", packet, ex);
return null; return null;
}); });
@ -288,18 +300,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ServerData packet) { public boolean handle(ServerData packet) {
server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer()) server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer()).thenComposeAsync(
.thenComposeAsync(
ping -> server.getEventManager() ping -> server.getEventManager()
.fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)), .fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)),
playerConnection.eventLoop() playerConnection.eventLoop()).thenAcceptAsync(pingEvent -> this.playerConnection.write(
)
.thenAcceptAsync(pingEvent ->
this.playerConnection.write(
new ServerData(pingEvent.getPing().getDescriptionComponent(), new ServerData(pingEvent.getPing().getDescriptionComponent(),
pingEvent.getPing().getFavicon().orElse(null), pingEvent.getPing().getFavicon().orElse(null), packet.isSecureChatEnforced())),
packet.isSecureChatEnforced()) playerConnection.eventLoop());
), playerConnection.eventLoop());
return true; return true;
} }

Datei anzeigen

@ -0,0 +1,234 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* A special session handler that catches "last minute" disconnects. This version is to accommodate
* 1.20.2+ switching. Yes, some of this is exceptionally stupid.
*/
public class ConfigSessionHandler implements MinecraftSessionHandler {
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$");
private static final Logger logger = LogManager.getLogger(ConfigSessionHandler.class);
private final VelocityServer server;
private final VelocityServerConnection serverConn;
private final CompletableFuture<Impl> resultFuture;
private ResourcePackInfo resourcePackToApply;
private State state;
/**
* Creates the new transition handler.
*
* @param server the Velocity server instance
* @param serverConn the server connection
* @param resultFuture the result future
*/
ConfigSessionHandler(VelocityServer server, VelocityServerConnection serverConn,
CompletableFuture<Impl> resultFuture) {
this.server = server;
this.serverConn = serverConn;
this.resultFuture = resultFuture;
this.state = State.START;
}
@Override
public void activated() {
resourcePackToApply = serverConn.getPlayer().getAppliedResourcePack();
serverConn.getPlayer().clearAppliedResourcePack();
}
@Override
public boolean beforeHandle() {
if (!serverConn.isActive()) {
// Obsolete connection
serverConn.disconnect();
return true;
}
return false;
}
@Override
public boolean handle(StartUpdate packet) {
serverConn.ensureConnected().write(packet);
return true;
}
@Override
public boolean handle(TagsUpdate packet) {
serverConn.getPlayer().getConnection().write(packet);
return true;
}
@Override
public boolean handle(KeepAlive packet) {
serverConn.ensureConnected().write(packet);
return true;
}
@Override
public boolean handle(ResourcePackRequest packet) {
final MinecraftConnection playerConnection = serverConn.getPlayer().getConnection();
ServerResourcePackSendEvent event =
new ServerResourcePackSendEvent(packet.toServerPromptedPack(), this.serverConn);
server.getEventManager().fire(event).thenAcceptAsync(serverResourcePackSendEvent -> {
if (playerConnection.isClosed()) {
return;
}
if (serverResourcePackSendEvent.getResult().isAllowed()) {
ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) {
((VelocityResourcePackInfo) toSend).setOriginalOrigin(
ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
}
resourcePackToApply = null;
serverConn.getPlayer().queueResourcePack(toSend);
} else if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponse(packet.getHash(),
PlayerResourcePackStatusEvent.Status.DECLINED));
}
}, playerConnection.eventLoop()).exceptionally((ex) -> {
if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponse(packet.getHash(),
PlayerResourcePackStatusEvent.Status.DECLINED));
}
logger.error("Exception while handling resource pack send for {}", playerConnection, ex);
return null;
});
return true;
}
@Override
public boolean handle(FinishedUpdate packet) {
MinecraftConnection smc = serverConn.ensureConnected();
ConnectedPlayer player = serverConn.getPlayer();
ClientConfigSessionHandler configHandler =
(ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
smc.setAutoReading(false);
// Even when not auto reading messages are still decoded. Decode them with the correct state
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> {
if (serverConn == player.getConnectedServer()) {
smc.setActiveSessionHandler(StateRegistry.PLAY);
player.sendPlayerListHeaderAndFooter(
player.getPlayerListHeader(), player.getPlayerListFooter());
// The client cleared the tab list. TODO: Restore changes done via TabList API
player.getTabList().clearAllSilent();
} else {
smc.setActiveSessionHandler(StateRegistry.PLAY,
new TransitionSessionHandler(server, serverConn, resultFuture));
}
if (player.getAppliedResourcePack() == null && resourcePackToApply != null) {
player.queueResourcePack(resourcePackToApply);
}
smc.setAutoReading(true);
}, smc.eventLoop());
return true;
}
@Override
public boolean handle(Disconnect packet) {
serverConn.disconnect();
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
return true;
}
@Override
public boolean handle(PluginMessage packet) {
if (PluginMessageUtil.isMcBrand(packet)) {
serverConn.getPlayer().getConnection().write(
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
serverConn.getPlayer().getProtocolVersion()));
} else {
// TODO: Change this so its usable for mod loaders
serverConn.disconnect();
resultFuture.complete(ConnectionRequestResults.forDisconnect(
Component.translatable("multiplayer.disconnect.missing_tags"), serverConn.getServer()));
}
return true;
}
@Override
public boolean handle(RegistrySync packet) {
serverConn.getPlayer().getConnection().write(packet.retain());
return true;
}
@Override
public void disconnected() {
resultFuture.completeExceptionally(
new IOException("Unexpectedly disconnected from remote server"));
}
@Override
public void handleGeneric(MinecraftPacket packet) {
serverConn.getPlayer().getConnection().write(packet);
}
private void switchFailure(Throwable cause) {
logger.error("Unable to switch to new server {} for {}", serverConn.getServerInfo().getName(),
serverConn.getPlayer().getUsername(), cause);
serverConn.getPlayer().disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
resultFuture.completeExceptionally(cause);
}
/**
* Represents the state of the configuration stage.
*/
public static enum State {
START, NEGOTIATING, PLUGIN_MESSAGE_INTERRUPT, RESOURCE_PACK_INTERRUPT, COMPLETE
}
}

Datei anzeigen

@ -27,6 +27,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
@ -34,6 +35,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
@ -59,8 +61,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class); private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final Component MODERN_IP_FORWARDING_FAILURE = Component private static final Component MODERN_IP_FORWARDING_FAILURE =
.translatable("velocity.error.modern-forwarding-failed"); Component.translatable("velocity.error.modern-forwarding-failed");
private final VelocityServer server; private final VelocityServer server;
private final VelocityServerConnection serverConn; private final VelocityServerConnection serverConn;
@ -150,10 +152,26 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
// Move into the PLAY phase. // Move into the PLAY phase.
MinecraftConnection smc = serverConn.ensureConnected(); MinecraftConnection smc = serverConn.ensureConnected();
smc.setState(StateRegistry.PLAY); if (smc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
smc.setActiveSessionHandler(StateRegistry.PLAY,
new TransitionSessionHandler(server, serverConn, resultFuture));
} else {
smc.write(new LoginAcknowledged());
smc.setActiveSessionHandler(StateRegistry.CONFIG,
new ConfigSessionHandler(server, serverConn, resultFuture));
ConnectedPlayer player = serverConn.getPlayer();
if (player.getClientSettingsPacket() != null) {
smc.write(player.getClientSettingsPacket());
}
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
smc.setAutoReading(false);
((ClientPlaySessionHandler) player.getConnection()
.getActiveSessionHandler()).doSwitch().thenAcceptAsync((unused) -> {
smc.setAutoReading(true);
}, smc.eventLoop());
}
}
// Switch to the transition handler.
smc.setSessionHandler(new TransitionSessionHandler(server, serverConn, resultFuture));
return true; return true;
} }
@ -165,12 +183,12 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override @Override
public void disconnected() { public void disconnected() {
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
resultFuture.completeExceptionally( resultFuture.completeExceptionally(new QuietRuntimeException(
new QuietRuntimeException("The connection to the remote server was unexpectedly closed.\n" "The connection to the remote server was unexpectedly closed.\n"
+ "This is usually because the remote server does not have BungeeCord IP forwarding " + "This is usually because the remote server "
+ "does not have BungeeCord IP forwarding "
+ "correctly enabled.\nSee https://velocitypowered.com/wiki/users/forwarding/ " + "correctly enabled.\nSee https://velocitypowered.com/wiki/users/forwarding/ "
+ "for instructions on how to configure player info forwarding correctly.") + "for instructions on how to configure player info forwarding correctly."));
);
} else { } else {
resultFuture.completeExceptionally( resultFuture.completeExceptionally(
new QuietRuntimeException("The connection to the remote server was unexpectedly closed.") new QuietRuntimeException("The connection to the remote server was unexpectedly closed.")

Datei anzeigen

@ -22,6 +22,7 @@ import static com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHands
import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.ConnectionTypes;
@ -32,11 +33,11 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.JoinGame; import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -104,7 +105,7 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
player.sendKeepAlive(); player.sendKeepAlive();
// Reset Tablist header and footer to prevent desync // Reset Tablist header and footer to prevent desync
player.clearHeaderAndFooter(); player.clearPlayerListHeaderAndFooter();
} }
// The goods are in hand! We got JoinGame. Let's transition completely to the new state. // The goods are in hand! We got JoinGame. Let's transition completely to the new state.
@ -121,17 +122,21 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Change the client to use the ClientPlaySessionHandler if required. // Change the client to use the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler; ClientPlaySessionHandler playHandler;
if (player.getConnection().getSessionHandler() instanceof ClientPlaySessionHandler) { if (player.getConnection()
playHandler = (ClientPlaySessionHandler) player.getConnection().getSessionHandler(); .getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
playHandler =
(ClientPlaySessionHandler) player.getConnection().getActiveSessionHandler();
} else { } else {
playHandler = new ClientPlaySessionHandler(server, player); playHandler = new ClientPlaySessionHandler(server, player);
player.getConnection().setSessionHandler(playHandler); player.getConnection().setActiveSessionHandler(StateRegistry.PLAY, playHandler);
} }
assert playHandler != null;
playHandler.handleBackendJoinGame(packet, serverConn); playHandler.handleBackendJoinGame(packet, serverConn);
// Set the new play session handler for the server. We will have nothing more to do // Set the new play session handler for the server. We will have nothing more to do
// with this connection once this task finishes up. // with this connection once this task finishes up.
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn)); smc.setActiveSessionHandler(StateRegistry.PLAY,
new BackendPlaySessionHandler(server, serverConn));
// Clean up disabling auto-read while the connected event was being processed. // Clean up disabling auto-read while the connected event was being processed.
smc.setAutoReading(true); smc.setAutoReading(true);
@ -139,12 +144,17 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Now set the connected server. // Now set the connected server.
serverConn.getPlayer().setConnectedServer(serverConn); serverConn.getPlayer().setConnectedServer(serverConn);
// Send client settings. In 1.20.2+ this is done in the config state.
if (smc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0
&& player.getClientSettingsPacket() != null) {
serverConn.ensureConnected().write(player.getClientSettingsPacket());
}
// We're done! :) // We're done! :)
server.getEventManager().fireAndForget(new ServerPostConnectEvent(player, server.getEventManager().fireAndForget(new ServerPostConnectEvent(player,
previousServer)); previousServer));
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer())); resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
}, smc.eventLoop()) }, smc.eventLoop()).exceptionally(exc -> {
.exceptionally(exc -> {
logger.error("Unable to switch to new server {} for {}", logger.error("Unable to switch to new server {} for {}",
serverConn.getServerInfo().getName(), serverConn.getServerInfo().getName(),
player.getUsername(), exc); player.getUsername(), exc);
@ -180,12 +190,6 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
if (PluginMessageUtil.isRegister(packet)) {
serverConn.getPlayer().getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
} else if (PluginMessageUtil.isUnregister(packet)) {
serverConn.getPlayer().getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
}
// We always need to handle plugin messages, for Forge compatibility. // We always need to handle plugin messages, for Forge compatibility.
if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) { if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) {
// Handled, but check the server connection phase. // Handled, but check the server connection phase.

Datei anzeigen

@ -34,6 +34,7 @@ import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
@ -93,8 +94,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
/** /**
* Connects to the server. * Connects to the server.
* *
* @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result} representing * @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result}
* whether or not the connect succeeded * representing whether the connection succeeded
*/ */
public CompletableFuture<Impl> connect() { public CompletableFuture<Impl> connect() {
CompletableFuture<Impl> result = new CompletableFuture<>(); CompletableFuture<Impl> result = new CompletableFuture<>();
@ -111,15 +112,21 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
future.channel().pipeline().addLast(HANDLER, connection); future.channel().pipeline().addLast(HANDLER, connection);
// Kick off the connection process // Kick off the connection process
connection.setSessionHandler( if (!connection.setActiveSessionHandler(StateRegistry.HANDSHAKE)) {
new LoginSessionHandler(server, VelocityServerConnection.this, result)); MinecraftSessionHandler handler =
new LoginSessionHandler(server, VelocityServerConnection.this, result);
connection.setActiveSessionHandler(StateRegistry.HANDSHAKE, handler);
connection.addSessionHandler(StateRegistry.LOGIN, handler);
}
// Set the connection phase, which may, for future forge (or whatever), be determined // Set the connection phase, which may, for future forge (or whatever), be
// determined
// at this point already // at this point already
connectionPhase = connection.getType().getInitialBackendPhase(); connectionPhase = connection.getType().getInitialBackendPhase();
startHandshake(); startHandshake();
} else { } else {
// Complete the result immediately. ConnectedPlayer will reset the in-flight connection. // Complete the result immediately. ConnectedPlayer will reset the in-flight
// connection.
result.completeExceptionally(future.cause()); result.completeExceptionally(future.cause());
} }
}); });
@ -174,12 +181,10 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private String createBungeeGuardForwardingAddress(byte[] forwardingSecret) { private String createBungeeGuardForwardingAddress(byte[] forwardingSecret) {
// Append forwarding secret as a BungeeGuard token. // Append forwarding secret as a BungeeGuard token.
Property property = new Property("bungeeguard-token", Property property =
new String(forwardingSecret, StandardCharsets.UTF_8), ""); new Property("bungeeguard-token", new String(forwardingSecret, StandardCharsets.UTF_8), "");
return createLegacyForwardingAddress(properties -> ImmutableList.<Property>builder() return createLegacyForwardingAddress(
.addAll(properties) properties -> ImmutableList.<Property>builder().addAll(properties).add(property).build());
.add(property)
.build());
} }
private void startHandshake() { private void startHandshake() {
@ -209,7 +214,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
mc.delayedWrite(handshake); mc.delayedWrite(handshake);
mc.setProtocolVersion(protocolVersion); mc.setProtocolVersion(protocolVersion);
mc.setState(StateRegistry.LOGIN); mc.setActiveSessionHandler(StateRegistry.LOGIN);
if (proxyPlayer.getIdentifiedKey() == null if (proxyPlayer.getIdentifiedKey() == null
&& proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { && proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId())); mc.delayedWrite(new ServerLogin(proxyPlayer.getUsername(), proxyPlayer.getUniqueId()));

Datei anzeigen

@ -26,6 +26,7 @@ import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
@ -38,6 +39,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
import com.velocitypowered.proxy.protocol.packet.SetCompression; import com.velocitypowered.proxy.protocol.packet.SetCompression;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -64,6 +66,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
private GameProfile profile; private GameProfile profile;
private @MonotonicNonNull ConnectedPlayer connectedPlayer; private @MonotonicNonNull ConnectedPlayer connectedPlayer;
private final boolean onlineMode; private final boolean onlineMode;
private State loginState = State.START; // 1.20.2+
AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound, AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound,
GameProfile profile, boolean onlineMode) { GameProfile profile, boolean onlineMode) {
@ -95,8 +98,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
inbound.getIdentifiedKey()); inbound.getIdentifiedKey());
this.connectedPlayer = player; this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) { if (!server.canRegisterConnection(player)) {
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy", player.disconnect0(
NamedTextColor.RED), true); Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED),
true);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@ -109,16 +113,14 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
// wait for permissions to load, then set the players permission function // wait for permissions to load, then set the players permission function
final PermissionFunction function = event.createFunction(player); final PermissionFunction function = event.createFunction(player);
if (function == null) { if (function == null) {
logger.error( logger.error("A plugin permission provider {} provided an invalid permission "
"A plugin permission provider {} provided an invalid permission function" + "function for player {}. This is a bug in the plugin, not in "
+ " for player {}. This is a bug in the plugin, not in Velocity. Falling" + "Velocity. Falling back to the default permission function.",
+ " back to the default permission function.", event.getProvider().getClass().getName(), player.getUsername());
event.getProvider().getClass().getName(),
player.getUsername());
} else { } else {
player.setPermissionFunction(function); player.setPermissionFunction(function);
} }
completeLoginProtocolPhaseAndInitialize(player); startLoginCompletion(player);
} }
}, mcConnection.eventLoop()); }, mcConnection.eventLoop());
}, mcConnection.eventLoop()).exceptionally((ex) -> { }, mcConnection.eventLoop()).exceptionally((ex) -> {
@ -127,7 +129,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
}); });
} }
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) { private void startLoginCompletion(ConnectedPlayer player) {
int threshold = server.getConfiguration().getCompressionThreshold(); int threshold = server.getConfiguration().getCompressionThreshold();
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
mcConnection.write(new SetCompression(threshold)); mcConnection.write(new SetCompression(threshold));
@ -165,17 +167,31 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
} }
ServerLoginSuccess success = new ServerLoginSuccess(); completeLoginProtocolPhaseAndInitialize(player);
success.setUsername(player.getUsername()); }
success.setProperties(player.getGameProfileProperties());
success.setUuid(playerUniqueId);
mcConnection.write(success);
@Override
public boolean handle(LoginAcknowledged packet) {
if (loginState != State.SUCCESS_SENT) {
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_data"));
} else {
loginState = State.ACKNOWLEDGED;
mcConnection.setActiveSessionHandler(StateRegistry.CONFIG,
new ClientConfigSessionHandler(server, connectedPlayer));
server.getEventManager().fire(new PostLoginEvent(connectedPlayer))
.thenCompose((ignored) -> connectToInitialServer(connectedPlayer)).exceptionally((ex) -> {
logger.error("Exception while connecting {} to initial server", connectedPlayer, ex);
return null;
});
}
return true;
}
private void completeLoginProtocolPhaseAndInitialize(ConnectedPlayer player) {
mcConnection.setAssociation(player); mcConnection.setAssociation(player);
mcConnection.setState(StateRegistry.PLAY);
server.getEventManager().fire(new LoginEvent(player)) server.getEventManager().fire(new LoginEvent(player)).thenAcceptAsync(event -> {
.thenAcceptAsync(event -> {
if (mcConnection.isClosed()) { if (mcConnection.isClosed()) {
// The player was disconnected // The player was disconnected
server.getEventManager().fireAndForget(new DisconnectEvent(player, server.getEventManager().fireAndForget(new DisconnectEvent(player,
@ -193,16 +209,25 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
return; return;
} }
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player, server)); ServerLoginSuccess success = new ServerLoginSuccess();
success.setUsername(player.getUsername());
success.setProperties(player.getGameProfileProperties());
success.setUuid(player.getUniqueId());
mcConnection.write(success);
loginState = State.SUCCESS_SENT;
if (inbound.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
loginState = State.ACKNOWLEDGED;
mcConnection.setActiveSessionHandler(StateRegistry.PLAY,
new InitialConnectSessionHandler(player, server));
server.getEventManager().fire(new PostLoginEvent(player)) server.getEventManager().fire(new PostLoginEvent(player))
.thenCompose((ignored) -> connectToInitialServer(player)) .thenCompose((ignored) -> connectToInitialServer(player)).exceptionally((ex) -> {
.exceptionally((ex) -> {
logger.error("Exception while connecting {} to initial server", player, ex); logger.error("Exception while connecting {} to initial server", player, ex);
return null; return null;
}); });
} }
}, mcConnection.eventLoop()) }
.exceptionally((ex) -> { }, mcConnection.eventLoop()).exceptionally((ex) -> {
logger.error("Exception while completing login initialisation phase for {}", player, ex); logger.error("Exception while completing login initialisation phase for {}", player, ex);
return null; return null;
}); });
@ -210,15 +235,15 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
private CompletableFuture<Void> connectToInitialServer(ConnectedPlayer player) { private CompletableFuture<Void> connectToInitialServer(ConnectedPlayer player) {
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry(); Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player, PlayerChooseInitialServerEvent event =
initialFromConfig.orElse(null)); new PlayerChooseInitialServerEvent(player, initialFromConfig.orElse(null));
return server.getEventManager().fire(event) return server.getEventManager().fire(event).thenRunAsync(() -> {
.thenRunAsync(() -> {
Optional<RegisteredServer> toTry = event.getInitialServer(); Optional<RegisteredServer> toTry = event.getInitialServer();
if (!toTry.isPresent()) { if (!toTry.isPresent()) {
player.disconnect0(Component.translatable("velocity.error.no-available-servers", player.disconnect0(
NamedTextColor.RED), true); Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED),
true);
return; return;
} }
player.createConnectionRequest(toTry.get()).fireAndForget(); player.createConnectionRequest(toTry.get()).fireAndForget();
@ -237,4 +262,8 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
this.inbound.cleanup(); this.inbound.cleanup();
} }
static enum State {
START, SUCCESS_SENT, ACKNOWLEDGED
}
} }

Datei anzeigen

@ -0,0 +1,161 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import io.netty.buffer.ByteBuf;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Handles the client config stage.
*/
public class ClientConfigSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(ClientConfigSessionHandler.class);
private final VelocityServer server;
private final ConnectedPlayer player;
private CompletableFuture<Void> configSwitchFuture;
/**
* Constructs a client config session handler.
*
* @param server the Velocity server instance
* @param player the player
*/
public ClientConfigSessionHandler(VelocityServer server, ConnectedPlayer player) {
this.server = server;
this.player = player;
}
@Override
public void activated() {
configSwitchFuture = new CompletableFuture<>();
}
@Override
public void deactivated() {
}
@Override
public boolean handle(KeepAlive packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(System.currentTimeMillis() - sentTime);
smc.write(packet);
}
}
}
return true;
}
@Override
public boolean handle(ClientSettings packet) {
player.setClientSettingsPacket(packet);
return true;
}
@Override
public boolean handle(ResourcePackResponse packet) {
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().ensureConnected().write(packet);
}
return player.onResourcePackResponse(packet.getStatus());
}
@Override
public boolean handle(FinishedUpdate packet) {
player.getConnection()
.setActiveSessionHandler(StateRegistry.PLAY, new ClientPlaySessionHandler(server, player));
configSwitchFuture.complete(null);
return true;
}
@Override
public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && serverConnection.getPhase().consideredComplete()) {
if (packet instanceof PluginMessage) {
((PluginMessage) packet).retain();
}
smc.write(packet);
}
}
@Override
public void handleUnknown(ByteBuf buf) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
MinecraftConnection smc = serverConnection.getConnection();
if (smc != null && !smc.isClosed() && serverConnection.getPhase().consideredComplete()) {
smc.write(buf.retain());
}
}
@Override
public void disconnected() {
player.teardown();
}
@Override
public void exception(Throwable throwable) {
player.disconnect(
Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
}
/**
* Handles the backend finishing the config stage.
*
* @param serverConn the server connection
* @return a future that completes when the config stage is finished
*/
public CompletableFuture<Void> handleBackendFinishUpdate(VelocityServerConnection serverConn) {
player.getConnection().write(new FinishedUpdate());
serverConn.ensureConnected().write(new FinishedUpdate());
return configSwitchFuture;
}
}

Datei anzeigen

@ -17,9 +17,6 @@
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.client;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_16;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket; import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.constructChannelsPacket;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -67,6 +64,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatHandler
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.CharacterUtil; import com.velocitypowered.proxy.util.CharacterUtil;
@ -80,6 +78,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -105,6 +104,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private final CommandHandler<? extends MinecraftPacket> commandHandler; private final CommandHandler<? extends MinecraftPacket> commandHandler;
private final ChatTimeKeeper timeKeeper = new ChatTimeKeeper(); private final ChatTimeKeeper timeKeeper = new ChatTimeKeeper();
private CompletableFuture<Void> configSwitchFuture;
/** /**
* Constructs a client play session handler. * Constructs a client play session handler.
* *
@ -151,12 +152,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { public void activated() {
Collection<String> channels = server.getChannelRegistrar() configSwitchFuture = new CompletableFuture<>();
.getChannelsForProtocol(player.getProtocolVersion()); Collection<String> channels =
server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
if (!channels.isEmpty()) { if (!channels.isEmpty()) {
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels); PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
player.getConnection().write(register); player.getConnection().write(register);
player.getKnownChannels().addAll(channels);
} }
} }
@ -186,7 +187,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ClientSettings packet) { public boolean handle(ClientSettings packet) {
player.setPlayerSettings(packet); player.setPlayerSettings(packet);
return false; // will forward onto the server VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return true;
}
player.getConnectedServer().ensureConnected().write(packet);
return true; // will forward onto the server
} }
@Override @Override
@ -289,13 +296,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null; MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
if (serverConn != null && backendConn != null) { if (serverConn != null && backendConn != null) {
if (backendConn.getState() != StateRegistry.PLAY) { if (backendConn.getState() != StateRegistry.PLAY) {
logger.warn( logger.warn("A plugin message was received while the backend server was not "
"A plugin message was received while the backend server was not " + "ready. Channel: {}. Packet discarded.", packet.getChannel());
+ "ready. Channel: {}. Packet discarded.",
packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) { } else if (PluginMessageUtil.isRegister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
player.getKnownChannels().addAll(channels);
List<ChannelIdentifier> channelIdentifiers = new ArrayList<>(); List<ChannelIdentifier> channelIdentifiers = new ArrayList<>();
for (String channel : channels) { for (String channel : channels) {
try { try {
@ -309,7 +313,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers))); new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
backendConn.write(packet.retain()); backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) { } else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet.retain()); backendConn.write(packet.retain());
} else if (PluginMessageUtil.isMcBrand(packet)) { } else if (PluginMessageUtil.isMcBrand(packet)) {
String brand = PluginMessageUtil.readBrandMessage(packet.content()); String brand = PluginMessageUtil.readBrandMessage(packet.content());
@ -380,6 +383,26 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return player.onResourcePackResponse(packet.getStatus()); return player.onResourcePackResponse(packet.getStatus());
} }
@Override
public boolean handle(FinishedUpdate packet) {
// Complete client switch
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
MinecraftConnection smc = serverConnection.ensureConnected();
CompletableFuture.runAsync(() -> {
smc.write(packet);
smc.setActiveSessionHandler(StateRegistry.CONFIG);
smc.setAutoReading(true);
}, smc.eventLoop()).exceptionally((ex) -> {
logger.error("Error forwarding config state acknowledgement to server:", ex);
return null;
});
}
configSwitchFuture.complete(null);
return true;
}
@Override @Override
public void handleGeneric(MinecraftPacket packet) { public void handleGeneric(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();
@ -443,6 +466,34 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
/**
* Handles switching stages for swapping between servers.
*
* @return a future that completes when the switch is complete
*/
public CompletableFuture<Void> doSwitch() {
VelocityServerConnection existingConnection = player.getConnectedServer();
if (existingConnection != null) {
// Shut down the existing server connection.
player.setConnectedServer(null);
existingConnection.disconnect();
// Send keep alive to try to avoid timeouts
player.sendKeepAlive();
// Config state clears everything in the client. No need to clear later.
spawned = false;
serverBossBars.clear();
player.clearPlayerListHeaderAndFooterSilent();
player.getTabList().clearAllSilent();
}
player.switchToConfigState();
return configSwitchFuture;
}
/** /**
* Handles the {@code JoinGame} packet. This function is responsible for handling the client-side * Handles the {@code JoinGame} packet. This function is responsible for handling the client-side
* switching servers in Velocity. * switching servers in Velocity.
@ -485,10 +536,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
serverBossBars.clear(); serverBossBars.clear();
// Tell the server about this client's plugin message channels. // Tell the server about the proxy's plugin message channels.
ProtocolVersion serverVersion = serverMc.getProtocolVersion(); ProtocolVersion serverVersion = serverMc.getProtocolVersion();
if (!player.getKnownChannels().isEmpty()) { final Collection<String> channels = server.getChannelRegistrar()
serverMc.delayedWrite(constructChannelsPacket(serverVersion, player.getKnownChannels())); .getChannelsForProtocol(serverMc.getProtocolVersion());
if (!channels.isEmpty()) {
serverMc.delayedWrite(constructChannelsPacket(serverVersion, channels));
} }
// If we had plugin messages queued during login/FML handshake, send them now. // If we had plugin messages queued during login/FML handshake, send them now.
@ -498,7 +551,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
// Clear any title from the previous server. // Clear any title from the previous server.
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
player.getConnection().delayedWrite( player.getConnection().delayedWrite(
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET,
player.getProtocolVersion())); player.getProtocolVersion()));
@ -521,7 +574,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// improving compatibility with mods. // improving compatibility with mods.
final Respawn respawn = Respawn.fromJoinGame(joinGame); final Respawn respawn = Respawn.fromJoinGame(joinGame);
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) {
// Before Minecraft 1.16, we could not switch to the same dimension without sending an // Before Minecraft 1.16, we could not switch to the same dimension without sending an
// additional respawn. On older versions of Minecraft this forces the client to perform // additional respawn. On older versions of Minecraft this forces the client to perform
// garbage collection which adds additional latency. // garbage collection which adds additional latency.
@ -563,7 +616,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String commandLabel = command.substring(0, commandEndPosition); String commandLabel = command.substring(0, commandEndPosition);
if (!server.getCommandManager().hasCommand(commandLabel)) { if (!server.getCommandManager().hasCommand(commandLabel)) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide // Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// additional tab completion support. // additional tab completion support.
outstandingTabComplete = packet; outstandingTabComplete = packet;
@ -605,7 +658,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
private boolean handleRegularTabComplete(TabCompleteRequest packet) { private boolean handleRegularTabComplete(TabCompleteRequest packet) {
if (player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0) { if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
// Outstanding tab completes are recorded for use with 1.12 clients and below to provide // Outstanding tab completes are recorded for use with 1.12 clients and below to provide
// additional tab completion support. // additional tab completion support.
outstandingTabComplete = packet; outstandingTabComplete = packet;
@ -636,7 +689,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String command = request.getCommand().substring(1); String command = request.getCommand().substring(1);
server.getCommandManager().offerBrigadierSuggestions(player, command) server.getCommandManager().offerBrigadierSuggestions(player, command)
.thenAcceptAsync(offers -> { .thenAcceptAsync(offers -> {
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0; boolean legacy =
player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0;
try { try {
for (Suggestion suggestion : offers.getList()) { for (Suggestion suggestion : offers.getList()) {
String offer = suggestion.getText(); String offer = suggestion.getText();
@ -660,9 +714,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
}, player.getConnection().eventLoop()).exceptionally((ex) -> { }, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error( logger.error(
"Exception while finishing command tab completion, with request {} and response {}", "Exception while finishing command tab completion,"
request, + " with request {} and response {}",
response, ex); request, response, ex);
return null; return null;
}); });
} }
@ -681,9 +735,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
player.getConnection().write(response); player.getConnection().write(response);
}, player.getConnection().eventLoop()).exceptionally((ex) -> { }, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error( logger.error(
"Exception while finishing regular tab completion, with request {} and response{}", "Exception while finishing regular tab completion,"
request, + " with request {} and response{}",
response, ex); request, response, ex);
return null; return null;
}); });
} }
@ -703,5 +757,4 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
} }
} }

Datei anzeigen

@ -59,6 +59,7 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter; import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
@ -69,6 +70,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.tablist.InternalTabList; import com.velocitypowered.proxy.tablist.InternalTabList;
@ -77,13 +79,12 @@ import com.velocitypowered.proxy.tablist.VelocityTabList;
import com.velocitypowered.proxy.tablist.VelocityTabListLegacy; import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.DurationUtils; import com.velocitypowered.proxy.util.DurationUtils;
import com.velocitypowered.proxy.util.collect.CappedSet; import com.velocitypowered.proxy.util.TranslatableMapper;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -101,9 +102,6 @@ import net.kyori.adventure.platform.facet.FacetPointers;
import net.kyori.adventure.platform.facet.FacetPointers.Type; import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.KeybindComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@ -126,12 +124,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private static final int MAX_PLUGIN_CHANNELS = 1024; private static final int MAX_PLUGIN_CHANNELS = 1024;
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
PlainTextComponentSerializer.builder() PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
.flattener(ComponentFlattener.basic().toBuilder()
.mapper(KeybindComponent.class, c -> "")
.mapper(TranslatableComponent.class, TranslatableComponent::key)
.build())
.build();
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class); private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
@ -156,24 +149,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private final InternalTabList tabList; private final InternalTabList tabList;
private final VelocityServer server; private final VelocityServer server;
private ClientConnectionPhase connectionPhase; private ClientConnectionPhase connectionPhase;
private final Collection<String> knownChannels;
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>(); private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
private @MonotonicNonNull List<String> serversToTry = null; private @MonotonicNonNull List<String> serversToTry = null;
private @MonotonicNonNull Boolean previousResourceResponse; private @MonotonicNonNull Boolean previousResourceResponse;
private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>(); private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>();
private @Nullable ResourcePackInfo pendingResourcePack; private @Nullable ResourcePackInfo pendingResourcePack;
private @Nullable ResourcePackInfo appliedResourcePack; private @Nullable ResourcePackInfo appliedResourcePack;
private final @NotNull Pointers pointers = Player.super.pointers().toBuilder() private final @NotNull Pointers pointers =
.withDynamic(Identity.UUID, this::getUniqueId) Player.super.pointers().toBuilder().withDynamic(Identity.UUID, this::getUniqueId)
.withDynamic(Identity.NAME, this::getUsername) .withDynamic(Identity.NAME, this::getUsername)
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername())) .withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
.withDynamic(Identity.LOCALE, this::getEffectiveLocale) .withDynamic(Identity.LOCALE, this::getEffectiveLocale)
.withStatic(PermissionChecker.POINTER, getPermissionChecker()) .withStatic(PermissionChecker.POINTER, getPermissionChecker())
.withStatic(FacetPointers.TYPE, Type.PLAYER) .withStatic(FacetPointers.TYPE, Type.PLAYER).build();
.build();
private @Nullable String clientBrand; private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale; private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey; private @Nullable IdentifiedKey playerKey;
private @Nullable ClientSettings clientSettingsPacket;
private final ChatQueue chatQueue; private final ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory; private final ChatBuilderFactory chatBuilderFactory;
@ -186,7 +178,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.virtualHost = virtualHost; this.virtualHost = virtualHost;
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED; this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
this.connectionPhase = connection.getType().getInitialClientPhase(); this.connectionPhase = connection.getType().getInitialClientPhase();
this.knownChannels = CappedSet.create(MAX_PLUGIN_CHANNELS);
this.onlineMode = onlineMode; this.onlineMode = onlineMode;
if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
@ -283,11 +274,19 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
} }
public ClientSettings getClientSettingsPacket() {
return clientSettingsPacket;
}
@Override @Override
public boolean hasSentPlayerSettings() { public boolean hasSentPlayerSettings() {
return settings != null; return settings != null;
} }
public void setClientSettingsPacket(ClientSettings clientSettingsPacket) {
this.clientSettingsPacket = clientSettingsPacket;
}
void setPlayerSettings(ClientSettings settings) { void setPlayerSettings(ClientSettings settings) {
ClientSettingsWrapper cs = new ClientSettingsWrapper(settings); ClientSettingsWrapper cs = new ClientSettingsWrapper(settings);
this.settings = cs; this.settings = cs;
@ -542,8 +541,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
@Override @Override
public void clearHeaderAndFooter() { public void clearPlayerListHeaderAndFooter() {
tabList.clearHeaderAndFooter(); clearPlayerListHeaderAndFooterSilent();
if (this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.connection.write(HeaderAndFooter.reset());
}
}
public void clearPlayerListHeaderAndFooterSilent() {
this.playerListHeader = Component.empty();
this.playerListFooter = Component.empty();
} }
@Override @Override
@ -679,8 +686,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
ServerKickResult result; ServerKickResult result;
if (kickedFromCurrent) { if (kickedFromCurrent) {
Optional<RegisteredServer> next = getNextServerToTry(rs); Optional<RegisteredServer> next = getNextServerToTry(rs);
result = next.map(RedirectPlayer::create) result =
.orElseGet(() -> DisconnectPlayer.create(friendlyReason)); next.map(RedirectPlayer::create).orElseGet(() -> DisconnectPlayer.create(friendlyReason));
} else { } else {
// If we were kicked by going to another server, the connection should not be in flight // If we were kicked by going to another server, the connection should not be in flight
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) { if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
@ -695,8 +702,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason, private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason,
boolean kickedFromCurrent) { boolean kickedFromCurrent) {
server.getEventManager().fire(originalEvent) server.getEventManager().fire(originalEvent).thenAcceptAsync(event -> {
.thenAcceptAsync(event -> {
// There can't be any connection in flight now. // There can't be any connection in flight now.
connectionInFlight = null; connectionInFlight = null;
@ -716,22 +722,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
disconnect(res.getReasonComponent()); disconnect(res.getReasonComponent());
} else if (event.getResult() instanceof RedirectPlayer) { } else if (event.getResult() instanceof RedirectPlayer) {
RedirectPlayer res = (RedirectPlayer) event.getResult(); RedirectPlayer res = (RedirectPlayer) event.getResult();
createConnectionRequest(res.getServer(), previousConnection) createConnectionRequest(res.getServer(), previousConnection).connect()
.connect()
.whenCompleteAsync((status, throwable) -> { .whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
handleConnectionException(status != null ? status.getAttemptedConnection() handleConnectionException(
: res.getServer(), throwable, true); status != null ? status.getAttemptedConnection() : res.getServer(), throwable,
true);
return; return;
} }
switch (status.getStatus()) { switch (status.getStatus()) {
// Impossible/nonsensical cases // Impossible/nonsensical cases
case ALREADY_CONNECTED: case ALREADY_CONNECTED:
logger.error("{}: already connected to {}", logger.error("{}: already connected to {}", this,
this, status.getAttemptedConnection().getServerInfo().getName());
status.getAttemptedConnection().getServerInfo().getName()
);
break; break;
case CONNECTION_IN_PROGRESS: case CONNECTION_IN_PROGRESS:
// Fatal case // Fatal case
@ -745,8 +749,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
case SERVER_DISCONNECTED: case SERVER_DISCONNECTED:
Component reason = status.getReasonComponent() Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(res.getServer(), Disconnect.create(reason, handleConnectionException(res.getServer(),
getProtocolVersion()), ((Impl) status).isSafe()); Disconnect.create(reason, getProtocolVersion()), ((Impl) status).isSafe());
break; break;
case SUCCESS: case SUCCESS:
Component requestedMessage = res.getMessageComponent(); Component requestedMessage = res.getMessageComponent();
@ -1026,6 +1030,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return pendingResourcePack; return pendingResourcePack;
} }
/**
* Clears the applied resource pack field.
*/
public void clearAppliedResourcePack() {
appliedResourcePack = null;
}
/** /**
* Processes a client response to a sent resource-pack. * Processes a client response to a sent resource-pack.
*/ */
@ -1073,18 +1084,42 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER; && queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
} }
/**
* Gives an indication about the previous resource pack responses.
*/
public @Nullable Boolean getPreviousResourceResponse() {
return previousResourceResponse;
}
/** /**
* Sends a {@link KeepAlive} packet to the player with a random ID. The response will be ignored * Sends a {@link KeepAlive} packet to the player with a random ID. The response will be ignored
* by Velocity as it will not match the ID last sent by the server. * by Velocity as it will not match the ID last sent by the server.
*/ */
public void sendKeepAlive() { public void sendKeepAlive() {
if (connection.getState() == StateRegistry.PLAY) { if (connection.getState() == StateRegistry.PLAY
|| connection.getState() == StateRegistry.CONFIG) {
KeepAlive keepAlive = new KeepAlive(); KeepAlive keepAlive = new KeepAlive();
keepAlive.setRandomId(ThreadLocalRandom.current().nextLong()); keepAlive.setRandomId(ThreadLocalRandom.current().nextLong());
connection.write(keepAlive); connection.write(keepAlive);
} }
} }
/**
* Switches the connection to the client into config state.
*/
public void switchToConfigState() {
CompletableFuture.runAsync(() -> {
connection.write(new StartUpdate());
connection.getChannel().pipeline()
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start
connection.addPlayPacketQueueHandler();
}, connection.eventLoop()).exceptionally((ex) -> {
logger.error("Error switching player connection to config state:", ex);
return null;
});
}
/** /**
* Gets the current "phase" of the connection, mostly used for tracking modded negotiation for * Gets the current "phase" of the connection, mostly used for tracking modded negotiation for
* legacy forge servers and provides methods for performing phase specific actions. * legacy forge servers and provides methods for performing phase specific actions.
@ -1104,15 +1139,6 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.connectionPhase = connectionPhase; this.connectionPhase = connectionPhase;
} }
/**
* Return all the plugin message channels "known" to the client.
*
* @return the channels
*/
public Collection<String> getKnownChannels() {
return knownChannels;
}
@Override @Override
public @Nullable IdentifiedKey getIdentifiedKey() { public @Nullable IdentifiedKey getIdentifiedKey() {
return playerKey; return playerKey;
@ -1161,21 +1187,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
private CompletableFuture<Impl> internalConnect() { private CompletableFuture<Impl> internalConnect() {
return this.getInitialStatus() return this.getInitialStatus().thenCompose(initialCheck -> {
.thenCompose(initialCheck -> {
if (initialCheck.isPresent()) { if (initialCheck.isPresent()) {
return completedFuture(plainResult(initialCheck.get(), toConnect)); return completedFuture(plainResult(initialCheck.get(), toConnect));
} }
ServerPreConnectEvent event = new ServerPreConnectEvent(ConnectedPlayer.this, ServerPreConnectEvent event =
toConnect, previousServer); new ServerPreConnectEvent(ConnectedPlayer.this, toConnect, previousServer);
return server.getEventManager().fire(event) return server.getEventManager().fire(event).thenComposeAsync(newEvent -> {
.thenComposeAsync(newEvent -> {
Optional<RegisteredServer> newDest = newEvent.getResult().getServer(); Optional<RegisteredServer> newDest = newEvent.getResult().getServer();
if (!newDest.isPresent()) { if (!newDest.isPresent()) {
return completedFuture( return completedFuture(
plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect) plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect));
);
} }
RegisteredServer realDestination = newDest.get(); RegisteredServer realDestination = newDest.get();
@ -1185,11 +1208,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
VelocityRegisteredServer vrs = (VelocityRegisteredServer) realDestination; VelocityRegisteredServer vrs = (VelocityRegisteredServer) realDestination;
VelocityServerConnection con = new VelocityServerConnection(vrs, VelocityServerConnection con =
previousServer, ConnectedPlayer.this, server); new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server);
connectionInFlight = con; connectionInFlight = con;
return con.connect().whenCompleteAsync( return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(con),
(result, exception) -> this.resetIfInFlightIs(con), connection.eventLoop()); connection.eventLoop());
}, connection.eventLoop()); }, connection.eventLoop());
}); });
} }
@ -1202,25 +1225,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public CompletableFuture<Result> connect() { public CompletableFuture<Result> connect() {
return this.internalConnect() return this.internalConnect().whenCompleteAsync((status, throwable) -> {
.whenCompleteAsync((status, throwable) -> {
if (status != null && !status.isSuccessful()) { if (status != null && !status.isSuccessful()) {
if (!status.isSafe()) { if (!status.isSafe()) {
handleConnectionException(status.getAttemptedConnection(), throwable, false); handleConnectionException(status.getAttemptedConnection(), throwable, false);
} }
} }
}, connection.eventLoop()) }, connection.eventLoop()).thenApply(x -> x);
.thenApply(x -> x);
} }
@Override @Override
public CompletableFuture<Boolean> connectWithIndication() { public CompletableFuture<Boolean> connectWithIndication() {
return internalConnect() return internalConnect().whenCompleteAsync((status, throwable) -> {
.whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
// TODO: The exception handling from this is not very good. Find a better way. // TODO: The exception handling from this is not very good. Find a better way.
handleConnectionException(status != null ? status.getAttemptedConnection() handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect,
: toConnect, throwable, true); throwable, true);
return; return;
} }
@ -1237,15 +1257,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
case SERVER_DISCONNECTED: case SERVER_DISCONNECTED:
Component reason = status.getReasonComponent() Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(toConnect, Disconnect.create(reason, handleConnectionException(toConnect, Disconnect.create(reason, getProtocolVersion()),
getProtocolVersion()), status.isSafe()); status.isSafe());
break; break;
default: default:
// The only remaining value is successful (no need to do anything!) // The only remaining value is successful (no need to do anything!)
break; break;
} }
}, connection.eventLoop()) }, connection.eventLoop()).thenApply(Result::isSuccessful);
.thenApply(Result::isSuccessful);
} }
@Override @Override

Datei anzeigen

@ -47,8 +47,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* The initial handler used when a connection is established to the proxy. This will either * The initial handler used when a connection is established to the proxy. This will either
* transition to {@link StatusSessionHandler} or {@link InitialLoginSessionHandler} as soon * transition to {@link StatusSessionHandler} or {@link InitialLoginSessionHandler} as soon as the
* as the handshake packet is received. * handshake packet is received.
*/ */
public class HandshakeSessionHandler implements MinecraftSessionHandler { public class HandshakeSessionHandler implements MinecraftSessionHandler {
@ -65,9 +65,9 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(LegacyPing packet) { public boolean handle(LegacyPing packet) {
connection.setProtocolVersion(ProtocolVersion.LEGACY); connection.setProtocolVersion(ProtocolVersion.LEGACY);
StatusSessionHandler handler = new StatusSessionHandler(server, StatusSessionHandler handler =
new LegacyInboundConnection(connection, packet)); new StatusSessionHandler(server, new LegacyInboundConnection(connection, packet));
connection.setSessionHandler(handler); connection.setActiveSessionHandler(StateRegistry.STATUS, handler);
handler.handle(packet); handler.handle(packet);
return true; return true;
} }
@ -90,13 +90,13 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus()); LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
connection.close(true); connection.close(true);
} else { } else {
connection.setState(nextState);
connection.setProtocolVersion(handshake.getProtocolVersion()); connection.setProtocolVersion(handshake.getProtocolVersion());
connection.setAssociation(ic); connection.setAssociation(ic);
switch (nextState) { switch (nextState) {
case STATUS: case STATUS:
connection.setSessionHandler(new StatusSessionHandler(server, ic)); connection.setActiveSessionHandler(StateRegistry.STATUS,
new StatusSessionHandler(server, ic));
break; break;
case LOGIN: case LOGIN:
this.handleLogin(handshake, ic); this.handleLogin(handshake, ic);
@ -140,14 +140,15 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
// and lower, otherwise IP information will never get forwarded. // and lower, otherwise IP information will never get forwarded.
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { && handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
ic.disconnectQuietly(Component.translatable( ic.disconnectQuietly(
"velocity.error.modern-forwarding-needs-new-client")); Component.translatable("velocity.error.modern-forwarding-needs-new-client"));
return; return;
} }
LoginInboundConnection lic = new LoginInboundConnection(ic); LoginInboundConnection lic = new LoginInboundConnection(ic);
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic)); server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic));
connection.setSessionHandler(new InitialLoginSessionHandler(server, connection, lic)); connection.setActiveSessionHandler(StateRegistry.LOGIN,
new InitialLoginSessionHandler(server, connection, lic));
} }
private ConnectionType getHandshakeConnectionType(Handshake handshake) { private ConnectionType getHandshakeConnectionType(Handshake handshake) {

Datei anzeigen

@ -24,7 +24,6 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder; import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -55,15 +54,7 @@ public class InitialConnectSessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
if (PluginMessageUtil.isRegister(packet)) { if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
serverConn.ensureConnected().write(packet.retain());
return true;
} else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
serverConn.ensureConnected().write(packet.retain());
return true;
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
return true; return true;
} }

Datei anzeigen

@ -34,6 +34,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
@ -120,8 +121,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
this.login = packet; this.login = packet;
PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername()); PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername());
server.getEventManager().fire(event) server.getEventManager().fire(event).thenRunAsync(() -> {
.thenRunAsync(() -> {
if (mcConnection.isClosed()) { if (mcConnection.isClosed()) {
// The player was disconnected // The player was disconnected
return; return;
@ -142,22 +142,21 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
} }
mcConnection.eventLoop().execute(() -> { mcConnection.eventLoop().execute(() -> {
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() if (!result.isForceOfflineMode()
|| result.isOnlineModeAllowed())) { && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
// Request encryption. // Request encryption.
EncryptionRequest request = generateEncryptionRequest(); EncryptionRequest request = generateEncryptionRequest();
this.verify = Arrays.copyOf(request.getVerifyToken(), 4); this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
mcConnection.write(request); mcConnection.write(request);
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT; this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
} else { } else {
mcConnection.setSessionHandler(new AuthSessionHandler( mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false new AuthSessionHandler(server, inbound,
)); GameProfile.forOfflinePlayer(login.getUsername()), false));
} }
}); });
}); });
}, mcConnection.eventLoop()) }, mcConnection.eventLoop()).exceptionally((ex) -> {
.exceptionally((ex) -> {
logger.error("Exception in pre-login stage", ex); logger.error("Exception in pre-login stage", ex);
return null; return null;
}); });
@ -246,13 +245,12 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
} }
} }
// All went well, initialize the session. // All went well, initialize the session.
mcConnection.setSessionHandler(new AuthSessionHandler( mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
server, inbound, profile, true new AuthSessionHandler(server, inbound, profile, true));
));
} else if (profileResponse.getStatusCode() == 204) { } else if (profileResponse.getStatusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy. // Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(Component.translatable("velocity.error.online-mode-only", inbound.disconnect(
NamedTextColor.RED)); Component.translatable("velocity.error.online-mode-only", NamedTextColor.RED));
} else { } else {
// Something else went wrong // Something else went wrong
logger.error( logger.error(

Datei anzeigen

@ -76,9 +76,9 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
}, },
/** /**
* The Mod list is sent to the server, captured by Velocity. Transition to * The Mod list is sent to the server, captured by Velocity. Transition to {@link
* {@link #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start * #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start sending state
* sending state data. * data.
*/ */
MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) { MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) {
@Override @Override
@ -138,11 +138,10 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
/** /**
* The handshake is complete. The handshake can be reset. * The handshake is complete. The handshake can be reset.
* *
* <p>Note that a successful connection to a server does not mean that * <p>Note that a successful connection to a server does not mean that we will be in this state.
* we will be in this state. After a handshake reset, if the next server is vanilla we will still * After a handshake reset, if the next server is vanilla we will still be in the {@link
* be in the {@link #NOT_STARTED} phase, which means we must NOT send a reset packet. This is * #NOT_STARTED} phase, which means we must NOT send a reset packet. This is handled by overriding
* handled by overriding the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is * the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is usually a no-op).
* usually a no-op).</p>
*/ */
COMPLETE(null) { COMPLETE(null) {
@Override @Override
@ -165,7 +164,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
// just in case the timing is awful // just in case the timing is awful
player.sendKeepAlive(); player.sendKeepAlive();
MinecraftSessionHandler handler = backendConn.getSessionHandler(); MinecraftSessionHandler handler = backendConn.getActiveSessionHandler();
if (handler instanceof ClientPlaySessionHandler) { if (handler instanceof ClientPlaySessionHandler) {
((ClientPlaySessionHandler) handler).flushQueuedMessages(); ((ClientPlaySessionHandler) handler).flushQueuedMessages();
} }
@ -182,8 +181,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
* *
* @param packetToAdvanceOn The ID of the packet discriminator that indicates that the client has * @param packetToAdvanceOn The ID of the packet discriminator that indicates that the client has
* moved onto a new phase, and as such, Velocity should do so too * moved onto a new phase, and as such, Velocity should do so too
* (inspecting {@link #nextPhase()}. A null indicates there is no further * (inspecting {@link #nextPhase()}. A null indicates there is
* phase to transition to. * no further phase to transition to.
*/ */
LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) { LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) {
this.packetToAdvanceOn = packetToAdvanceOn; this.packetToAdvanceOn = packetToAdvanceOn;

Datei anzeigen

@ -0,0 +1,128 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.registry;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.Nullable;
/**
* Holds the registry data that is sent
* to the client during the config stage.
*/
public class ClientConfigData {
private final @Nullable VelocityResourcePackInfo resourcePackInfo;
private final DataTag tag;
private final RegistrySync registry;
private final Key[] features;
private final String brand;
private ClientConfigData(@Nullable VelocityResourcePackInfo resourcePackInfo, DataTag tag,
RegistrySync registry, Key[] features, String brand) {
this.resourcePackInfo = resourcePackInfo;
this.tag = tag;
this.registry = registry;
this.features = features;
this.brand = brand;
}
public RegistrySync getRegistry() {
return registry;
}
public DataTag getTag() {
return tag;
}
public Key[] getFeatures() {
return features;
}
public @Nullable VelocityResourcePackInfo getResourcePackInfo() {
return resourcePackInfo;
}
public String getBrand() {
return brand;
}
/**
* Creates a new builder.
*
* @return ClientConfigData.Builder
*/
public static ClientConfigData.Builder builder() {
return new Builder();
}
/**
* Builder for ClientConfigData.
*/
public static class Builder {
private VelocityResourcePackInfo resourcePackInfo;
private DataTag tag;
private RegistrySync registry;
private Key[] features;
private String brand;
private Builder() {
}
/**
* Clears the builder.
*/
public void clear() {
this.resourcePackInfo = null;
this.tag = null;
this.registry = null;
this.features = null;
this.brand = null;
}
public Builder resourcePack(@Nullable VelocityResourcePackInfo resourcePackInfo) {
this.resourcePackInfo = resourcePackInfo;
return this;
}
public Builder dataTag(DataTag tag) {
this.tag = tag;
return this;
}
public Builder registry(RegistrySync registry) {
this.registry = registry;
return this;
}
public Builder features(Key[] features) {
this.features = features;
return this;
}
public Builder brand(String brand) {
this.brand = brand;
return this;
}
public ClientConfigData build() {
return new ClientConfigData(resourcePackInfo, tag, registry, features, brand);
}
}
}

Datei anzeigen

@ -0,0 +1,95 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.registry;
import com.google.common.collect.ImmutableList;
import java.util.List;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.key.Keyed;
import org.jetbrains.annotations.NotNull;
/**
* Represents a data tag.
*/
public class DataTag {
private final ImmutableList<DataTag.Set> entrySets;
public DataTag(ImmutableList<DataTag.Set> entrySets) {
this.entrySets = entrySets;
}
/**
* Returns the entry sets.
*
* @return List of entry sets
*/
public List<Set> getEntrySets() {
return entrySets;
}
/**
* Represents a data tag set.
*/
public static class Set implements Keyed {
private final Key key;
private final ImmutableList<Entry> entries;
public Set(Key key, ImmutableList<Entry> entries) {
this.key = key;
this.entries = entries;
}
/**
* Returns the entries.
*
* @return List of entries
*/
public List<Entry> getEntries() {
return entries;
}
@Override
public @NotNull Key key() {
return key;
}
}
/**
* Represents a data tag entry.
*/
public static class Entry implements Keyed {
private final Key key;
private final int[] elements;
public Entry(Key key, int[] elements) {
this.key = key;
this.elements = elements;
}
public int[] getElements() {
return elements;
}
@Override
public @NotNull Key key() {
return key;
}
}
}

Datei anzeigen

@ -35,8 +35,7 @@ import net.kyori.adventure.platform.facet.FacetPointers.Type;
import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.pointer.Pointers;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.kyori.adventure.translation.GlobalTranslator;
import net.minecrell.terminalconsole.SimpleTerminalConsole; import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -55,6 +54,8 @@ import org.jline.reader.LineReaderBuilder;
public final class VelocityConsole extends SimpleTerminalConsole implements ConsoleCommandSource { public final class VelocityConsole extends SimpleTerminalConsole implements ConsoleCommandSource {
private static final Logger logger = LogManager.getLogger(VelocityConsole.class); private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
private static final ComponentLogger componentLogger = ComponentLogger
.logger(VelocityConsole.class);
private final VelocityServer server; private final VelocityServer server;
private PermissionFunction permissionFunction = ALWAYS_TRUE; private PermissionFunction permissionFunction = ALWAYS_TRUE;
@ -72,9 +73,7 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
@Override @Override
public void sendMessage(@NonNull Identity identity, @NonNull Component message, public void sendMessage(@NonNull Identity identity, @NonNull Component message,
@NonNull MessageType messageType) { @NonNull MessageType messageType) {
Component translated = GlobalTranslator.render(message, ClosestLocaleMatcher.INSTANCE componentLogger.info(message);
.lookupClosest(Locale.getDefault()));
logger.info(LegacyComponentSerializer.legacySection().serialize(translated));
} }
@Override @Override

Datei anzeigen

@ -318,8 +318,7 @@ public class VelocityEventManager implements EventManager {
if (returnType != void.class && continuationType == Continuation.class) { if (returnType != void.class && continuationType == Continuation.class) {
errors.add("method return type must be void if a continuation parameter is provided"); errors.add("method return type must be void if a continuation parameter is provided");
} else if (returnType != void.class && returnType != EventTask.class) { } else if (returnType != void.class && returnType != EventTask.class) {
errors.add("method return type must be void, EventTask, " errors.add("method return type must be void or EventTask");
+ "EventTask.Basic or EventTask.WithContinuation");
} else if (returnType == EventTask.class) { } else if (returnType == EventTask.class) {
asyncType = AsyncType.SOMETIMES; asyncType = AsyncType.SOMETIMES;
} }

Datei anzeigen

@ -216,9 +216,11 @@ public final class ConnectionManager {
} }
/** /**
* Closes all endpoints. * Closes all the currently registered endpoints.
*
* @param interrupt should closing forward interruptions
*/ */
public void shutdown() { public void closeEndpoints(boolean interrupt) {
for (final Map.Entry<SocketAddress, Endpoint> entry : this.endpoints.entrySet()) { for (final Map.Entry<SocketAddress, Endpoint> entry : this.endpoints.entrySet()) {
final SocketAddress address = entry.getKey(); final SocketAddress address = entry.getKey();
final Endpoint endpoint = entry.getValue(); final Endpoint endpoint = entry.getValue();
@ -227,14 +229,26 @@ public final class ConnectionManager {
// should have a chance to be notified before the server stops accepting connections. // should have a chance to be notified before the server stops accepting connections.
server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join(); server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join();
try {
LOGGER.info("Closing endpoint {}", address); LOGGER.info("Closing endpoint {}", address);
if (interrupt) {
try {
endpoint.getChannel().close().sync(); endpoint.getChannel().close().sync();
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
LOGGER.info("Interrupted whilst closing endpoint", e); LOGGER.info("Interrupted whilst closing endpoint", e);
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} else {
endpoint.getChannel().close().syncUninterruptibly();
} }
}
this.endpoints.clear();
}
/**
* Closes all endpoints.
*/
public void shutdown() {
this.closeEndpoints(true);
this.resolver.shutdown(); this.resolver.shutdown();
} }

Datei anzeigen

@ -35,6 +35,7 @@ public class Connections {
public static final String MINECRAFT_DECODER = "minecraft-decoder"; public static final String MINECRAFT_DECODER = "minecraft-decoder";
public static final String MINECRAFT_ENCODER = "minecraft-encoder"; public static final String MINECRAFT_ENCODER = "minecraft-encoder";
public static final String READ_TIMEOUT = "read-timeout"; public static final String READ_TIMEOUT = "read-timeout";
public static final String PLAY_PACKET_QUEUE = "play-packet-queue";
private Connections() { private Connections() {
throw new AssertionError(); throw new AssertionError();

Datei anzeigen

@ -29,6 +29,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder; import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder; import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
@ -67,7 +68,8 @@ public class ServerChannelInitializer extends ChannelInitializer<Channel> {
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.CLIENTBOUND)); .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.CLIENTBOUND));
final MinecraftConnection connection = new MinecraftConnection(ch, this.server); final MinecraftConnection connection = new MinecraftConnection(ch, this.server);
connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server)); connection.setActiveSessionHandler(StateRegistry.HANDSHAKE,
new HandshakeSessionHandler(connection, this.server));
ch.pipeline().addLast(Connections.HANDLER, connection); ch.pipeline().addLast(Connections.HANDLER, connection);
if (this.server.getConfiguration().isProxyProtocol()) { if (this.server.getConfiguration().isProxyProtocol()) {

Datei anzeigen

@ -94,7 +94,7 @@ public class VelocityPluginManager implements PluginManager {
for (Path path : stream) { for (Path path : stream) {
try { try {
found.add(loader.loadCandidate(path)); found.add(loader.loadCandidate(path));
} catch (Exception e) { } catch (Throwable e) {
logger.error("Unable to load plugin {}", path, e); logger.error("Unable to load plugin {}", path, e);
} }
} }
@ -126,7 +126,7 @@ public class VelocityPluginManager implements PluginManager {
VelocityPluginContainer container = new VelocityPluginContainer(realPlugin); VelocityPluginContainer container = new VelocityPluginContainer(realPlugin);
pluginContainers.put(container, loader.createModule(container)); pluginContainers.put(container, loader.createModule(container));
loadedPluginsById.add(realPlugin.getId()); loadedPluginsById.add(realPlugin.getId());
} catch (Exception e) { } catch (Throwable e) {
logger.error("Can't create module for plugin {}", candidate.getId(), e); logger.error("Can't create module for plugin {}", candidate.getId(), e);
} }
} }
@ -153,7 +153,7 @@ public class VelocityPluginManager implements PluginManager {
try { try {
loader.createPlugin(container, plugin.getValue(), commonModule); loader.createPlugin(container, plugin.getValue(), commonModule);
} catch (Exception e) { } catch (Throwable e) {
logger.error("Can't create plugin {}", description.getId(), e); logger.error("Can't create plugin {}", description.getId(), e);
continue; continue;
} }

Datei anzeigen

@ -71,4 +71,8 @@ public class VelocityPluginContainer implements PluginContainer {
return this.service; return this.service;
} }
public boolean hasExecutorService() {
return this.service != null;
}
} }

Datei anzeigen

@ -26,6 +26,7 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -49,6 +50,7 @@ class VelocityPluginModule implements Module {
binder.bind(description.getMainClass()).in(Scopes.SINGLETON); binder.bind(description.getMainClass()).in(Scopes.SINGLETON);
binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId())); binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId()));
binder.bind(ComponentLogger.class).toInstance(ComponentLogger.logger(description.getId()));
binder.bind(Path.class).annotatedWith(DataDirectory.class) binder.bind(Path.class).annotatedWith(DataDirectory.class)
.toInstance(basePluginPath.resolve(description.getId())); .toInstance(basePluginPath.resolve(description.getId()));
binder.bind(PluginDescription.class).toInstance(description); binder.bind(PluginDescription.class).toInstance(description);

Datei anzeigen

@ -41,6 +41,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@ -202,8 +203,7 @@ public enum ProtocolUtils {
buf.readableBytes()); buf.readableBytes());
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8); String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
buf.skipBytes(length); buf.skipBytes(length);
checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", checkFrame(str.length() <= cap, "Got a too-long string (got %s, max %s)", str.length(), cap);
str.length(), cap);
return str; return str;
} }
@ -219,6 +219,59 @@ public enum ProtocolUtils {
buf.writeCharSequence(str, StandardCharsets.UTF_8); buf.writeCharSequence(str, StandardCharsets.UTF_8);
} }
/**
* Reads a standard Mojang Text namespaced:key from the buffer.
*
* @param buf the buffer to read from
* @return the decoded key
*/
public static Key readKey(ByteBuf buf) {
return Key.key(readString(buf), Key.DEFAULT_SEPARATOR);
}
/**
* Writes a standard Mojang Text namespaced:key to the buffer.
*
* @param buf the buffer to write to
* @param key the key to write
*/
public static void writeKey(ByteBuf buf, Key key) {
writeString(buf, key.asString());
}
/**
* Reads a standard Mojang Text namespaced:key array from the buffer.
*
* @param buf the buffer to read from
* @return the decoded key array
*/
public static Key[] readKeyArray(ByteBuf buf) {
int length = readVarInt(buf);
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
checkFrame(buf.isReadable(length),
"Trying to read an array that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
Key[] ret = new Key[length];
for (int i = 0; i < ret.length; i++) {
ret[i] = ProtocolUtils.readKey(buf);
}
return ret;
}
/**
* Writes a standard Mojang Text namespaced:key array to the buffer.
*
* @param buf the buffer to write to
* @param keys the keys to write
*/
public static void writeKeyArray(ByteBuf buf, Key[] keys) {
writeVarInt(buf, keys.length);
for (Key key : keys) {
writeKey(buf, key);
}
}
public static byte[] readByteArray(ByteBuf buf) { public static byte[] readByteArray(ByteBuf buf) {
return readByteArray(buf, DEFAULT_MAX_STRING_SIZE); return readByteArray(buf, DEFAULT_MAX_STRING_SIZE);
} }
@ -368,6 +421,38 @@ public enum ProtocolUtils {
} }
} }
/**
* Reads an Integer array from the {@code buf}.
*
* @param buf the buffer to read from
* @return the Integer array from the buffer
*/
public static int[] readVarIntArray(ByteBuf buf) {
int length = readVarInt(buf);
checkFrame(length >= 0, "Got a negative-length array (%s)", length);
checkFrame(buf.isReadable(length),
"Trying to read an array that is too long (wanted %s, only have %s)", length,
buf.readableBytes());
int[] ret = new int[length];
for (int i = 0; i < length; i++) {
ret[i] = readVarInt(buf);
}
return ret;
}
/**
* Writes an Integer Array to the {@code buf}.
*
* @param buf the buffer to write to
* @param intArray the array to write
*/
public static void writeVarIntArray(ByteBuf buf, int[] intArray) {
writeVarInt(buf, intArray.length);
for (int i = 0; i < intArray.length; i++) {
writeVarInt(buf, intArray[i]);
}
}
/** /**
* Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer. * Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer.
* *

Datei anzeigen

@ -33,6 +33,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_4;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@ -55,8 +56,10 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.JoinGame; import com.velocitypowered.proxy.protocol.packet.JoinGame;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.LoginAcknowledged;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.PingIdentify;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest;
@ -72,6 +75,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgement;
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion;
import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat;
import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChat;
@ -79,6 +83,11 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChat;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommand;
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeatures;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdate;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySync;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdate;
import com.velocitypowered.proxy.protocol.packet.config.TagsUpdate;
import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleActionbarPacket;
import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket; import com.velocitypowered.proxy.protocol.packet.title.TitleClearPacket;
@ -98,9 +107,7 @@ import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
/** /** Registry of all Minecraft protocol states and the packets for each state. */
* Registry of all Minecraft protocol states and the packets for each state.
*/
public enum StateRegistry { public enum StateRegistry {
HANDSHAKE { HANDSHAKE {
@ -111,15 +118,46 @@ public enum StateRegistry {
}, },
STATUS { STATUS {
{ {
serverbound.register(StatusRequest.class, () -> StatusRequest.INSTANCE, serverbound.register(
map(0x00, MINECRAFT_1_7_2, false)); StatusRequest.class, () -> StatusRequest.INSTANCE, map(0x00, MINECRAFT_1_7_2, false));
serverbound.register(StatusPing.class, StatusPing::new, serverbound.register(StatusPing.class, StatusPing::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false));
clientbound.register(StatusResponse.class, StatusResponse::new, clientbound.register(
map(0x00, MINECRAFT_1_7_2, false)); StatusResponse.class, StatusResponse::new, map(0x00, MINECRAFT_1_7_2, false));
clientbound.register(StatusPing.class, StatusPing::new, clientbound.register(StatusPing.class, StatusPing::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false)); }
},
CONFIG {
{
serverbound.register(
ClientSettings.class, ClientSettings::new, map(0x00, MINECRAFT_1_20_2, false));
serverbound.register(
PluginMessage.class, PluginMessage::new, map(0x01, MINECRAFT_1_20_2, false));
serverbound.register(
FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false));
serverbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false));
serverbound.register(
PingIdentify.class, PingIdentify::new, map(0x04, MINECRAFT_1_20_2, false));
serverbound.register(
ResourcePackResponse.class,
ResourcePackResponse::new,
map(0x05, MINECRAFT_1_20_2, false));
clientbound.register(
PluginMessage.class, PluginMessage::new, map(0x00, MINECRAFT_1_20_2, false));
clientbound.register(Disconnect.class, Disconnect::new, map(0x01, MINECRAFT_1_20_2, false));
clientbound.register(
FinishedUpdate.class, FinishedUpdate::new, map(0x02, MINECRAFT_1_20_2, false));
clientbound.register(KeepAlive.class, KeepAlive::new, map(0x03, MINECRAFT_1_20_2, false));
clientbound.register(
PingIdentify.class, PingIdentify::new, map(0x04, MINECRAFT_1_20_2, false));
clientbound.register(
RegistrySync.class, RegistrySync::new, map(0x05, MINECRAFT_1_20_2, false));
clientbound.register(
ResourcePackRequest.class, ResourcePackRequest::new, map(0x06, MINECRAFT_1_20_2, false));
clientbound.register(
ActiveFeatures.class, ActiveFeatures::new, map(0x07, MINECRAFT_1_20_2, false));
clientbound.register(TagsUpdate.class, TagsUpdate::new, map(0x08, MINECRAFT_1_20_2, false));
} }
}, },
PLAY { PLAY {
@ -137,13 +175,20 @@ public enum StateRegistry {
map(0x08, MINECRAFT_1_19, false), map(0x08, MINECRAFT_1_19, false),
map(0x09, MINECRAFT_1_19_1, false), map(0x09, MINECRAFT_1_19_1, false),
map(0x08, MINECRAFT_1_19_3, false), map(0x08, MINECRAFT_1_19_3, false),
map(0x09, MINECRAFT_1_19_4, false)); map(0x09, MINECRAFT_1_19_4, false),
serverbound.register(LegacyChat.class, LegacyChat::new, map(0x0A, MINECRAFT_1_20_2, false));
serverbound.register(
LegacyChat.class,
LegacyChat::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false), map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false), map(0x03, MINECRAFT_1_12, false),
map(0x02, MINECRAFT_1_12_1, false), map(0x02, MINECRAFT_1_12_1, false),
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false)); map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
serverbound.register(
ChatAcknowledgement.class,
ChatAcknowledgement::new,
map(0x03, MINECRAFT_1_19_3, false));
serverbound.register(KeyedPlayerCommand.class, KeyedPlayerCommand::new, serverbound.register(KeyedPlayerCommand.class, KeyedPlayerCommand::new,
map(0x03, MINECRAFT_1_19, false), map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
@ -152,9 +197,13 @@ public enum StateRegistry {
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new, serverbound.register(SessionPlayerCommand.class, SessionPlayerCommand::new,
map(0x04, MINECRAFT_1_19_3, false)); map(0x04, MINECRAFT_1_19_3, false));
serverbound.register(SessionPlayerChat.class, SessionPlayerChat::new, serverbound.register(
map(0x05, MINECRAFT_1_19_3, false)); SessionPlayerChat.class,
serverbound.register(ClientSettings.class, ClientSettings::new, SessionPlayerChat::new,
map(0x05, MINECRAFT_1_19_3, MINECRAFT_1_20_2, false));
serverbound.register(
ClientSettings.class,
ClientSettings::new,
map(0x15, MINECRAFT_1_7_2, false), map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false), map(0x04, MINECRAFT_1_9, false),
map(0x05, MINECRAFT_1_12, false), map(0x05, MINECRAFT_1_12, false),
@ -163,8 +212,11 @@ public enum StateRegistry {
map(0x07, MINECRAFT_1_19, false), map(0x07, MINECRAFT_1_19, false),
map(0x08, MINECRAFT_1_19_1, false), map(0x08, MINECRAFT_1_19_1, false),
map(0x07, MINECRAFT_1_19_3, false), map(0x07, MINECRAFT_1_19_3, false),
map(0x08, MINECRAFT_1_19_4, false)); map(0x08, MINECRAFT_1_19_4, false),
serverbound.register(PluginMessage.class, PluginMessage::new, map(0x09, MINECRAFT_1_20_2, false));
serverbound.register(
PluginMessage.class,
PluginMessage::new,
map(0x17, MINECRAFT_1_7_2, false), map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false), map(0x09, MINECRAFT_1_9, false),
map(0x0A, MINECRAFT_1_12, false), map(0x0A, MINECRAFT_1_12, false),
@ -175,8 +227,11 @@ public enum StateRegistry {
map(0x0C, MINECRAFT_1_19, false), map(0x0C, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_1, false), map(0x0D, MINECRAFT_1_19_1, false),
map(0x0C, MINECRAFT_1_19_3, false), map(0x0C, MINECRAFT_1_19_3, false),
map(0x0D, MINECRAFT_1_19_4, false)); map(0x0D, MINECRAFT_1_19_4, false),
serverbound.register(KeepAlive.class, KeepAlive::new, map(0x0F, MINECRAFT_1_20_2, false));
serverbound.register(
KeepAlive.class,
KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false), map(0x0B, MINECRAFT_1_9, false),
map(0x0C, MINECRAFT_1_12, false), map(0x0C, MINECRAFT_1_12, false),
@ -188,8 +243,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_19, false), map(0x11, MINECRAFT_1_19, false),
map(0x12, MINECRAFT_1_19_1, false), map(0x12, MINECRAFT_1_19_1, false),
map(0x11, MINECRAFT_1_19_3, false), map(0x11, MINECRAFT_1_19_3, false),
map(0x12, MINECRAFT_1_19_4, false)); map(0x12, MINECRAFT_1_19_4, false),
serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new, map(0x14, MINECRAFT_1_20_2, false));
serverbound.register(
ResourcePackResponse.class,
ResourcePackResponse::new,
map(0x19, MINECRAFT_1_8, false), map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false), map(0x16, MINECRAFT_1_9, false),
map(0x18, MINECRAFT_1_12, false), map(0x18, MINECRAFT_1_12, false),
@ -198,16 +256,24 @@ public enum StateRegistry {
map(0x20, MINECRAFT_1_16, false), map(0x20, MINECRAFT_1_16, false),
map(0x21, MINECRAFT_1_16_2, false), map(0x21, MINECRAFT_1_16_2, false),
map(0x23, MINECRAFT_1_19, false), map(0x23, MINECRAFT_1_19, false),
map(0x24, MINECRAFT_1_19_1, false)); map(0x24, MINECRAFT_1_19_1, false),
map(0x27, MINECRAFT_1_20_2, false));
serverbound.register(
FinishedUpdate.class, FinishedUpdate::new, map(0x0B, MINECRAFT_1_20_2, false));
clientbound.register(BossBar.class, BossBar::new, clientbound.register(
BossBar.class,
BossBar::new,
map(0x0C, MINECRAFT_1_9, false), map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false), map(0x0D, MINECRAFT_1_15, false),
map(0x0C, MINECRAFT_1_16, false), map(0x0C, MINECRAFT_1_16, false),
map(0x0D, MINECRAFT_1_17, false), map(0x0D, MINECRAFT_1_17, false),
map(0x0A, MINECRAFT_1_19, false), map(0x0A, MINECRAFT_1_19, false),
map(0x0B, MINECRAFT_1_19_4, false)); map(0x0B, MINECRAFT_1_19_4, false),
clientbound.register(LegacyChat.class, LegacyChat::new, map(0x0A, MINECRAFT_1_20_2, false));
clientbound.register(
LegacyChat.class,
LegacyChat::new,
map(0x02, MINECRAFT_1_7_2, true), map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true), map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true), map(0x0E, MINECRAFT_1_13, true),
@ -224,8 +290,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_17, false), map(0x11, MINECRAFT_1_17, false),
map(0x0E, MINECRAFT_1_19, false), map(0x0E, MINECRAFT_1_19, false),
map(0x0D, MINECRAFT_1_19_3, false), map(0x0D, MINECRAFT_1_19_3, false),
map(0x0F, MINECRAFT_1_19_4, false)); map(0x0F, MINECRAFT_1_19_4, false),
clientbound.register(AvailableCommands.class, AvailableCommands::new, map(0x10, MINECRAFT_1_20_2, false));
clientbound.register(
AvailableCommands.class,
AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false), map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false), map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false), map(0x11, MINECRAFT_1_16, false),
@ -233,8 +302,11 @@ public enum StateRegistry {
map(0x12, MINECRAFT_1_17, false), map(0x12, MINECRAFT_1_17, false),
map(0x0F, MINECRAFT_1_19, false), map(0x0F, MINECRAFT_1_19, false),
map(0x0E, MINECRAFT_1_19_3, false), map(0x0E, MINECRAFT_1_19_3, false),
map(0x10, MINECRAFT_1_19_4, false)); map(0x10, MINECRAFT_1_19_4, false),
clientbound.register(PluginMessage.class, PluginMessage::new, map(0x11, MINECRAFT_1_20_2, false));
clientbound.register(
PluginMessage.class,
PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false), map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_9, false),
map(0x19, MINECRAFT_1_13, false), map(0x19, MINECRAFT_1_13, false),
@ -246,8 +318,11 @@ public enum StateRegistry {
map(0x15, MINECRAFT_1_19, false), map(0x15, MINECRAFT_1_19, false),
map(0x16, MINECRAFT_1_19_1, false), map(0x16, MINECRAFT_1_19_1, false),
map(0x15, MINECRAFT_1_19_3, false), map(0x15, MINECRAFT_1_19_3, false),
map(0x17, MINECRAFT_1_19_4, false)); map(0x17, MINECRAFT_1_19_4, false),
clientbound.register(Disconnect.class, Disconnect::new, map(0x18, MINECRAFT_1_20_2, false));
clientbound.register(
Disconnect.class,
Disconnect::new,
map(0x40, MINECRAFT_1_7_2, false), map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false), map(0x1A, MINECRAFT_1_9, false),
map(0x1B, MINECRAFT_1_13, false), map(0x1B, MINECRAFT_1_13, false),
@ -259,8 +334,11 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_19, false), map(0x17, MINECRAFT_1_19, false),
map(0x19, MINECRAFT_1_19_1, false), map(0x19, MINECRAFT_1_19_1, false),
map(0x17, MINECRAFT_1_19_3, false), map(0x17, MINECRAFT_1_19_3, false),
map(0x1A, MINECRAFT_1_19_4, false)); map(0x1A, MINECRAFT_1_19_4, false),
clientbound.register(KeepAlive.class, KeepAlive::new, map(0x1B, MINECRAFT_1_20_2, false));
clientbound.register(
KeepAlive.class,
KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false), map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false), map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false), map(0x21, MINECRAFT_1_13, false),
@ -272,8 +350,11 @@ public enum StateRegistry {
map(0x1E, MINECRAFT_1_19, false), map(0x1E, MINECRAFT_1_19, false),
map(0x20, MINECRAFT_1_19_1, false), map(0x20, MINECRAFT_1_19_1, false),
map(0x1F, MINECRAFT_1_19_3, false), map(0x1F, MINECRAFT_1_19_3, false),
map(0x23, MINECRAFT_1_19_4, false)); map(0x23, MINECRAFT_1_19_4, false),
clientbound.register(JoinGame.class, JoinGame::new, map(0x24, MINECRAFT_1_20_2, false));
clientbound.register(
JoinGame.class,
JoinGame::new,
map(0x01, MINECRAFT_1_7_2, false), map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false), map(0x23, MINECRAFT_1_9, false),
map(0x25, MINECRAFT_1_13, false), map(0x25, MINECRAFT_1_13, false),
@ -285,8 +366,11 @@ public enum StateRegistry {
map(0x23, MINECRAFT_1_19, false), map(0x23, MINECRAFT_1_19, false),
map(0x25, MINECRAFT_1_19_1, false), map(0x25, MINECRAFT_1_19_1, false),
map(0x24, MINECRAFT_1_19_3, false), map(0x24, MINECRAFT_1_19_3, false),
map(0x28, MINECRAFT_1_19_4, false)); map(0x28, MINECRAFT_1_19_4, false),
clientbound.register(Respawn.class, Respawn::new, map(0x29, MINECRAFT_1_20_2, false));
clientbound.register(
Respawn.class,
Respawn::new,
map(0x07, MINECRAFT_1_7_2, true), map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true), map(0x33, MINECRAFT_1_9, true),
map(0x34, MINECRAFT_1_12, true), map(0x34, MINECRAFT_1_12, true),
@ -300,8 +384,11 @@ public enum StateRegistry {
map(0x3B, MINECRAFT_1_19, true), map(0x3B, MINECRAFT_1_19, true),
map(0x3E, MINECRAFT_1_19_1, true), map(0x3E, MINECRAFT_1_19_1, true),
map(0x3D, MINECRAFT_1_19_3, true), map(0x3D, MINECRAFT_1_19_3, true),
map(0x41, MINECRAFT_1_19_4, true)); map(0x41, MINECRAFT_1_19_4, true),
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, map(0x43, MINECRAFT_1_20_2, true));
clientbound.register(
ResourcePackRequest.class,
ResourcePackRequest::new,
map(0x48, MINECRAFT_1_8, false), map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false), map(0x32, MINECRAFT_1_9, false),
map(0x33, MINECRAFT_1_12, false), map(0x33, MINECRAFT_1_12, false),
@ -315,8 +402,11 @@ public enum StateRegistry {
map(0x3A, MINECRAFT_1_19, false), map(0x3A, MINECRAFT_1_19, false),
map(0x3D, MINECRAFT_1_19_1, false), map(0x3D, MINECRAFT_1_19_1, false),
map(0x3C, MINECRAFT_1_19_3, false), map(0x3C, MINECRAFT_1_19_3, false),
map(0x40, MINECRAFT_1_19_4, false)); map(0x40, MINECRAFT_1_19_4, false),
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, map(0x42, MINECRAFT_1_20_2, false));
clientbound.register(
HeaderAndFooter.class,
HeaderAndFooter::new,
map(0x47, MINECRAFT_1_8, true), map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true), map(0x48, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_9_4, true), map(0x47, MINECRAFT_1_9_4, true),
@ -331,8 +421,11 @@ public enum StateRegistry {
map(0x60, MINECRAFT_1_19, true), map(0x60, MINECRAFT_1_19, true),
map(0x63, MINECRAFT_1_19_1, true), map(0x63, MINECRAFT_1_19_1, true),
map(0x61, MINECRAFT_1_19_3, true), map(0x61, MINECRAFT_1_19_3, true),
map(0x65, MINECRAFT_1_19_4, true)); map(0x65, MINECRAFT_1_19_4, true),
clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new, map(0x68, MINECRAFT_1_20_2, true));
clientbound.register(
LegacyTitlePacket.class,
LegacyTitlePacket::new,
map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true), map(0x45, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_12, true), map(0x47, MINECRAFT_1_12, true),
@ -346,31 +439,46 @@ public enum StateRegistry {
map(0x58, MINECRAFT_1_18, true), map(0x58, MINECRAFT_1_18, true),
map(0x5B, MINECRAFT_1_19_1, true), map(0x5B, MINECRAFT_1_19_1, true),
map(0x59, MINECRAFT_1_19_3, true), map(0x59, MINECRAFT_1_19_3, true),
map(0x5D, MINECRAFT_1_19_4, true)); map(0x5D, MINECRAFT_1_19_4, true),
clientbound.register(TitleTextPacket.class, TitleTextPacket::new, map(0x5F, MINECRAFT_1_20_2, true));
clientbound.register(
TitleTextPacket.class,
TitleTextPacket::new,
map(0x59, MINECRAFT_1_17, true), map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true), map(0x5A, MINECRAFT_1_18, true),
map(0x5D, MINECRAFT_1_19_1, true), map(0x5D, MINECRAFT_1_19_1, true),
map(0x5B, MINECRAFT_1_19_3, true), map(0x5B, MINECRAFT_1_19_3, true),
map(0x5F, MINECRAFT_1_19_4, true)); map(0x5F, MINECRAFT_1_19_4, true),
clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new, map(0x61, MINECRAFT_1_20_2, true));
clientbound.register(
TitleActionbarPacket.class,
TitleActionbarPacket::new,
map(0x41, MINECRAFT_1_17, true), map(0x41, MINECRAFT_1_17, true),
map(0x40, MINECRAFT_1_19, true), map(0x40, MINECRAFT_1_19, true),
map(0x43, MINECRAFT_1_19_1, true), map(0x43, MINECRAFT_1_19_1, true),
map(0x42, MINECRAFT_1_19_3, true), map(0x42, MINECRAFT_1_19_3, true),
map(0x46, MINECRAFT_1_19_4, true)); map(0x46, MINECRAFT_1_19_4, true),
clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new, map(0x48, MINECRAFT_1_20_2, true));
clientbound.register(
TitleTimesPacket.class,
TitleTimesPacket::new,
map(0x5A, MINECRAFT_1_17, true), map(0x5A, MINECRAFT_1_17, true),
map(0x5B, MINECRAFT_1_18, true), map(0x5B, MINECRAFT_1_18, true),
map(0x5E, MINECRAFT_1_19_1, true), map(0x5E, MINECRAFT_1_19_1, true),
map(0x5C, MINECRAFT_1_19_3, true), map(0x5C, MINECRAFT_1_19_3, true),
map(0x60, MINECRAFT_1_19_4, true)); map(0x60, MINECRAFT_1_19_4, true),
clientbound.register(TitleClearPacket.class, TitleClearPacket::new, map(0x62, MINECRAFT_1_20_2, true));
clientbound.register(
TitleClearPacket.class,
TitleClearPacket::new,
map(0x10, MINECRAFT_1_17, true), map(0x10, MINECRAFT_1_17, true),
map(0x0D, MINECRAFT_1_19, true), map(0x0D, MINECRAFT_1_19, true),
map(0x0C, MINECRAFT_1_19_3, true), map(0x0C, MINECRAFT_1_19_3, true),
map(0x0E, MINECRAFT_1_19_4, true)); map(0x0E, MINECRAFT_1_19_4, true),
clientbound.register(LegacyPlayerListItem.class, LegacyPlayerListItem::new, map(0x0F, MINECRAFT_1_20_2, true));
clientbound.register(
LegacyPlayerListItem.class,
LegacyPlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false), map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false), map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false), map(0x2E, MINECRAFT_1_12_1, false),
@ -384,68 +492,83 @@ public enum StateRegistry {
map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false)); map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new, clientbound.register(RemovePlayerInfo.class, RemovePlayerInfo::new,
map(0x35, MINECRAFT_1_19_3, false), map(0x35, MINECRAFT_1_19_3, false),
map(0x39, MINECRAFT_1_19_4, false)); map(0x39, MINECRAFT_1_19_4, false),
clientbound.register(UpsertPlayerInfo.class, UpsertPlayerInfo::new, map(0x3B, MINECRAFT_1_20_2, false));
clientbound.register(
UpsertPlayerInfo.class,
UpsertPlayerInfo::new,
map(0x36, MINECRAFT_1_19_3, false), map(0x36, MINECRAFT_1_19_3, false),
map(0x3A, MINECRAFT_1_19_4, false)); map(0x3A, MINECRAFT_1_19_4, false),
clientbound.register(SystemChat.class, SystemChat::new, map(0x3C, MINECRAFT_1_20_2, false));
clientbound.register(
SystemChat.class,
SystemChat::new,
map(0x5F, MINECRAFT_1_19, true), map(0x5F, MINECRAFT_1_19, true),
map(0x62, MINECRAFT_1_19_1, true), map(0x62, MINECRAFT_1_19_1, true),
map(0x60, MINECRAFT_1_19_3, true), map(0x60, MINECRAFT_1_19_3, true),
map(0x64, MINECRAFT_1_19_4, true)); map(0x64, MINECRAFT_1_19_4, true),
clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new, map(0x67, MINECRAFT_1_20_2, true));
clientbound.register(
PlayerChatCompletion.class,
PlayerChatCompletion::new,
map(0x15, MINECRAFT_1_19_1, true), map(0x15, MINECRAFT_1_19_1, true),
map(0x14, MINECRAFT_1_19_3, true), map(0x14, MINECRAFT_1_19_3, true),
map(0x16, MINECRAFT_1_19_4, true)); map(0x16, MINECRAFT_1_19_4, true),
clientbound.register(ServerData.class, ServerData::new, map(0x17, MINECRAFT_1_20_2, true));
clientbound.register(
ServerData.class,
ServerData::new,
map(0x3F, MINECRAFT_1_19, false), map(0x3F, MINECRAFT_1_19, false),
map(0x42, MINECRAFT_1_19_1, false), map(0x42, MINECRAFT_1_19_1, false),
map(0x41, MINECRAFT_1_19_3, false), map(0x41, MINECRAFT_1_19_3, false),
map(0x45, MINECRAFT_1_19_4, false)); map(0x45, MINECRAFT_1_19_4, false),
map(0x47, MINECRAFT_1_20_2, false));
clientbound.register(StartUpdate.class, StartUpdate::new, map(0x65, MINECRAFT_1_20_2, false));
} }
}, },
LOGIN { LOGIN {
{ {
serverbound.register(ServerLogin.class, ServerLogin::new, serverbound.register(ServerLogin.class, ServerLogin::new, map(0x00, MINECRAFT_1_7_2, false));
map(0x00, MINECRAFT_1_7_2, false)); serverbound.register(
serverbound.register(EncryptionResponse.class, EncryptionResponse::new, EncryptionResponse.class, EncryptionResponse::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false)); serverbound.register(
serverbound.register(LoginPluginResponse.class, LoginPluginResponse::new, LoginPluginResponse.class, LoginPluginResponse::new, map(0x02, MINECRAFT_1_13, false));
map(0x02, MINECRAFT_1_13, false)); serverbound.register(
clientbound.register(Disconnect.class, Disconnect::new, LoginAcknowledged.class, LoginAcknowledged::new, map(0x03, MINECRAFT_1_20_2, false));
map(0x00, MINECRAFT_1_7_2, false));
clientbound.register(EncryptionRequest.class, EncryptionRequest::new, clientbound.register(Disconnect.class, Disconnect::new, map(0x00, MINECRAFT_1_7_2, false));
map(0x01, MINECRAFT_1_7_2, false)); clientbound.register(
clientbound.register(ServerLoginSuccess.class, ServerLoginSuccess::new, EncryptionRequest.class, EncryptionRequest::new, map(0x01, MINECRAFT_1_7_2, false));
map(0x02, MINECRAFT_1_7_2, false)); clientbound.register(
clientbound.register(SetCompression.class, SetCompression::new, ServerLoginSuccess.class, ServerLoginSuccess::new, map(0x02, MINECRAFT_1_7_2, false));
map(0x03, MINECRAFT_1_8, false)); clientbound.register(
clientbound.register(LoginPluginMessage.class, LoginPluginMessage::new, SetCompression.class, SetCompression::new, map(0x03, MINECRAFT_1_8, false));
map(0x04, MINECRAFT_1_13, false)); clientbound.register(
LoginPluginMessage.class, LoginPluginMessage::new, map(0x04, MINECRAFT_1_13, false));
} }
}; };
public static final int STATUS_ID = 1; public static final int STATUS_ID = 1;
public static final int LOGIN_ID = 2; public static final int LOGIN_ID = 2;
protected final PacketRegistry clientbound = new PacketRegistry(CLIENTBOUND); protected final PacketRegistry clientbound = new PacketRegistry(CLIENTBOUND, this);
protected final PacketRegistry serverbound = new PacketRegistry(SERVERBOUND); protected final PacketRegistry serverbound = new PacketRegistry(SERVERBOUND, this);
public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(Direction direction, public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(Direction direction,
ProtocolVersion version) { ProtocolVersion version) {
return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version); return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version);
} }
/** /** Packet registry. */
* Packet registry.
*/
public static class PacketRegistry { public static class PacketRegistry {
private final Direction direction; private final Direction direction;
private final StateRegistry registry;
private final Map<ProtocolVersion, ProtocolRegistry> versions; private final Map<ProtocolVersion, ProtocolRegistry> versions;
private boolean fallback = true; private boolean fallback = true;
PacketRegistry(Direction direction) { PacketRegistry(Direction direction, StateRegistry registry) {
this.direction = direction; this.direction = direction;
this.registry = registry;
Map<ProtocolVersion, ProtocolRegistry> mutableVersions = new EnumMap<>(ProtocolVersion.class); Map<ProtocolVersion, ProtocolRegistry> mutableVersions = new EnumMap<>(ProtocolVersion.class);
for (ProtocolVersion version : ProtocolVersion.values()) { for (ProtocolVersion version : ProtocolVersion.values()) {
@ -505,19 +628,24 @@ public enum StateRegistry {
} }
ProtocolRegistry registry = this.versions.get(protocol); ProtocolRegistry registry = this.versions.get(protocol);
if (registry == null) { if (registry == null) {
throw new IllegalArgumentException("Unknown protocol version " throw new IllegalArgumentException(
+ current.protocolVersion); "Unknown protocol version " + current.protocolVersion);
} }
if (registry.packetIdToSupplier.containsKey(current.id)) { if (registry.packetIdToSupplier.containsKey(current.id)) {
throw new IllegalArgumentException("Can not register class " + clazz.getSimpleName() throw new IllegalArgumentException(
+ " with id " + current.id + " for " + registry.version "Can not register class "
+ clazz.getSimpleName()
+ " with id "
+ current.id
+ " for "
+ registry.version
+ " because another packet is already registered"); + " because another packet is already registered");
} }
if (registry.packetClassToId.containsKey(clazz)) { if (registry.packetClassToId.containsKey(clazz)) {
throw new IllegalArgumentException(clazz.getSimpleName() throw new IllegalArgumentException(
+ " is already registered for version " + registry.version); clazz.getSimpleName() + " is already registered for version " + registry.version);
} }
if (!current.encodeOnly) { if (!current.encodeOnly) {
@ -528,9 +656,7 @@ public enum StateRegistry {
} }
} }
/** /** Protocol registry. */
* Protocol registry.
*/
public class ProtocolRegistry { public class ProtocolRegistry {
public final ProtocolVersion version; public final ProtocolVersion version;
@ -569,18 +695,27 @@ public enum StateRegistry {
final int id = this.packetClassToId.getInt(packet.getClass()); final int id = this.packetClassToId.getInt(packet.getClass());
if (id == Integer.MIN_VALUE) { if (id == Integer.MIN_VALUE) {
throw new IllegalArgumentException(String.format( throw new IllegalArgumentException(String.format(
"Unable to find id for packet of type %s in %s protocol %s", "Unable to find id for packet of type %s in %s protocol %s phase %s",
packet.getClass().getName(), PacketRegistry.this.direction, this.version packet.getClass().getName(), PacketRegistry.this.direction,
this.version, PacketRegistry.this.registry
)); ));
} }
return id; return id;
} }
/**
* Checks if the registry contains a packet with the specified {@code id}.
*
* @param packet the packet to check
* @return {@code true} if the packet is registered, {@code false} otherwise
*/
public boolean containsPacket(final MinecraftPacket packet) {
return this.packetClassToId.containsKey(packet.getClass());
}
} }
} }
/** /** Packet mapping. */
* Packet mapping.
*/
public static final class PacketMapping { public static final class PacketMapping {
private final int id; private final int id;
@ -599,9 +734,12 @@ public enum StateRegistry {
@Override @Override
public String toString() { public String toString() {
return "PacketMapping{" return "PacketMapping{"
+ "id=" + id + "id="
+ ", protocolVersion=" + protocolVersion + id
+ ", encodeOnly=" + encodeOnly + ", protocolVersion="
+ protocolVersion
+ ", encodeOnly="
+ encodeOnly
+ '}'; + '}';
} }

Datei anzeigen

@ -34,7 +34,7 @@ import java.util.List;
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> { public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB
private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 128 * 1024 * 1024; // 128MiB
private static final int UNCOMPRESSED_CAP = private static final int UNCOMPRESSED_CAP =
Boolean.getBoolean("velocity.increased-compression-cap") Boolean.getBoolean("velocity.increased-compression-cap")

Datei anzeigen

@ -35,8 +35,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
public static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging"); public static final boolean DEBUG = Boolean.getBoolean("velocity.packet-decode-logging");
private static final QuietRuntimeException DECODE_FAILED = private static final QuietRuntimeException DECODE_FAILED =
new QuietRuntimeException("A packet did not decode successfully (invalid data). If you are a " new QuietRuntimeException("A packet did not decode successfully (invalid data). For more "
+ "developer, launch Velocity with -Dvelocity.packet-decode-logging=true to see more."); + "information, launch Velocity with -Dvelocity.packet-decode-logging=true to see more.");
private final ProtocolUtils.Direction direction; private final ProtocolUtils.Direction direction;
private StateRegistry state; private StateRegistry state;

Datei anzeigen

@ -62,4 +62,8 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
this.state = state; this.state = state;
this.setProtocolVersion(registry.version); this.setProtocolVersion(registry.version);
} }
public ProtocolUtils.Direction getDirection() {
return direction;
}
} }

Datei anzeigen

@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.PlatformDependent;
import java.util.Queue;
import org.jetbrains.annotations.NotNull;
/**
* Queues up any pending PLAY packets while the client is in the CONFIG state.
*
* <p>Much of the Velocity API (i.e. chat messages) utilize PLAY packets, however the client is
* incapable of receiving these packets during the CONFIG state. Certain events such as the
* ServerPreConnectEvent may be called during this time, and we need to ensure that any API that
* uses these packets will work as expected.
*
* <p>This handler will queue up any packets that are sent to the client during this time, and send
* them once the client has (re)entered the PLAY state.
*/
public class PlayPacketQueueHandler extends ChannelDuplexHandler {
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private final Queue<MinecraftPacket> queue = PlatformDependent.newMpscQueue();
/**
* Provides registries for client & server bound packets.
*
* @param version the protocol version
*/
public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
this.registry =
StateRegistry.CONFIG.getProtocolRegistry(direction, version);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
if (!(msg instanceof MinecraftPacket)) {
ctx.write(msg, promise);
return;
}
// If the packet exists in the CONFIG state, we want to always
// ensure that it gets sent out to the client
if (this.registry.containsPacket(((MinecraftPacket) msg))) {
ctx.write(msg, promise);
return;
}
// Otherwise, queue the packet
this.queue.offer((MinecraftPacket) msg);
}
@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
this.releaseQueue(ctx, false);
super.channelInactive(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
this.releaseQueue(ctx, ctx.channel().isActive());
}
private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
if (this.queue.isEmpty()) {
return;
}
// Send out all the queued packets
MinecraftPacket packet;
while ((packet = this.queue.poll()) != null) {
if (active) {
ctx.write(packet, ctx.voidPromise());
} else {
ReferenceCountUtil.release(packet);
}
}
if (active) {
ctx.flush();
}
}
}

Datei anzeigen

@ -23,10 +23,10 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.Objects; import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettings implements MinecraftPacket { public class ClientSettings implements MinecraftPacket {
private @Nullable String locale; private @Nullable String locale;
private byte viewDistance; private byte viewDistance;
private int chatVisibility; private int chatVisibility;
@ -120,16 +120,10 @@ public class ClientSettings implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ClientSettings{" return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
+ "locale='" + locale + '\'' ", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
+ ", viewDistance=" + viewDistance skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + chatFilteringEnabled +
+ ", chatVisibility=" + chatVisibility ", clientListingAllowed=" + clientListingAllowed + '}';
+ ", chatColors=" + chatColors
+ ", skinParts=" + skinParts
+ ", mainHand=" + mainHand
+ ", chatFilteringEnabled=" + chatFilteringEnabled
+ ", clientListingAllowed=" + clientListingAllowed
+ '}';
} }
@Override @Override

Datei anzeigen

@ -42,6 +42,7 @@ public class JoinGame implements MinecraftPacket {
private int viewDistance; // 1.14+ private int viewDistance; // 1.14+
private boolean reducedDebugInfo; private boolean reducedDebugInfo;
private boolean showRespawnScreen; private boolean showRespawnScreen;
private boolean doLimitedCrafting; // 1.20.2+
private ImmutableSet<String> levelNames; // 1.16+ private ImmutableSet<String> levelNames; // 1.16+
private CompoundBinaryTag registry; // 1.16+ private CompoundBinaryTag registry; // 1.16+
private DimensionInfo dimensionInfo; // 1.16+ private DimensionInfo dimensionInfo; // 1.16+
@ -49,6 +50,7 @@ public class JoinGame implements MinecraftPacket {
private short previousGamemode; // 1.16+ private short previousGamemode; // 1.16+
private int simulationDistance; // 1.18+ private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+ private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
public int getEntityId() { public int getEntityId() {
return entityId; return entityId;
@ -142,6 +144,14 @@ public class JoinGame implements MinecraftPacket {
this.isHardcore = isHardcore; this.isHardcore = isHardcore;
} }
public boolean getDoLimitedCrafting() {
return doLimitedCrafting;
}
public void setDoLimitedCrafting(boolean doLimitedCrafting) {
this.doLimitedCrafting = doLimitedCrafting;
}
public CompoundBinaryTag getCurrentDimensionData() { public CompoundBinaryTag getCurrentDimensionData() {
return currentDimensionData; return currentDimensionData;
} }
@ -162,37 +172,38 @@ public class JoinGame implements MinecraftPacket {
this.lastDeathPosition = lastDeathPosition; this.lastDeathPosition = lastDeathPosition;
} }
public int getPortalCooldown() {
return portalCooldown;
}
public void setPortalCooldown(int portalCooldown) {
this.portalCooldown = portalCooldown;
}
public CompoundBinaryTag getRegistry() { public CompoundBinaryTag getRegistry() {
return registry; return registry;
} }
@Override @Override
public String toString() { public String toString() {
return "JoinGame{" return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" +
+ "entityId=" + entityId dimension + ", partialHashedSeed=" + partialHashedSeed + ", difficulty=" + difficulty +
+ ", gamemode=" + gamemode ", isHardcore=" + isHardcore + ", maxPlayers=" + maxPlayers + ", levelType='" + levelType +
+ ", dimension=" + dimension '\'' + ", viewDistance=" + viewDistance + ", reducedDebugInfo=" + reducedDebugInfo +
+ ", partialHashedSeed=" + partialHashedSeed ", showRespawnScreen=" + showRespawnScreen + ", doLimitedCrafting=" + doLimitedCrafting +
+ ", difficulty=" + difficulty ", levelNames=" + levelNames + ", registry='" + registry + '\'' + ", dimensionInfo='" +
+ ", isHardcore=" + isHardcore dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
+ ", maxPlayers=" + maxPlayers ", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
+ ", levelType='" + levelType + '\'' ", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
+ ", viewDistance=" + viewDistance '}';
+ ", reducedDebugInfo=" + reducedDebugInfo
+ ", showRespawnScreen=" + showRespawnScreen
+ ", levelNames=" + levelNames
+ ", registry='" + registry + '\''
+ ", dimensionInfo='" + dimensionInfo + '\''
+ ", currentDimensionData='" + currentDimensionData + '\''
+ ", previousGamemode=" + previousGamemode
+ ", simulationDistance=" + simulationDistance
+ ", lastDeathPosition='" + lastDeathPosition + '\''
+ '}';
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
// haha funny, they made 1.20.2 more complicated
this.decode1202Up(buf, version);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet, // Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out. // so separate it out.
this.decode116Up(buf, version); this.decode116Up(buf, version);
@ -279,11 +290,52 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
}
private void decode1202Up(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
this.isHardcore = buf.readBoolean();
this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
this.maxPlayers = ProtocolUtils.readVarInt(buf);
this.viewDistance = ProtocolUtils.readVarInt(buf);
this.simulationDistance = ProtocolUtils.readVarInt(buf);
this.reducedDebugInfo = buf.readBoolean();
this.showRespawnScreen = buf.readBoolean();
this.doLimitedCrafting = buf.readBoolean();
String dimensionIdentifier = ProtocolUtils.readString(buf);
String levelName = ProtocolUtils.readString(buf);
this.partialHashedSeed = buf.readLong();
this.gamemode = buf.readByte();
this.previousGamemode = buf.readByte();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug);
// optional death location
if (buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
this.portalCooldown = ProtocolUtils.readVarInt(buf);
} }
@Override @Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
// haha funny, they made 1.20.2 more complicated
this.encode1202Up(buf, version);
} else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet, // Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out. // so separate it out.
this.encode116Up(buf, version); this.encode116Up(buf, version);
@ -376,6 +428,47 @@ public class JoinGame implements MinecraftPacket {
buf.writeBoolean(false); buf.writeBoolean(false);
} }
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
ProtocolUtils.writeVarInt(buf, portalCooldown);
}
}
private void encode1202Up(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
buf.writeBoolean(isHardcore);
ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new));
ProtocolUtils.writeVarInt(buf, maxPlayers);
ProtocolUtils.writeVarInt(buf, viewDistance);
ProtocolUtils.writeVarInt(buf, simulationDistance);
buf.writeBoolean(reducedDebugInfo);
buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(doLimitedCrafting);
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
buf.writeLong(partialHashedSeed);
buf.writeByte(gamemode);
buf.writeByte(previousGamemode);
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
// optional death location
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
ProtocolUtils.writeVarInt(buf, portalCooldown);
} }
@Override @Override

Datei anzeigen

@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class LoginAcknowledged implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,49 @@
/*
* Copyright (C) 2018-2021 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class PingIdentify implements MinecraftPacket {
private int id;
@Override
public String toString() {
return "Ping{" + "id=" + id + '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
id = buf.readInt();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeInt(id);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -17,17 +17,23 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.regex.Pattern;
public class ResourcePackRequest implements MinecraftPacket { public class ResourcePackRequest implements MinecraftPacket {
private @MonotonicNonNull String url; private @MonotonicNonNull String url;
@ -35,6 +41,8 @@ public class ResourcePackRequest implements MinecraftPacket {
private boolean isRequired; // 1.17+ private boolean isRequired; // 1.17+
private @Nullable Component prompt; // 1.17+ private @Nullable Component prompt; // 1.17+
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+
public @Nullable String getUrl() { public @Nullable String getUrl() {
return url; return url;
} }
@ -99,6 +107,19 @@ public class ResourcePackRequest implements MinecraftPacket {
} }
} }
public VelocityResourcePackInfo toServerPromptedPack() {
ResourcePackInfo.Builder builder =
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)).setPrompt(prompt)
.setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
if (hash != null && !hash.isEmpty()) {
if (PLAUSIBLE_SHA1_HASH.matcher(hash).matches()) {
builder.setHash(ByteBufUtil.decodeHexDump(hash));
}
}
return (VelocityResourcePackInfo) builder.build();
}
@Override @Override
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);
@ -106,11 +127,7 @@ public class ResourcePackRequest implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ResourcePackRequest{" return "ResourcePackRequest{" + "url='" + url + '\'' + ", hash='" + hash + '\'' +
+ "url='" + url + '\'' ", isRequired=" + isRequired + ", prompt='" + prompt + '\'' + '}';
+ ", hash='" + hash + '\''
+ ", isRequired=" + isRequired
+ ", prompt='" + prompt + '\''
+ '}';
} }
} }

Datei anzeigen

@ -73,9 +73,6 @@ public class ResourcePackResponse implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ResourcePackResponse{" return "ResourcePackResponse{" + "hash=" + hash + ", " + "status=" + status + '}';
+ "hash=" + hash + ", "
+ "status=" + status
+ '}';
} }
} }

Datei anzeigen

@ -40,6 +40,7 @@ public class Respawn implements MinecraftPacket {
private short previousGamemode; // 1.16+ private short previousGamemode; // 1.16+
private CompoundBinaryTag currentDimensionData; // 1.16.2+ private CompoundBinaryTag currentDimensionData; // 1.16.2+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+ private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
public Respawn() { public Respawn() {
} }
@ -47,7 +48,7 @@ public class Respawn implements MinecraftPacket {
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode, public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo, String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, CompoundBinaryTag currentDimensionData, short previousGamemode, CompoundBinaryTag currentDimensionData,
@Nullable Pair<String, Long> lastDeathPosition) { @Nullable Pair<String, Long> lastDeathPosition, int portalCooldown) {
this.dimension = dimension; this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed; this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty; this.difficulty = difficulty;
@ -58,13 +59,14 @@ public class Respawn implements MinecraftPacket {
this.previousGamemode = previousGamemode; this.previousGamemode = previousGamemode;
this.currentDimensionData = currentDimensionData; this.currentDimensionData = currentDimensionData;
this.lastDeathPosition = lastDeathPosition; this.lastDeathPosition = lastDeathPosition;
this.portalCooldown = portalCooldown;
} }
public static Respawn fromJoinGame(JoinGame joinGame) { public static Respawn fromJoinGame(JoinGame joinGame) {
return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(), return new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(), (byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition()); joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(), joinGame.getPortalCooldown());
} }
public int getDimension() { public int getDimension() {
@ -123,12 +125,20 @@ public class Respawn implements MinecraftPacket {
this.previousGamemode = previousGamemode; this.previousGamemode = previousGamemode;
} }
public Pair<String, Long> getLastDeathPosition() {
return lastDeathPosition;
}
public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) { public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition; this.lastDeathPosition = lastDeathPosition;
} }
public Pair<String, Long> getLastDeathPosition() { public int getPortalCooldown() {
return lastDeathPosition; return portalCooldown;
}
public void setPortalCooldown(int portalCooldown) {
this.portalCooldown = portalCooldown;
} }
@Override @Override
@ -144,6 +154,7 @@ public class Respawn implements MinecraftPacket {
+ ", dimensionInfo=" + dimensionInfo + ", dimensionInfo=" + dimensionInfo
+ ", previousGamemode=" + previousGamemode + ", previousGamemode=" + previousGamemode
+ ", dimensionData=" + currentDimensionData + ", dimensionData=" + currentDimensionData
+ ", portalCooldown=" + portalCooldown
+ '}'; + '}';
} }
@ -188,6 +199,9 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong()); this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
} }
@Override @Override
@ -234,6 +248,10 @@ public class Respawn implements MinecraftPacket {
buf.writeBoolean(false); buf.writeBoolean(false);
} }
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) {
ProtocolUtils.writeVarInt(buf, portalCooldown);
}
} }
@Override @Override

Datei anzeigen

@ -74,10 +74,7 @@ public class ServerLogin implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "ServerLogin{" return "ServerLogin{" + "username='" + username + '\'' + "playerKey='" + playerKey + '\'' + '}';
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ '}';
} }
@Override @Override
@ -98,6 +95,11 @@ public class ServerLogin implements MinecraftPacket {
} }
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
this.holderUuid = ProtocolUtils.readUuid(buf);
return;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (buf.readBoolean()) { if (buf.readBoolean()) {
holderUuid = ProtocolUtils.readUuid(buf); holderUuid = ProtocolUtils.readUuid(buf);
@ -125,6 +127,11 @@ public class ServerLogin implements MinecraftPacket {
} }
} }
if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) {
ProtocolUtils.writeUuid(buf, this.holderUuid);
return;
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
if (playerKey != null && playerKey.getSignatureHolder() != null) { if (playerKey != null && playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true); buf.writeBoolean(true);

Datei anzeigen

@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class ChatAcknowledgement implements MinecraftPacket {
int offset;
public ChatAcknowledgement(int offset) {
this.offset = offset;
}
public ChatAcknowledgement() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
offset = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, offset);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public String toString() {
return "ChatAcknowledgement{" +
"offset=" + offset +
'}';
}
}

Datei anzeigen

@ -49,4 +49,16 @@ public class LastSeenMessages {
public boolean isEmpty() { public boolean isEmpty() {
return acknowledged.isEmpty(); return acknowledged.isEmpty();
} }
public int getOffset() {
return this.offset;
}
@Override
public String toString() {
return "LastSeenMessages{" +
"offset=" + offset +
", acknowledged=" + acknowledged +
'}';
}
} }

Datei anzeigen

@ -52,7 +52,7 @@ public class KeyedCommandHandler implements CommandHandler<KeyedPlayerCommand> {
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to deny a command with signable component(s). " logger.fatal("A plugin tried to deny a command with signable component(s). "
+ "This is not supported. " + "This is not supported. "
+ "Disconnecting player " + player.getUsername()); + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text( player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " "A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator.")); + "Contact your network administrator."));
@ -75,7 +75,7 @@ public class KeyedCommandHandler implements CommandHandler<KeyedPlayerCommand> {
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). " logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. " + "This is not supported. "
+ "Disconnecting player " + player.getUsername()); + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text( player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " "A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator.")); + "Contact your network administrator."));
@ -95,7 +95,7 @@ public class KeyedCommandHandler implements CommandHandler<KeyedPlayerCommand> {
&& playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { && playerKey.getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) {
logger.fatal("A plugin tried to change a command with signed component(s). " logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. " + "This is not supported. "
+ "Disconnecting player " + player.getUsername()); + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text( player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " "A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator.")); + "Contact your network administrator."));

Datei anzeigen

@ -18,8 +18,10 @@
package com.velocitypowered.proxy.protocol.packet.chat.session; package com.velocitypowered.proxy.protocol.packet.chat.session;
import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgement;
import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler; import com.velocitypowered.proxy.protocol.packet.chat.CommandHandler;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -47,11 +49,15 @@ public class SessionCommandHandler implements CommandHandler<SessionPlayerComman
if (packet.isSigned()) { if (packet.isSigned()) {
logger.fatal("A plugin tried to deny a command with signable component(s). " logger.fatal("A plugin tried to deny a command with signable component(s). "
+ "This is not supported. " + "This is not supported. "
+ "Disconnecting player " + player.getUsername()); + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text( player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " "A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator.")); + "Contact your network administrator."));
} }
// We seemingly can't actually do this if signed args exist, if not, we can probs keep stuff happy
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
return CompletableFuture.completedFuture(new ChatAcknowledgement(packet.lastSeenMessages.getOffset()));
}
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@ -63,7 +69,7 @@ public class SessionCommandHandler implements CommandHandler<SessionPlayerComman
if (packet.isSigned()) { if (packet.isSigned()) {
logger.fatal("A plugin tried to change a command with signed component(s). " logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. " + "This is not supported. "
+ "Disconnecting player " + player.getUsername()); + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text( player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " "A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator.")); + "Contact your network administrator."));
@ -87,7 +93,7 @@ public class SessionCommandHandler implements CommandHandler<SessionPlayerComman
if (packet.isSigned()) { if (packet.isSigned()) {
logger.fatal("A plugin tried to change a command with signed component(s). " logger.fatal("A plugin tried to change a command with signed component(s). "
+ "This is not supported. " + "This is not supported. "
+ "Disconnecting player " + player.getUsername()); + "Disconnecting player " + player.getUsername() + ". Command packet: " + packet);
player.disconnect(Component.text( player.disconnect(Component.text(
"A proxy plugin caused an illegal protocol state. " "A proxy plugin caused an illegal protocol state. "
+ "Contact your network administrator.")); + "Contact your network administrator."));
@ -102,6 +108,9 @@ public class SessionCommandHandler implements CommandHandler<SessionPlayerComman
.toServer(); .toServer();
} }
} }
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
return new ChatAcknowledgement(packet.lastSeenMessages.getOffset());
}
return null; return null;
}); });
}, packet.command, packet.timeStamp); }, packet.command, packet.timeStamp);

Datei anzeigen

@ -65,7 +65,8 @@ public class SessionPlayerCommand implements MinecraftPacket {
} }
public boolean isSigned() { public boolean isSigned() {
return salt != 0 || !lastSeenMessages.isEmpty() || !argumentSignatures.isEmpty(); if (salt == 0) return false;
return !lastSeenMessages.isEmpty() || !argumentSignatures.isEmpty();
} }
@Override @Override
@ -73,6 +74,17 @@ public class SessionPlayerCommand implements MinecraftPacket {
return handler.handle(this); return handler.handle(this);
} }
@Override
public String toString() {
return "SessionPlayerCommand{" +
"command='" + command + '\'' +
", timeStamp=" + timeStamp +
", salt=" + salt +
", argumentSignatures=" + argumentSignatures +
", lastSeenMessages=" + lastSeenMessages +
'}';
}
public static class ArgumentSignatures { public static class ArgumentSignatures {
private final List<ArgumentSignature> entries; private final List<ArgumentSignature> entries;
@ -104,6 +116,12 @@ public class SessionPlayerCommand implements MinecraftPacket {
entry.encode(buf); entry.encode(buf);
} }
} }
@Override
public String toString() {
return "ArgumentSignatures{" +
"entries=" + entries +
'}';
}
} }
public static class ArgumentSignature { public static class ArgumentSignature {
@ -120,5 +138,12 @@ public class SessionPlayerCommand implements MinecraftPacket {
ProtocolUtils.writeString(buf, name); ProtocolUtils.writeString(buf, name);
buf.writeBytes(signature); buf.writeBytes(signature);
} }
@Override
public String toString() {
return "ArgumentSignature{" +
"name='" + name + '\'' +
'}';
}
} }
} }

Datei anzeigen

@ -0,0 +1,63 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ActiveFeatures implements MinecraftPacket {
private Key[] activeFeatures;
public ActiveFeatures(Key[] activeFeatures) {
this.activeFeatures = activeFeatures;
}
public ActiveFeatures() {
this.activeFeatures = new Key[0];
}
public void setActiveFeatures(Key[] activeFeatures) {
this.activeFeatures = activeFeatures;
}
public Key[] getActiveFeatures() {
return activeFeatures;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
activeFeatures = ProtocolUtils.readKeyArray(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKeyArray(buf, activeFeatures);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,48 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class FinishedUpdate implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
public class RegistrySync extends DeferredByteBufHolder implements MinecraftPacket {
public RegistrySync() {
super(null);
}
// NBT change in 1.20.2 makes it difficult to parse this packet.
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
buf.writeBytes(content());
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,48 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class StartUpdate implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet.config;
import com.google.common.collect.ImmutableMap;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Map;
public class TagsUpdate implements MinecraftPacket {
private Map<String, Map<String, int[]>> tags;
public TagsUpdate(Map<String, Map<String, int[]>> tags) {
this.tags = tags;
}
public TagsUpdate() {
this.tags = Map.of();
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ImmutableMap.Builder<String, Map<String, int[]>> builder = ImmutableMap.builder();
int size = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < size; i++) {
String key = ProtocolUtils.readString(buf);
int innerSize = ProtocolUtils.readVarInt(buf);
ImmutableMap.Builder<String, int[]> innerBuilder = ImmutableMap.builder();
for (int j = 0; j < innerSize; j++) {
String innerKey = ProtocolUtils.readString(buf);
int[] innerValue = ProtocolUtils.readVarIntArray(buf);
innerBuilder.put(innerKey, innerValue);
}
builder.put(key, innerBuilder.build());
}
tags = builder.build();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, tags.size());
for (Map.Entry<String, Map<String, int[]>> entry : tags.entrySet()) {
ProtocolUtils.writeString(buf, entry.getKey());
// Oh, joy
ProtocolUtils.writeVarInt(buf, entry.getValue().size());
for (Map.Entry<String, int[]> innerEntry : entry.getValue().entrySet()) {
// Yea, object oriented programming be damned
ProtocolUtils.writeString(buf, innerEntry.getKey());
ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue());
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -0,0 +1,43 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.provider;
import com.velocitypowered.proxy.util.TranslatableMapper;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider;
import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
/**
* Velocity ComponentLogger Provider.
*/
@SuppressWarnings("UnstableApiUsage")
public final class ComponentLoggerProviderImpl implements ComponentLoggerProvider {
private static final ANSIComponentSerializer SERIALIZER = ANSIComponentSerializer.builder()
.flattener(TranslatableMapper.FLATTENER)
.build();
@Override
public @NotNull ComponentLogger logger(
final @NotNull LoggerHelper helper,
final @NotNull String name
) {
return helper.delegating(LoggerFactory.getLogger(name), SERIALIZER::serialize);
}
}

Datei anzeigen

@ -30,9 +30,12 @@ import com.velocitypowered.api.scheduler.ScheduledTask;
import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.scheduler.TaskStatus; import com.velocitypowered.api.scheduler.TaskStatus;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer; import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -116,17 +119,24 @@ public class VelocityScheduler implements Scheduler {
task.cancel(); task.cancel();
} }
timerExecutionService.shutdown(); timerExecutionService.shutdown();
for (final PluginContainer container : this.pluginManager.getPlugins()) { final List<PluginContainer> plugins = new ArrayList<>(this.pluginManager.getPlugins());
final Iterator<PluginContainer> pluginIterator = plugins.iterator();
while (pluginIterator.hasNext()) {
final PluginContainer container = pluginIterator.next();
if (container instanceof VelocityPluginContainer) { if (container instanceof VelocityPluginContainer) {
(container).getExecutorService().shutdown(); final VelocityPluginContainer pluginContainer = (VelocityPluginContainer) container;
if (pluginContainer.hasExecutorService()) {
container.getExecutorService().shutdown();
} else {
pluginIterator.remove();
}
} else {
pluginIterator.remove();
} }
} }
boolean allShutdown = true; boolean allShutdown = true;
for (final PluginContainer container : this.pluginManager.getPlugins()) { for (final PluginContainer container : plugins) {
if (!(container instanceof VelocityPluginContainer)) {
continue;
}
final String id = container.getDescription().getId(); final String id = container.getDescription().getId();
final ExecutorService service = (container).getExecutorService(); final ExecutorService service = (container).getExecutorService();

Datei anzeigen

@ -34,8 +34,8 @@ import java.net.SocketAddress;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* Session handler used to implement * Session handler used to implement {@link VelocityRegisteredServer#ping(EventLoop,
* {@link VelocityRegisteredServer#ping(EventLoop, ProtocolVersion)}. * ProtocolVersion)}.
*/ */
public class PingSessionHandler implements MinecraftSessionHandler { public class PingSessionHandler implements MinecraftSessionHandler {
@ -70,6 +70,7 @@ public class PingSessionHandler implements MinecraftSessionHandler {
handshake.setProtocolVersion(version); handshake.setProtocolVersion(version);
connection.delayedWrite(handshake); connection.delayedWrite(handshake);
connection.setActiveSessionHandler(StateRegistry.STATUS);
connection.setState(StateRegistry.STATUS); connection.setState(StateRegistry.STATUS);
connection.delayedWrite(StatusRequest.INSTANCE); connection.delayedWrite(StatusRequest.INSTANCE);

Datei anzeigen

@ -37,6 +37,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -94,8 +95,8 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
} }
/** /**
* Pings the specified server using the specified event {@code loop}, claiming to be * Pings the specified server using the specified event {@code loop}, claiming to be {@code
* {@code version}. * version}.
* *
* @param loop the event loop to use * @param loop the event loop to use
* @param pingOptions the options to apply to this ping * @param pingOptions the options to apply to this ping
@ -106,31 +107,26 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud
throw new IllegalStateException("No Velocity proxy instance available"); throw new IllegalStateException("No Velocity proxy instance available");
} }
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>(); CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.createBootstrap(loop, serverInfo.getAddress()) server.createBootstrap(loop, serverInfo.getAddress()).handler(new ChannelInitializer<Channel>() {
.handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ch.pipeline() ch.pipeline().addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(READ_TIMEOUT, new ReadTimeoutHandler(
.addLast(READ_TIMEOUT, pingOptions.getTimeout() == 0
new ReadTimeoutHandler(pingOptions.getTimeout() == 0 ? server.getConfiguration().getReadTimeout()
? server.getConfiguration().getReadTimeout() : pingOptions.getTimeout(), : pingOptions.getTimeout(), TimeUnit.MILLISECONDS))
TimeUnit.MILLISECONDS))
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
.addLast(MINECRAFT_DECODER, .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND)) .addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
.addLast(MINECRAFT_ENCODER,
new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server)); ch.pipeline().addLast(HANDLER, new MinecraftConnection(ch, server));
} }
}) }).connect(serverInfo.getAddress()).addListener((ChannelFutureListener) future -> {
.connect(serverInfo.getAddress())
.addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) { if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler(new PingSessionHandler( conn.setActiveSessionHandler(StateRegistry.HANDSHAKE,
pingFuture, VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion())); new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn,
pingOptions.getProtocolVersion()));
} else { } else {
pingFuture.completeExceptionally(future.cause()); pingFuture.completeExceptionally(future.cause());
} }

Datei anzeigen

@ -35,4 +35,6 @@ public interface InternalTabList extends TabList {
default void processRemove(RemovePlayerInfo infoPacket) { default void processRemove(RemovePlayerInfo infoPacket) {
} }
void clearAllSilent();
} }

Datei anzeigen

@ -26,7 +26,6 @@ import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.ArrayList; import java.util.ArrayList;
@ -70,7 +69,7 @@ public class KeyedVelocityTabList implements InternalTabList {
@Override @Override
public void clearHeaderAndFooter() { public void clearHeaderAndFooter() {
connection.write(HeaderAndFooter.reset()); this.player.clearPlayerListHeaderAndFooter();
} }
@Override @Override
@ -131,10 +130,15 @@ public class KeyedVelocityTabList implements InternalTabList {
for (TabListEntry value : listEntries) { for (TabListEntry value : listEntries) {
items.add(LegacyPlayerListItem.Item.from(value)); items.add(LegacyPlayerListItem.Item.from(value));
} }
entries.clear(); clearAllSilent();
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER, items)); connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER, items));
} }
@Override
public void clearAllSilent() {
entries.clear();
}
@Override @Override
public Collection<TabListEntry> getEntries() { public Collection<TabListEntry> getEntries() {
return Collections.unmodifiableCollection(this.entries.values()); return Collections.unmodifiableCollection(this.entries.values());

Datei anzeigen

@ -25,7 +25,6 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo;
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
@ -74,7 +73,7 @@ public class VelocityTabList implements InternalTabList {
@Override @Override
public void clearHeaderAndFooter() { public void clearHeaderAndFooter() {
connection.write(HeaderAndFooter.reset()); this.player.clearPlayerListHeaderAndFooter();
} }
@Override @Override
@ -175,6 +174,11 @@ public class VelocityTabList implements InternalTabList {
@Override @Override
public void clearAll() { public void clearAll() {
this.connection.delayedWrite(new RemovePlayerInfo(new ArrayList<>(this.entries.keySet()))); this.connection.delayedWrite(new RemovePlayerInfo(new ArrayList<>(this.entries.keySet())));
clearAllSilent();
}
@Override
public void clearAllSilent() {
this.entries.clear(); this.entries.clear();
} }

Datei anzeigen

@ -71,6 +71,11 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList {
connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER, connection.delayedWrite(new LegacyPlayerListItem(LegacyPlayerListItem.REMOVE_PLAYER,
Collections.singletonList(LegacyPlayerListItem.Item.from(value)))); Collections.singletonList(LegacyPlayerListItem.Item.from(value))));
} }
clearAllSilent();
}
@Override
public void clearAllSilent() {
entries.clear(); entries.clear();
nameMapping.clear(); nameMapping.clear();
} }

Datei anzeigen

@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util;
import java.util.Locale;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.translation.Translator;
import org.jetbrains.annotations.Nullable;
/**
* Velocity Translation Mapper.
*/
public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Consumer<Component>> {
INSTANCE;
public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder()
.complexMapper(TranslatableComponent.class, TranslatableMapper.INSTANCE)
.build();
@Override
public void accept(
final TranslatableComponent translatableComponent,
final Consumer<Component> componentConsumer
) {
for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry
&& ((TranslationRegistry) source).contains(translatableComponent.key())) {
componentConsumer.accept(GlobalTranslator.render(translatableComponent,
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
return;
}
}
final @Nullable String fallback = translatableComponent.fallback();
if (fallback == null) {
return;
}
for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry
&& ((TranslationRegistry) source).contains(fallback)) {
componentConsumer.accept(
GlobalTranslator.render(Component.translatable(fallback),
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
return;
}
}
}
}

Datei anzeigen

@ -1,70 +0,0 @@
/*
* Copyright (C) 2019-2023 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util.collect;
import com.google.common.base.Preconditions;
import com.google.common.collect.ForwardingSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* An unsynchronized collection that puts an upper bound on the size of the collection.
*/
public final class CappedSet<T> extends ForwardingSet<T> {
private final Set<T> delegate;
private final int upperSize;
private CappedSet(Set<T> delegate, int upperSize) {
this.delegate = delegate;
this.upperSize = upperSize;
}
/**
* Creates a capped collection backed by a {@link HashSet}.
*
* @param maxSize the maximum size of the collection
* @param <T> the type of elements in the collection
* @return the new collection
*/
public static <T> Set<T> create(int maxSize) {
return new CappedSet<>(new HashSet<>(), maxSize);
}
@Override
protected Set<T> delegate() {
return delegate;
}
@Override
public boolean add(T element) {
if (this.delegate.size() >= upperSize) {
Preconditions.checkState(this.delegate.contains(element),
"collection is too large (%s >= %s)",
this.delegate.size(), this.upperSize);
return false;
}
return this.delegate.add(element);
}
@Override
public boolean addAll(Collection<? extends T> collection) {
return this.standardAddAll(collection);
}
}

Datei anzeigen

@ -0,0 +1 @@
com.velocitypowered.proxy.provider.ComponentLoggerProviderImpl

Datei anzeigen

@ -44,7 +44,7 @@ class PacketRegistryTest {
private StateRegistry.PacketRegistry setupRegistry() { private StateRegistry.PacketRegistry setupRegistry() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND); ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(Handshake.class, Handshake::new, registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, null, false), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, null, false),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false),
@ -84,7 +84,7 @@ class PacketRegistryTest {
@Test @Test
void failOnNoMappings() { void failOnNoMappings() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND); ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
() -> registry.register(Handshake.class, Handshake::new)); () -> registry.register(Handshake.class, Handshake::new));
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
@ -94,7 +94,7 @@ class PacketRegistryTest {
@Test @Test
void failOnWrongOrder() { void failOnWrongOrder() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND); ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
() -> registry.register(Handshake.class, Handshake::new, () -> registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false),
@ -115,7 +115,7 @@ class PacketRegistryTest {
@Test @Test
void failOnDuplicate() { void failOnDuplicate() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND); ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(Handshake.class, Handshake::new, registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false)); new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false));
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
@ -129,7 +129,7 @@ class PacketRegistryTest {
@Test @Test
void shouldNotFailWhenRegisterLatestProtocolVersion() { void shouldNotFailWhenRegisterLatestProtocolVersion() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND); ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertDoesNotThrow(() -> registry.register(Handshake.class, Handshake::new, assertDoesNotThrow(() -> registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false),
new StateRegistry.PacketMapping(0x01, getLast(ProtocolVersion.SUPPORTED_VERSIONS), new StateRegistry.PacketMapping(0x01, getLast(ProtocolVersion.SUPPORTED_VERSIONS),
@ -139,7 +139,7 @@ class PacketRegistryTest {
@Test @Test
void registrySuppliesCorrectPacketsByProtocol() { void registrySuppliesCorrectPacketsByProtocol() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry( StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND); ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(Handshake.class, Handshake::new, registry.register(Handshake.class, Handshake::new,
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false), new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, null, false), new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, null, false),

Datei anzeigen

@ -1,71 +0,0 @@
/*
* Copyright (C) 2019-2021 Velocity Contributors
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util.collect;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Set;
import org.junit.jupiter.api.Test;
class CappedSetTest {
@Test
void basicVerification() {
Collection<String> coll = CappedSet.create(1);
assertTrue(coll.add("coffee"), "did not add single item");
assertThrows(IllegalStateException.class, () -> coll.add("tea"),
"item was added to collection although it is too full");
assertEquals(1, coll.size(), "collection grew in size unexpectedly");
}
@Test
void testAddAll() {
Set<String> doesFill1 = ImmutableSet.of("coffee", "tea");
Set<String> doesFill2 = ImmutableSet.of("chocolate");
Set<String> overfill = ImmutableSet.of("Coke", "Pepsi");
Collection<String> coll = CappedSet.create(3);
assertTrue(coll.addAll(doesFill1), "did not add items");
assertTrue(coll.addAll(doesFill2), "did not add items");
assertThrows(IllegalStateException.class, () -> coll.addAll(overfill),
"items added to collection although it is too full");
assertEquals(3, coll.size(), "collection grew in size unexpectedly");
}
@Test
void handlesSetBehaviorCorrectly() {
Set<String> doesFill1 = ImmutableSet.of("coffee", "tea");
Set<String> doesFill2 = ImmutableSet.of("coffee", "chocolate");
Set<String> overfill = ImmutableSet.of("coffee", "Coke", "Pepsi");
Collection<String> coll = CappedSet.create(3);
assertTrue(coll.addAll(doesFill1), "did not add items");
assertTrue(coll.addAll(doesFill2), "did not add items");
assertThrows(IllegalStateException.class, () -> coll.addAll(overfill),
"items added to collection although it is too full");
assertFalse(coll.addAll(doesFill1), "added items?!?");
assertEquals(3, coll.size(), "collection grew in size unexpectedly");
}
}

Datei anzeigen

@ -1,3 +1,22 @@
@file:Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") // adventure
maven("https://repo.papermc.io/repository/maven-public/")
}
}
pluginManagement {
includeBuild("build-logic")
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins { plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0"
} }
@ -6,8 +25,8 @@ rootProject.name = "velocity"
sequenceOf( sequenceOf(
"api", "api",
"proxy",
"native", "native",
"proxy",
).forEach { ).forEach {
val project = ":velocity-$it" val project = ":velocity-$it"
include(project) include(project)