Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-12-24 15:20:35 +01:00
An Easter basket bearing gifts! (#191)
* Delay switch to new server until after JoinGame is sent. Unfortunately, in some cases (especially vanilla Minecraft) some login disconnects are sent after ServerLoginSuccess but before JoinGame. We've been using ServerLoginSuccess but it has caused too many problems. Now Velocity will switch to a stripped-down version of the play session handler until JoinGame is received. This handler does very little by itself: it simply forwards plugin messages (for Forge) and waits for the JoinGame packet from the server. This is an initial version: only vanilla Minecraft 1.12.2 was tested. However this is the way Waterfall without entity rewriting does server switches (which, in turn, is inherited from BungeeCord). * Move to Gradle 5 and Error Prone.
Dieser Commit ist enthalten in:
Ursprung
c8e33eef60
Commit
1661cece2d
@ -2,10 +2,11 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'checkstyle'
|
id 'checkstyle'
|
||||||
|
id "net.ltgt.errorprone" version "0.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../gradle/checkerframework.gradle'
|
|
||||||
apply from: '../gradle/checkstyle.gradle'
|
apply from: '../gradle/checkstyle.gradle'
|
||||||
|
apply from: '../gradle/errorprone.gradle'
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -38,7 +38,8 @@ public class PluginAnnotationProcessor extends AbstractProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
public synchronized boolean process(Set<? extends TypeElement> annotations,
|
||||||
|
RoundEnvironment roundEnv) {
|
||||||
if (roundEnv.processingOver()) {
|
if (roundEnv.processingOver()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import java.util.regex.Pattern;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
public class SerializedPluginDescription {
|
public final class SerializedPluginDescription {
|
||||||
|
|
||||||
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
|
public static final Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ public class SerializedPluginDescription {
|
|||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Dependency {
|
public static final class Dependency {
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final boolean optional;
|
private final boolean optional;
|
||||||
|
@ -70,6 +70,7 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
|||||||
*
|
*
|
||||||
* @param component the chat message to send
|
* @param component the chat message to send
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
default void sendMessage(Component component) {
|
default void sendMessage(Component component) {
|
||||||
sendMessage(component, MessagePosition.CHAT);
|
sendMessage(component, MessagePosition.CHAT);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.velocitypowered.api.proxy.server;
|
|||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableCollection;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -26,14 +27,14 @@ public final class QueryResponse {
|
|||||||
private final int maxPlayers;
|
private final int maxPlayers;
|
||||||
private final String proxyHost;
|
private final String proxyHost;
|
||||||
private final int proxyPort;
|
private final int proxyPort;
|
||||||
private final Collection<String> players;
|
private final ImmutableCollection<String> players;
|
||||||
private final String proxyVersion;
|
private final String proxyVersion;
|
||||||
private final Collection<PluginInformation> plugins;
|
private final ImmutableCollection<PluginInformation> plugins;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
QueryResponse(String hostname, String gameVersion, String map, int currentPlayers,
|
QueryResponse(String hostname, String gameVersion, String map, int currentPlayers,
|
||||||
int maxPlayers, String proxyHost, int proxyPort, Collection<String> players,
|
int maxPlayers, String proxyHost, int proxyPort, ImmutableCollection<String> players,
|
||||||
String proxyVersion, Collection<PluginInformation> plugins) {
|
String proxyVersion, ImmutableCollection<PluginInformation> plugins) {
|
||||||
this.hostname = hostname;
|
this.hostname = hostname;
|
||||||
this.gameVersion = gameVersion;
|
this.gameVersion = gameVersion;
|
||||||
this.map = map;
|
this.map = map;
|
||||||
@ -403,7 +404,7 @@ public final class QueryResponse {
|
|||||||
/**
|
/**
|
||||||
* Represents a plugin in the query response.
|
* Represents a plugin in the query response.
|
||||||
*/
|
*/
|
||||||
public static class PluginInformation {
|
public static final class PluginInformation {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final @Nullable String version;
|
private final @Nullable String version;
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
/// Checker Framework pluggable type-checking
|
|
||||||
///
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
checkerFrameworkCheckerJar {
|
|
||||||
description = 'the Checker Framework, including the Type Annotations compiler'
|
|
||||||
}
|
|
||||||
|
|
||||||
checkerFrameworkAnnotatedJDK {
|
|
||||||
description = 'a copy of JDK classes with Checker Framework type qualifers inserted'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By default, use Checker Framework from Maven Central.
|
|
||||||
// Pass -PcfLocal to use a locally-built version of the Checker Framework.
|
|
||||||
dependencies {
|
|
||||||
if (!rootProject.hasProperty('cfLocal')) {
|
|
||||||
checkerFrameworkAnnotatedJDK "org.checkerframework:jdk8:${checkerFrameworkVersion}"
|
|
||||||
checkerFrameworkCheckerJar "org.checkerframework:checker:${checkerFrameworkVersion}"
|
|
||||||
implementation "org.checkerframework:checker-qual:${checkerFrameworkVersion}"
|
|
||||||
} else if (System.getenv("CHECKERFRAMEWORK") == null) {
|
|
||||||
throw new GradleException("Environment variable CHECKERFRAMEWORK is not set")
|
|
||||||
} else if (!file(System.getenv("CHECKERFRAMEWORK")).exists()) {
|
|
||||||
throw new GradleException("Environment variable CHECKERFRAMEWORK is set to non-existent directory " + System.getenv("CHECKERFRAMEWORK"));
|
|
||||||
} else {
|
|
||||||
ext.checkerframeworkdist = "$System.env.CHECKERFRAMEWORK/checker/dist"
|
|
||||||
checkerFrameworkAnnotatedJDK fileTree(dir: "${ext.checkerframeworkdist}", include: "jdk8.jar")
|
|
||||||
checkerFrameworkCheckerJar fileTree(dir: "${ext.checkerframeworkdist}", include: 'checker.jar')
|
|
||||||
implementation fileTree(dir: "${ext.checkerframeworkdist}", include: 'checker-qual.jar')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// // To type-check all projects.
|
|
||||||
// allprojects {
|
|
||||||
// tasks.withType(JavaCompile).all { JavaCompile compile ->
|
|
||||||
// compile.doFirst {
|
|
||||||
// compile.options.compilerArgs = [
|
|
||||||
// '-processor', 'org.checkerframework.checker.formatter.FormatterChecker,org.checkerframework.checker.index.IndexChecker,org.checkerframework.checker.lock.LockChecker,org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.signature.SignatureChecker',
|
|
||||||
// '-Xmaxerrs', '10000',
|
|
||||||
// '-Awarns', // -Awarns turns Checker Framework errors into warnings
|
|
||||||
// '-AcheckPurityAnnotations',
|
|
||||||
// '-processorpath', "${configurations.checkerFrameworkCheckerJar.asPath}",
|
|
||||||
// "-Xbootclasspath/p:${configurations.checkerFrameworkAnnotatedJDK.asPath}",
|
|
||||||
// "-Astubs=$System.env.CHECKERFRAMEWORK/checker/resources/javadoc.astub" // TODO: does not work when downloading from Maven Central
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// To typecheck only the current project's main source set (in a multi-project
|
|
||||||
// build), use this instead:
|
|
||||||
compileJava {
|
|
||||||
doFirst {
|
|
||||||
options.compilerArgs = [
|
|
||||||
'-processor', 'org.checkerframework.checker.nullness.NullnessChecker',
|
|
||||||
'-processor', 'org.checkerframework.checker.optional.OptionalChecker',
|
|
||||||
'-Xmaxerrs', '10000',
|
|
||||||
'-Xmaxwarns', '10000',
|
|
||||||
// '-Awarns', // -Awarns turns Checker Framework errors into warnings
|
|
||||||
//'-AcheckPurityAnnotations', // Disabled for Velocity, wish we could do better
|
|
||||||
'-processorpath', "${configurations.checkerFrameworkCheckerJar.asPath}",
|
|
||||||
"-Xbootclasspath/p:${configurations.checkerFrameworkAnnotatedJDK.asPath}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
4
gradle/errorprone.gradle
Normale Datei
4
gradle/errorprone.gradle
Normale Datei
@ -0,0 +1,4 @@
|
|||||||
|
dependencies {
|
||||||
|
errorprone("com.google.errorprone:error_prone_core:2.3.3")
|
||||||
|
errorproneJavac("com.google.errorprone:javac:9+181-r4173-1")
|
||||||
|
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-all.zip
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'checkstyle'
|
id 'checkstyle'
|
||||||
|
id "net.ltgt.errorprone" version "0.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../gradle/checkerframework.gradle'
|
|
||||||
apply from: '../gradle/checkstyle.gradle'
|
apply from: '../gradle/checkstyle.gradle'
|
||||||
|
apply from: '../gradle/errorprone.gradle'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "com.google.guava:guava:${guavaVersion}"
|
compile "com.google.guava:guava:${guavaVersion}"
|
||||||
|
@ -21,13 +21,13 @@ public class NativeConstraints {
|
|||||||
return NATIVES_ENABLED
|
return NATIVES_ENABLED
|
||||||
&& CAN_GET_MEMORYADDRESS
|
&& CAN_GET_MEMORYADDRESS
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X")
|
||||||
&& System.getProperty("os.arch").equals("x86_64");
|
&& System.getProperty("os.arch", "").equals("x86_64");
|
||||||
};
|
};
|
||||||
|
|
||||||
static final BooleanSupplier LINUX = () -> {
|
static final BooleanSupplier LINUX = () -> {
|
||||||
return NATIVES_ENABLED
|
return NATIVES_ENABLED
|
||||||
&& CAN_GET_MEMORYADDRESS
|
&& CAN_GET_MEMORYADDRESS
|
||||||
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
|
||||||
&& System.getProperty("os.arch").equals("amd64");
|
&& System.getProperty("os.arch", "").equals("amd64");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'checkstyle'
|
id 'checkstyle'
|
||||||
|
id "net.ltgt.errorprone" version "0.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../gradle/checkerframework.gradle'
|
|
||||||
apply from: '../gradle/checkstyle.gradle'
|
apply from: '../gradle/checkstyle.gradle'
|
||||||
|
apply from: '../gradle/errorprone.gradle'
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@ -20,7 +23,7 @@ jar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
transform(com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer)
|
transform(Log4j2PluginsCacheFileTransformer)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Checkstyle) {
|
tasks.withType(Checkstyle) {
|
||||||
|
@ -109,6 +109,7 @@ public class VelocityServer implements ProxyServer {
|
|||||||
return ensureInitialized(serverKeyPair);
|
return ensureInitialized(serverKeyPair);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public VelocityConfiguration getConfiguration() {
|
public VelocityConfiguration getConfiguration() {
|
||||||
return ensureInitialized(this.configuration);
|
return ensureInitialized(this.configuration);
|
||||||
}
|
}
|
||||||
|
@ -93,14 +93,14 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
String alias = split[0];
|
String alias = split[0];
|
||||||
if (split.length == 1) {
|
if (split.length == 1) {
|
||||||
// Offer to fill in commands.
|
// Offer to fill in commands.
|
||||||
List<String> availableCommands = new ArrayList<>();
|
ImmutableList.Builder<String> availableCommands = ImmutableList.builder();
|
||||||
for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
||||||
if (entry.getKey().regionMatches(true, 0, alias, 0, alias.length())
|
if (entry.getKey().regionMatches(true, 0, alias, 0, alias.length())
|
||||||
&& entry.getValue().hasPermission(source, new String[0])) {
|
&& entry.getValue().hasPermission(source, new String[0])) {
|
||||||
availableCommands.add("/" + entry.getKey());
|
availableCommands.add("/" + entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return availableCommands;
|
return availableCommands.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||||
@ -116,7 +116,7 @@ public class VelocityCommandManager implements CommandManager {
|
|||||||
return ImmutableList.of();
|
return ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
return command.suggest(source, actualArgs);
|
return ImmutableList.copyOf(command.suggest(source, actualArgs));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Unable to invoke suggestions for command " + alias + " for " + source, e);
|
"Unable to invoke suggestions for command " + alias + " for " + source, e);
|
||||||
|
@ -245,14 +245,17 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
return AddressUtil.parseAddress(bind);
|
return AddressUtil.parseAddress(bind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isQueryEnabled() {
|
public boolean isQueryEnabled() {
|
||||||
return query.isQueryEnabled();
|
return query.isQueryEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getQueryPort() {
|
public int getQueryPort() {
|
||||||
return query.getQueryPort();
|
return query.getQueryPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getQueryMap() {
|
public String getQueryMap() {
|
||||||
return query.getQueryMap();
|
return query.getQueryMap();
|
||||||
}
|
}
|
||||||
@ -271,6 +274,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
*
|
*
|
||||||
* @return the MOTD
|
* @return the MOTD
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Component getMotdComponent() {
|
public Component getMotdComponent() {
|
||||||
if (motdAsComponent == null) {
|
if (motdAsComponent == null) {
|
||||||
if (motd.startsWith("{")) {
|
if (motd.startsWith("{")) {
|
||||||
@ -282,10 +286,12 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
return motdAsComponent;
|
return motdAsComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getShowMaxPlayers() {
|
public int getShowMaxPlayers() {
|
||||||
return showMaxPlayers;
|
return showMaxPlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isOnlineMode() {
|
public boolean isOnlineMode() {
|
||||||
return onlineMode;
|
return onlineMode;
|
||||||
}
|
}
|
||||||
@ -298,42 +304,52 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
return forwardingSecret;
|
return forwardingSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Map<String, String> getServers() {
|
public Map<String, String> getServers() {
|
||||||
return servers.getServers();
|
return servers.getServers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<String> getAttemptConnectionOrder() {
|
public List<String> getAttemptConnectionOrder() {
|
||||||
return servers.getAttemptConnectionOrder();
|
return servers.getAttemptConnectionOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Map<String, List<String>> getForcedHosts() {
|
public Map<String, List<String>> getForcedHosts() {
|
||||||
return forcedHosts.getForcedHosts();
|
return forcedHosts.getForcedHosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getCompressionThreshold() {
|
public int getCompressionThreshold() {
|
||||||
return advanced.getCompressionThreshold();
|
return advanced.getCompressionThreshold();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getCompressionLevel() {
|
public int getCompressionLevel() {
|
||||||
return advanced.getCompressionLevel();
|
return advanced.getCompressionLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getLoginRatelimit() {
|
public int getLoginRatelimit() {
|
||||||
return advanced.getLoginRatelimit();
|
return advanced.getLoginRatelimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Optional<Favicon> getFavicon() {
|
public Optional<Favicon> getFavicon() {
|
||||||
return Optional.ofNullable(favicon);
|
return Optional.ofNullable(favicon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isAnnounceForge() {
|
public boolean isAnnounceForge() {
|
||||||
return announceForge;
|
return announceForge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getConnectTimeout() {
|
public int getConnectTimeout() {
|
||||||
return advanced.getConnectionTimeout();
|
return advanced.getConnectionTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int getReadTimeout() {
|
public int getReadTimeout() {
|
||||||
return advanced.getReadTimeout();
|
return advanced.getReadTimeout();
|
||||||
}
|
}
|
||||||
@ -402,15 +418,9 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
advanced,
|
advanced,
|
||||||
query
|
query
|
||||||
);
|
);
|
||||||
upgradeConfig(configuration, toml);
|
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void upgradeConfig(VelocityConfiguration configuration, Toml toml) {
|
|
||||||
// Will be implemented once there has been a backwards-incompatible change in the config file
|
|
||||||
// format.
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateRandomString(int length) {
|
private static String generateRandomString(int length) {
|
||||||
String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890";
|
String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890";
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
@ -547,7 +557,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
"compress all packets, and setting it to -1 will disable compression entirely."
|
"compress all packets, and setting it to -1 will disable compression entirely."
|
||||||
})
|
})
|
||||||
@ConfigKey("compression-threshold")
|
@ConfigKey("compression-threshold")
|
||||||
private int compressionThreshold = 1024;
|
private int compressionThreshold = 256;
|
||||||
|
|
||||||
@Comment({"How much compression should be done (from 0-9). The default is -1, which uses the",
|
@Comment({"How much compression should be done (from 0-9). The default is -1, which uses the",
|
||||||
"default level of 6."})
|
"default level of 6."})
|
||||||
@ -579,7 +589,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
|
|
||||||
private Advanced(Toml toml) {
|
private Advanced(Toml toml) {
|
||||||
if (toml != null) {
|
if (toml != null) {
|
||||||
this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue();
|
this.compressionThreshold = toml.getLong("compression-threshold", 256L).intValue();
|
||||||
this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
|
this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
|
||||||
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
|
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
|
||||||
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
|
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
|
||||||
|
@ -224,6 +224,26 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAutoReading() {
|
||||||
|
return channel.config().isAutoRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the channel should continue reading data automaticaly.
|
||||||
|
* @param autoReading whether or not we should read data automatically
|
||||||
|
*/
|
||||||
|
public void setAutoReading(boolean autoReading) {
|
||||||
|
channel.config().setAutoRead(autoReading);
|
||||||
|
if (autoReading) {
|
||||||
|
// For some reason, the channel may not completely read its queued contents once autoread
|
||||||
|
// is turned back on, even though toggling autoreading on should handle things automatically.
|
||||||
|
// We will issue an explicit read after turning on autoread.
|
||||||
|
//
|
||||||
|
// Much thanks to @creeper123123321.
|
||||||
|
channel.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the state of the Minecraft connection.
|
* Changes the state of the Minecraft connection.
|
||||||
* @param state the new state
|
* @param state the new state
|
||||||
|
@ -36,6 +36,18 @@ public final class BackendConnectionPhases {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special backend phase used to indicate that this connection is about to become
|
||||||
|
* obsolete (transfer to a new server, for instance) and that Forge messages ought to be
|
||||||
|
* forwarded on to an in-flight connection instead.
|
||||||
|
*/
|
||||||
|
public static final BackendConnectionPhase IN_TRANSITION = new BackendConnectionPhase() {
|
||||||
|
@Override
|
||||||
|
public boolean consideredComplete() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private BackendConnectionPhases() {
|
private BackendConnectionPhases() {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
@ -70,13 +70,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public boolean handle(Disconnect packet) {
|
public boolean handle(Disconnect packet) {
|
||||||
serverConn.disconnect();
|
serverConn.disconnect();
|
||||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet);
|
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet, true);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handle(JoinGame packet) {
|
|
||||||
playerSessionHandler.handleBackendJoinGame(packet);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +163,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void exception(Throwable throwable) {
|
public void exception(Throwable throwable) {
|
||||||
exceptionTriggered = true;
|
exceptionTriggered = true;
|
||||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), throwable);
|
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), throwable, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VelocityServer getServer() {
|
public VelocityServer getServer() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.velocitypowered.proxy.connection.backend;
|
package com.velocitypowered.proxy.connection.backend;
|
||||||
|
|
||||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
|
||||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||||
import com.velocitypowered.api.util.GameProfile;
|
import com.velocitypowered.api.util.GameProfile;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
@ -9,8 +8,8 @@ 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.util.ConnectionRequestResults;
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||||
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
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.packet.Disconnect;
|
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||||
@ -37,24 +36,16 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
private final VelocityServerConnection serverConn;
|
private final VelocityServerConnection serverConn;
|
||||||
private final CompletableFuture<ConnectionRequestBuilder.Result> resultFuture;
|
private final CompletableFuture<Impl> resultFuture;
|
||||||
private boolean informationForwarded;
|
private boolean informationForwarded;
|
||||||
|
|
||||||
LoginSessionHandler(VelocityServer server, VelocityServerConnection serverConn,
|
LoginSessionHandler(VelocityServer server, VelocityServerConnection serverConn,
|
||||||
CompletableFuture<ConnectionRequestBuilder.Result> resultFuture) {
|
CompletableFuture<Impl> resultFuture) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.serverConn = serverConn;
|
this.serverConn = serverConn;
|
||||||
this.resultFuture = resultFuture;
|
this.resultFuture = resultFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinecraftConnection ensureMinecraftConnection() {
|
|
||||||
MinecraftConnection mc = serverConn.getConnection();
|
|
||||||
if (mc == null) {
|
|
||||||
throw new IllegalStateException("Not connected to backend server!");
|
|
||||||
}
|
|
||||||
return mc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(EncryptionRequest packet) {
|
public boolean handle(EncryptionRequest packet) {
|
||||||
throw new IllegalStateException("Backend server is online-mode!");
|
throw new IllegalStateException("Backend server is online-mode!");
|
||||||
@ -62,7 +53,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(LoginPluginMessage packet) {
|
public boolean handle(LoginPluginMessage packet) {
|
||||||
MinecraftConnection mc = ensureMinecraftConnection();
|
MinecraftConnection mc = serverConn.ensureConnected();
|
||||||
VelocityConfiguration configuration = server.getConfiguration();
|
VelocityConfiguration configuration = server.getConfiguration();
|
||||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet
|
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet
|
||||||
.getChannel()
|
.getChannel()
|
||||||
@ -95,7 +86,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(SetCompression packet) {
|
public boolean handle(SetCompression packet) {
|
||||||
ensureMinecraftConnection().setCompressionThreshold(packet.getThreshold());
|
serverConn.ensureConnected().setCompressionThreshold(packet.getThreshold());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,36 +100,15 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The player has been logged on to the backend server.
|
// The player has been logged on to the backend server, but we're not done yet. There could be
|
||||||
MinecraftConnection smc = ensureMinecraftConnection();
|
// other problems that could arise before we get a JoinGame packet from the server.
|
||||||
|
|
||||||
|
// Move into the PLAY phase.
|
||||||
|
MinecraftConnection smc = serverConn.ensureConnected();
|
||||||
smc.setState(StateRegistry.PLAY);
|
smc.setState(StateRegistry.PLAY);
|
||||||
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
|
||||||
if (existingConnection == null) {
|
|
||||||
// Strap on the play session handler
|
|
||||||
serverConn.getPlayer().getMinecraftConnection()
|
|
||||||
.setSessionHandler(new ClientPlaySessionHandler(server, serverConn.getPlayer()));
|
|
||||||
} else {
|
|
||||||
// For Legacy Forge
|
|
||||||
existingConnection.getPhase().onDepartForNewServer(serverConn, serverConn.getPlayer());
|
|
||||||
|
|
||||||
// Shut down the existing server connection.
|
// Switch to the transition handler.
|
||||||
serverConn.getPlayer().setConnectedServer(null);
|
smc.setSessionHandler(new TransitionSessionHandler(server, serverConn, resultFuture));
|
||||||
existingConnection.disconnect();
|
|
||||||
|
|
||||||
// Send keep alive to try to avoid timeouts
|
|
||||||
serverConn.getPlayer().sendKeepAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
smc.getChannel().config().setAutoRead(false);
|
|
||||||
server.getEventManager()
|
|
||||||
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
|
|
||||||
.whenCompleteAsync((x, error) -> {
|
|
||||||
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
|
|
||||||
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
|
|
||||||
serverConn.getPlayer().setConnectedServer(serverConn);
|
|
||||||
smc.getChannel().config().setAutoRead(true);
|
|
||||||
smc.getChannel().read();
|
|
||||||
}, smc.eventLoop());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,177 @@
|
|||||||
|
package com.velocitypowered.proxy.connection.backend;
|
||||||
|
|
||||||
|
import static com.velocitypowered.proxy.connection.backend.BackendConnectionPhases.IN_TRANSITION;
|
||||||
|
import static com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeBackendPhase.HELLO;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||||
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
|
import com.velocitypowered.proxy.connection.ConnectionTypes;
|
||||||
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||||
|
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
|
||||||
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||||
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special session handler that catches "last minute" disconnects.
|
||||||
|
*/
|
||||||
|
public class TransitionSessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
|
private final VelocityServer server;
|
||||||
|
private final VelocityServerConnection serverConn;
|
||||||
|
private final CompletableFuture<Impl> resultFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the new transition handler.
|
||||||
|
* @param server the Velocity server instance
|
||||||
|
* @param serverConn the server connection
|
||||||
|
* @param resultFuture the result future
|
||||||
|
*/
|
||||||
|
TransitionSessionHandler(VelocityServer server,
|
||||||
|
VelocityServerConnection serverConn,
|
||||||
|
CompletableFuture<Impl> resultFuture) {
|
||||||
|
this.server = server;
|
||||||
|
this.serverConn = serverConn;
|
||||||
|
this.resultFuture = resultFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean beforeHandle() {
|
||||||
|
if (!serverConn.isActive()) {
|
||||||
|
// Obsolete connection
|
||||||
|
serverConn.disconnect();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(KeepAlive packet) {
|
||||||
|
serverConn.ensureConnected().write(packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(JoinGame packet) {
|
||||||
|
MinecraftConnection smc = serverConn.ensureConnected();
|
||||||
|
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
||||||
|
|
||||||
|
if (existingConnection != null) {
|
||||||
|
// Shut down the existing server connection.
|
||||||
|
serverConn.getPlayer().setConnectedServer(null);
|
||||||
|
existingConnection.disconnect();
|
||||||
|
|
||||||
|
// Send keep alive to try to avoid timeouts
|
||||||
|
serverConn.getPlayer().sendKeepAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
|
||||||
|
smc.setAutoReading(false);
|
||||||
|
server.getEventManager()
|
||||||
|
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
|
||||||
|
.whenCompleteAsync((x, error) -> {
|
||||||
|
// Strap on the ClientPlaySessionHandler if required.
|
||||||
|
ClientPlaySessionHandler playHandler;
|
||||||
|
if (serverConn.getPlayer().getMinecraftConnection().getSessionHandler()
|
||||||
|
instanceof ClientPlaySessionHandler) {
|
||||||
|
playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getMinecraftConnection()
|
||||||
|
.getSessionHandler();
|
||||||
|
} else {
|
||||||
|
playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer());
|
||||||
|
serverConn.getPlayer().getMinecraftConnection().setSessionHandler(playHandler);
|
||||||
|
}
|
||||||
|
playHandler.handleBackendJoinGame(packet, serverConn);
|
||||||
|
|
||||||
|
// Strap on the correct session handler for the server. We will have nothing more to do
|
||||||
|
// with this connection once this task finishes up.
|
||||||
|
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
|
||||||
|
|
||||||
|
// Clean up disabling auto-read while the connected event was being processed.
|
||||||
|
smc.setAutoReading(true);
|
||||||
|
|
||||||
|
// Now set the connected server.
|
||||||
|
serverConn.getPlayer().setConnectedServer(serverConn);
|
||||||
|
|
||||||
|
// We're done! :)
|
||||||
|
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
|
||||||
|
}, smc.eventLoop());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(Disconnect packet) {
|
||||||
|
final MinecraftConnection connection = serverConn.ensureConnected();
|
||||||
|
serverConn.disconnect();
|
||||||
|
|
||||||
|
// If we were in the middle of the Forge handshake, it is not safe to proceed. We must kick
|
||||||
|
// the client.
|
||||||
|
if (connection.getType() == ConnectionTypes.LEGACY_FORGE
|
||||||
|
&& !serverConn.getPhase().consideredComplete()) {
|
||||||
|
resultFuture.complete(ConnectionRequestResults.forUnsafeDisconnect(packet,
|
||||||
|
serverConn.getServer()));
|
||||||
|
} else {
|
||||||
|
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(PluginMessage packet) {
|
||||||
|
if (!canForwardPluginMessage(packet)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always need to handle plugin messages, for Forge compatibility.
|
||||||
|
if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) {
|
||||||
|
// Handled, but check the server connection phase.
|
||||||
|
if (serverConn.getPhase() == HELLO) {
|
||||||
|
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
||||||
|
if (existingConnection != null && existingConnection.getPhase() != IN_TRANSITION) {
|
||||||
|
// Indicate that this connection is "in transition"
|
||||||
|
existingConnection.setConnectionPhase(IN_TRANSITION);
|
||||||
|
|
||||||
|
// Tell the player that we're leaving and we just aren't coming back.
|
||||||
|
existingConnection.getPhase().onDepartForNewServer(existingConnection,
|
||||||
|
serverConn.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConn.getPlayer().getMinecraftConnection().write(packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disconnected() {
|
||||||
|
resultFuture
|
||||||
|
.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||||
|
MinecraftConnection mc = serverConn.getConnection();
|
||||||
|
if (mc == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean minecraftOrFmlMessage;
|
||||||
|
if (mc.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_12_2) <= 0) {
|
||||||
|
String channel = message.getChannel();
|
||||||
|
minecraftOrFmlMessage = channel.startsWith("MC|") || channel
|
||||||
|
.startsWith(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||||
|
} else {
|
||||||
|
minecraftOrFmlMessage = message.getChannel().startsWith("minecraft:");
|
||||||
|
}
|
||||||
|
return minecraftOrFmlMessage
|
||||||
|
|| server.getChannelRegistrar().registered(message.getChannel());
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ 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.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
|
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
|
||||||
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.MinecraftDecoder;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||||
@ -71,8 +72,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
* @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result} representing
|
* @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result} representing
|
||||||
* whether or not the connect succeeded
|
* whether or not the connect succeeded
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
|
public CompletableFuture<Impl> connect() {
|
||||||
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
|
CompletableFuture<Impl> result = new CompletableFuture<>();
|
||||||
// Note: we use the event loop for the connection the player is on. This reduces context
|
// Note: we use the event loop for the connection the player is on. This reduces context
|
||||||
// switches.
|
// switches.
|
||||||
server.initializeGenericBootstrap(proxyPlayer.getMinecraftConnection().eventLoop())
|
server.initializeGenericBootstrap(proxyPlayer.getMinecraftConnection().eventLoop())
|
||||||
@ -164,6 +165,18 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the connection is still active and throws an exception if it is not.
|
||||||
|
* @return the active connection
|
||||||
|
* @throws IllegalStateException if the connection is inactive
|
||||||
|
*/
|
||||||
|
public MinecraftConnection ensureConnected() {
|
||||||
|
if (connection == null) {
|
||||||
|
throw new IllegalStateException("Not connected to server!");
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VelocityRegisteredServer getServer() {
|
public VelocityRegisteredServer getServer() {
|
||||||
return registeredServer;
|
return registeredServer;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeClientPhase;
|
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeHandshakeClientPhase;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
|
|
||||||
@ -16,14 +17,13 @@ public interface ClientConnectionPhase {
|
|||||||
* this phase.
|
* this phase.
|
||||||
*
|
*
|
||||||
* @param player The player
|
* @param player The player
|
||||||
* @param handler The {@link ClientPlaySessionHandler} that is handling
|
|
||||||
* packets
|
|
||||||
* @param message The message to handle
|
* @param message The message to handle
|
||||||
|
* @param server The backend connection to use
|
||||||
* @return true if handled, false otherwise.
|
* @return true if handled, false otherwise.
|
||||||
*/
|
*/
|
||||||
default boolean handle(ConnectedPlayer player,
|
default boolean handle(ConnectedPlayer player,
|
||||||
ClientPlaySessionHandler handler,
|
PluginMessage message,
|
||||||
PluginMessage message) {
|
VelocityServerConnection server) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
|||||||
import com.velocitypowered.proxy.VelocityServer;
|
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.connection.backend.BackendConnectionPhases;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
@ -160,7 +161,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<String> suggestions = server.getCommandManager().offerSuggestions(player, command);
|
List<String> suggestions = server.getCommandManager().offerSuggestions(player, command);
|
||||||
if (suggestions.isEmpty()) {
|
if (suggestions.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
@ -226,23 +226,34 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
backendConn.write(packet);
|
backendConn.write(packet);
|
||||||
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
||||||
backendConn.write(PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion()));
|
backendConn.write(PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion()));
|
||||||
} else if (!player.getPhase().handle(player, this, packet)) {
|
} else {
|
||||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) {
|
||||||
.consideredComplete()) {
|
// We must bypass the currently-connected server when forwarding Forge packets.
|
||||||
// The client is trying to send messages too early. This is primarily caused by mods, but
|
VelocityServerConnection inFlight = player.getConnectionInFlight();
|
||||||
// it's further aggravated by Velocity. To work around these issues, we will queue any
|
if (inFlight != null) {
|
||||||
// non-FML handshake messages to be sent once the FML handshake has completed or the
|
player.getPhase().handle(player, packet, inFlight);
|
||||||
// JoinGame packet has been received by the proxy, whichever comes first.
|
}
|
||||||
loginPluginMessages.add(packet);
|
return true;
|
||||||
} else {
|
}
|
||||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
|
||||||
if (id == null) {
|
if (!player.getPhase().handle(player, packet, serverConn)) {
|
||||||
backendConn.write(packet);
|
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
||||||
|
.consideredComplete()) {
|
||||||
|
// The client is trying to send messages too early. This is primarily caused by mods,
|
||||||
|
// but further aggravated by Velocity. To work around these issues, we will queue any
|
||||||
|
// non-FML handshake messages to be sent once the FML handshake has completed or the
|
||||||
|
// JoinGame packet has been received by the proxy, whichever comes first.
|
||||||
|
loginPluginMessages.add(packet);
|
||||||
} else {
|
} else {
|
||||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
|
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||||
packet.getData());
|
if (id == null) {
|
||||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet),
|
backendConn.write(packet);
|
||||||
backendConn.eventLoop());
|
} else {
|
||||||
|
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
|
||||||
|
packet.getData());
|
||||||
|
server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet),
|
||||||
|
backendConn.eventLoop());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,7 +315,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
boolean writable = player.getMinecraftConnection().getChannel().isWritable();
|
boolean writable = player.getMinecraftConnection().getChannel().isWritable();
|
||||||
MinecraftConnection smc = serverConn.getConnection();
|
MinecraftConnection smc = serverConn.getConnection();
|
||||||
if (smc != null) {
|
if (smc != null) {
|
||||||
smc.getChannel().config().setAutoRead(writable);
|
smc.setAutoReading(writable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,18 +324,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
* 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.
|
||||||
* @param joinGame the join game packet
|
* @param joinGame the join game packet
|
||||||
|
* @param destination the new server we are connecting to
|
||||||
*/
|
*/
|
||||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
public void handleBackendJoinGame(JoinGame joinGame, VelocityServerConnection destination) {
|
||||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
final MinecraftConnection serverMc = destination.ensureConnected();
|
||||||
if (serverConn == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"No server connection for " + player + ", but JoinGame packet received");
|
|
||||||
}
|
|
||||||
MinecraftConnection serverMc = serverConn.getConnection();
|
|
||||||
if (serverMc == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Server connection for " + player + " is disconnected, but JoinGame packet received");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!spawned) {
|
if (!spawned) {
|
||||||
// Nothing special to do with regards to spawning the player
|
// Nothing special to do with regards to spawning the player
|
||||||
@ -394,7 +397,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
|||||||
// Flush everything
|
// Flush everything
|
||||||
player.getMinecraftConnection().flush();
|
player.getMinecraftConnection().flush();
|
||||||
serverMc.flush();
|
serverMc.flush();
|
||||||
serverConn.completeJoin();
|
destination.completeJoin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<UUID> getServerBossBars() {
|
public List<UUID> getServerBossBars() {
|
||||||
|
@ -33,6 +33,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
|||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
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.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||||
@ -132,6 +133,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
this.ping = ping;
|
this.ping = ping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public PlayerSettings getPlayerSettings() {
|
public PlayerSettings getPlayerSettings() {
|
||||||
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
|
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
|
||||||
}
|
}
|
||||||
@ -142,6 +144,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, cs));
|
server.getEventManager().fireAndForget(new PlayerSettingsChangedEvent(this, cs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Optional<ModInfo> getModInfo() {
|
public Optional<ModInfo> getModInfo() {
|
||||||
return Optional.ofNullable(modInfo);
|
return Optional.ofNullable(modInfo);
|
||||||
}
|
}
|
||||||
@ -293,6 +296,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
return connectedServer;
|
return connectedServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable VelocityServerConnection getConnectionInFlight() {
|
||||||
|
return connectionInFlight;
|
||||||
|
}
|
||||||
|
|
||||||
public void resetInFlightConnection() {
|
public void resetInFlightConnection() {
|
||||||
connectionInFlight = null;
|
connectionInFlight = null;
|
||||||
}
|
}
|
||||||
@ -301,8 +308,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
* Handles unexpected disconnects.
|
* Handles unexpected disconnects.
|
||||||
* @param server the server we disconnected from
|
* @param server the server we disconnected from
|
||||||
* @param throwable the exception
|
* @param throwable the exception
|
||||||
|
* @param safe whether or not we can safely reconnect to a new server
|
||||||
*/
|
*/
|
||||||
public void handleConnectionException(RegisteredServer server, Throwable throwable) {
|
public void handleConnectionException(RegisteredServer server, Throwable throwable,
|
||||||
|
boolean safe) {
|
||||||
|
if (!isActive()) {
|
||||||
|
// If the connection is no longer active, it makes no sense to try and recover it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (throwable == null) {
|
if (throwable == null) {
|
||||||
throw new NullPointerException("throwable");
|
throw new NullPointerException("throwable");
|
||||||
}
|
}
|
||||||
@ -317,22 +331,29 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
String userMessage;
|
String userMessage;
|
||||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||||
userMessage = "Your connection to " + server.getServerInfo().getName() + " encountered an "
|
userMessage = "Your connection to " + server.getServerInfo().getName() + " encountered an "
|
||||||
+ " error.";
|
+ "error.";
|
||||||
} else {
|
} else {
|
||||||
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(),
|
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(),
|
||||||
wrapped);
|
wrapped);
|
||||||
userMessage = "Unable to connect to " + server.getServerInfo().getName() + ". Try again "
|
userMessage = "Unable to connect to " + server.getServerInfo().getName() + ". Try again "
|
||||||
+ "later.";
|
+ "later.";
|
||||||
}
|
}
|
||||||
handleConnectionException(server, null, TextComponent.of(userMessage, TextColor.RED));
|
handleConnectionException(server, null, TextComponent.of(userMessage, TextColor.RED), safe);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles unexpected disconnects.
|
* Handles unexpected disconnects.
|
||||||
* @param server the server we disconnected from
|
* @param server the server we disconnected from
|
||||||
* @param disconnect the disconnect packet
|
* @param disconnect the disconnect packet
|
||||||
|
* @param safe whether or not we can safely reconnect to a new server
|
||||||
*/
|
*/
|
||||||
public void handleConnectionException(RegisteredServer server, Disconnect disconnect) {
|
public void handleConnectionException(RegisteredServer server, Disconnect disconnect,
|
||||||
|
boolean safe) {
|
||||||
|
if (!isActive()) {
|
||||||
|
// If the connection is no longer active, it makes no sense to try and recover it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||||
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
|
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
|
||||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||||
@ -342,7 +363,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
.content("Kicked from " + server.getServerInfo().getName() + ": ")
|
.content("Kicked from " + server.getServerInfo().getName() + ": ")
|
||||||
.color(TextColor.RED)
|
.color(TextColor.RED)
|
||||||
.append(disconnectReason)
|
.append(disconnectReason)
|
||||||
.build());
|
.build(), safe);
|
||||||
} else {
|
} else {
|
||||||
logger.error("{}: disconnected while connecting to {}: {}", this,
|
logger.error("{}: disconnected while connecting to {}: {}", this,
|
||||||
server.getServerInfo().getName(), plainTextReason);
|
server.getServerInfo().getName(), plainTextReason);
|
||||||
@ -350,12 +371,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
.content("Can't connect to server " + server.getServerInfo().getName() + ": ")
|
.content("Can't connect to server " + server.getServerInfo().getName() + ": ")
|
||||||
.color(TextColor.RED)
|
.color(TextColor.RED)
|
||||||
.append(disconnectReason)
|
.append(disconnectReason)
|
||||||
.build());
|
.build(), safe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleConnectionException(RegisteredServer rs,
|
private void handleConnectionException(RegisteredServer rs,
|
||||||
@Nullable Component kickReason, Component friendlyReason) {
|
@Nullable Component kickReason, Component friendlyReason, boolean safe) {
|
||||||
|
if (!isActive()) {
|
||||||
|
// If the connection is no longer active, it makes no sense to try and recover it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!safe) {
|
||||||
|
// /!\ IT IS UNSAFE TO CONTINUE /!\
|
||||||
|
//
|
||||||
|
// This is usually triggered by a failed Forge handshake.
|
||||||
|
disconnect(friendlyReason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (connectedServer == null) {
|
if (connectedServer == null) {
|
||||||
// The player isn't yet connected to a server.
|
// The player isn't yet connected to a server.
|
||||||
Optional<RegisteredServer> nextServer = getNextServerToTry(rs);
|
Optional<RegisteredServer> nextServer = getNextServerToTry(rs);
|
||||||
@ -620,8 +654,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private CompletableFuture<Impl> internalConnect() {
|
||||||
public CompletableFuture<Result> connect() {
|
|
||||||
Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(toConnect);
|
Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(toConnect);
|
||||||
if (initialCheck.isPresent()) {
|
if (initialCheck.isPresent()) {
|
||||||
return CompletableFuture
|
return CompletableFuture
|
||||||
@ -655,14 +688,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Result> connect() {
|
||||||
|
return this.internalConnect()
|
||||||
|
.whenCompleteAsync((status, throwable) -> {
|
||||||
|
if (status != null && !status.isSafe()) {
|
||||||
|
// If it's not safe to continue the connection we need to shut it down.
|
||||||
|
handleConnectionException(status.getAttemptedConnection(), throwable, true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.thenApply(x -> x);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> connectWithIndication() {
|
public CompletableFuture<Boolean> connectWithIndication() {
|
||||||
return connect()
|
return internalConnect()
|
||||||
.whenCompleteAsync((status, throwable) -> {
|
.whenCompleteAsync((status, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
// TODO: The exception handling from this is not very good. Find a better way.
|
// TODO: The exception handling from this is not very good. Find a better way.
|
||||||
handleConnectionException(status != null ? status.getAttemptedConnection()
|
handleConnectionException(status != null ? status.getAttemptedConnection()
|
||||||
: toConnect, throwable);
|
: toConnect, throwable, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,7 +723,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
|||||||
break;
|
break;
|
||||||
case SERVER_DISCONNECTED:
|
case SERVER_DISCONNECTED:
|
||||||
handleConnectionException(toConnect, Disconnect.create(status.getReason()
|
handleConnectionException(toConnect, Disconnect.create(status.getReason()
|
||||||
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
|
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)), 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!)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||||
|
|
||||||
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
@ -12,8 +13,14 @@ public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleGeneric(MinecraftPacket packet) {
|
public boolean handle(PluginMessage packet) {
|
||||||
// No-op: will never handle packets
|
VelocityServerConnection serverConn = player.getConnectionInFlight();
|
||||||
|
if (serverConn != null) {
|
||||||
|
if (!player.getPhase().handle(player, packet, serverConn)) {
|
||||||
|
serverConn.ensureConnected().write(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,9 +38,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean onHandle(ConnectedPlayer player,
|
boolean onHandle(ConnectedPlayer player,
|
||||||
ClientPlaySessionHandler handler,
|
PluginMessage message,
|
||||||
PluginMessage message,
|
MinecraftConnection backendConn) {
|
||||||
MinecraftConnection backendConn) {
|
|
||||||
// If we stay in this phase, we do nothing because it means the packet wasn't handled.
|
// If we stay in this phase, we do nothing because it means the packet wasn't handled.
|
||||||
// Returning false indicates this
|
// Returning false indicates this
|
||||||
return false;
|
return false;
|
||||||
@ -73,9 +72,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean onHandle(ConnectedPlayer player,
|
boolean onHandle(ConnectedPlayer player,
|
||||||
ClientPlaySessionHandler handler,
|
PluginMessage message,
|
||||||
PluginMessage message,
|
MinecraftConnection backendConn) {
|
||||||
MinecraftConnection backendConn) {
|
|
||||||
// Read the mod list if we haven't already.
|
// Read the mod list if we haven't already.
|
||||||
if (!player.getModInfo().isPresent()) {
|
if (!player.getModInfo().isPresent()) {
|
||||||
List<ModInfo.Mod> mods = LegacyForgeUtil.readModList(message);
|
List<ModInfo.Mod> mods = LegacyForgeUtil.readModList(message);
|
||||||
@ -84,7 +82,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onHandle(player, handler, message, backendConn);
|
return super.onHandle(player, message, backendConn);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -148,14 +146,16 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean onHandle(ConnectedPlayer player,
|
boolean onHandle(ConnectedPlayer player,
|
||||||
ClientPlaySessionHandler handler,
|
PluginMessage message,
|
||||||
PluginMessage message,
|
MinecraftConnection backendConn) {
|
||||||
MinecraftConnection backendConn) {
|
super.onHandle(player, message, backendConn);
|
||||||
super.onHandle(player, handler, message, backendConn);
|
|
||||||
|
|
||||||
// just in case the timing is awful
|
// just in case the timing is awful
|
||||||
player.sendKeepAlive();
|
player.sendKeepAlive();
|
||||||
handler.flushQueuedMessages();
|
|
||||||
|
if (backendConn.getSessionHandler() instanceof ClientPlaySessionHandler) {
|
||||||
|
((ClientPlaySessionHandler) backendConn.getSessionHandler()).flushQueuedMessages();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -178,11 +178,10 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean handle(ConnectedPlayer player,
|
public final boolean handle(ConnectedPlayer player,
|
||||||
ClientPlaySessionHandler handler,
|
PluginMessage message,
|
||||||
PluginMessage message) {
|
VelocityServerConnection server) {
|
||||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
if (server != null) {
|
||||||
if (serverConn != null) {
|
MinecraftConnection backendConn = server.getConnection();
|
||||||
MinecraftConnection backendConn = serverConn.getConnection();
|
|
||||||
if (backendConn != null
|
if (backendConn != null
|
||||||
&& message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
&& message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||||
// Get the phase and check if we need to start the next phase.
|
// Get the phase and check if we need to start the next phase.
|
||||||
@ -192,7 +191,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
player.setPhase(newPhase);
|
player.setPhase(newPhase);
|
||||||
|
|
||||||
// Perform phase handling
|
// Perform phase handling
|
||||||
return newPhase.onHandle(player, handler, message, backendConn);
|
return newPhase.onHandle(player, message, backendConn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,17 +203,14 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
|||||||
* Handles the phase tasks.
|
* Handles the phase tasks.
|
||||||
*
|
*
|
||||||
* @param player The player
|
* @param player The player
|
||||||
* @param handler The {@link ClientPlaySessionHandler} that is handling
|
|
||||||
* packets
|
|
||||||
* @param message The message to handle
|
* @param message The message to handle
|
||||||
* @param backendConn The backend connection to write to, if required.
|
* @param backendConn The backend connection to write to, if required.
|
||||||
*
|
*
|
||||||
* @return true if handled, false otherwise.
|
* @return true if handled, false otherwise.
|
||||||
*/
|
*/
|
||||||
boolean onHandle(ConnectedPlayer player,
|
boolean onHandle(ConnectedPlayer player,
|
||||||
ClientPlaySessionHandler handler,
|
PluginMessage message,
|
||||||
PluginMessage message,
|
MinecraftConnection backendConn) {
|
||||||
MinecraftConnection backendConn) {
|
|
||||||
// Send the packet on to the server.
|
// Send the packet on to the server.
|
||||||
backendConn.write(message);
|
backendConn.write(message);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import com.velocitypowered.api.proxy.ConnectionRequestBuilder.Status;
|
|||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import net.kyori.text.Component;
|
import net.kyori.text.Component;
|
||||||
import net.kyori.text.serializer.ComponentSerializers;
|
import net.kyori.text.serializer.ComponentSerializers;
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ public class ConnectionRequestResults {
|
|||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result successful(RegisteredServer server) {
|
public static Impl successful(RegisteredServer server) {
|
||||||
return plainResult(Status.SUCCESS, server);
|
return plainResult(Status.SUCCESS, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,31 +26,10 @@ public class ConnectionRequestResults {
|
|||||||
* @param server the server to use
|
* @param server the server to use
|
||||||
* @return the result
|
* @return the result
|
||||||
*/
|
*/
|
||||||
public static ConnectionRequestBuilder.Result plainResult(
|
public static Impl plainResult(
|
||||||
ConnectionRequestBuilder.Status status,
|
ConnectionRequestBuilder.Status status,
|
||||||
RegisteredServer server) {
|
RegisteredServer server) {
|
||||||
return new ConnectionRequestBuilder.Result() {
|
return new Impl(status, null, server, true);
|
||||||
@Override
|
|
||||||
public ConnectionRequestBuilder.Status getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Component> getReason() {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RegisteredServer getAttemptedConnection() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ConnectionRequestBuilder.Result forDisconnect(Disconnect disconnect,
|
|
||||||
RegisteredServer server) {
|
|
||||||
Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
|
||||||
return forDisconnect(deserialized, server);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,23 +38,56 @@ public class ConnectionRequestResults {
|
|||||||
* @param server the server to use
|
* @param server the server to use
|
||||||
* @return the result
|
* @return the result
|
||||||
*/
|
*/
|
||||||
public static ConnectionRequestBuilder.Result forDisconnect(Component component,
|
public static Impl forDisconnect(Component component, RegisteredServer server) {
|
||||||
RegisteredServer server) {
|
return new Impl(Status.SERVER_DISCONNECTED, component, server, true);
|
||||||
return new ConnectionRequestBuilder.Result() {
|
}
|
||||||
@Override
|
|
||||||
public ConnectionRequestBuilder.Status getStatus() {
|
|
||||||
return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public static Impl forDisconnect(Disconnect disconnect, RegisteredServer server) {
|
||||||
public Optional<Component> getReason() {
|
Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||||
return Optional.of(component);
|
return forDisconnect(deserialized, server);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static Impl forUnsafeDisconnect(Disconnect disconnect, RegisteredServer server) {
|
||||||
public RegisteredServer getAttemptedConnection() {
|
Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||||
return server;
|
return new Impl(Status.SERVER_DISCONNECTED, deserialized, server, false);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
public static class Impl implements ConnectionRequestBuilder.Result {
|
||||||
|
|
||||||
|
private final Status status;
|
||||||
|
private final @Nullable Component component;
|
||||||
|
private final RegisteredServer attemptedConnection;
|
||||||
|
private final boolean safe;
|
||||||
|
|
||||||
|
Impl(Status status, @Nullable Component component,
|
||||||
|
RegisteredServer attemptedConnection, boolean safe) {
|
||||||
|
this.status = status;
|
||||||
|
this.component = component;
|
||||||
|
this.attemptedConnection = attemptedConnection;
|
||||||
|
this.safe = safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Component> getReason() {
|
||||||
|
return Optional.ofNullable(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisteredServer getAttemptedConnection() {
|
||||||
|
return attemptedConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not it is safe to attempt a reconnect.
|
||||||
|
* @return whether we can try to reconnect
|
||||||
|
*/
|
||||||
|
public boolean isSafe() {
|
||||||
|
return safe;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,40 +85,4 @@ public class NettyHttpClient {
|
|||||||
});
|
});
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class HostAndSsl {
|
|
||||||
private final InetSocketAddress address;
|
|
||||||
private final boolean ssl;
|
|
||||||
|
|
||||||
private HostAndSsl(InetSocketAddress address, boolean ssl) {
|
|
||||||
this.address = address;
|
|
||||||
this.ssl = ssl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "HostAndSsl{"
|
|
||||||
+ "address=" + address
|
|
||||||
+ ", ssl=" + ssl
|
|
||||||
+ '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
HostAndSsl that = (HostAndSsl) o;
|
|
||||||
return ssl == that.ssl
|
|
||||||
&& Objects.equals(address, that.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(address, ssl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import java.io.BufferedInputStream;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -92,7 +93,7 @@ public class JavaPluginLoader implements PluginLoader {
|
|||||||
JarEntry entry;
|
JarEntry entry;
|
||||||
while ((entry = in.getNextJarEntry()) != null) {
|
while ((entry = in.getNextJarEntry()) != null) {
|
||||||
if (entry.getName().equals("velocity-plugin.json")) {
|
if (entry.getName().equals("velocity-plugin.json")) {
|
||||||
try (Reader pluginInfoReader = new InputStreamReader(in)) {
|
try (Reader pluginInfoReader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||||
return Optional.of(VelocityServer.GSON
|
return Optional.of(VelocityServer.GSON
|
||||||
.fromJson(pluginInfoReader, SerializedPluginDescription.class));
|
.fromJson(pluginInfoReader, SerializedPluginDescription.class));
|
||||||
}
|
}
|
||||||
|
@ -361,7 +361,7 @@ public enum StateRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PacketMapping {
|
public static final class PacketMapping {
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
private final ProtocolVersion protocolVersion;
|
private final ProtocolVersion protocolVersion;
|
||||||
|
@ -46,7 +46,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
0x79, 0x65, 0x72, 0x5F, 0x00, 0x00};
|
0x79, 0x65, 0x72, 0x5F, 0x00, 0x00};
|
||||||
|
|
||||||
// Contents to add into basic stat response. See ResponseWriter class below
|
// Contents to add into basic stat response. See ResponseWriter class below
|
||||||
private static final Set<String> QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of(
|
private static final ImmutableSet<String> QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of(
|
||||||
"hostname",
|
"hostname",
|
||||||
"gametype",
|
"gametype",
|
||||||
"map",
|
"map",
|
||||||
@ -118,7 +118,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
|
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
|
||||||
queryResponse.writeInt(sessionId);
|
queryResponse.writeInt(sessionId);
|
||||||
writeString(queryResponse, Integer.toString(challengeToken));
|
writeString(queryResponse, Integer.toString(challengeToken));
|
||||||
ctx.writeAndFlush(responsePacket);
|
ctx.writeAndFlush(responsePacket, ctx.voidPromise());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send the response
|
// Send the response
|
||||||
ctx.writeAndFlush(responsePacket);
|
ctx.writeAndFlush(responsePacket, ctx.voidPromise());
|
||||||
}, ctx.channel().eventLoop());
|
}, ctx.channel().eventLoop());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -74,7 +74,7 @@ public class LegacyDisconnect {
|
|||||||
*/
|
*/
|
||||||
public static LegacyDisconnect from(TextComponent component) {
|
public static LegacyDisconnect from(TextComponent component) {
|
||||||
// We intentionally use the legacy serializers, because the old clients can't understand JSON.
|
// We intentionally use the legacy serializers, because the old clients can't understand JSON.
|
||||||
@SuppressWarnings("deprecated")
|
@SuppressWarnings("deprecation")
|
||||||
String serialized = ComponentSerializers.LEGACY.serialize(component);
|
String serialized = ComponentSerializers.LEGACY.serialize(component);
|
||||||
return new LegacyDisconnect(serialized);
|
return new LegacyDisconnect(serialized);
|
||||||
}
|
}
|
||||||
|
@ -175,8 +175,8 @@ public class PlayerListItem implements MinecraftPacket {
|
|||||||
return gameMode;
|
return gameMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Item setGameMode(int gamemode) {
|
public Item setGameMode(int gameMode) {
|
||||||
this.gameMode = gamemode;
|
this.gameMode = gameMode;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ public class PluginMessage implements MinecraftPacket {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "PluginMessage{"
|
return "PluginMessage{"
|
||||||
+ "channel='" + channel + '\''
|
+ "channel='" + channel + '\''
|
||||||
+ ", data=" + ByteBufUtil.hexDump(data)
|
+ ", data=<removed>" +
|
||||||
+ '}';
|
+ '}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class PluginDependencyUtilsTest {
|
|||||||
private static final PluginDescription CIRCULAR_DEPENDENCY_2 = testDescription("oval",
|
private static final PluginDescription CIRCULAR_DEPENDENCY_2 = testDescription("oval",
|
||||||
ImmutableList.of(new PluginDependency("circle", "", false)));
|
ImmutableList.of(new PluginDependency("circle", "", false)));
|
||||||
|
|
||||||
private static final List<PluginDescription> EXPECTED = ImmutableList.of(
|
private static final ImmutableList<PluginDescription> EXPECTED = ImmutableList.of(
|
||||||
NEVER_DEPENDED,
|
NEVER_DEPENDED,
|
||||||
NO_DEPENDENCY_1_EXAMPLE,
|
NO_DEPENDENCY_1_EXAMPLE,
|
||||||
MULTI_DEPENDENCY,
|
MULTI_DEPENDENCY,
|
||||||
|
@ -6,6 +6,4 @@ include(
|
|||||||
)
|
)
|
||||||
findProject(':api')?.name = 'velocity-api'
|
findProject(':api')?.name = 'velocity-api'
|
||||||
findProject(':proxy')?.name = 'velocity-proxy'
|
findProject(':proxy')?.name = 'velocity-proxy'
|
||||||
findProject(':native')?.name = 'velocity-native'
|
findProject(':native')?.name = 'velocity-native'
|
||||||
|
|
||||||
enableFeaturePreview('STABLE_PUBLISHING')
|
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren