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:
Commit
a037ab4956
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
38
Jenkinsfile
vendored
@ -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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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
17
build-logic/build.gradle.kts
Normale Datei
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("UnstableApiUsage")
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -9,3 +11,5 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rootProject.name = "build-logic"
|
6
build-logic/src/main/kotlin/LibsAccessor.kt
Normale Datei
6
build-logic/src/main/kotlin/LibsAccessor.kt
Normale Datei
@ -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()
|
10
build-logic/src/main/kotlin/velocity-checkstyle.gradle.kts
Normale Datei
10
build-logic/src/main/kotlin/velocity-checkstyle.gradle.kts
Normale Datei
@ -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()
|
||||||
|
}
|
29
build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts
Normale Datei
29
build-logic/src/main/kotlin/velocity-init-manifest.gradle.kts
Normale Datei
@ -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
|
||||||
|
}
|
||||||
|
}
|
33
build-logic/src/main/kotlin/velocity-publish.gradle.kts
Normale Datei
33
build-logic/src/main/kotlin/velocity-publish.gradle.kts
Normale Datei
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
build-logic/src/main/kotlin/velocity-spotless.gradle.kts
Normale Datei
15
build-logic/src/main/kotlin/velocity-spotless.gradle.kts
Normale Datei
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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"))
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
`java-library`
|
||||||
`maven-publish`
|
id("velocity-publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -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)
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
|
||||||
|
MinecraftConnection serverMc = serverConn.ensureConnected();
|
||||||
if (server.getConfiguration().isBungeePluginChannelEnabled()) {
|
if (server.getConfiguration().isBungeePluginChannelEnabled()) {
|
||||||
MinecraftConnection serverMc = serverConn.ensureConnected();
|
|
||||||
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,20 +242,16 @@ 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)
|
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
|
||||||
.thenAcceptAsync(pme -> {
|
PluginMessage copied = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
|
||||||
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
|
playerConnection.write(copied);
|
||||||
PluginMessage copied = new PluginMessage(packet.getChannel(),
|
}
|
||||||
Unpooled.wrappedBuffer(copy));
|
}, playerConnection.eventLoop()).exceptionally((ex) -> {
|
||||||
playerConnection.write(copied);
|
logger.error("Exception while handling plugin message {}", packet, ex);
|
||||||
}
|
return null;
|
||||||
}, playerConnection.eventLoop())
|
});
|
||||||
.exceptionally((ex) -> {
|
|
||||||
logger.error("Exception while handling plugin message {}", packet, ex);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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()).thenAcceptAsync(pingEvent -> this.playerConnection.write(
|
||||||
playerConnection.eventLoop()
|
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
||||||
)
|
pingEvent.getPing().getFavicon().orElse(null), packet.isSecureChatEnforced())),
|
||||||
.thenAcceptAsync(pingEvent ->
|
playerConnection.eventLoop());
|
||||||
this.playerConnection.write(
|
|
||||||
new ServerData(pingEvent.getPing().getDescriptionComponent(),
|
|
||||||
pingEvent.getPing().getFavicon().orElse(null),
|
|
||||||
packet.isSecureChatEnforced())
|
|
||||||
), playerConnection.eventLoop());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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.")
|
||||||
|
@ -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.
|
||||||
|
@ -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()));
|
||||||
|
@ -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,64 +167,87 @@ 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,
|
DisconnectEvent.LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE));
|
||||||
DisconnectEvent.LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Optional<Component> reason = event.getResult().getReasonComponent();
|
Optional<Component> reason = event.getResult().getReasonComponent();
|
||||||
if (reason.isPresent()) {
|
if (reason.isPresent()) {
|
||||||
player.disconnect0(reason.get(), true);
|
player.disconnect0(reason.get(), true);
|
||||||
} else {
|
} else {
|
||||||
if (!server.registerConnection(player)) {
|
if (!server.registerConnection(player)) {
|
||||||
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"),
|
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"),
|
||||||
true);
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player, server));
|
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||||
server.getEventManager().fire(new PostLoginEvent(player))
|
success.setUsername(player.getUsername());
|
||||||
.thenCompose((ignored) -> connectToInitialServer(player))
|
success.setProperties(player.getGameProfileProperties());
|
||||||
.exceptionally((ex) -> {
|
success.setUuid(player.getUniqueId());
|
||||||
logger.error("Exception while connecting {} to initial server", player, ex);
|
mcConnection.write(success);
|
||||||
return null;
|
|
||||||
});
|
loginState = State.SUCCESS_SENT;
|
||||||
}
|
if (inbound.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) {
|
||||||
}, mcConnection.eventLoop())
|
loginState = State.ACKNOWLEDGED;
|
||||||
.exceptionally((ex) -> {
|
mcConnection.setActiveSessionHandler(StateRegistry.PLAY,
|
||||||
logger.error("Exception while completing login initialisation phase for {}", player, ex);
|
new InitialConnectSessionHandler(player, server));
|
||||||
return null;
|
server.getEventManager().fire(new PostLoginEvent(player))
|
||||||
});
|
.thenCompose((ignored) -> connectToInitialServer(player)).exceptionally((ex) -> {
|
||||||
|
logger.error("Exception while connecting {} to initial server", player, ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, mcConnection.eventLoop()).exceptionally((ex) -> {
|
||||||
|
logger.error("Exception while completing login initialisation phase for {}", player, ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
player.disconnect0(Component.translatable("velocity.error.no-available-servers",
|
Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED),
|
||||||
NamedTextColor.RED), true);
|
true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||||
}, mcConnection.eventLoop());
|
}, mcConnection.eventLoop());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -237,4 +262,8 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
this.inbound.cleanup();
|
this.inbound.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum State {
|
||||||
|
START, SUCCESS_SENT, ACKNOWLEDGED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)) {
|
||||||
@ -694,86 +701,83 @@ 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;
|
|
||||||
|
|
||||||
// Make sure we clear the current connected server as the connection is invalid.
|
// Make sure we clear the current connected server as the connection is invalid.
|
||||||
VelocityServerConnection previousConnection = connectedServer;
|
VelocityServerConnection previousConnection = connectedServer;
|
||||||
if (kickedFromCurrent) {
|
if (kickedFromCurrent) {
|
||||||
connectedServer = null;
|
connectedServer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isActive()) {
|
if (!isActive()) {
|
||||||
// If the connection is no longer active, it makes no sense to try and recover it.
|
// If the connection is no longer active, it makes no sense to try and recover it.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getResult() instanceof DisconnectPlayer) {
|
if (event.getResult() instanceof DisconnectPlayer) {
|
||||||
DisconnectPlayer res = (DisconnectPlayer) event.getResult();
|
DisconnectPlayer res = (DisconnectPlayer) event.getResult();
|
||||||
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(
|
||||||
handleConnectionException(status != null ? status.getAttemptedConnection()
|
status != null ? status.getAttemptedConnection() : res.getServer(), throwable,
|
||||||
: res.getServer(), throwable, true);
|
true);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status.getStatus()) {
|
||||||
|
// Impossible/nonsensical cases
|
||||||
|
case ALREADY_CONNECTED:
|
||||||
|
logger.error("{}: already connected to {}", this,
|
||||||
|
status.getAttemptedConnection().getServerInfo().getName());
|
||||||
|
break;
|
||||||
|
case CONNECTION_IN_PROGRESS:
|
||||||
|
// Fatal case
|
||||||
|
case CONNECTION_CANCELLED:
|
||||||
|
Component fallbackMsg = res.getMessageComponent();
|
||||||
|
if (fallbackMsg == null) {
|
||||||
|
fallbackMsg = friendlyReason;
|
||||||
}
|
}
|
||||||
|
disconnect(status.getReasonComponent().orElse(fallbackMsg));
|
||||||
switch (status.getStatus()) {
|
break;
|
||||||
// Impossible/nonsensical cases
|
case SERVER_DISCONNECTED:
|
||||||
case ALREADY_CONNECTED:
|
Component reason = status.getReasonComponent()
|
||||||
logger.error("{}: already connected to {}",
|
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
|
||||||
this,
|
handleConnectionException(res.getServer(),
|
||||||
status.getAttemptedConnection().getServerInfo().getName()
|
Disconnect.create(reason, getProtocolVersion()), ((Impl) status).isSafe());
|
||||||
);
|
break;
|
||||||
break;
|
case SUCCESS:
|
||||||
case CONNECTION_IN_PROGRESS:
|
Component requestedMessage = res.getMessageComponent();
|
||||||
// Fatal case
|
if (requestedMessage == null) {
|
||||||
case CONNECTION_CANCELLED:
|
requestedMessage = friendlyReason;
|
||||||
Component fallbackMsg = res.getMessageComponent();
|
|
||||||
if (fallbackMsg == null) {
|
|
||||||
fallbackMsg = friendlyReason;
|
|
||||||
}
|
|
||||||
disconnect(status.getReasonComponent().orElse(fallbackMsg));
|
|
||||||
break;
|
|
||||||
case SERVER_DISCONNECTED:
|
|
||||||
Component reason = status.getReasonComponent()
|
|
||||||
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
|
|
||||||
handleConnectionException(res.getServer(), Disconnect.create(reason,
|
|
||||||
getProtocolVersion()), ((Impl) status).isSafe());
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
Component requestedMessage = res.getMessageComponent();
|
|
||||||
if (requestedMessage == null) {
|
|
||||||
requestedMessage = friendlyReason;
|
|
||||||
}
|
|
||||||
if (requestedMessage != Component.empty()) {
|
|
||||||
sendMessage(requestedMessage);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// The only remaining value is successful (no need to do anything!)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}, connection.eventLoop());
|
if (requestedMessage != Component.empty()) {
|
||||||
} else if (event.getResult() instanceof Notify) {
|
sendMessage(requestedMessage);
|
||||||
Notify res = (Notify) event.getResult();
|
}
|
||||||
if (event.kickedDuringServerConnect() && previousConnection != null) {
|
break;
|
||||||
sendMessage(Identity.nil(), res.getMessageComponent());
|
default:
|
||||||
} else {
|
// The only remaining value is successful (no need to do anything!)
|
||||||
disconnect(res.getMessageComponent());
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
}, connection.eventLoop());
|
||||||
// In case someone gets creative, assume we want to disconnect the player.
|
} else if (event.getResult() instanceof Notify) {
|
||||||
disconnect(friendlyReason);
|
Notify res = (Notify) event.getResult();
|
||||||
}
|
if (event.kickedDuringServerConnect() && previousConnection != null) {
|
||||||
}, connection.eventLoop());
|
sendMessage(Identity.nil(), res.getMessageComponent());
|
||||||
|
} else {
|
||||||
|
disconnect(res.getMessageComponent());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In case someone gets creative, assume we want to disconnect the player.
|
||||||
|
disconnect(friendlyReason);
|
||||||
|
}
|
||||||
|
}, connection.eventLoop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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,37 +1187,34 @@ 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();
|
||||||
Optional<ConnectionRequestBuilder.Status> check = checkServer(realDestination);
|
Optional<ConnectionRequestBuilder.Status> check = checkServer(realDestination);
|
||||||
if (check.isPresent()) {
|
if (check.isPresent()) {
|
||||||
return completedFuture(plainResult(check.get(), realDestination));
|
return completedFuture(plainResult(check.get(), realDestination));
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetIfInFlightIs(VelocityServerConnection establishedConnection) {
|
private void resetIfInFlightIs(VelocityServerConnection establishedConnection) {
|
||||||
@ -1202,50 +1225,46 @@ 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()).thenApply(x -> x);
|
||||||
}, connection.eventLoop())
|
|
||||||
.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() : toConnect,
|
||||||
handleConnectionException(status != null ? status.getAttemptedConnection()
|
throwable, true);
|
||||||
: toConnect, throwable, true);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch (status.getStatus()) {
|
switch (status.getStatus()) {
|
||||||
case ALREADY_CONNECTED:
|
case ALREADY_CONNECTED:
|
||||||
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED);
|
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED);
|
||||||
break;
|
break;
|
||||||
case CONNECTION_IN_PROGRESS:
|
case CONNECTION_IN_PROGRESS:
|
||||||
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
|
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
|
||||||
break;
|
break;
|
||||||
case CONNECTION_CANCELLED:
|
case CONNECTION_CANCELLED:
|
||||||
// Ignored; the plugin probably already handled this.
|
// Ignored; the plugin probably already handled this.
|
||||||
break;
|
break;
|
||||||
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
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,47 +121,45 @@ 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;
|
}
|
||||||
|
|
||||||
|
PreLoginComponentResult result = event.getResult();
|
||||||
|
Optional<Component> disconnectReason = result.getReasonComponent();
|
||||||
|
if (disconnectReason.isPresent()) {
|
||||||
|
// The component is guaranteed to be provided if the connection was denied.
|
||||||
|
inbound.disconnect(disconnectReason.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound.loginEventFired(() -> {
|
||||||
|
if (mcConnection.isClosed()) {
|
||||||
|
// The player was disconnected
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mcConnection.eventLoop().execute(() -> {
|
||||||
|
if (!result.isForceOfflineMode()
|
||||||
|
&& (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
|
||||||
|
// Request encryption.
|
||||||
|
EncryptionRequest request = generateEncryptionRequest();
|
||||||
|
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||||
|
mcConnection.write(request);
|
||||||
|
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
|
||||||
|
} else {
|
||||||
|
mcConnection.setActiveSessionHandler(StateRegistry.LOGIN,
|
||||||
|
new AuthSessionHandler(server, inbound,
|
||||||
|
GameProfile.forOfflinePlayer(login.getUsername()), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
PreLoginComponentResult result = event.getResult();
|
|
||||||
Optional<Component> disconnectReason = result.getReasonComponent();
|
|
||||||
if (disconnectReason.isPresent()) {
|
|
||||||
// The component is guaranteed to be provided if the connection was denied.
|
|
||||||
inbound.disconnect(disconnectReason.get());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inbound.loginEventFired(() -> {
|
|
||||||
if (mcConnection.isClosed()) {
|
|
||||||
// The player was disconnected
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mcConnection.eventLoop().execute(() -> {
|
|
||||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode()
|
|
||||||
|| result.isOnlineModeAllowed())) {
|
|
||||||
// Request encryption.
|
|
||||||
EncryptionRequest request = generateEncryptionRequest();
|
|
||||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
|
||||||
mcConnection.write(request);
|
|
||||||
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
|
|
||||||
} else {
|
|
||||||
mcConnection.setSessionHandler(new AuthSessionHandler(
|
|
||||||
server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, mcConnection.eventLoop())
|
|
||||||
.exceptionally((ex) -> {
|
|
||||||
logger.error("Exception in pre-login stage", ex);
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}, mcConnection.eventLoop()).exceptionally((ex) -> {
|
||||||
|
logger.error("Exception in pre-login stage", ex);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -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(
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
endpoint.getChannel().close().sync();
|
try {
|
||||||
} catch (final InterruptedException e) {
|
endpoint.getChannel().close().sync();
|
||||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
} catch (final InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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()) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -71,4 +71,8 @@ public class VelocityPluginContainer implements PluginContainer {
|
|||||||
|
|
||||||
return this.service;
|
return this.service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasExecutorService() {
|
||||||
|
return this.service != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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 "
|
||||||
+ " because another packet is already registered");
|
+ clazz.getSimpleName()
|
||||||
|
+ " with id "
|
||||||
|
+ current.id
|
||||||
|
+ " for "
|
||||||
|
+ registry.version
|
||||||
|
+ " 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
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 + '\''
|
|
||||||
+ '}';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
+ '}';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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."));
|
||||||
|
@ -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);
|
||||||
|
@ -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 + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,35 +107,30 @@ 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().addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||||
ch.pipeline()
|
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(
|
||||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
pingOptions.getTimeout() == 0
|
||||||
.addLast(READ_TIMEOUT,
|
? server.getConfiguration().getReadTimeout()
|
||||||
new ReadTimeoutHandler(pingOptions.getTimeout() == 0
|
: pingOptions.getTimeout(), TimeUnit.MILLISECONDS))
|
||||||
? server.getConfiguration().getReadTimeout() : pingOptions.getTimeout(),
|
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||||
TimeUnit.MILLISECONDS))
|
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
|
||||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolUtils.Direction.SERVERBOUND));
|
||||||
.addLast(MINECRAFT_DECODER,
|
|
||||||
new MinecraftDecoder(ProtocolUtils.Direction.CLIENTBOUND))
|
|
||||||
.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())
|
if (future.isSuccess()) {
|
||||||
.addListener((ChannelFutureListener) future -> {
|
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
|
||||||
if (future.isSuccess()) {
|
conn.setActiveSessionHandler(StateRegistry.HANDSHAKE,
|
||||||
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
|
new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn,
|
||||||
conn.setSessionHandler(new PingSessionHandler(
|
pingOptions.getProtocolVersion()));
|
||||||
pingFuture, VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion()));
|
} else {
|
||||||
} else {
|
pingFuture.completeExceptionally(future.cause());
|
||||||
pingFuture.completeExceptionally(future.cause());
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
return pingFuture;
|
return pingFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,4 +35,6 @@ public interface InternalTabList extends TabList {
|
|||||||
|
|
||||||
default void processRemove(RemovePlayerInfo infoPacket) {
|
default void processRemove(RemovePlayerInfo infoPacket) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearAllSilent();
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1 @@
|
|||||||
|
com.velocitypowered.proxy.provider.ComponentLoggerProviderImpl
|
@ -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),
|
||||||
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren