Convert Velocity buildscripts to Kotlin DSL (#918)
Spiritually indebted to #518 and @alexstaeding. There's a minor break - we're going up to 3.2.0-SNAPSHOT as the API now compiles against Java 11. But this is more academic in practice.
This commit is contained in:
@@ -1,162 +0,0 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'checkstyle'
|
||||
}
|
||||
|
||||
apply plugin: 'org.cadixdev.licenser'
|
||||
apply from: '../gradle/checkstyle.gradle'
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
license {
|
||||
header = project.rootProject.file('HEADER.txt')
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
def buildNumber = System.getenv("BUILD_NUMBER") ?: "unknown"
|
||||
def version
|
||||
if (project.version.endsWith("-SNAPSHOT")) {
|
||||
version = "${project.version} (git-${project.ext.getCurrentShortRevision()}-b${buildNumber})"
|
||||
} else {
|
||||
version = "${project.version}"
|
||||
}
|
||||
|
||||
attributes 'Main-Class': 'com.velocitypowered.proxy.Velocity'
|
||||
attributes 'Implementation-Title': "Velocity"
|
||||
attributes 'Implementation-Version': version
|
||||
attributes 'Implementation-Vendor': "Velocity Contributors"
|
||||
attributes 'Multi-Release': 'true'
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
transform(Log4j2PluginsCacheFileTransformer)
|
||||
}
|
||||
|
||||
tasks.withType(Checkstyle) {
|
||||
exclude('**/com/velocitypowered/proxy/protocol/packet/*.java')
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Note: we depend on the API twice, first the main sourceset, and then the annotation processor.
|
||||
implementation project(':velocity-api')
|
||||
implementation project(':velocity-api').sourceSets.ap.output
|
||||
implementation project(':velocity-native')
|
||||
|
||||
implementation "io.netty:netty-codec:${nettyVersion}"
|
||||
implementation "io.netty:netty-codec-haproxy:${nettyVersion}"
|
||||
implementation "io.netty:netty-codec-http:${nettyVersion}"
|
||||
implementation "io.netty:netty-handler:${nettyVersion}"
|
||||
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}"
|
||||
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
|
||||
implementation "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch_64"
|
||||
|
||||
implementation "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
||||
implementation "org.apache.logging.log4j:log4j-core:${log4jVersion}"
|
||||
implementation "org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}"
|
||||
implementation "org.apache.logging.log4j:log4j-iostreams:${log4jVersion}"
|
||||
implementation "org.apache.logging.log4j:log4j-jul:${log4jVersion}"
|
||||
|
||||
implementation 'net.sf.jopt-simple:jopt-simple:5.0.4' // command-line options
|
||||
implementation 'net.minecrell:terminalconsoleappender:1.3.0'
|
||||
runtimeOnly 'org.jline:jline-terminal-jansi:3.21.0' // Needed for JLine
|
||||
runtimeOnly 'com.lmax:disruptor:3.4.4' // Async loggers
|
||||
|
||||
implementation 'it.unimi.dsi:fastutil-core:8.5.8'
|
||||
|
||||
implementation(platform("net.kyori:adventure-bom:${adventureVersion}"))
|
||||
implementation("net.kyori:adventure-nbt")
|
||||
implementation("net.kyori:adventure-platform-facet:4.1.2")
|
||||
|
||||
implementation 'org.asynchttpclient:async-http-client:2.12.3'
|
||||
|
||||
implementation 'com.spotify:completable-futures:0.3.5'
|
||||
|
||||
implementation 'com.electronwill.night-config:toml:3.6.4'
|
||||
|
||||
implementation 'org.bstats:bstats-base:2.2.1'
|
||||
implementation 'org.lanternpowered:lmbda:2.0.0'
|
||||
|
||||
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.1'
|
||||
|
||||
implementation 'space.vectrix.flare:flare:2.0.1'
|
||||
implementation 'space.vectrix.flare:flare-fastutil:2.0.1'
|
||||
|
||||
compileOnly 'com.github.spotbugs:spotbugs-annotations:4.4.0'
|
||||
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
|
||||
testImplementation "org.mockito:mockito-core:3.+"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
// Exclude all the collection types we don't intend to use
|
||||
exclude 'it/unimi/dsi/fastutil/booleans/**'
|
||||
exclude 'it/unimi/dsi/fastutil/bytes/**'
|
||||
exclude 'it/unimi/dsi/fastutil/chars/**'
|
||||
exclude 'it/unimi/dsi/fastutil/doubles/**'
|
||||
exclude 'it/unimi/dsi/fastutil/floats/**'
|
||||
exclude 'it/unimi/dsi/fastutil/longs/**'
|
||||
exclude 'it/unimi/dsi/fastutil/shorts/**'
|
||||
|
||||
// Exclude the fastutil IO utilities - we don't use them.
|
||||
exclude 'it/unimi/dsi/fastutil/io/**'
|
||||
|
||||
// Exclude most of the int types - Object2IntMap have a values() method that returns an
|
||||
// IntCollection, and we need Int2ObjectMap
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Boolean*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Byte*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Char*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Double*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Float*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Int*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Long*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Short*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Int2Reference*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntAVL*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntArray*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*IntBi*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/Int*Pair'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntLinked*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntList*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntHeap*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntOpen*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntRB*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/IntSorted*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*Priority*'
|
||||
exclude 'it/unimi/dsi/fastutil/ints/*BigList*'
|
||||
|
||||
// Try to exclude everything BUT Object2Int{LinkedOpen,Open,CustomOpen}HashMap
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*ObjectArray*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*ObjectAVL*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object*Big*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Boolean*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Byte*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Char*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Double*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Float*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2IntArray*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2IntAVL*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2IntRB*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Long*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Object*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Reference*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Object2Short*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*ObjectRB*'
|
||||
exclude 'it/unimi/dsi/fastutil/objects/*Reference*'
|
||||
|
||||
// Exclude Checker Framework annotations
|
||||
exclude 'org/checkerframework/checker/**'
|
||||
|
||||
relocate 'org.bstats', 'com.velocitypowered.proxy.bstats'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives shadowJar
|
||||
}
|
159
proxy/build.gradle.kts
Normal file
159
proxy/build.gradle.kts
Normal file
@@ -0,0 +1,159 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer
|
||||
|
||||
plugins {
|
||||
application
|
||||
`set-manifest-impl-version`
|
||||
id("com.github.johnrengelman.shadow") version "7.1.0"
|
||||
}
|
||||
|
||||
license {
|
||||
header(project.rootProject.file("HEADER.txt"))
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("com.velocitypowered.proxy.Velocity")
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<Checkstyle> {
|
||||
exclude("**/com/velocitypowered/proxy/protocol/packet/**")
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes["Implementation-Title"] = "Velocity"
|
||||
attributes["Implementation-Vendor"] = "Velocity Contributors"
|
||||
attributes["Multi-Release"] = "true"
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
transform(Log4j2PluginsCacheFileTransformer::class.java)
|
||||
|
||||
// Exclude all the collection types we don"t intend to use
|
||||
exclude("it/unimi/dsi/fastutil/booleans/**")
|
||||
exclude("it/unimi/dsi/fastutil/bytes/**")
|
||||
exclude("it/unimi/dsi/fastutil/chars/**")
|
||||
exclude("it/unimi/dsi/fastutil/doubles/**")
|
||||
exclude("it/unimi/dsi/fastutil/floats/**")
|
||||
exclude("it/unimi/dsi/fastutil/longs/**")
|
||||
exclude("it/unimi/dsi/fastutil/shorts/**")
|
||||
|
||||
// Exclude the fastutil IO utilities - we don"t use them.
|
||||
exclude("it/unimi/dsi/fastutil/io/**")
|
||||
|
||||
// Exclude most of the int types - Object2IntMap have a values() method that returns an
|
||||
// IntCollection, and we need Int2ObjectMap
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Boolean*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Byte*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Char*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Double*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Float*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Int*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Long*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Short*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Int2Reference*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntAVL*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntArray*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*IntBi*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/Int*Pair")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntLinked*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntList*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntHeap*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntOpen*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntRB*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/IntSorted*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*Priority*")
|
||||
exclude("it/unimi/dsi/fastutil/ints/*BigList*")
|
||||
|
||||
// Try to exclude everything BUT Object2Int{LinkedOpen,Open,CustomOpen}HashMap
|
||||
exclude("it/unimi/dsi/fastutil/objects/*ObjectArray*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*ObjectAVL*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object*Big*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Boolean*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Byte*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Char*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Double*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Float*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2IntArray*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2IntAVL*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2IntRB*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Long*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Object*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Reference*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Object2Short*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*ObjectRB*")
|
||||
exclude("it/unimi/dsi/fastutil/objects/*Reference*")
|
||||
|
||||
// Exclude Checker Framework annotations
|
||||
exclude("org/checkerframework/checker/**")
|
||||
|
||||
relocate("org.bstats", "com.velocitypowered.proxy.bstats")
|
||||
}
|
||||
}
|
||||
|
||||
val adventureVersion: String by project.extra
|
||||
val adventureFacetVersion: String by project.extra
|
||||
val asyncHttpClientVersion: String by project.extra
|
||||
val bstatsVersion: String by project.extra
|
||||
val caffeineVersion: String by project.extra
|
||||
val completableFuturesVersion: String by project.extra
|
||||
val disruptorVersion: String by project.extra
|
||||
val fastutilVersion: String by project.extra
|
||||
val flareVersion: String by project.extra
|
||||
val jansiVersion: String by project.extra
|
||||
val joptSimpleVersion: String by project.extra
|
||||
val lmbdaVersion: String by project.extra
|
||||
val log4jVersion: String by project.extra
|
||||
val nettyVersion: String by project.extra
|
||||
val nightConfigVersion: String by project.extra
|
||||
val semver4jVersion: String by project.extra
|
||||
val terminalConsoleAppenderVersion: String by project.extra
|
||||
|
||||
dependencies {
|
||||
implementation(project(":velocity-api"))
|
||||
implementation(project(":velocity-native"))
|
||||
|
||||
implementation("io.netty:netty-codec:${nettyVersion}")
|
||||
implementation("io.netty:netty-codec-haproxy:${nettyVersion}")
|
||||
implementation("io.netty:netty-codec-http:${nettyVersion}")
|
||||
implementation("io.netty:netty-handler:${nettyVersion}")
|
||||
implementation("io.netty:netty-transport-native-epoll:${nettyVersion}")
|
||||
implementation("io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64")
|
||||
implementation("io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch_64")
|
||||
|
||||
implementation("org.apache.logging.log4j:log4j-api:${log4jVersion}")
|
||||
implementation("org.apache.logging.log4j:log4j-core:${log4jVersion}")
|
||||
implementation("org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}")
|
||||
implementation("org.apache.logging.log4j:log4j-iostreams:${log4jVersion}")
|
||||
implementation("org.apache.logging.log4j:log4j-jul:${log4jVersion}")
|
||||
|
||||
implementation("net.sf.jopt-simple:jopt-simple:$joptSimpleVersion") // command-line options
|
||||
implementation("net.minecrell:terminalconsoleappender:$terminalConsoleAppenderVersion")
|
||||
runtimeOnly("org.jline:jline-terminal-jansi:$jansiVersion") // Needed for JLine
|
||||
runtimeOnly("com.lmax:disruptor:$disruptorVersion") // Async loggers
|
||||
|
||||
implementation("it.unimi.dsi:fastutil-core:$fastutilVersion")
|
||||
|
||||
implementation(platform("net.kyori:adventure-bom:$adventureVersion"))
|
||||
implementation("net.kyori:adventure-nbt")
|
||||
implementation("net.kyori:adventure-platform-facet:$adventureFacetVersion")
|
||||
|
||||
implementation("org.asynchttpclient:async-http-client:$asyncHttpClientVersion")
|
||||
|
||||
implementation("com.spotify:completable-futures:$completableFuturesVersion")
|
||||
|
||||
implementation("com.electronwill.night-config:toml:$nightConfigVersion")
|
||||
|
||||
implementation("org.bstats:bstats-base:$bstatsVersion")
|
||||
implementation("org.lanternpowered:lmbda:$lmbdaVersion")
|
||||
|
||||
implementation("com.github.ben-manes.caffeine:caffeine:$caffeineVersion")
|
||||
|
||||
implementation("space.vectrix.flare:flare:$flareVersion")
|
||||
implementation("space.vectrix.flare:flare-fastutil:$flareVersion")
|
||||
|
||||
compileOnly("com.github.spotbugs:spotbugs-annotations:4.4.0")
|
||||
|
||||
testImplementation("org.mockito:mockito-core:3.+")
|
||||
}
|
@@ -35,6 +35,9 @@ import org.bstats.charts.SingleLineChart;
|
||||
import org.bstats.config.MetricsConfig;
|
||||
import org.bstats.json.JsonObjectBuilder;
|
||||
|
||||
/**
|
||||
* Initializes bStats.
|
||||
*/
|
||||
public class Metrics {
|
||||
|
||||
private MetricsBase metricsBase;
|
||||
|
@@ -26,7 +26,11 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Holds parsed command line options.
|
||||
*/
|
||||
public final class ProxyOptions {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(ProxyOptions.class);
|
||||
private final boolean help;
|
||||
private final @Nullable Integer port;
|
||||
@@ -37,7 +41,7 @@ public final class ProxyOptions {
|
||||
final OptionSpec<Void> help = parser.acceptsAll(Arrays.asList("h", "help"), "Print help")
|
||||
.forHelp();
|
||||
final OptionSpec<Integer> port = parser.acceptsAll(Arrays.asList("p", "port"),
|
||||
"Specify the bind port to be used. The configuration bind port will be ignored.")
|
||||
"Specify the bind port to be used. The configuration bind port will be ignored.")
|
||||
.withRequiredArg().ofType(Integer.class);
|
||||
final OptionSet set = parser.parse(args);
|
||||
|
||||
|
@@ -23,6 +23,10 @@ import java.text.DecimalFormat;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* The main class. Responsible for parsing command line arguments and then launching the
|
||||
* proxy.
|
||||
*/
|
||||
public class Velocity {
|
||||
|
||||
private static final Logger logger;
|
||||
@@ -50,6 +54,7 @@ public class Velocity {
|
||||
|
||||
/**
|
||||
* Main method that the JVM will call when {@code java -jar velocity.jar} is executed.
|
||||
*
|
||||
* @param args the arguments to the proxy
|
||||
*/
|
||||
public static void main(String... args) {
|
||||
|
@@ -57,7 +57,7 @@ import com.velocitypowered.proxy.scheduler.VelocityScheduler;
|
||||
import com.velocitypowered.proxy.server.ServerMap;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
|
||||
import com.velocitypowered.proxy.util.FileSystemUtils;
|
||||
import com.velocitypowered.proxy.util.ResourceUtils;
|
||||
import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
|
||||
import com.velocitypowered.proxy.util.bossbar.AdventureBossBarManager;
|
||||
import com.velocitypowered.proxy.util.ratelimit.Ratelimiter;
|
||||
@@ -105,6 +105,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ProxyServer}.
|
||||
*/
|
||||
public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||
@@ -250,7 +253,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
.create(Key.key("velocity", "translations"));
|
||||
translationRegistry.defaultLocale(Locale.US);
|
||||
try {
|
||||
FileSystemUtils.visitResources(VelocityServer.class, path -> {
|
||||
ResourceUtils.visitResources(VelocityServer.class, path -> {
|
||||
logger.info("Loading localizations...");
|
||||
|
||||
final Path langPath = Path.of("lang");
|
||||
@@ -275,7 +278,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Files.walk(langPath).forEach(file -> {
|
||||
if (!Files.isRegularFile(file)) {
|
||||
return;
|
||||
@@ -468,10 +470,10 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the proxy, kicking players with the specified {@param reason}.
|
||||
* Shuts down the proxy, kicking players with the specified reason.
|
||||
*
|
||||
* @param explicitExit whether the user explicitly shut down the proxy
|
||||
* @param reason message to kick online players with
|
||||
* @param reason message to kick online players with
|
||||
*/
|
||||
public void shutdown(boolean explicitExit, Component reason) {
|
||||
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
|
||||
@@ -502,8 +504,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
// makes sure that all the disconnect events are being fired
|
||||
|
||||
CompletableFuture<Void> playersTeardownFuture = CompletableFuture.allOf(players.stream()
|
||||
.map(ConnectedPlayer::getTeardownFuture)
|
||||
.toArray((IntFunction<CompletableFuture<Void>[]>) CompletableFuture[]::new));
|
||||
.map(ConnectedPlayer::getTeardownFuture)
|
||||
.toArray((IntFunction<CompletableFuture<Void>[]>) CompletableFuture[]::new));
|
||||
|
||||
playersTeardownFuture.get(10, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
@@ -580,6 +582,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
/**
|
||||
* Checks if the {@code connection} can be registered with the proxy.
|
||||
*
|
||||
* @param connection the connection to check
|
||||
* @return {@code true} if we can register the connection, {@code false} if not
|
||||
*/
|
||||
@@ -591,9 +594,10 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
return !(connectionsByName.containsKey(lowerName)
|
||||
|| connectionsByUuid.containsKey(connection.getUniqueId()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to register the {@code connection} with the proxy.
|
||||
*
|
||||
* @param connection the connection to register
|
||||
* @return {@code true} if we registered the connection, {@code false} if not
|
||||
*/
|
||||
@@ -650,7 +654,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
return getAllPlayers().stream().filter(p -> p.getUsername()
|
||||
.regionMatches(true, 0, partialName, 0, partialName.length()))
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -659,7 +663,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
|
||||
return getAllServers().stream().filter(s -> s.getServerInfo().getName()
|
||||
.regionMatches(true, 0, partialName, 0, partialName.length()))
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -32,9 +32,8 @@ import org.checkerframework.checker.lock.qual.GuardedBy;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Copies the nodes of a {@link RootCommandNode} to a possibly non-empty
|
||||
* destination {@link RootCommandNode}, respecting the requirements satisfied
|
||||
* by a given command source.
|
||||
* Copies the nodes of a {@link RootCommandNode} to a possibly non-empty destination
|
||||
* {@link RootCommandNode}, respecting the requirements satisfied by a given command source.
|
||||
*
|
||||
* @param <S> the type of the source to inject the nodes for
|
||||
*/
|
||||
@@ -55,13 +54,13 @@ public final class CommandGraphInjector<S> {
|
||||
// the root node we are copying nodes from to the destination node.
|
||||
|
||||
/**
|
||||
* Adds the node from the root node of this injector to the given root node,
|
||||
* respecting the requirements satisfied by the given source.
|
||||
* Adds the node from the root node of this injector to the given root node, respecting the
|
||||
* requirements satisfied by the given source.
|
||||
*
|
||||
* <p>Prior to adding a literal with the same name as one previously contained
|
||||
* in the destination node, the old node is removed from the destination node.
|
||||
*
|
||||
* @param dest the root node to add the permissible nodes to
|
||||
* @param dest the root node to add the permissible nodes to
|
||||
* @param source the command source to inject the nodes for
|
||||
*/
|
||||
public void inject(final RootCommandNode<S> dest, final S source) {
|
||||
@@ -69,7 +68,7 @@ public final class CommandGraphInjector<S> {
|
||||
try {
|
||||
final RootCommandNode<S> origin = this.dispatcher.getRoot();
|
||||
final CommandContextBuilder<S> rootContext =
|
||||
new CommandContextBuilder<>(this.dispatcher, source, origin, 0);
|
||||
new CommandContextBuilder<>(this.dispatcher, source, origin, 0);
|
||||
|
||||
// Filter alias nodes
|
||||
for (final CommandNode<S> node : origin.getChildren()) {
|
||||
@@ -78,7 +77,7 @@ public final class CommandGraphInjector<S> {
|
||||
}
|
||||
|
||||
final CommandContextBuilder<S> context = rootContext.copy()
|
||||
.withNode(node, ALIAS_RANGE);
|
||||
.withNode(node, ALIAS_RANGE);
|
||||
if (!node.canUse(context, ALIAS_READER)) {
|
||||
continue;
|
||||
}
|
||||
@@ -86,7 +85,7 @@ public final class CommandGraphInjector<S> {
|
||||
final LiteralCommandNode<S> asLiteral = (LiteralCommandNode<S>) node;
|
||||
final LiteralCommandNode<S> copy = asLiteral.createBuilder().build();
|
||||
final VelocityArgumentCommandNode<S, ?> argsNode =
|
||||
VelocityCommands.getArgumentsNode(asLiteral);
|
||||
VelocityCommands.getArgumentsNode(asLiteral);
|
||||
if (argsNode == null) {
|
||||
// This literal is associated to a BrigadierCommand, filter normally.
|
||||
this.copyChildren(node, copy, source);
|
||||
@@ -126,7 +125,7 @@ public final class CommandGraphInjector<S> {
|
||||
}
|
||||
|
||||
private void copyChildren(final CommandNode<S> parent, final CommandNode<S> dest,
|
||||
final S source) {
|
||||
final S source) {
|
||||
for (final CommandNode<S> child : parent.getChildren()) {
|
||||
final CommandNode<S> filtered = this.filterNode(child, source);
|
||||
if (filtered != null) {
|
||||
|
@@ -50,8 +50,8 @@ import org.checkerframework.checker.lock.qual.GuardedBy;
|
||||
* Provides suggestions for a given command input.
|
||||
*
|
||||
* <p>Similar to {@link CommandDispatcher#getCompletionSuggestions(ParseResults)}, except it
|
||||
* avoids fully parsing the given input and performs exactly one requirement predicate check
|
||||
* per considered node.
|
||||
* avoids fully parsing the given input and performs exactly one requirement predicate check per
|
||||
* considered node.
|
||||
*
|
||||
* @param <S> the type of the command source
|
||||
*/
|
||||
@@ -74,31 +74,31 @@ final class SuggestionsProvider<S> {
|
||||
/**
|
||||
* Provides suggestions for the given input and source.
|
||||
*
|
||||
* @param input the partial input
|
||||
* @param input the partial input
|
||||
* @param source the command source invoking the command
|
||||
* @return a future that completes with the suggestions
|
||||
*/
|
||||
public CompletableFuture<Suggestions> provideSuggestions(final String input, final S source) {
|
||||
final CommandContextBuilder<S> context = new CommandContextBuilder<>(
|
||||
this.dispatcher, source, this.dispatcher.getRoot(), 0);
|
||||
this.dispatcher, source, this.dispatcher.getRoot(), 0);
|
||||
return this.provideSuggestions(new StringReader(input), context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides suggestions for the given input and context.
|
||||
*
|
||||
* @param reader the input reader
|
||||
* @param reader the input reader
|
||||
* @param context an empty context
|
||||
* @return a future that completes with the suggestions
|
||||
*/
|
||||
private CompletableFuture<Suggestions> provideSuggestions(
|
||||
final StringReader reader, final CommandContextBuilder<S> context) {
|
||||
final StringReader reader, final CommandContextBuilder<S> context) {
|
||||
lock.lock();
|
||||
try {
|
||||
final StringRange aliasRange = this.consumeAlias(reader);
|
||||
final String alias = aliasRange.get(reader).toLowerCase(Locale.ENGLISH);
|
||||
final LiteralCommandNode<S> literal =
|
||||
(LiteralCommandNode<S>) context.getRootNode().getChild(alias);
|
||||
(LiteralCommandNode<S>) context.getRootNode().getChild(alias);
|
||||
|
||||
final boolean hasArguments = reader.canRead();
|
||||
if (hasArguments) {
|
||||
@@ -119,9 +119,9 @@ final class SuggestionsProvider<S> {
|
||||
|
||||
private StringRange consumeAlias(final StringReader reader) {
|
||||
final int firstSep = reader.getString().indexOf(
|
||||
CommandDispatcher.ARGUMENT_SEPARATOR_CHAR, reader.getCursor());
|
||||
CommandDispatcher.ARGUMENT_SEPARATOR_CHAR, reader.getCursor());
|
||||
final StringRange range = StringRange.between(
|
||||
reader.getCursor(), firstSep == -1 ? reader.getTotalLength() : firstSep);
|
||||
reader.getCursor(), firstSep == -1 ? reader.getTotalLength() : firstSep);
|
||||
reader.setCursor(range.getEnd());
|
||||
return range;
|
||||
}
|
||||
@@ -130,7 +130,7 @@ final class SuggestionsProvider<S> {
|
||||
* Returns whether a literal node with the given lowercase name should be considered for
|
||||
* suggestions given the specified input.
|
||||
*
|
||||
* @param name the lowercase literal name
|
||||
* @param name the lowercase literal name
|
||||
* @param input the partial input
|
||||
* @return true if the literal should be considered; false otherwise
|
||||
*/
|
||||
@@ -141,12 +141,12 @@ final class SuggestionsProvider<S> {
|
||||
/**
|
||||
* Returns alias suggestions for the given input.
|
||||
*
|
||||
* @param reader the input reader
|
||||
* @param reader the input reader
|
||||
* @param contextSoFar an empty context
|
||||
* @return a future that completes with the suggestions
|
||||
*/
|
||||
private CompletableFuture<Suggestions> provideAliasSuggestions(
|
||||
final StringReader reader, final CommandContextBuilder<S> contextSoFar) {
|
||||
final StringReader reader, final CommandContextBuilder<S> contextSoFar) {
|
||||
final S source = contextSoFar.getSource();
|
||||
// Lowercase the alias here so all comparisons can be case-sensitive (cheaper)
|
||||
// TODO Is this actually faster? It may incur an allocation
|
||||
@@ -166,7 +166,7 @@ final class SuggestionsProvider<S> {
|
||||
|
||||
if (shouldConsider(alias, input) && node.canUse(source)) {
|
||||
final CommandContextBuilder<S> context = contextSoFar.copy()
|
||||
.withNode(node, ALIAS_SUGGESTION_RANGE);
|
||||
.withNode(node, ALIAS_SUGGESTION_RANGE);
|
||||
if (node.canUse(context, reader)) {
|
||||
// LiteralCommandNode#listSuggestions is case insensitive
|
||||
final SuggestionsBuilder builder = new SuggestionsBuilder(input, 0);
|
||||
@@ -179,19 +179,19 @@ final class SuggestionsProvider<S> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the suggestions provided by the {@link Command} associated to the given
|
||||
* alias node and the hints given during registration for the given input.
|
||||
* Merges the suggestions provided by the {@link Command} associated to the given alias node and
|
||||
* the hints given during registration for the given input.
|
||||
*
|
||||
* <p>The context is not mutated by this method. The reader's cursor may be modified.
|
||||
*
|
||||
* @param alias the alias node
|
||||
* @param reader the input reader
|
||||
* @param alias the alias node
|
||||
* @param reader the input reader
|
||||
* @param contextSoFar the context, containing {@code alias}
|
||||
* @return a future that completes with the suggestions
|
||||
*/
|
||||
private CompletableFuture<Suggestions> provideArgumentsSuggestions(
|
||||
final LiteralCommandNode<S> alias, final StringReader reader,
|
||||
final CommandContextBuilder<S> contextSoFar) {
|
||||
final LiteralCommandNode<S> alias, final StringReader reader,
|
||||
final CommandContextBuilder<S> contextSoFar) {
|
||||
final S source = contextSoFar.getSource();
|
||||
final String fullInput = reader.getString();
|
||||
final VelocityArgumentCommandNode<S, ?> argsNode = VelocityCommands.getArgumentsNode(alias);
|
||||
@@ -227,7 +227,7 @@ final class SuggestionsProvider<S> {
|
||||
// Ask the command for suggestions via the arguments node
|
||||
reader.setCursor(start);
|
||||
final CompletableFuture<Suggestions> cmdSuggestions =
|
||||
this.getArgumentsNodeSuggestions(argsNode, reader, context);
|
||||
this.getArgumentsNodeSuggestions(argsNode, reader, context);
|
||||
final boolean hasHints = alias.getChildren().size() > 1;
|
||||
if (!hasHints) {
|
||||
return this.merge(fullInput, cmdSuggestions);
|
||||
@@ -236,24 +236,24 @@ final class SuggestionsProvider<S> {
|
||||
// Parse the hint nodes to get remaining suggestions
|
||||
reader.setCursor(start);
|
||||
final CompletableFuture<Suggestions> hintSuggestions =
|
||||
this.getHintSuggestions(alias, reader, contextSoFar);
|
||||
this.getHintSuggestions(alias, reader, contextSoFar);
|
||||
return this.merge(fullInput, cmdSuggestions, hintSuggestions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the suggestions provided by the {@link Command} associated to
|
||||
* the specified arguments node for the given input.
|
||||
* Returns the suggestions provided by the {@link Command} associated to the specified arguments
|
||||
* node for the given input.
|
||||
*
|
||||
* <p>The reader and context are not mutated by this method.
|
||||
*
|
||||
* @param node the arguments node of the command
|
||||
* @param reader the input reader
|
||||
* @param node the arguments node of the command
|
||||
* @param reader the input reader
|
||||
* @param context the context, containing an alias node and {@code node}
|
||||
* @return a future that completes with the suggestions
|
||||
*/
|
||||
private CompletableFuture<Suggestions> getArgumentsNodeSuggestions(
|
||||
final VelocityArgumentCommandNode<S, ?> node, final StringReader reader,
|
||||
final CommandContextBuilder<S> context) {
|
||||
final VelocityArgumentCommandNode<S, ?> node, final StringReader reader,
|
||||
final CommandContextBuilder<S> context) {
|
||||
final int start = reader.getCursor();
|
||||
final String fullInput = reader.getString();
|
||||
final CommandContext<S> built = context.build(fullInput);
|
||||
@@ -271,14 +271,14 @@ final class SuggestionsProvider<S> {
|
||||
*
|
||||
* <p>The reader and context are not mutated by this method.
|
||||
*
|
||||
* @param alias the alias node
|
||||
* @param reader the input reader
|
||||
* @param alias the alias node
|
||||
* @param reader the input reader
|
||||
* @param context the context, containing {@code alias}
|
||||
* @return a future that completes with the suggestions
|
||||
*/
|
||||
private CompletableFuture<Suggestions> getHintSuggestions(
|
||||
final LiteralCommandNode<S> alias, final StringReader reader,
|
||||
final CommandContextBuilder<S> context) {
|
||||
final LiteralCommandNode<S> alias, final StringReader reader,
|
||||
final CommandContextBuilder<S> context) {
|
||||
final ParseResults<S> parse = this.parseHints(alias, reader, context);
|
||||
try {
|
||||
return this.dispatcher.getCompletionSuggestions(parse);
|
||||
@@ -290,20 +290,20 @@ final class SuggestionsProvider<S> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the hint nodes under the given node, which is either an alias node of
|
||||
* a {@link Command} or another hint node.
|
||||
* Parses the hint nodes under the given node, which is either an alias node of a {@link Command}
|
||||
* or another hint node.
|
||||
*
|
||||
* <p>The reader and context are not mutated by this method.
|
||||
*
|
||||
* @param node the node to parse
|
||||
* @param node the node to parse
|
||||
* @param originalReader the input reader
|
||||
* @param contextSoFar the context, containing the alias node of the command
|
||||
* @param contextSoFar the context, containing the alias node of the command
|
||||
* @return the parse results containing the parsed hint nodes
|
||||
* @see VelocityCommandMeta#copyHints(CommandMeta) for the conditions under which the returned
|
||||
* hints can be suggested to a {@link CommandSource}.
|
||||
*/
|
||||
private ParseResults<S> parseHints(final CommandNode<S> node, final StringReader originalReader,
|
||||
final CommandContextBuilder<S> contextSoFar) {
|
||||
final CommandContextBuilder<S> contextSoFar) {
|
||||
// This is a stripped-down version of CommandDispatcher#parseNodes that doesn't
|
||||
// check the requirements are satisfied and ignores redirects, neither of which
|
||||
// are used by hint nodes. Parsing errors are ignored.
|
||||
@@ -350,17 +350,16 @@ final class SuggestionsProvider<S> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future that is completed with the result of merging the {@link Suggestions}
|
||||
* the given futures complete with. The results of the futures that complete exceptionally
|
||||
* are ignored.
|
||||
* Returns a future that is completed with the result of merging the {@link Suggestions} the given
|
||||
* futures complete with. The results of the futures that complete exceptionally are ignored.
|
||||
*
|
||||
* @param fullInput the command input
|
||||
* @param futures the futures that complete with the suggestions
|
||||
* @param futures the futures that complete with the suggestions
|
||||
* @return the future that completes with the merged suggestions
|
||||
*/
|
||||
@SafeVarargs
|
||||
private CompletableFuture<Suggestions> merge(
|
||||
final String fullInput, final CompletableFuture<Suggestions>... futures) {
|
||||
final String fullInput, final CompletableFuture<Suggestions>... futures) {
|
||||
// https://github.com/Mojang/brigadier/pull/81
|
||||
return CompletableFuture.allOf(futures).handle((unused, throwable) -> {
|
||||
final List<Suggestions> suggestions = new ArrayList<>(futures.length);
|
||||
|
@@ -54,6 +54,9 @@ import org.checkerframework.checker.lock.qual.GuardedBy;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Impelements Velocity's command handler.
|
||||
*/
|
||||
public class VelocityCommandManager implements CommandManager {
|
||||
|
||||
private final @GuardedBy("lock") CommandDispatcher<CommandSource> dispatcher;
|
||||
@@ -76,9 +79,9 @@ public class VelocityCommandManager implements CommandManager {
|
||||
this.eventManager = Preconditions.checkNotNull(eventManager);
|
||||
final RootCommandNode<CommandSource> root = this.dispatcher.getRoot();
|
||||
this.registrars = ImmutableList.of(
|
||||
new BrigadierCommandRegistrar(root, this.lock.writeLock()),
|
||||
new SimpleCommandRegistrar(root, this.lock.writeLock()),
|
||||
new RawCommandRegistrar(root, this.lock.writeLock()));
|
||||
new BrigadierCommandRegistrar(root, this.lock.writeLock()),
|
||||
new SimpleCommandRegistrar(root, this.lock.writeLock()),
|
||||
new RawCommandRegistrar(root, this.lock.writeLock()));
|
||||
this.suggestionsProvider = new SuggestionsProvider<>(this.dispatcher, this.lock.readLock());
|
||||
this.injector = new CommandGraphInjector<>(this.dispatcher, this.lock.readLock());
|
||||
this.commandMetas = new ConcurrentHashMap<>();
|
||||
@@ -118,24 +121,24 @@ public class VelocityCommandManager implements CommandManager {
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
command + " does not implement a registrable Command subinterface");
|
||||
command + " does not implement a registrable Command subinterface");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to register the given command if it implements the
|
||||
* {@linkplain CommandRegistrar#registrableSuperInterface() registrable superinterface}
|
||||
* of the given registrar.
|
||||
* {@linkplain CommandRegistrar#registrableSuperInterface() registrable superinterface} of the
|
||||
* given registrar.
|
||||
*
|
||||
* @param registrar the registrar to register the command
|
||||
* @param command the command to register
|
||||
* @param meta the command metadata
|
||||
* @param <T> the type of the command
|
||||
* @return true if the command implements the registrable superinterface of the registrar;
|
||||
* false otherwise.
|
||||
* @param command the command to register
|
||||
* @param meta the command metadata
|
||||
* @param <T> the type of the command
|
||||
* @return true if the command implements the registrable superinterface of the registrar; false
|
||||
* otherwise.
|
||||
* @throws IllegalArgumentException if the registrar cannot register the command
|
||||
*/
|
||||
private <T extends Command> boolean tryRegister(final CommandRegistrar<T> registrar,
|
||||
final Command command, final CommandMeta meta) {
|
||||
final Command command, final CommandMeta meta) {
|
||||
final Class<T> superInterface = registrar.registrableSuperInterface();
|
||||
if (!superInterface.isInstance(command)) {
|
||||
return false;
|
||||
@@ -188,7 +191,7 @@ public class VelocityCommandManager implements CommandManager {
|
||||
/**
|
||||
* Fires a {@link CommandExecuteEvent}.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the command to execute
|
||||
* @return the {@link CompletableFuture} of the event
|
||||
*/
|
||||
@@ -251,10 +254,9 @@ public class VelocityCommandManager implements CommandManager {
|
||||
/**
|
||||
* Returns suggestions to fill in the given command.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the partially completed command
|
||||
* @return a {@link CompletableFuture} eventually completed with a {@link List},
|
||||
* possibly empty
|
||||
* @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty
|
||||
*/
|
||||
public CompletableFuture<List<String>> offerSuggestions(final CommandSource source,
|
||||
final String cmdLine) {
|
||||
@@ -265,10 +267,10 @@ public class VelocityCommandManager implements CommandManager {
|
||||
/**
|
||||
* Returns suggestions to fill in the given command.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the partially completed command
|
||||
* @return a {@link CompletableFuture} eventually completed with {@link Suggestions},
|
||||
* possibly empty
|
||||
* @return a {@link CompletableFuture} eventually completed with {@link Suggestions}, possibly
|
||||
* empty
|
||||
*/
|
||||
public CompletableFuture<Suggestions> offerBrigadierSuggestions(
|
||||
final CommandSource source, final String cmdLine) {
|
||||
@@ -281,14 +283,15 @@ public class VelocityCommandManager implements CommandManager {
|
||||
} catch (final Throwable e) {
|
||||
// Again, plugins are naughty
|
||||
return CompletableFuture.failedFuture(
|
||||
new RuntimeException("Unable to provide suggestions for " + cmdLine + " for " + source, e));
|
||||
new RuntimeException("Unable to provide suggestions for " + cmdLine + " for " + source,
|
||||
e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given command input.
|
||||
*
|
||||
* @param input the normalized command input, without the leading slash ('/')
|
||||
* @param input the normalized command input, without the leading slash ('/')
|
||||
* @param source the command source to parse the command for
|
||||
* @return the parse results
|
||||
*/
|
||||
@@ -307,8 +310,8 @@ public class VelocityCommandManager implements CommandManager {
|
||||
try {
|
||||
// A RootCommandNode may only contain LiteralCommandNode children instances
|
||||
return dispatcher.getRoot().getChildren().stream()
|
||||
.map(CommandNode::getName)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
.map(CommandNode::getName)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
@@ -33,6 +33,9 @@ import java.util.stream.Stream;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Handles building commands for plugins to register.
|
||||
*/
|
||||
public final class VelocityCommandMeta implements CommandMeta {
|
||||
|
||||
static final class Builder implements CommandMeta.Builder {
|
||||
@@ -44,7 +47,7 @@ public final class VelocityCommandMeta implements CommandMeta {
|
||||
public Builder(final String alias) {
|
||||
Preconditions.checkNotNull(alias, "alias");
|
||||
this.aliases = ImmutableSet.<String>builder()
|
||||
.add(alias.toLowerCase(Locale.ENGLISH));
|
||||
.add(alias.toLowerCase(Locale.ENGLISH));
|
||||
this.hints = ImmutableList.builder();
|
||||
this.plugin = null;
|
||||
}
|
||||
@@ -87,12 +90,12 @@ public final class VelocityCommandMeta implements CommandMeta {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a node to use for hinting the arguments of a {@link Command}. Hint nodes are
|
||||
* sent to 1.13+ clients and the proxy uses them for providing suggestions.
|
||||
* Creates a node to use for hinting the arguments of a {@link Command}. Hint nodes are sent to
|
||||
* 1.13+ clients and the proxy uses them for providing suggestions.
|
||||
*
|
||||
* <p>A hint node is used to provide suggestions if and only if the requirements of
|
||||
* the corresponding {@link CommandNode} are satisfied. The requirement predicate
|
||||
* of the returned node always returns {@code false}.
|
||||
* the corresponding {@link CommandNode} are satisfied. The requirement predicate of the returned
|
||||
* node always returns {@code false}.
|
||||
*
|
||||
* @param hint the node containing hinting metadata
|
||||
* @return the hinting command node
|
||||
@@ -101,8 +104,8 @@ public final class VelocityCommandMeta implements CommandMeta {
|
||||
// We need to perform a deep copy of the hint to prevent the user
|
||||
// from modifying the nodes and adding a Command or a redirect.
|
||||
final ArgumentBuilder<CommandSource, ?> builder = hint.createBuilder()
|
||||
// Requirement checking is performed by SuggestionProvider
|
||||
.requires(source -> false);
|
||||
// Requirement checking is performed by SuggestionProvider
|
||||
.requires(source -> false);
|
||||
for (final CommandNode<CommandSource> child : hint.getChildren()) {
|
||||
builder.then(copyForHinting(child));
|
||||
}
|
||||
@@ -125,9 +128,9 @@ public final class VelocityCommandMeta implements CommandMeta {
|
||||
private final Object plugin;
|
||||
|
||||
private VelocityCommandMeta(
|
||||
final Set<String> aliases,
|
||||
final List<CommandNode<CommandSource>> hints,
|
||||
final @Nullable Object plugin
|
||||
final Set<String> aliases,
|
||||
final List<CommandNode<CommandSource>> hints,
|
||||
final @Nullable Object plugin
|
||||
) {
|
||||
this.aliases = aliases;
|
||||
this.hints = hints;
|
||||
|
@@ -38,10 +38,9 @@ import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Provides utility methods common to most {@link Command} implementations.
|
||||
* In particular, {@link InvocableCommand} implementations use the same logic for
|
||||
* creating and parsing the alias and arguments command nodes, which is contained
|
||||
* in this class.
|
||||
* Provides utility methods common to most {@link Command} implementations. In particular,
|
||||
* {@link InvocableCommand} implementations use the same logic for creating and parsing the alias
|
||||
* and arguments command nodes, which is contained in this class.
|
||||
*/
|
||||
public final class VelocityCommands {
|
||||
|
||||
@@ -51,7 +50,7 @@ public final class VelocityCommands {
|
||||
* Normalizes the given command input.
|
||||
*
|
||||
* @param input the raw command input, without the leading slash ('/')
|
||||
* @param trim whether to remove leading and trailing whitespace from the input
|
||||
* @param trim whether to remove leading and trailing whitespace from the input
|
||||
* @return the normalized command input
|
||||
*/
|
||||
static String normalizeInput(final String input, final boolean trim) {
|
||||
@@ -60,7 +59,7 @@ public final class VelocityCommands {
|
||||
if (firstSep != -1) {
|
||||
// Aliases are case-insensitive, arguments are not
|
||||
return command.substring(0, firstSep).toLowerCase(Locale.ENGLISH)
|
||||
+ command.substring(firstSep);
|
||||
+ command.substring(firstSep);
|
||||
} else {
|
||||
return command.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
@@ -85,18 +84,19 @@ public final class VelocityCommands {
|
||||
public static final String ARGS_NODE_NAME = "arguments";
|
||||
|
||||
/**
|
||||
* Returns the parsed arguments that come after the command alias, or {@code fallback} if
|
||||
* no arguments were provided.
|
||||
* Returns the parsed arguments that come after the command alias, or {@code fallback} if no
|
||||
* arguments were provided.
|
||||
*
|
||||
* @param arguments the map of parsed arguments, as returned by
|
||||
* {@link CommandContext#getArguments()} or {@link CommandContextBuilder#getArguments()}
|
||||
* @param type the type class of the arguments
|
||||
* @param fallback the value to return if no arguments were provided
|
||||
* @param <V> the type of the arguments
|
||||
* {@link CommandContext#getArguments()} or
|
||||
* {@link CommandContextBuilder#getArguments()}
|
||||
* @param type the type class of the arguments
|
||||
* @param fallback the value to return if no arguments were provided
|
||||
* @param <V> the type of the arguments
|
||||
* @return the command arguments
|
||||
*/
|
||||
public static <V> V readArguments(final Map<String, ? extends ParsedArgument<?, ?>> arguments,
|
||||
final Class<V> type, final V fallback) {
|
||||
final Class<V> type, final V fallback) {
|
||||
final ParsedArgument<?, ?> argument = arguments.get(ARGS_NODE_NAME);
|
||||
if (argument == null) {
|
||||
return fallback; // either no arguments were given or this isn't an InvocableCommand
|
||||
@@ -106,15 +106,15 @@ public final class VelocityCommands {
|
||||
return type.cast(result);
|
||||
} catch (final ClassCastException e) {
|
||||
throw new IllegalArgumentException("Parsed argument is of type " + result.getClass()
|
||||
+ ", expected " + type, e);
|
||||
+ ", expected " + type, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Alias nodes
|
||||
|
||||
/**
|
||||
* Returns whether a literal node with the given name can be added to
|
||||
* the {@link RootCommandNode} associated to a {@link CommandManager}.
|
||||
* Returns whether a literal node with the given name can be added to the {@link RootCommandNode}
|
||||
* associated to a {@link CommandManager}.
|
||||
*
|
||||
* <p>This is an internal method and should not be used in user-facing
|
||||
* methods. Instead, they should lowercase the given aliases themselves.
|
||||
@@ -130,11 +130,11 @@ public final class VelocityCommands {
|
||||
* Creates a copy of the given literal with the specified name.
|
||||
*
|
||||
* @param original the literal node to copy
|
||||
* @param newName the name of the returned literal node
|
||||
* @param newName the name of the returned literal node
|
||||
* @return a copy of the literal with the given name
|
||||
*/
|
||||
public static LiteralCommandNode<CommandSource> shallowCopy(
|
||||
final LiteralCommandNode<CommandSource> original, final String newName) {
|
||||
final LiteralCommandNode<CommandSource> original, final String newName) {
|
||||
// Brigadier resolves the redirect of a node if further input can be parsed.
|
||||
// Let <bar> be a literal node having a redirect to a <foo> literal. Then,
|
||||
// the context returned by CommandDispatcher#parseNodes when given the input
|
||||
@@ -145,11 +145,11 @@ public final class VelocityCommands {
|
||||
Preconditions.checkNotNull(original, "original");
|
||||
Preconditions.checkNotNull(newName, "secondaryAlias");
|
||||
final LiteralArgumentBuilder<CommandSource> builder = LiteralArgumentBuilder
|
||||
.<CommandSource>literal(newName)
|
||||
.requires(original.getRequirement())
|
||||
.requiresWithContext(original.getContextRequirement())
|
||||
.forward(original.getRedirect(), original.getRedirectModifier(), original.isFork())
|
||||
.executes(original.getCommand());
|
||||
.<CommandSource>literal(newName)
|
||||
.requires(original.getRequirement())
|
||||
.requiresWithContext(original.getContextRequirement())
|
||||
.forward(original.getRedirect(), original.getRedirectModifier(), original.isFork())
|
||||
.executes(original.getCommand());
|
||||
for (final CommandNode<CommandSource> child : original.getChildren()) {
|
||||
builder.then(child);
|
||||
}
|
||||
@@ -159,15 +159,15 @@ public final class VelocityCommands {
|
||||
// Arguments node
|
||||
|
||||
/**
|
||||
* Returns the arguments node for the command represented by the given alias node,
|
||||
* if present; otherwise returns {@code null}.
|
||||
* Returns the arguments node for the command represented by the given alias node, if present;
|
||||
* otherwise returns {@code null}.
|
||||
*
|
||||
* @param alias the alias node
|
||||
* @param <S> the type of the command source
|
||||
* @param <S> the type of the command source
|
||||
* @return the arguments node, or null if not present
|
||||
*/
|
||||
static <S> @Nullable VelocityArgumentCommandNode<S, ?> getArgumentsNode(
|
||||
final LiteralCommandNode<S> alias) {
|
||||
final LiteralCommandNode<S> alias) {
|
||||
final CommandNode<S> node = alias.getChild(ARGS_NODE_NAME);
|
||||
if (node instanceof VelocityArgumentCommandNode) {
|
||||
return (VelocityArgumentCommandNode<S, ?>) node;
|
||||
|
@@ -27,8 +27,8 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An argument type that parses the remaining contents of a {@link StringReader},
|
||||
* splitting the input into words and placing the results in a string array.
|
||||
* An argument type that parses the remaining contents of a {@link StringReader}, splitting the
|
||||
* input into words and placing the results in a string array.
|
||||
*/
|
||||
public final class StringArrayArgumentType implements ArgumentType<String[]> {
|
||||
|
||||
@@ -36,10 +36,11 @@ public final class StringArrayArgumentType implements ArgumentType<String[]> {
|
||||
public static final String[] EMPTY = new String[0];
|
||||
|
||||
private static final Splitter WORD_SPLITTER =
|
||||
Splitter.on(CommandDispatcher.ARGUMENT_SEPARATOR_CHAR);
|
||||
Splitter.on(CommandDispatcher.ARGUMENT_SEPARATOR_CHAR);
|
||||
private static final List<String> EXAMPLES = Arrays.asList("word", "some words");
|
||||
|
||||
private StringArrayArgumentType() {}
|
||||
private StringArrayArgumentType() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] parse(final StringReader reader) throws CommandSyntaxException {
|
||||
|
@@ -31,20 +31,20 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
* @param <T> the type of the argument to parse
|
||||
*/
|
||||
public final class VelocityArgumentBuilder<S, T>
|
||||
extends ArgumentBuilder<S, VelocityArgumentBuilder<S, T>> {
|
||||
extends ArgumentBuilder<S, VelocityArgumentBuilder<S, T>> {
|
||||
|
||||
/**
|
||||
* Creates a builder for creating {@link VelocityArgumentCommandNode}s with
|
||||
* the given name and type.
|
||||
* Creates a builder for creating {@link VelocityArgumentCommandNode}s with the given name and
|
||||
* type.
|
||||
*
|
||||
* @param name the name of the node
|
||||
* @param type the type of the argument to parse
|
||||
* @param <S> the type of the command source
|
||||
* @param <T> the type of the argument to parse
|
||||
* @param <S> the type of the command source
|
||||
* @param <T> the type of the argument to parse
|
||||
* @return a builder
|
||||
*/
|
||||
public static <S, T> VelocityArgumentBuilder<S, T> velocityArgument(final String name,
|
||||
final ArgumentType<T> type) {
|
||||
final ArgumentType<T> type) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
return new VelocityArgumentBuilder<>(name, type);
|
||||
@@ -82,7 +82,7 @@ public final class VelocityArgumentBuilder<S, T>
|
||||
@Override
|
||||
public VelocityArgumentCommandNode<S, T> build() {
|
||||
return new VelocityArgumentCommandNode<>(this.name, this.type, getCommand(), getRequirement(),
|
||||
getContextRequirement(), getRedirect(), getRedirectModifier(), isFork(),
|
||||
this.suggestionsProvider);
|
||||
getContextRequirement(), getRedirect(), getRedirectModifier(), isFork(),
|
||||
this.suggestionsProvider);
|
||||
}
|
||||
}
|
||||
|
@@ -40,9 +40,9 @@ import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* An argument node that uses the given (possibly custom) {@link ArgumentType}
|
||||
* for parsing, while maintaining compatibility with the vanilla client.
|
||||
* The argument type must be greedy and accept any input.
|
||||
* An argument node that uses the given (possibly custom) {@link ArgumentType} for parsing, while
|
||||
* maintaining compatibility with the vanilla client. The argument type must be greedy and accept
|
||||
* any input.
|
||||
*
|
||||
* @param <S> the type of the command source
|
||||
* @param <T> the type of the argument to parse
|
||||
@@ -52,25 +52,25 @@ public class VelocityArgumentCommandNode<S, T> extends ArgumentCommandNode<S, St
|
||||
private final ArgumentType<T> type;
|
||||
|
||||
VelocityArgumentCommandNode(
|
||||
final String name, final ArgumentType<T> type, final Command<S> command,
|
||||
final Predicate<S> requirement,
|
||||
final BiPredicate<CommandContextBuilder<S>, ImmutableStringReader> contextRequirement,
|
||||
final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks,
|
||||
final SuggestionProvider<S> customSuggestions) {
|
||||
final String name, final ArgumentType<T> type, final Command<S> command,
|
||||
final Predicate<S> requirement,
|
||||
final BiPredicate<CommandContextBuilder<S>, ImmutableStringReader> contextRequirement,
|
||||
final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks,
|
||||
final SuggestionProvider<S> customSuggestions) {
|
||||
super(name, StringArgumentType.greedyString(), command, requirement, contextRequirement,
|
||||
redirect, modifier, forks, customSuggestions);
|
||||
redirect, modifier, forks, customSuggestions);
|
||||
this.type = Preconditions.checkNotNull(type, "type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder)
|
||||
throws CommandSyntaxException {
|
||||
throws CommandSyntaxException {
|
||||
// Same as super, except we use the rich ArgumentType
|
||||
final int start = reader.getCursor();
|
||||
final T result = this.type.parse(reader);
|
||||
if (reader.canRead()) {
|
||||
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException()
|
||||
.createWithContext(reader, "Expected greedy ArgumentType to parse all input");
|
||||
.createWithContext(reader, "Expected greedy ArgumentType to parse all input");
|
||||
}
|
||||
|
||||
final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);
|
||||
@@ -80,8 +80,8 @@ public class VelocityArgumentCommandNode<S, T> extends ArgumentCommandNode<S, St
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Suggestions> listSuggestions(
|
||||
final CommandContext<S> context, final SuggestionsBuilder builder)
|
||||
throws CommandSyntaxException {
|
||||
final CommandContext<S> context, final SuggestionsBuilder builder)
|
||||
throws CommandSyntaxException {
|
||||
if (getCustomSuggestions() == null) {
|
||||
return Suggestions.empty();
|
||||
}
|
||||
|
@@ -21,6 +21,9 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
/**
|
||||
* Basic, common command messages.
|
||||
*/
|
||||
public class CommandMessages {
|
||||
|
||||
public static final TranslatableComponent PLAYERS_ONLY = Component.translatable(
|
||||
|
@@ -40,6 +40,9 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
/**
|
||||
* Implements the Velocity default {@code /glist} command.
|
||||
*/
|
||||
public class GlistCommand {
|
||||
|
||||
private static final String SERVER_ARG = "server";
|
||||
@@ -55,22 +58,22 @@ public class GlistCommand {
|
||||
*/
|
||||
public void register() {
|
||||
LiteralCommandNode<CommandSource> totalNode = LiteralArgumentBuilder
|
||||
.<CommandSource>literal("glist")
|
||||
.requires(source ->
|
||||
source.getPermissionValue("velocity.command.glist") == Tristate.TRUE)
|
||||
.executes(this::totalCount)
|
||||
.build();
|
||||
.<CommandSource>literal("glist")
|
||||
.requires(source ->
|
||||
source.getPermissionValue("velocity.command.glist") == Tristate.TRUE)
|
||||
.executes(this::totalCount)
|
||||
.build();
|
||||
ArgumentCommandNode<CommandSource, String> serverNode = RequiredArgumentBuilder
|
||||
.<CommandSource, String>argument(SERVER_ARG, StringArgumentType.string())
|
||||
.suggests((context, builder) -> {
|
||||
for (RegisteredServer server : server.getAllServers()) {
|
||||
builder.suggest(server.getServerInfo().getName());
|
||||
}
|
||||
builder.suggest("all");
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(this::serverCount)
|
||||
.build();
|
||||
.<CommandSource, String>argument(SERVER_ARG, StringArgumentType.string())
|
||||
.suggests((context, builder) -> {
|
||||
for (RegisteredServer server : server.getAllServers()) {
|
||||
builder.suggest(server.getServerInfo().getName());
|
||||
}
|
||||
builder.suggest("all");
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.executes(this::serverCount)
|
||||
.build();
|
||||
totalNode.addChild(serverNode);
|
||||
server.getCommandManager().register(new BrigadierCommand(totalNode));
|
||||
}
|
||||
|
@@ -39,6 +39,9 @@ import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
/**
|
||||
* Implements Velocity's {@code /server} command.
|
||||
*/
|
||||
public class ServerCommand implements SimpleCommand {
|
||||
|
||||
public static final int MAX_SERVERS_TO_LIST = 50;
|
||||
@@ -54,7 +57,7 @@ public class ServerCommand implements SimpleCommand {
|
||||
final String[] args = invocation.arguments();
|
||||
|
||||
if (!(source instanceof Player)) {
|
||||
source.sendMessage(Identity.nil(), CommandMessages.PLAYERS_ONLY);
|
||||
source.sendMessage(CommandMessages.PLAYERS_ONLY);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,9 +66,8 @@ public class ServerCommand implements SimpleCommand {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (!toConnect.isPresent()) {
|
||||
player.sendMessage(Identity.nil(), CommandMessages.SERVER_DOES_NOT_EXIST
|
||||
.args(Component.text(serverName)));
|
||||
if (toConnect.isEmpty()) {
|
||||
player.sendMessage(CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,14 +80,14 @@ public class ServerCommand implements SimpleCommand {
|
||||
private void outputServerInformation(Player executor) {
|
||||
String currentServer = executor.getCurrentServer().map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName).orElse("<unknown>");
|
||||
executor.sendMessage(Identity.nil(), Component.translatable(
|
||||
executor.sendMessage(Component.translatable(
|
||||
"velocity.command.server-current-server",
|
||||
NamedTextColor.YELLOW,
|
||||
Component.text(currentServer)));
|
||||
|
||||
List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server);
|
||||
if (servers.size() > MAX_SERVERS_TO_LIST) {
|
||||
executor.sendMessage(Identity.nil(), Component.translatable(
|
||||
executor.sendMessage(Component.translatable(
|
||||
"velocity.command.server-too-many", NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
@@ -103,7 +105,7 @@ public class ServerCommand implements SimpleCommand {
|
||||
}
|
||||
}
|
||||
|
||||
executor.sendMessage(Identity.nil(), serverListBuilder.build());
|
||||
executor.sendMessage(serverListBuilder.build());
|
||||
}
|
||||
|
||||
private TextComponent formatServerComponent(String currentPlayerServer, RegisteredServer server) {
|
||||
@@ -113,9 +115,11 @@ public class ServerCommand implements SimpleCommand {
|
||||
int connectedPlayers = server.getPlayersConnected().size();
|
||||
TranslatableComponent playersTextComponent;
|
||||
if (connectedPlayers == 1) {
|
||||
playersTextComponent = Component.translatable("velocity.command.server-tooltip-player-online");
|
||||
playersTextComponent = Component.translatable(
|
||||
"velocity.command.server-tooltip-player-online");
|
||||
} else {
|
||||
playersTextComponent = Component.translatable("velocity.command.server-tooltip-players-online");
|
||||
playersTextComponent = Component.translatable(
|
||||
"velocity.command.server-tooltip-players-online");
|
||||
}
|
||||
playersTextComponent = playersTextComponent.args(Component.text(connectedPlayers));
|
||||
if (serverInfo.getName().equals(currentPlayerServer)) {
|
||||
@@ -130,9 +134,10 @@ public class ServerCommand implements SimpleCommand {
|
||||
serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY)
|
||||
.clickEvent(ClickEvent.runCommand("/server " + serverInfo.getName()))
|
||||
.hoverEvent(
|
||||
showText(Component.translatable("velocity.command.server-tooltip-offer-connect-server")
|
||||
.append(Component.newline())
|
||||
.append(playersTextComponent))
|
||||
showText(
|
||||
Component.translatable("velocity.command.server-tooltip-offer-connect-server")
|
||||
.append(Component.newline())
|
||||
.append(playersTextComponent))
|
||||
);
|
||||
}
|
||||
return serverTextComponent;
|
||||
@@ -142,7 +147,7 @@ public class ServerCommand implements SimpleCommand {
|
||||
public List<String> suggest(final SimpleCommand.Invocation invocation) {
|
||||
final String[] currentArgs = invocation.arguments();
|
||||
Stream<String> possibilities = server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName());
|
||||
.map(rs -> rs.getServerInfo().getName());
|
||||
|
||||
if (currentArgs.length == 0) {
|
||||
return possibilities.collect(Collectors.toList());
|
||||
|
@@ -27,31 +27,38 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
public final class ShutdownCommand {
|
||||
private ShutdownCommand() {}
|
||||
/**
|
||||
* Shuts down the proxy.
|
||||
*/
|
||||
public final class ShutdownCommand {
|
||||
|
||||
private ShutdownCommand() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Velocity Shutdown Command.
|
||||
*
|
||||
* @param server the proxy instance
|
||||
* @return the Shutdown Command
|
||||
*/
|
||||
public static BrigadierCommand command(final VelocityServer server) {
|
||||
return new BrigadierCommand(LiteralArgumentBuilder.<CommandSource>literal("shutdown")
|
||||
.requires(source -> source == server.getConsoleCommandSource())
|
||||
.executes(context -> {
|
||||
server.shutdown(true);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
.then(RequiredArgumentBuilder.<CommandSource, String>argument("reason", StringArgumentType.greedyString())
|
||||
.requires(source -> source == server.getConsoleCommandSource())
|
||||
.executes(context -> {
|
||||
String reason = context.getArgument("reason", String.class);
|
||||
server.shutdown(true, MiniMessage.miniMessage().deserialize(
|
||||
MiniMessage.miniMessage().serialize(
|
||||
LegacyComponentSerializer.legacy('&').deserialize(reason)
|
||||
)
|
||||
));
|
||||
server.shutdown(true);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build());
|
||||
.then(RequiredArgumentBuilder.<CommandSource, String>argument("reason",
|
||||
StringArgumentType.greedyString())
|
||||
.executes(context -> {
|
||||
String reason = context.getArgument("reason", String.class);
|
||||
server.shutdown(true, MiniMessage.miniMessage().deserialize(
|
||||
MiniMessage.miniMessage().serialize(
|
||||
LegacyComponentSerializer.legacy('&').deserialize(reason)
|
||||
)
|
||||
));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
})
|
||||
).build());
|
||||
}
|
||||
}
|
||||
|
@@ -60,6 +60,9 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Implements the {@code /velocity} command and friends.
|
||||
*/
|
||||
public class VelocityCommand implements SimpleCommand {
|
||||
|
||||
private interface SubCommand {
|
||||
@@ -125,9 +128,9 @@ public class VelocityCommand implements SimpleCommand {
|
||||
|
||||
if (currentArgs.length == 0) {
|
||||
return commands.entrySet().stream()
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
if (currentArgs.length == 1) {
|
||||
@@ -353,11 +356,11 @@ public class VelocityCommand implements SimpleCommand {
|
||||
JsonObject servers = new JsonObject();
|
||||
for (RegisteredServer iter : allServers) {
|
||||
servers.add(iter.getServerInfo().getName(),
|
||||
InformationUtils.collectServerInfo(iter));
|
||||
InformationUtils.collectServerInfo(iter));
|
||||
}
|
||||
JsonArray connectOrder = new JsonArray();
|
||||
List<String> attemptedConnectionOrder = ImmutableList.copyOf(
|
||||
server.getConfiguration().getAttemptConnectionOrder());
|
||||
server.getConfiguration().getAttemptConnectionOrder());
|
||||
for (String s : attemptedConnectionOrder) {
|
||||
connectOrder.add(s);
|
||||
}
|
||||
@@ -366,7 +369,7 @@ public class VelocityCommand implements SimpleCommand {
|
||||
proxyConfig.add("servers", servers);
|
||||
proxyConfig.add("connectOrder", connectOrder);
|
||||
proxyConfig.add("forcedHosts",
|
||||
InformationUtils.collectForcedHosts(server.getConfiguration()));
|
||||
InformationUtils.collectForcedHosts(server.getConfiguration()));
|
||||
|
||||
JsonObject dump = new JsonObject();
|
||||
dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion()));
|
||||
@@ -383,7 +386,8 @@ public class VelocityCommand implements SimpleCommand {
|
||||
|
||||
source.sendMessage(Component.text(
|
||||
"An anonymised report containing useful information about "
|
||||
+ "this proxy has been saved at " + dumpPath.toAbsolutePath(), NamedTextColor.GREEN));
|
||||
+ "this proxy has been saved at " + dumpPath.toAbsolutePath(),
|
||||
NamedTextColor.GREEN));
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to complete dump command, "
|
||||
+ "the executor was interrupted: " + e.getMessage());
|
||||
|
@@ -27,14 +27,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Creates command invocation objects from a command context builder or
|
||||
* a command context.
|
||||
* Creates command invocation objects from a command context builder or a command context.
|
||||
*
|
||||
* <p>Let {@code builder} be a command context builder, and {@code context}
|
||||
* a context returned by calling {@link CommandContextBuilder#build(String)} on
|
||||
* {@code builder}. The invocations returned by {@link #create(CommandContext)}
|
||||
* when given {@code context}, and {@link #create(CommandContextBuilder)} when
|
||||
* given {@code builder} are equal.
|
||||
* a context returned by calling {@link CommandContextBuilder#build(String)} on {@code builder}. The
|
||||
* invocations returned by {@link #create(CommandContext)} when given {@code context}, and
|
||||
* {@link #create(CommandContextBuilder)} when given {@code builder} are equal.
|
||||
*
|
||||
* @param <I> the type of the built invocation
|
||||
*/
|
||||
@@ -63,16 +61,17 @@ public interface CommandInvocationFactory<I extends CommandInvocation<?>> {
|
||||
/**
|
||||
* Creates an invocation from the given parsed nodes and arguments.
|
||||
*
|
||||
* @param source the command source
|
||||
* @param nodes the list of parsed nodes, as returned by {@link CommandContext#getNodes()} and
|
||||
* {@link CommandContextBuilder#getNodes()}
|
||||
* @param source the command source
|
||||
* @param nodes the list of parsed nodes, as returned by {@link CommandContext#getNodes()} and
|
||||
* {@link CommandContextBuilder#getNodes()}
|
||||
* @param arguments the list of parsed arguments, as returned by
|
||||
* {@link CommandContext#getArguments()} and {@link CommandContextBuilder#getArguments()}
|
||||
* {@link CommandContext#getArguments()} and
|
||||
* {@link CommandContextBuilder#getArguments()}
|
||||
* @return the built invocation context
|
||||
*/
|
||||
// This provides an abstraction over methods common to CommandContext and CommandContextBuilder.
|
||||
// Annoyingly, they mostly have the same getters but one is (correctly) not a subclass of
|
||||
// the other. Subclasses may override the methods above to obtain class-specific data.
|
||||
I create(final CommandSource source, final List<? extends ParsedCommandNode<?>> nodes,
|
||||
final Map<String, ? extends ParsedArgument<?, ?>> arguments);
|
||||
final Map<String, ? extends ParsedArgument<?, ?>> arguments);
|
||||
}
|
||||
|
@@ -22,12 +22,16 @@ import com.mojang.brigadier.context.ParsedArgument;
|
||||
import com.mojang.brigadier.context.ParsedCommandNode;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.RawCommand;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.proxy.command.VelocityCommands;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implements {@link RawCommand.Invocation}.
|
||||
*/
|
||||
public final class RawCommandInvocation extends AbstractCommandInvocation<String>
|
||||
implements RawCommand.Invocation {
|
||||
implements RawCommand.Invocation {
|
||||
|
||||
public static final Factory FACTORY = new Factory();
|
||||
|
||||
@@ -35,8 +39,8 @@ public final class RawCommandInvocation extends AbstractCommandInvocation<String
|
||||
|
||||
@Override
|
||||
public RawCommand.Invocation create(
|
||||
final CommandSource source, final List<? extends ParsedCommandNode<?>> nodes,
|
||||
final Map<String, ? extends ParsedArgument<?, ?>> arguments) {
|
||||
final CommandSource source, final List<? extends ParsedCommandNode<?>> nodes,
|
||||
final Map<String, ? extends ParsedArgument<?, ?>> arguments) {
|
||||
final String alias = VelocityCommands.readAlias(nodes);
|
||||
final String args = VelocityCommands.readArguments(arguments, String.class, "");
|
||||
return new RawCommandInvocation(source, alias, args);
|
||||
@@ -46,7 +50,7 @@ public final class RawCommandInvocation extends AbstractCommandInvocation<String
|
||||
private final String alias;
|
||||
|
||||
private RawCommandInvocation(final CommandSource source,
|
||||
final String alias, final String arguments) {
|
||||
final String alias, final String arguments) {
|
||||
super(source, arguments);
|
||||
this.alias = Preconditions.checkNotNull(alias, "alias");
|
||||
}
|
||||
@@ -82,9 +86,9 @@ public final class RawCommandInvocation extends AbstractCommandInvocation<String
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RawCommandInvocation{"
|
||||
+ "source='" + this.source() + '\''
|
||||
+ ", alias='" + this.alias + '\''
|
||||
+ ", arguments='" + this.arguments() + '\''
|
||||
+ '}';
|
||||
+ "source='" + this.source() + '\''
|
||||
+ ", alias='" + this.alias + '\''
|
||||
+ ", arguments='" + this.arguments() + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
@@ -28,8 +28,11 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implements {@link SimpleCommand.Invocation}.
|
||||
*/
|
||||
public final class SimpleCommandInvocation extends AbstractCommandInvocation<String[]>
|
||||
implements SimpleCommand.Invocation {
|
||||
implements SimpleCommand.Invocation {
|
||||
|
||||
public static final Factory FACTORY = new Factory();
|
||||
|
||||
@@ -37,11 +40,11 @@ public final class SimpleCommandInvocation extends AbstractCommandInvocation<Str
|
||||
|
||||
@Override
|
||||
public SimpleCommand.Invocation create(
|
||||
final CommandSource source, final List<? extends ParsedCommandNode<?>> nodes,
|
||||
final Map<String, ? extends ParsedArgument<?, ?>> arguments) {
|
||||
final CommandSource source, final List<? extends ParsedCommandNode<?>> nodes,
|
||||
final Map<String, ? extends ParsedArgument<?, ?>> arguments) {
|
||||
final String alias = VelocityCommands.readAlias(nodes);
|
||||
final String[] args = VelocityCommands.readArguments(
|
||||
arguments, String[].class, StringArrayArgumentType.EMPTY);
|
||||
arguments, String[].class, StringArrayArgumentType.EMPTY);
|
||||
return new SimpleCommandInvocation(source, alias, args);
|
||||
}
|
||||
}
|
||||
@@ -49,7 +52,7 @@ public final class SimpleCommandInvocation extends AbstractCommandInvocation<Str
|
||||
private final String alias;
|
||||
|
||||
SimpleCommandInvocation(final CommandSource source, final String alias,
|
||||
final String[] arguments) {
|
||||
final String[] arguments) {
|
||||
super(source, arguments);
|
||||
this.alias = Preconditions.checkNotNull(alias, "alias");
|
||||
}
|
||||
@@ -85,9 +88,9 @@ public final class SimpleCommandInvocation extends AbstractCommandInvocation<Str
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleCommandInvocation{"
|
||||
+ "source='" + this.source() + '\''
|
||||
+ ", alias='" + this.alias + '\''
|
||||
+ ", arguments='" + Arrays.toString(this.arguments()) + '\''
|
||||
+ '}';
|
||||
+ "source='" + this.source() + '\''
|
||||
+ ", alias='" + this.alias + '\''
|
||||
+ ", arguments='" + Arrays.toString(this.arguments()) + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
@@ -53,9 +53,9 @@ abstract class AbstractCommandRegistrar<T extends Command> implements CommandReg
|
||||
}
|
||||
|
||||
protected void register(final LiteralCommandNode<CommandSource> node,
|
||||
final String secondaryAlias) {
|
||||
final String secondaryAlias) {
|
||||
final LiteralCommandNode<CommandSource> copy =
|
||||
VelocityCommands.shallowCopy(node, secondaryAlias);
|
||||
VelocityCommands.shallowCopy(node, secondaryAlias);
|
||||
this.register(copy);
|
||||
}
|
||||
}
|
||||
|
@@ -23,8 +23,8 @@ import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandMeta;
|
||||
|
||||
/**
|
||||
* Creates and registers the {@link LiteralCommandNode} representations of
|
||||
* a given {@link Command} in a {@link RootCommandNode}.
|
||||
* Creates and registers the {@link LiteralCommandNode} representations of a given {@link Command}
|
||||
* in a {@link RootCommandNode}.
|
||||
*
|
||||
* @param <T> the type of the command to register
|
||||
*/
|
||||
@@ -33,16 +33,16 @@ public interface CommandRegistrar<T extends Command> {
|
||||
/**
|
||||
* Registers the given command.
|
||||
*
|
||||
* @param meta the command metadata, including the case-insensitive aliases
|
||||
* @param meta the command metadata, including the case-insensitive aliases
|
||||
* @param command the command to register
|
||||
* @throws IllegalArgumentException if the given command cannot be registered
|
||||
*/
|
||||
void register(final CommandMeta meta, final T command);
|
||||
|
||||
/**
|
||||
* Returns the superclass or superinterface of all {@link Command} classes
|
||||
* compatible with this registrar. Note that {@link #register(CommandMeta, Command)}
|
||||
* may impose additional restrictions on individual {@link Command} instances.
|
||||
* Returns the superclass or superinterface of all {@link Command} classes compatible with this
|
||||
* registrar. Note that {@link #register(CommandMeta, Command)} may impose additional restrictions
|
||||
* on individual {@link Command} instances.
|
||||
*
|
||||
* @return the superclass of all the classes compatible with this registrar
|
||||
*/
|
||||
|
@@ -42,14 +42,14 @@ import java.util.function.Predicate;
|
||||
* {@link InvocableCommand} in a root node.
|
||||
*/
|
||||
abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
|
||||
I extends CommandInvocation<A>, A> extends AbstractCommandRegistrar<T> {
|
||||
I extends CommandInvocation<A>, A> extends AbstractCommandRegistrar<T> {
|
||||
|
||||
private final CommandInvocationFactory<I> invocationFactory;
|
||||
private final ArgumentType<A> argumentsType;
|
||||
|
||||
protected InvocableCommandRegistrar(final RootCommandNode<CommandSource> root, final Lock lock,
|
||||
final CommandInvocationFactory<I> invocationFactory,
|
||||
final ArgumentType<A> argumentsType) {
|
||||
final CommandInvocationFactory<I> invocationFactory,
|
||||
final ArgumentType<A> argumentsType) {
|
||||
super(root, lock);
|
||||
this.invocationFactory = Preconditions.checkNotNull(invocationFactory, "invocationFactory");
|
||||
this.argumentsType = Preconditions.checkNotNull(argumentsType, "argumentsType");
|
||||
@@ -61,7 +61,7 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
|
||||
|
||||
final String primaryAlias = aliases.next();
|
||||
final LiteralCommandNode<CommandSource> literal =
|
||||
this.createLiteral(command, meta, primaryAlias);
|
||||
this.createLiteral(command, meta, primaryAlias);
|
||||
this.register(literal);
|
||||
|
||||
while (aliases.hasNext()) {
|
||||
@@ -71,7 +71,7 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
|
||||
}
|
||||
|
||||
private LiteralCommandNode<CommandSource> createLiteral(final T command, final CommandMeta meta,
|
||||
final String alias) {
|
||||
final String alias) {
|
||||
final Predicate<CommandContextBuilder<CommandSource>> requirement = context -> {
|
||||
final I invocation = invocationFactory.create(context);
|
||||
return command.hasPermission(invocation);
|
||||
@@ -83,35 +83,35 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
|
||||
};
|
||||
|
||||
final LiteralCommandNode<CommandSource> literal = LiteralArgumentBuilder
|
||||
.<CommandSource>literal(alias)
|
||||
.requiresWithContext((context, reader) -> {
|
||||
if (reader.canRead()) {
|
||||
// InvocableCommands do not follow a tree-like permissions checking structure.
|
||||
// Thus, a CommandSource may be able to execute a command with arguments while
|
||||
// not being able to execute the argument-less variant.
|
||||
// Only check for permissions once parsing is complete.
|
||||
return true;
|
||||
}
|
||||
return requirement.test(context);
|
||||
})
|
||||
.executes(callback)
|
||||
.build();
|
||||
.<CommandSource>literal(alias)
|
||||
.requiresWithContext((context, reader) -> {
|
||||
if (reader.canRead()) {
|
||||
// InvocableCommands do not follow a tree-like permissions checking structure.
|
||||
// Thus, a CommandSource may be able to execute a command with arguments while
|
||||
// not being able to execute the argument-less variant.
|
||||
// Only check for permissions once parsing is complete.
|
||||
return true;
|
||||
}
|
||||
return requirement.test(context);
|
||||
})
|
||||
.executes(callback)
|
||||
.build();
|
||||
|
||||
final ArgumentCommandNode<CommandSource, String> arguments = VelocityArgumentBuilder
|
||||
.<CommandSource, A>velocityArgument(VelocityCommands.ARGS_NODE_NAME, argumentsType)
|
||||
.requiresWithContext((context, reader) -> requirement.test(context))
|
||||
.executes(callback)
|
||||
.suggests((context, builder) -> {
|
||||
final I invocation = invocationFactory.create(context);
|
||||
return command.suggestAsync(invocation).thenApply(suggestions -> {
|
||||
for (String value : suggestions) {
|
||||
Preconditions.checkNotNull(value, "suggestion");
|
||||
builder.suggest(value);
|
||||
}
|
||||
return builder.build();
|
||||
});
|
||||
})
|
||||
.build();
|
||||
.<CommandSource, A>velocityArgument(VelocityCommands.ARGS_NODE_NAME, argumentsType)
|
||||
.requiresWithContext((context, reader) -> requirement.test(context))
|
||||
.executes(callback)
|
||||
.suggests((context, builder) -> {
|
||||
final I invocation = invocationFactory.create(context);
|
||||
return command.suggestAsync(invocation).thenApply(suggestions -> {
|
||||
for (String value : suggestions) {
|
||||
Preconditions.checkNotNull(value, "suggestion");
|
||||
builder.suggest(value);
|
||||
}
|
||||
return builder.build();
|
||||
});
|
||||
})
|
||||
.build();
|
||||
literal.addChild(arguments);
|
||||
|
||||
// Add hinting nodes
|
||||
|
@@ -28,7 +28,7 @@ import java.util.concurrent.locks.Lock;
|
||||
* Registers {@link RawCommand}s in a root node.
|
||||
*/
|
||||
public final class RawCommandRegistrar
|
||||
extends InvocableCommandRegistrar<RawCommand, RawCommand.Invocation, String> {
|
||||
extends InvocableCommandRegistrar<RawCommand, RawCommand.Invocation, String> {
|
||||
|
||||
public RawCommandRegistrar(final RootCommandNode<CommandSource> root, final Lock lock) {
|
||||
super(root, lock, RawCommandInvocation.FACTORY, StringArgumentType.greedyString());
|
||||
|
@@ -28,7 +28,7 @@ import java.util.concurrent.locks.Lock;
|
||||
* Registers {@link SimpleCommand}s in a root node.
|
||||
*/
|
||||
public final class SimpleCommandRegistrar
|
||||
extends InvocableCommandRegistrar<SimpleCommand, SimpleCommand.Invocation, String[]> {
|
||||
extends InvocableCommandRegistrar<SimpleCommand, SimpleCommand.Invocation, String[]> {
|
||||
|
||||
public SimpleCommandRegistrar(final RootCommandNode<CommandSource> root, final Lock lock) {
|
||||
super(root, lock, SimpleCommandInvocation.FACTORY, StringArrayArgumentType.INSTANCE);
|
||||
|
@@ -17,6 +17,9 @@
|
||||
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
/**
|
||||
* Supported passthrough modes for ping passthrough.
|
||||
*/
|
||||
public enum PingPassthroughMode {
|
||||
DISABLED,
|
||||
MODS,
|
||||
|
@@ -17,6 +17,9 @@
|
||||
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
/**
|
||||
* Supported player info forwarding methods.
|
||||
*/
|
||||
public enum PlayerInfoForwarding {
|
||||
NONE,
|
||||
LEGACY,
|
||||
|
@@ -52,29 +52,45 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Velocity's configuration.
|
||||
*/
|
||||
public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class);
|
||||
|
||||
@Expose private String bind = "0.0.0.0:25577";
|
||||
@Expose private String motd = "&3A Velocity Server";
|
||||
@Expose private int showMaxPlayers = 500;
|
||||
@Expose private boolean onlineMode = true;
|
||||
@Expose private boolean preventClientProxyConnections = false;
|
||||
@Expose private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE;
|
||||
@Expose
|
||||
private String bind = "0.0.0.0:25577";
|
||||
@Expose
|
||||
private String motd = "&3A Velocity Server";
|
||||
@Expose
|
||||
private int showMaxPlayers = 500;
|
||||
@Expose
|
||||
private boolean onlineMode = true;
|
||||
@Expose
|
||||
private boolean preventClientProxyConnections = false;
|
||||
@Expose
|
||||
private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE;
|
||||
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8);
|
||||
@Expose private boolean announceForge = false;
|
||||
@Expose private boolean onlineModeKickExistingPlayers = false;
|
||||
@Expose private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
@Expose
|
||||
private boolean announceForge = false;
|
||||
@Expose
|
||||
private boolean onlineModeKickExistingPlayers = false;
|
||||
@Expose
|
||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
private final Servers servers;
|
||||
private final ForcedHosts forcedHosts;
|
||||
@Expose private final Advanced advanced;
|
||||
@Expose private final Query query;
|
||||
@Expose
|
||||
private final Advanced advanced;
|
||||
@Expose
|
||||
private final Query query;
|
||||
private final Metrics metrics;
|
||||
@Expose private boolean enablePlayerAddressLogging = true;
|
||||
@Expose
|
||||
private boolean enablePlayerAddressLogging = true;
|
||||
private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
|
||||
private @Nullable Favicon favicon;
|
||||
@Expose private boolean forceKeyAuthentication = true; // Added in 1.19
|
||||
@Expose
|
||||
private boolean forceKeyAuthentication = true; // Added in 1.19
|
||||
|
||||
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
||||
Query query, Metrics metrics) {
|
||||
@@ -112,6 +128,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
/**
|
||||
* Attempts to validate the configuration.
|
||||
*
|
||||
* @return {@code true} if the configuration is sound, {@code false} if not
|
||||
*/
|
||||
public boolean validate() {
|
||||
@@ -408,6 +425,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
/**
|
||||
* Reads the Velocity configuration from {@code path}.
|
||||
*
|
||||
* @param path the path to read from
|
||||
* @return the deserialized Velocity configuration
|
||||
* @throws IOException if we could not read from the {@code path}.
|
||||
@@ -455,23 +473,25 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
configVersion = 1.0;
|
||||
}
|
||||
|
||||
// Whether or not this config version is older than 2.0 which uses the deprecated "forwarding-secret" parameter
|
||||
// Whether or not this config version is older than 2.0 which uses the deprecated
|
||||
// "forwarding-secret" parameter
|
||||
boolean legacyConfig = configVersion < 2.0;
|
||||
|
||||
String forwardingSecretString;
|
||||
byte[] forwardingSecret;
|
||||
|
||||
// Handle the previous (version 1.0) config
|
||||
// There is duplicate/old code here in effort to make the future commit which abandons legacy config handling
|
||||
// easier to implement. All that would be required is removing the if statement here and keeping the contents
|
||||
// of the else block (with slight tidying).
|
||||
// There is duplicate/old code here in effort to make the future commit which abandons legacy
|
||||
// config handling easier to implement. All that would be required is removing the if statement
|
||||
// here and keeping the contents of the else block (with slight tidying).
|
||||
if (legacyConfig) {
|
||||
logger.warn("You are currently using a deprecated configuration version. The \"forwarding-secret\""
|
||||
+ " parameter has been recognized as a security concern and has been removed in config version 2.0."
|
||||
+ " It's recommended you rename your current \"velocity.toml\" to something else to allow Velocity"
|
||||
+ " to generate a config file of the new version. You may then configure that file as you normally would."
|
||||
+ " The only differences are the config-version and \"forwarding-secret\" has been replaced"
|
||||
+ " by \"forwarding-secret-file\".");
|
||||
logger.warn(
|
||||
"You are currently using a deprecated configuration version. The \"forwarding-secret\""
|
||||
+ " parameter is a security hazard and was removed in config version 2.0."
|
||||
+ " You should rename your current \"velocity.toml\" to something else to allow"
|
||||
+ " Velocity to generate a config file for the new version. You may then configure "
|
||||
+ " that file as you normally would. The only differences are the config-version "
|
||||
+ "and \"forwarding-secret\" has been replaced by \"forwarding-secret-file\".");
|
||||
|
||||
// Default legacy handling
|
||||
forwardingSecretString = System.getenv()
|
||||
@@ -493,7 +513,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
if (Files.isRegularFile(secretPath)) {
|
||||
forwardingSecretString = String.join("", Files.readAllLines(secretPath));
|
||||
} else {
|
||||
throw new RuntimeException("The file " + forwardSecretFile + " is not a valid file or it is a directory.");
|
||||
throw new RuntimeException(
|
||||
"The file " + forwardSecretFile + " is not a valid file or it is a directory.");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("The forwarding-secret-file does not exist.");
|
||||
@@ -505,7 +526,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
if (configVersion == 1.0 || configVersion == 2.0) {
|
||||
config.set("force-key-authentication", config.getOrElse("force-key-authentication", true));
|
||||
config.setComment("force-key-authentication",
|
||||
"Should the proxy enforce the new public key security standard? By default, this is on.");
|
||||
"Should the proxy enforce the new public key security standard? By default, this is on.");
|
||||
config.set("config-version", configVersion == 2.0 ? "2.5" : "1.5");
|
||||
mustResave = true;
|
||||
}
|
||||
@@ -632,12 +653,11 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* TOML requires keys to match a regex of {@code [A-Za-z0-9_-]} unless it is wrapped in
|
||||
* quotes; however, the TOML parser returns the key with the quotes so we need to clean the
|
||||
* server name before we pass it onto server registration to keep proper server name behavior.
|
||||
* TOML requires keys to match a regex of {@code [A-Za-z0-9_-]} unless it is wrapped in quotes;
|
||||
* however, the TOML parser returns the key with the quotes so we need to clean the server name
|
||||
* before we pass it onto server registration to keep proper server name behavior.
|
||||
*
|
||||
* @param name the server name to clean
|
||||
*
|
||||
* @return the cleaned server name
|
||||
*/
|
||||
private String cleanServerName(String name) {
|
||||
@@ -705,19 +725,32 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
private static class Advanced {
|
||||
|
||||
@Expose private int compressionThreshold = 256;
|
||||
@Expose private int compressionLevel = -1;
|
||||
@Expose private int loginRatelimit = 3000;
|
||||
@Expose private int connectionTimeout = 5000;
|
||||
@Expose private int readTimeout = 30000;
|
||||
@Expose private boolean proxyProtocol = false;
|
||||
@Expose private boolean tcpFastOpen = false;
|
||||
@Expose private boolean bungeePluginMessageChannel = true;
|
||||
@Expose private boolean showPingRequests = false;
|
||||
@Expose private boolean failoverOnUnexpectedServerDisconnect = true;
|
||||
@Expose private boolean announceProxyCommands = true;
|
||||
@Expose private boolean logCommandExecutions = false;
|
||||
@Expose private boolean logPlayerConnections = true;
|
||||
@Expose
|
||||
private int compressionThreshold = 256;
|
||||
@Expose
|
||||
private int compressionLevel = -1;
|
||||
@Expose
|
||||
private int loginRatelimit = 3000;
|
||||
@Expose
|
||||
private int connectionTimeout = 5000;
|
||||
@Expose
|
||||
private int readTimeout = 30000;
|
||||
@Expose
|
||||
private boolean proxyProtocol = false;
|
||||
@Expose
|
||||
private boolean tcpFastOpen = false;
|
||||
@Expose
|
||||
private boolean bungeePluginMessageChannel = true;
|
||||
@Expose
|
||||
private boolean showPingRequests = false;
|
||||
@Expose
|
||||
private boolean failoverOnUnexpectedServerDisconnect = true;
|
||||
@Expose
|
||||
private boolean announceProxyCommands = true;
|
||||
@Expose
|
||||
private boolean logCommandExecutions = false;
|
||||
@Expose
|
||||
private boolean logPlayerConnections = true;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
@@ -819,10 +852,14 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
|
||||
private static class Query {
|
||||
|
||||
@Expose private boolean queryEnabled = false;
|
||||
@Expose private int queryPort = 25577;
|
||||
@Expose private String queryMap = "Velocity";
|
||||
@Expose private boolean showPlugins = false;
|
||||
@Expose
|
||||
private boolean queryEnabled = false;
|
||||
@Expose
|
||||
private int queryPort = 25577;
|
||||
@Expose
|
||||
private String queryMap = "Velocity";
|
||||
@Expose
|
||||
private boolean showPlugins = false;
|
||||
|
||||
private Query() {
|
||||
}
|
||||
@@ -870,7 +907,11 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for metrics.
|
||||
*/
|
||||
public static class Metrics {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private Metrics(CommentedConfig toml) {
|
||||
|
@@ -42,13 +42,13 @@ public interface ConnectionType {
|
||||
BackendConnectionPhase getInitialBackendPhase();
|
||||
|
||||
/**
|
||||
* Adds properties to the {@link GameProfile} if required. If any properties
|
||||
* are added, the returned {@link GameProfile} will be a copy.
|
||||
* Adds properties to the {@link GameProfile} if required. If any properties are added, the
|
||||
* returned {@link GameProfile} will be a copy.
|
||||
*
|
||||
* @param original The original {@link GameProfile}
|
||||
* @param original The original {@link GameProfile}
|
||||
* @param forwardingType The Velocity {@link PlayerInfoForwarding}
|
||||
* @return The {@link GameProfile} with the properties added in.
|
||||
*/
|
||||
GameProfile addGameProfileTokensIfRequired(GameProfile original,
|
||||
PlayerInfoForwarding forwardingType);
|
||||
PlayerInfoForwarding forwardingType);
|
||||
}
|
||||
|
@@ -29,9 +29,8 @@ import com.velocitypowered.proxy.connection.util.ConnectionTypeImpl;
|
||||
public final class ConnectionTypes {
|
||||
|
||||
/**
|
||||
* Indicates that the connection has yet to reach the
|
||||
* point where we have a definitive answer as to what
|
||||
* type of connection we have.
|
||||
* Indicates that the connection has yet to reach the point where we have a definitive answer as
|
||||
* to what type of connection we have.
|
||||
*/
|
||||
public static final ConnectionType UNDETERMINED =
|
||||
new ConnectionTypeImpl(ClientConnectionPhases.VANILLA, BackendConnectionPhases.UNKNOWN);
|
||||
@@ -46,8 +45,7 @@ public final class ConnectionTypes {
|
||||
LegacyForgeHandshakeClientPhase.NOT_STARTED, BackendConnectionPhases.UNKNOWN);
|
||||
|
||||
/**
|
||||
* Indicates that the connection is a 1.8-1.12 Forge
|
||||
* connection.
|
||||
* Indicates that the connection is a 1.8-1.12 Forge connection.
|
||||
*/
|
||||
public static final ConnectionType LEGACY_FORGE = new LegacyForgeConnectionType();
|
||||
|
||||
|
@@ -87,8 +87,9 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Initializes a new {@link MinecraftConnection} instance.
|
||||
*
|
||||
* @param channel the channel on the connection
|
||||
* @param server the Velocity instance
|
||||
* @param server the Velocity instance
|
||||
*/
|
||||
public MinecraftConnection(Channel channel, VelocityServer server) {
|
||||
this.channel = channel;
|
||||
@@ -211,6 +212,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Writes and immediately flushes a message to the connection.
|
||||
*
|
||||
* @param msg the message to write
|
||||
*/
|
||||
public void write(Object msg) {
|
||||
@@ -223,6 +225,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Writes, but does not flush, a message to the connection.
|
||||
*
|
||||
* @param msg the message to write
|
||||
*/
|
||||
public void delayedWrite(Object msg) {
|
||||
@@ -244,6 +247,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Closes the connection after writing the {@code msg}.
|
||||
*
|
||||
* @param msg the message to write
|
||||
*/
|
||||
public void closeWith(Object msg) {
|
||||
@@ -273,6 +277,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Immediately closes the connection.
|
||||
*
|
||||
* @param markKnown whether the disconnection is known
|
||||
*/
|
||||
public void close(boolean markKnown) {
|
||||
@@ -319,6 +324,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -337,6 +343,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Changes the state of the Minecraft connection.
|
||||
*
|
||||
* @param state the new state
|
||||
*/
|
||||
public void setState(StateRegistry state) {
|
||||
@@ -353,6 +360,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Sets the new protocol version for the connection.
|
||||
*
|
||||
* @param protocolVersion the protocol version to use
|
||||
*/
|
||||
public void setProtocolVersion(ProtocolVersion protocolVersion) {
|
||||
@@ -380,6 +388,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Sets the session handler for this connection.
|
||||
*
|
||||
* @param sessionHandler the handler to use
|
||||
*/
|
||||
public void setSessionHandler(MinecraftSessionHandler sessionHandler) {
|
||||
@@ -399,6 +408,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
/**
|
||||
* Sets the compression threshold on the connection. You are responsible for sending
|
||||
* {@link com.velocitypowered.proxy.protocol.packet.SetCompression} beforehand.
|
||||
*
|
||||
* @param threshold the compression threshold to use
|
||||
*/
|
||||
public void setCompressionThreshold(int threshold) {
|
||||
@@ -440,6 +450,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Enables encryption on the connection.
|
||||
*
|
||||
* @param secret the secret key negotiated between the client and the server
|
||||
* @throws GeneralSecurityException if encryption can't be enabled
|
||||
*/
|
||||
@@ -471,6 +482,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Gets the detected {@link ConnectionType}.
|
||||
*
|
||||
* @return The {@link ConnectionType}
|
||||
*/
|
||||
public ConnectionType getType() {
|
||||
@@ -479,6 +491,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
/**
|
||||
* Sets the detected {@link ConnectionType}.
|
||||
*
|
||||
* @param connectionType The {@link ConnectionType}
|
||||
*/
|
||||
public void setType(ConnectionType connectionType) {
|
||||
|
@@ -17,6 +17,9 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
/**
|
||||
* Marker interface for something that can be associated with a {@link MinecraftConnection}.
|
||||
*/
|
||||
public interface MinecraftConnectionAssociation {
|
||||
|
||||
}
|
||||
|
@@ -63,6 +63,9 @@ import com.velocitypowered.proxy.protocol.packet.title.TitleTextPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.title.TitleTimesPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Interface for dispatching received Minecraft packets.
|
||||
*/
|
||||
public interface MinecraftSessionHandler {
|
||||
|
||||
default boolean beforeHandle() {
|
||||
|
@@ -17,6 +17,9 @@
|
||||
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
/**
|
||||
* Various useful constants.
|
||||
*/
|
||||
public class VelocityConstants {
|
||||
|
||||
private VelocityConstants() {
|
||||
|
@@ -30,20 +30,22 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
public interface BackendConnectionPhase {
|
||||
|
||||
/**
|
||||
* Handle a plugin message in the context of
|
||||
* this phase.
|
||||
* Handle a plugin message in the context of this phase.
|
||||
*
|
||||
* @param server the server connection
|
||||
* @param player the player
|
||||
* @param message The message to handle
|
||||
* @return true if handled, false otherwise.
|
||||
*/
|
||||
default boolean handle(VelocityServerConnection server,
|
||||
ConnectedPlayer player,
|
||||
PluginMessage message) {
|
||||
ConnectedPlayer player,
|
||||
PluginMessage message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the connection is considered complete.
|
||||
*
|
||||
* @return true if so
|
||||
*/
|
||||
default boolean consideredComplete() {
|
||||
@@ -51,13 +53,13 @@ public interface BackendConnectionPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the provided server connection is about to be terminated
|
||||
* because the provided player is connecting to a new server.
|
||||
* Fired when the provided server connection is about to be terminated because the provided player
|
||||
* is connecting to a new server.
|
||||
*
|
||||
* @param serverConnection The server the player is disconnecting from
|
||||
* @param player The player
|
||||
* @param player The player
|
||||
*/
|
||||
default void onDepartForNewServer(VelocityServerConnection serverConnection,
|
||||
ConnectedPlayer player) {
|
||||
ConnectedPlayer player) {
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,8 @@ public final class BackendConnectionPhases {
|
||||
/**
|
||||
* The backend connection is vanilla.
|
||||
*/
|
||||
public static final BackendConnectionPhase VANILLA = new BackendConnectionPhase() {};
|
||||
public static final BackendConnectionPhase VANILLA = new BackendConnectionPhase() {
|
||||
};
|
||||
|
||||
/**
|
||||
* The backend connection is unknown at this time.
|
||||
@@ -45,8 +46,8 @@ public final class BackendConnectionPhases {
|
||||
|
||||
@Override
|
||||
public boolean handle(VelocityServerConnection serverConn,
|
||||
ConnectedPlayer player,
|
||||
PluginMessage message) {
|
||||
ConnectedPlayer player,
|
||||
PluginMessage message) {
|
||||
// The connection may be legacy forge. If so, the Forge handler will deal with this
|
||||
// for us. Otherwise, we have nothing to do.
|
||||
return LegacyForgeHandshakeBackendPhase.NOT_STARTED.handle(serverConn, player, message);
|
||||
@@ -54,9 +55,9 @@ 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.
|
||||
* 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
|
||||
|
@@ -61,7 +61,11 @@ import java.util.regex.Pattern;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Handles a connected player.
|
||||
*/
|
||||
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
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 boolean BACKPRESSURE_LOG = Boolean
|
||||
@@ -286,7 +290,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
public boolean handle(ServerData packet) {
|
||||
server.getServerListPingHandler().getInitialPing(this.serverConn.getPlayer())
|
||||
.thenComposeAsync(
|
||||
ping -> server.getEventManager().fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)),
|
||||
ping -> server.getEventManager()
|
||||
.fire(new ProxyPingEvent(this.serverConn.getPlayer(), ping)),
|
||||
playerConnection.eventLoop()
|
||||
)
|
||||
.thenAcceptAsync(pingEvent ->
|
||||
|
@@ -44,9 +44,16 @@ import net.kyori.adventure.text.serializer.ComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
@SuppressFBWarnings(value = "OS_OPEN_STREAM", justification = "Most methods in this class open "
|
||||
+ "instances of ByteBufDataOutput backed by heap-allocated ByteBufs. Closing them does "
|
||||
+ "nothing.")
|
||||
/**
|
||||
* Handles messages coming from servers trying to communicate with the BungeeCord plugin
|
||||
* messaging channel interface.
|
||||
*/
|
||||
@SuppressFBWarnings(
|
||||
value = "OS_OPEN_STREAM",
|
||||
justification = "Most methods in this class open "
|
||||
+ "instances of ByteBufDataOutput backed by heap-allocated ByteBufs. Closing them does "
|
||||
+ "nothing."
|
||||
)
|
||||
public class BungeeCordMessageResponder {
|
||||
|
||||
private static final MinecraftChannelIdentifier MODERN_CHANNEL = MinecraftChannelIdentifier
|
||||
|
@@ -52,7 +52,11 @@ import net.kyori.adventure.text.Component;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Handles a player trying to log into the proxy.
|
||||
*/
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||
|
||||
private static final Component MODERN_IP_FORWARDING_FAILURE = Component
|
||||
@@ -88,7 +92,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
requestedForwardingVersion = packet.content().readByte();
|
||||
}
|
||||
ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(),
|
||||
serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer(), requestedForwardingVersion);
|
||||
serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer(),
|
||||
requestedForwardingVersion);
|
||||
|
||||
LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData);
|
||||
mc.write(response);
|
||||
@@ -104,7 +109,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
final MinecraftChannelIdentifier identifier = MinecraftChannelIdentifier
|
||||
.from(packet.getChannel());
|
||||
this.server.getEventManager().fire(new ServerLoginPluginMessageEvent(serverConn, identifier,
|
||||
contents, packet.getId()))
|
||||
contents, packet.getId()))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (event.getResult().isAllowed()) {
|
||||
mc.write(new LoginPluginResponse(packet.getId(), true, Unpooled
|
||||
@@ -178,7 +183,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION);
|
||||
if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) {
|
||||
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) {
|
||||
return requested >= VelocityConstants.MODERN_LAZY_SESSION ? VelocityConstants.MODERN_LAZY_SESSION
|
||||
return requested >= VelocityConstants.MODERN_LAZY_SESSION
|
||||
? VelocityConstants.MODERN_LAZY_SESSION
|
||||
: VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||
}
|
||||
if (player.getIdentifiedKey() != null) {
|
||||
@@ -189,7 +195,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
// Since V2 is not backwards compatible we have to throw the key if v2 and requested is v1
|
||||
case LINKED_V2:
|
||||
return requested >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2
|
||||
? VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2 : VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||
? VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2
|
||||
: VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||
default:
|
||||
return VelocityConstants.MODERN_FORWARDING_DEFAULT;
|
||||
}
|
||||
@@ -201,7 +208,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
|
||||
ConnectedPlayer player, int requestedVersion) {
|
||||
ConnectedPlayer player, int requestedVersion) {
|
||||
ByteBuf forwarded = Unpooled.buffer(2048);
|
||||
try {
|
||||
int actualVersion = findForwardingVersion(requestedVersion, player);
|
||||
|
@@ -56,8 +56,9 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
/**
|
||||
* Creates the new transition handler.
|
||||
* @param server the Velocity server instance
|
||||
* @param serverConn the server connection
|
||||
*
|
||||
* @param server the Velocity server instance
|
||||
* @param serverConn the server connection
|
||||
* @param resultFuture the result future
|
||||
*/
|
||||
TransitionSessionHandler(VelocityServer server,
|
||||
|
@@ -56,6 +56,9 @@ import java.util.function.UnaryOperator;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Handles a connection from the proxy to some backend server.
|
||||
*/
|
||||
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
|
||||
|
||||
private final VelocityRegisteredServer registeredServer;
|
||||
@@ -71,10 +74,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
|
||||
/**
|
||||
* Initializes a new server connection.
|
||||
*
|
||||
* @param registeredServer the server to connect to
|
||||
* @param previousServer the server the player is coming from
|
||||
* @param proxyPlayer the player connecting to the server
|
||||
* @param server the Velocity proxy instance
|
||||
* @param previousServer the server the player is coming from
|
||||
* @param proxyPlayer the player connecting to the server
|
||||
* @param server the Velocity proxy instance
|
||||
*/
|
||||
public VelocityServerConnection(VelocityRegisteredServer registeredServer,
|
||||
@Nullable VelocityRegisteredServer previousServer,
|
||||
@@ -87,6 +91,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
|
||||
/**
|
||||
* Connects to the server.
|
||||
*
|
||||
* @return a {@link com.velocitypowered.api.proxy.ConnectionRequestBuilder.Result} representing
|
||||
* whether or not the connect succeeded
|
||||
*/
|
||||
@@ -205,6 +210,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -259,8 +265,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
|
||||
/**
|
||||
* Sends a plugin message to the server through this connection.
|
||||
*
|
||||
* @param identifier the channel ID to use
|
||||
* @param data the data
|
||||
* @param data the data
|
||||
* @return whether or not the message was sent
|
||||
*/
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) {
|
||||
@@ -310,9 +317,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* @return The {@link BackendConnectionPhase}
|
||||
*/
|
||||
@@ -330,8 +336,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the {@link com.velocitypowered.proxy.protocol.packet.JoinGame}
|
||||
* packet has been sent by this server.
|
||||
* Gets whether the {@link com.velocitypowered.proxy.protocol.packet.JoinGame} packet has been
|
||||
* sent by this server.
|
||||
*
|
||||
* @return Whether the join has been completed.
|
||||
*/
|
||||
|
@@ -51,6 +51,9 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* A session handler that is activated to complete the login phase.
|
||||
*/
|
||||
public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(AuthSessionHandler.class);
|
||||
@@ -63,7 +66,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
private final boolean onlineMode;
|
||||
|
||||
AuthSessionHandler(VelocityServer server, LoginInboundConnection inbound,
|
||||
GameProfile profile, boolean onlineMode) {
|
||||
GameProfile profile, boolean onlineMode) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
this.profile = Preconditions.checkNotNull(profile, "profile");
|
||||
@@ -88,7 +91,8 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode, inbound.getIdentifiedKey());
|
||||
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode,
|
||||
inbound.getIdentifiedKey());
|
||||
this.connectedPlayer = player;
|
||||
if (!server.canRegisterConnection(player)) {
|
||||
player.disconnect0(Component.translatable("velocity.error.already-connected-proxy",
|
||||
@@ -143,7 +147,8 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
// Failsafe
|
||||
if (!unlinkedKey.internalAddHolder(player.getUniqueId())) {
|
||||
if (onlineMode) {
|
||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||
inbound.disconnect(
|
||||
Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||
return;
|
||||
} else {
|
||||
logger.warn("Key for player " + player.getUsername() + " could not be verified!");
|
||||
@@ -155,7 +160,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
|
||||
} else {
|
||||
if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) {
|
||||
logger.warn("UUID for Player " + player.getUsername() + " mismatches! "
|
||||
+ "Chat/Commands signatures will not work correctly for this player!");
|
||||
+ "Chat/Commands signatures will not work correctly for this player!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,12 +30,11 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
public interface ClientConnectionPhase {
|
||||
|
||||
/**
|
||||
* Handle a plugin message in the context of
|
||||
* this phase.
|
||||
* Handle a plugin message in the context of this phase.
|
||||
*
|
||||
* @param player The player
|
||||
* @param player The player
|
||||
* @param message The message to handle
|
||||
* @param server The backend connection to use
|
||||
* @param server The backend connection to use
|
||||
* @return true if handled, false otherwise.
|
||||
*/
|
||||
default boolean handle(ConnectedPlayer player,
|
||||
@@ -45,8 +44,7 @@ public interface ClientConnectionPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct Velocity to reset the connection phase
|
||||
* back to its default for the connection type.
|
||||
* Instruct Velocity to reset the connection phase back to its default for the connection type.
|
||||
*
|
||||
* @param player The player
|
||||
*/
|
||||
@@ -54,8 +52,7 @@ public interface ClientConnectionPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform actions just as the player joins the
|
||||
* server.
|
||||
* Perform actions just as the player joins the server.
|
||||
*
|
||||
* @param player The player
|
||||
*/
|
||||
@@ -64,6 +61,7 @@ public interface ClientConnectionPhase {
|
||||
|
||||
/**
|
||||
* Indicates whether the connection is considered complete.
|
||||
*
|
||||
* @return true if so
|
||||
*/
|
||||
default boolean consideredComplete() {
|
||||
|
@@ -28,7 +28,8 @@ public final class ClientConnectionPhases {
|
||||
/**
|
||||
* The client is connecting with a vanilla client (as far as we can tell).
|
||||
*/
|
||||
public static final ClientConnectionPhase VANILLA = new ClientConnectionPhase() {};
|
||||
public static final ClientConnectionPhase VANILLA = new ClientConnectionPhase() {
|
||||
};
|
||||
|
||||
private ClientConnectionPhases() {
|
||||
throw new AssertionError();
|
||||
|
@@ -141,7 +141,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean validateChat(String message) {
|
||||
if (CharacterUtil.containsIllegalCharacters(message)) {
|
||||
player.disconnect(Component.translatable("velocity.error.illegal-chat-characters", NamedTextColor.RED));
|
||||
player.disconnect(
|
||||
Component.translatable("velocity.error.illegal-chat-characters", NamedTextColor.RED));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -149,7 +150,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
Collection<String> channels = server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
|
||||
Collection<String> channels = server.getChannelRegistrar()
|
||||
.getChannelsForProtocol(player.getProtocolVersion());
|
||||
if (!channels.isEmpty()) {
|
||||
PluginMessage register = constructChannelsPacket(player.getProtocolVersion(), channels);
|
||||
player.getConnection().write(register);
|
||||
@@ -280,7 +282,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (serverConn != null && backendConn != null) {
|
||||
if (backendConn.getState() != StateRegistry.PLAY) {
|
||||
logger.warn(
|
||||
"A plugin message was received while the backend server was not " + "ready. Channel: {}. Packet discarded.",
|
||||
"A plugin message was received while the backend server was not "
|
||||
+ "ready. Channel: {}. Packet discarded.",
|
||||
packet.getChannel());
|
||||
} else if (PluginMessageUtil.isRegister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
@@ -294,7 +297,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
}
|
||||
server.getEventManager()
|
||||
.fireAndForget(new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
|
||||
.fireAndForget(
|
||||
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
|
||||
backendConn.write(packet.retain());
|
||||
} else if (PluginMessageUtil.isUnregister(packet)) {
|
||||
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
|
||||
@@ -304,7 +308,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
server.getEventManager().fireAndForget(new PlayerClientBrandEvent(player, brand));
|
||||
player.setClientBrand(brand);
|
||||
backendConn.write(
|
||||
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
|
||||
PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
|
||||
player.getProtocolVersion()));
|
||||
} else if (BungeeCordMessageResponder.isBungeeCordMessage(packet)) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -321,7 +326,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
// We don't have any plugins listening on this channel, process the packet now.
|
||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
|
||||
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
|
||||
@@ -339,8 +345,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
|
||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
PluginMessage message = new PluginMessage(packet.getChannel(), Unpooled.wrappedBuffer(copy));
|
||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
|
||||
PluginMessage message = new PluginMessage(packet.getChannel(),
|
||||
Unpooled.wrappedBuffer(copy));
|
||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
||||
.consideredComplete()) {
|
||||
// We're still processing the connection (see above), enqueue the packet for now.
|
||||
loginPluginMessages.add(message.retain());
|
||||
} else {
|
||||
@@ -402,7 +410,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
player.disconnect(Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
|
||||
player.disconnect(
|
||||
Component.translatable("velocity.error.player-connection-error", NamedTextColor.RED));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -483,7 +492,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
// Clear any title from the previous server.
|
||||
if (player.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||
player.getConnection().delayedWrite(
|
||||
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, player.getProtocolVersion()));
|
||||
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET,
|
||||
player.getProtocolVersion()));
|
||||
}
|
||||
|
||||
// Flush everything
|
||||
@@ -553,33 +563,36 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(suggestions -> {
|
||||
if (suggestions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
server.getCommandManager().offerBrigadierSuggestions(player, command)
|
||||
.thenAcceptAsync(suggestions -> {
|
||||
if (suggestions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Offer> offers = new ArrayList<>();
|
||||
for (Suggestion suggestion : suggestions.getList()) {
|
||||
String offer = suggestion.getText();
|
||||
Component tooltip = null;
|
||||
if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
||||
}
|
||||
offers.add(new Offer(offer, tooltip));
|
||||
}
|
||||
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
|
||||
if (startPos > 0) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.setTransactionId(packet.getTransactionId());
|
||||
resp.setStart(startPos);
|
||||
resp.setLength(packet.getCommand().length() - startPos);
|
||||
resp.getOffers().addAll(offers);
|
||||
player.getConnection().write(resp);
|
||||
}
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling command tab completion for player {} executing {}", player, command, ex);
|
||||
return null;
|
||||
});
|
||||
List<Offer> offers = new ArrayList<>();
|
||||
for (Suggestion suggestion : suggestions.getList()) {
|
||||
String offer = suggestion.getText();
|
||||
Component tooltip = null;
|
||||
if (suggestion.getTooltip() != null
|
||||
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
||||
}
|
||||
offers.add(new Offer(offer, tooltip));
|
||||
}
|
||||
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
|
||||
if (startPos > 0) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.setTransactionId(packet.getTransactionId());
|
||||
resp.setStart(startPos);
|
||||
resp.setLength(packet.getCommand().length() - startPos);
|
||||
resp.getOffers().addAll(offers);
|
||||
player.getConnection().write(resp);
|
||||
}
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling command tab completion for player {} executing {}",
|
||||
player, command, ex);
|
||||
return null;
|
||||
});
|
||||
return true; // Sorry, handler; we're just gonna have to lie to you here.
|
||||
}
|
||||
|
||||
@@ -613,32 +626,37 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private void finishCommandTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
|
||||
String command = request.getCommand().substring(1);
|
||||
server.getCommandManager().offerBrigadierSuggestions(player, command).thenAcceptAsync(offers -> {
|
||||
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
|
||||
try {
|
||||
for (Suggestion suggestion : offers.getList()) {
|
||||
String offer = suggestion.getText();
|
||||
offer = legacy && !offer.startsWith("/") ? "/" + offer : offer;
|
||||
if (legacy && offer.startsWith(command)) {
|
||||
offer = offer.substring(command.length());
|
||||
server.getCommandManager().offerBrigadierSuggestions(player, command)
|
||||
.thenAcceptAsync(offers -> {
|
||||
boolean legacy = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
|
||||
try {
|
||||
for (Suggestion suggestion : offers.getList()) {
|
||||
String offer = suggestion.getText();
|
||||
offer = legacy && !offer.startsWith("/") ? "/" + offer : offer;
|
||||
if (legacy && offer.startsWith(command)) {
|
||||
offer = offer.substring(command.length());
|
||||
}
|
||||
Component tooltip = null;
|
||||
if (suggestion.getTooltip() != null
|
||||
&& suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
||||
}
|
||||
response.getOffers().add(new Offer(offer, tooltip));
|
||||
}
|
||||
response.getOffers().sort(null);
|
||||
player.getConnection().write(response);
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for {} for command '{}'",
|
||||
player.getUsername(), command,
|
||||
e);
|
||||
}
|
||||
Component tooltip = null;
|
||||
if (suggestion.getTooltip() != null && suggestion.getTooltip() instanceof VelocityBrigadierMessage) {
|
||||
tooltip = ((VelocityBrigadierMessage) suggestion.getTooltip()).asComponent();
|
||||
}
|
||||
response.getOffers().add(new Offer(offer, tooltip));
|
||||
}
|
||||
response.getOffers().sort(null);
|
||||
player.getConnection().write(response);
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(), command,
|
||||
e);
|
||||
}
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while finishing command tab completion, with request {} and response {}", request,
|
||||
response, ex);
|
||||
return null;
|
||||
});
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error(
|
||||
"Exception while finishing command tab completion, with request {} and response {}",
|
||||
request,
|
||||
response, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void finishRegularTabComplete(TabCompleteRequest request, TabCompleteResponse response) {
|
||||
@@ -646,17 +664,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
for (Offer offer : response.getOffers()) {
|
||||
offers.add(offer.getText());
|
||||
}
|
||||
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers)).thenAcceptAsync(e -> {
|
||||
response.getOffers().clear();
|
||||
for (String s : e.getSuggestions()) {
|
||||
response.getOffers().add(new Offer(s));
|
||||
}
|
||||
player.getConnection().write(response);
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while finishing regular tab completion, with request {} and response{}", request,
|
||||
response, ex);
|
||||
return null;
|
||||
});
|
||||
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers))
|
||||
.thenAcceptAsync(e -> {
|
||||
response.getOffers().clear();
|
||||
for (String s : e.getSuggestions()) {
|
||||
response.getOffers().add(new Offer(s));
|
||||
}
|
||||
player.getConnection().write(response);
|
||||
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
|
||||
logger.error(
|
||||
"Exception while finishing regular tab completion, with request {} and response{}",
|
||||
request,
|
||||
response, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,6 +24,9 @@ import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Wraps the settings received in the Client Settings packet.
|
||||
*/
|
||||
public class ClientSettingsWrapper implements PlayerSettings {
|
||||
|
||||
static final PlayerSettings DEFAULT = new ClientSettingsWrapper(
|
||||
|
@@ -117,16 +117,20 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a player that is connected to the proxy.
|
||||
*/
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
|
||||
VelocityInboundConnection {
|
||||
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = PlainTextComponentSerializer.builder()
|
||||
.flattener(ComponentFlattener.basic().toBuilder()
|
||||
.mapper(KeybindComponent.class, c -> "")
|
||||
.mapper(TranslatableComponent.class, TranslatableComponent::key)
|
||||
.build())
|
||||
.build();
|
||||
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
|
||||
PlainTextComponentSerializer.builder()
|
||||
.flattener(ComponentFlattener.basic().toBuilder()
|
||||
.mapper(KeybindComponent.class, c -> "")
|
||||
.mapper(TranslatableComponent.class, TranslatableComponent::key)
|
||||
.build())
|
||||
.build();
|
||||
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
|
||||
@@ -173,7 +177,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private final ChatBuilderFactory chatBuilderFactory;
|
||||
|
||||
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
|
||||
@Nullable InetSocketAddress virtualHost, boolean onlineMode, @Nullable IdentifiedKey playerKey) {
|
||||
@Nullable InetSocketAddress virtualHost, boolean onlineMode,
|
||||
@Nullable IdentifiedKey playerKey) {
|
||||
this.server = server;
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
@@ -344,7 +349,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
|
||||
@NonNull MessageType type) {
|
||||
@NonNull MessageType type) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkNotNull(type, "type");
|
||||
|
||||
@@ -516,7 +521,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
private ConnectionRequestBuilder createConnectionRequest(RegisteredServer server,
|
||||
@Nullable VelocityServerConnection previousConnection) {
|
||||
@Nullable VelocityServerConnection previousConnection) {
|
||||
return new ConnectionRequestBuilderImpl(server, previousConnection);
|
||||
}
|
||||
|
||||
@@ -585,7 +590,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
* @param safe whether or not we can safely reconnect to a new server
|
||||
*/
|
||||
public void handleConnectionException(RegisteredServer server, Throwable throwable,
|
||||
boolean safe) {
|
||||
boolean safe) {
|
||||
if (!isActive()) {
|
||||
// If the connection is no longer active, it makes no sense to try and recover it.
|
||||
return;
|
||||
@@ -624,7 +629,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
* @param safe whether or not we can safely reconnect to a new server
|
||||
*/
|
||||
public void handleConnectionException(RegisteredServer server, Disconnect disconnect,
|
||||
boolean safe) {
|
||||
boolean safe) {
|
||||
if (!isActive()) {
|
||||
// If the connection is no longer active, it makes no sense to try and recover it.
|
||||
return;
|
||||
@@ -650,7 +655,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
private void handleConnectionException(RegisteredServer rs,
|
||||
@Nullable Component kickReason, Component friendlyReason, boolean safe) {
|
||||
@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;
|
||||
@@ -683,7 +688,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason,
|
||||
boolean kickedFromCurrent) {
|
||||
boolean kickedFromCurrent) {
|
||||
server.getEventManager().fire(originalEvent)
|
||||
.thenAcceptAsync(event -> {
|
||||
// There can't be any connection in flight now.
|
||||
@@ -890,8 +895,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
boolean isPlayerAddressLoggingEnabled = server.getConfiguration().isPlayerAddressLoggingEnabled();
|
||||
String playerIp = isPlayerAddressLoggingEnabled ? getRemoteAddress().toString() : "<ip address withheld>";
|
||||
boolean isPlayerAddressLoggingEnabled = server.getConfiguration()
|
||||
.isPlayerAddressLoggingEnabled();
|
||||
String playerIp =
|
||||
isPlayerAddressLoggingEnabled ? getRemoteAddress().toString() : "<ip address withheld>";
|
||||
return "[connected player] " + profile.getName() + " (" + playerIp + ")";
|
||||
}
|
||||
|
||||
@@ -956,8 +963,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a resource-pack for sending to the player and sends it
|
||||
* immediately if the queue is empty.
|
||||
* Queues a resource-pack for sending to the player and sends it immediately if the queue is
|
||||
* empty.
|
||||
*/
|
||||
public void queueResourcePack(ResourcePackInfo info) {
|
||||
outstandingResourcePacks.add(info);
|
||||
@@ -1061,9 +1068,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
public void sendKeepAlive() {
|
||||
if (connection.getState() == StateRegistry.PLAY) {
|
||||
@@ -1074,9 +1080,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* @return The {@link ClientConnectionPhase}
|
||||
*/
|
||||
@@ -1108,6 +1113,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
}
|
||||
|
||||
private class IdentityImpl implements Identity {
|
||||
|
||||
@Override
|
||||
public @NonNull UUID uuid() {
|
||||
return ConnectedPlayer.this.getUniqueId();
|
||||
@@ -1120,7 +1126,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private final @Nullable VelocityRegisteredServer previousServer;
|
||||
|
||||
ConnectionRequestBuilderImpl(RegisteredServer toConnect,
|
||||
@Nullable VelocityServerConnection previousConnection) {
|
||||
@Nullable VelocityServerConnection previousConnection) {
|
||||
this.toConnect = Preconditions.checkNotNull(toConnect, "info");
|
||||
this.previousServer = previousConnection == null ? null : previousConnection.getServer();
|
||||
}
|
||||
|
@@ -46,6 +46,11 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* The initial handler used when a connection is established to the proxy. This will either
|
||||
* transition to {@link StatusSessionHandler} or {@link InitialLoginSessionHandler} as soon
|
||||
* as the handshake packet is received.
|
||||
*/
|
||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(HandshakeSessionHandler.class);
|
||||
|
@@ -23,6 +23,10 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
|
||||
/**
|
||||
* Handles the play state between exiting the login phase and establishing the first connection
|
||||
* to a backend server.
|
||||
*/
|
||||
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
|
@@ -34,6 +34,9 @@ import net.kyori.adventure.translation.GlobalTranslator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Implements {@link InboundConnection} for a newly-established connection.
|
||||
*/
|
||||
public final class InitialInboundConnection implements VelocityInboundConnection,
|
||||
MinecraftConnectionAssociation {
|
||||
|
||||
@@ -82,6 +85,7 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
||||
|
||||
/**
|
||||
* Disconnects the connection from the server.
|
||||
*
|
||||
* @param reason the reason for disconnecting
|
||||
*/
|
||||
public void disconnect(Component reason) {
|
||||
@@ -96,6 +100,7 @@ public final class InitialInboundConnection implements VelocityInboundConnection
|
||||
|
||||
/**
|
||||
* Disconnects the connection from the server silently.
|
||||
*
|
||||
* @param reason the reason for disconnecting
|
||||
*/
|
||||
public void disconnectQuietly(Component reason) {
|
||||
|
@@ -56,11 +56,15 @@ import org.asynchttpclient.ListenableFuture;
|
||||
import org.asynchttpclient.Response;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Handles authenticating the player to Mojang's servers.
|
||||
*/
|
||||
public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(InitialLoginSessionHandler.class);
|
||||
private static final String MOJANG_HASJOINED_URL =
|
||||
System.getProperty("mojang.sessionserver", "https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||
System.getProperty("mojang.sessionserver",
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined")
|
||||
.concat("?username=%s&serverId=%s");
|
||||
|
||||
private final VelocityServer server;
|
||||
@@ -72,12 +76,13 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
private boolean forceKeyAuthentication;
|
||||
|
||||
InitialLoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
||||
LoginInboundConnection inbound) {
|
||||
LoginInboundConnection inbound) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection");
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
this.forceKeyAuthentication = System.getProperties().containsKey("auth.forceSecureProfiles")
|
||||
? Boolean.getBoolean("auth.forceSecureProfiles") : server.getConfiguration().isForceKeyAuthentication();
|
||||
? Boolean.getBoolean("auth.forceSecureProfiles")
|
||||
: server.getConfiguration().isForceKeyAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,13 +92,14 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
IdentifiedKey playerKey = packet.getPlayerKey();
|
||||
if (playerKey != null) {
|
||||
if (playerKey.hasExpired()) {
|
||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key_signature"));
|
||||
inbound.disconnect(
|
||||
Component.translatable("multiplayer.disconnect.invalid_public_key_signature"));
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isKeyValid;
|
||||
if (playerKey.getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
||||
&& playerKey instanceof IdentifiedKeyImpl) {
|
||||
&& playerKey instanceof IdentifiedKeyImpl) {
|
||||
IdentifiedKeyImpl keyImpl = (IdentifiedKeyImpl) playerKey;
|
||||
isKeyValid = keyImpl.internalAddHolder(packet.getHolderUuid());
|
||||
} else {
|
||||
@@ -105,8 +111,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
} else if (mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0
|
||||
&& forceKeyAuthentication
|
||||
&& mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||
&& forceKeyAuthentication
|
||||
&& mcConnection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) < 0) {
|
||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.missing_public_key"));
|
||||
return true;
|
||||
}
|
||||
@@ -145,7 +151,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
this.currentState = LoginState.ENCRYPTION_REQUEST_SENT;
|
||||
} else {
|
||||
mcConnection.setSessionHandler(new AuthSessionHandler(
|
||||
server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false
|
||||
server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false
|
||||
));
|
||||
}
|
||||
});
|
||||
@@ -182,7 +188,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
KeyPair serverKeyPair = server.getServerKeyPair();
|
||||
if (inbound.getIdentifiedKey() != null) {
|
||||
IdentifiedKey playerKey = inbound.getIdentifiedKey();
|
||||
if (!playerKey.verifyDataSignature(packet.getVerifyToken(), verify, Longs.toByteArray(packet.getSalt()))) {
|
||||
if (!playerKey.verifyDataSignature(packet.getVerifyToken(), verify,
|
||||
Longs.toByteArray(packet.getSalt()))) {
|
||||
throw new IllegalStateException("Invalid client public signature.");
|
||||
}
|
||||
} else {
|
||||
@@ -226,14 +233,16 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
try {
|
||||
Response profileResponse = hasJoinedResponse.get();
|
||||
if (profileResponse.getStatusCode() == 200) {
|
||||
final GameProfile profile = GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class);
|
||||
final GameProfile profile = GENERAL_GSON.fromJson(profileResponse.getResponseBody(),
|
||||
GameProfile.class);
|
||||
// Not so fast, now we verify the public key for 1.19.1+
|
||||
if (inbound.getIdentifiedKey() != null
|
||||
&& inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
||||
&& inbound.getIdentifiedKey() instanceof IdentifiedKeyImpl) {
|
||||
&& inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
|
||||
&& inbound.getIdentifiedKey() instanceof IdentifiedKeyImpl) {
|
||||
IdentifiedKeyImpl key = (IdentifiedKeyImpl) inbound.getIdentifiedKey();
|
||||
if (!key.internalAddHolder(profile.getId())) {
|
||||
inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||
inbound.disconnect(
|
||||
Component.translatable("multiplayer.disconnect.invalid_public_key"));
|
||||
}
|
||||
}
|
||||
// All went well, initialize the session.
|
||||
@@ -289,7 +298,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
|
||||
private void assertState(LoginState expectedState) {
|
||||
if (this.currentState != expectedState) {
|
||||
if (MinecraftDecoder.DEBUG) {
|
||||
logger.error("{} Received an unexpected packet requiring state {}, but we are in {}", inbound,
|
||||
logger.error("{} Received an unexpected packet requiring state {}, but we are in {}",
|
||||
inbound,
|
||||
expectedState, this.currentState);
|
||||
}
|
||||
mcConnection.close(true);
|
||||
|
@@ -37,6 +37,9 @@ import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import space.vectrix.flare.fastutil.Int2ObjectSyncMap;
|
||||
|
||||
/**
|
||||
* Handles the actual login stage of a player logging in.
|
||||
*/
|
||||
public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifiable {
|
||||
|
||||
private static final AtomicIntegerFieldUpdater<LoginInboundConnection> SEQUENCE_UPDATER =
|
||||
@@ -108,6 +111,7 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
|
||||
|
||||
/**
|
||||
* Disconnects the connection from the server.
|
||||
*
|
||||
* @param reason the reason for disconnecting
|
||||
*/
|
||||
public void disconnect(Component reason) {
|
||||
|
@@ -32,6 +32,9 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Handles server list ping packets from a client.
|
||||
*/
|
||||
public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(StatusSessionHandler.class);
|
||||
@@ -66,7 +69,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
server.getServerListPingHandler().getInitialPing(this.inbound)
|
||||
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
|
||||
.thenAcceptAsync(event -> connection.closeWith(
|
||||
LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())),
|
||||
LegacyDisconnect.fromServerPing(event.getPing(), packet.getVersion())),
|
||||
connection.eventLoop())
|
||||
.exceptionally((ex) -> {
|
||||
logger.error("Exception while handling legacy ping {}", packet, ex);
|
||||
|
@@ -23,9 +23,8 @@ package com.velocitypowered.proxy.connection.forge.legacy;
|
||||
public class LegacyForgeConstants {
|
||||
|
||||
/**
|
||||
* Clients attempting to connect to 1.8-1.12.2 Forge servers will have
|
||||
* this token appended to the hostname in the initial handshake
|
||||
* packet.
|
||||
* Clients attempting to connect to 1.8-1.12.2 Forge servers will have this token appended to the
|
||||
* hostname in the initial handshake packet.
|
||||
*/
|
||||
public static final String HANDSHAKE_HOSTNAME_TOKEN = "\0FML\0";
|
||||
|
||||
|
@@ -27,13 +27,13 @@ import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Allows for simple tracking of the phase that the Legacy
|
||||
* Forge handshake is in (server side).
|
||||
* Allows for simple tracking of the phase that the Legacy Forge handshake is in (server side).
|
||||
*/
|
||||
public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
|
||||
/**
|
||||
* Indicates that the handshake has not started, used for {@link BackendConnectionPhases#UNKNOWN}.
|
||||
* Indicates that the handshake has not started, used for
|
||||
* {@link BackendConnectionPhases#UNKNOWN}.
|
||||
*/
|
||||
NOT_STARTED(LegacyForgeConstants.SERVER_HELLO_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -43,8 +43,7 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
},
|
||||
|
||||
/**
|
||||
* Sent a hello to the client, waiting for a hello back before sending
|
||||
* the mod list.
|
||||
* Sent a hello to the client, waiting for a hello back before sending the mod list.
|
||||
*/
|
||||
HELLO(LegacyForgeConstants.MOD_LIST_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -65,8 +64,8 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
},
|
||||
|
||||
/**
|
||||
* The mod list from the client has been accepted and a server mod list
|
||||
* has been sent. Waiting for the client to acknowledge.
|
||||
* The mod list from the client has been accepted and a server mod list has been sent. Waiting for
|
||||
* the client to acknowledge.
|
||||
*/
|
||||
SENT_MOD_LIST(LegacyForgeConstants.REGISTRY_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -76,8 +75,8 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
},
|
||||
|
||||
/**
|
||||
* The server data is being sent or has been sent, and is waiting for
|
||||
* the client to acknowledge it has processed this.
|
||||
* The server data is being sent or has been sent, and is waiting for the client to acknowledge it
|
||||
* has processed this.
|
||||
*/
|
||||
SENT_SERVER_DATA(LegacyForgeConstants.ACK_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -106,16 +105,16 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable private final Integer packetToAdvanceOn;
|
||||
@Nullable
|
||||
private final Integer packetToAdvanceOn;
|
||||
|
||||
/**
|
||||
* Creates an instance of the {@link LegacyForgeHandshakeBackendPhase}.
|
||||
*
|
||||
* @param packetToAdvanceOn The ID of the packet discriminator that indicates
|
||||
* that the server has moved onto a new phase, and
|
||||
* as such, Velocity should do so too (inspecting
|
||||
* {@link #nextPhase()}. A null indicates there is no
|
||||
* further phase to transition to.
|
||||
* @param packetToAdvanceOn The ID of the packet discriminator that indicates that the server has
|
||||
* moved onto a new phase, and as such, Velocity should do so too
|
||||
* (inspecting {@link #nextPhase()}. A null indicates there is no further
|
||||
* phase to transition to.
|
||||
*/
|
||||
LegacyForgeHandshakeBackendPhase(@Nullable Integer packetToAdvanceOn) {
|
||||
this.packetToAdvanceOn = packetToAdvanceOn;
|
||||
@@ -123,8 +122,8 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
|
||||
@Override
|
||||
public final boolean handle(VelocityServerConnection serverConnection,
|
||||
ConnectedPlayer player,
|
||||
PluginMessage message) {
|
||||
ConnectedPlayer player,
|
||||
PluginMessage message) {
|
||||
if (message.getChannel().equals(LegacyForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
// Get the phase and check if we need to start the next phase.
|
||||
LegacyForgeHandshakeBackendPhase newPhase = getNewPhase(serverConnection, message);
|
||||
@@ -148,7 +147,7 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
|
||||
@Override
|
||||
public void onDepartForNewServer(VelocityServerConnection serverConnection,
|
||||
ConnectedPlayer player) {
|
||||
ConnectedPlayer player) {
|
||||
// If the server we are departing is modded, we must always reset the client's handshake.
|
||||
player.getPhase().resetConnectionPhase(player);
|
||||
}
|
||||
@@ -162,8 +161,7 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next phase, if any (will return self if we are at the end
|
||||
* of the handshake).
|
||||
* Gets the next phase, if any (will return self if we are at the end of the handshake).
|
||||
*
|
||||
* @return The next phase
|
||||
*/
|
||||
@@ -175,11 +173,11 @@ public enum LegacyForgeHandshakeBackendPhase implements BackendConnectionPhase {
|
||||
* Get the phase to act on, depending on the packet that has been sent.
|
||||
*
|
||||
* @param serverConnection The server Velocity is connecting to
|
||||
* @param packet The packet
|
||||
* @param packet The packet
|
||||
* @return The phase to transition to, which may be the same as before.
|
||||
*/
|
||||
private LegacyForgeHandshakeBackendPhase getNewPhase(VelocityServerConnection serverConnection,
|
||||
PluginMessage packet) {
|
||||
PluginMessage packet) {
|
||||
if (packetToAdvanceOn != null
|
||||
&& LegacyForgeUtil.getHandshakePacketDiscriminator(packet) == packetToAdvanceOn) {
|
||||
LegacyForgeHandshakeBackendPhase phaseToTransitionTo = nextPhase();
|
||||
|
@@ -34,8 +34,8 @@ import javax.annotation.Nullable;
|
||||
public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
|
||||
/**
|
||||
* No handshake packets have yet been sent. Transition to {@link #HELLO} when the ClientHello
|
||||
* is sent.
|
||||
* No handshake packets have yet been sent. Transition to {@link #HELLO} when the ClientHello is
|
||||
* sent.
|
||||
*/
|
||||
NOT_STARTED(LegacyForgeConstants.CLIENT_HELLO_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -75,12 +75,10 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The Mod list is sent to the server, captured by Velocity.
|
||||
* Transition to {@link #WAITING_SERVER_DATA} when an ACK is sent, which
|
||||
* indicates to the server to start sending state data.
|
||||
* The Mod list is sent to the server, captured by Velocity. Transition to
|
||||
* {@link #WAITING_SERVER_DATA} when an ACK is sent, which indicates to the server to start
|
||||
* sending state data.
|
||||
*/
|
||||
MOD_LIST(LegacyForgeConstants.ACK_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -105,9 +103,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
},
|
||||
|
||||
/**
|
||||
* Waiting for state data to be received.
|
||||
* Transition to {@link #WAITING_SERVER_COMPLETE} when this is complete
|
||||
* and the client sends an ACK packet to confirm
|
||||
* Waiting for state data to be received. Transition to {@link #WAITING_SERVER_COMPLETE} when this
|
||||
* is complete and the client sends an ACK packet to confirm
|
||||
*/
|
||||
WAITING_SERVER_DATA(LegacyForgeConstants.ACK_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -117,9 +114,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
},
|
||||
|
||||
/**
|
||||
* Waiting on the server to send another ACK.
|
||||
* Transition to {@link #PENDING_COMPLETE} when client sends another
|
||||
* ACK
|
||||
* Waiting on the server to send another ACK. Transition to {@link #PENDING_COMPLETE} when client
|
||||
* sends another ACK
|
||||
*/
|
||||
WAITING_SERVER_COMPLETE(LegacyForgeConstants.ACK_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -129,9 +125,8 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
},
|
||||
|
||||
/**
|
||||
* Waiting on the server to send yet another ACK.
|
||||
* Transition to {@link #COMPLETE} when client sends another
|
||||
* ACK
|
||||
* Waiting on the server to send yet another ACK. Transition to {@link #COMPLETE} when client
|
||||
* sends another ACK
|
||||
*/
|
||||
PENDING_COMPLETE(LegacyForgeConstants.ACK_DISCRIMINATOR) {
|
||||
@Override
|
||||
@@ -144,11 +139,10 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
* The handshake is complete. The handshake can be reset.
|
||||
*
|
||||
* <p>Note that a successful connection to a server does not mean that
|
||||
* we will be in this state. After a handshake reset, if the next server
|
||||
* is vanilla we will still be in the {@link #NOT_STARTED} phase,
|
||||
* which means we must NOT send a reset packet. This is handled by
|
||||
* overriding the {@link #resetConnectionPhase(ConnectedPlayer)} in this
|
||||
* element (it is usually a no-op).</p>
|
||||
* we will be in this state. After a handshake reset, if the next server is vanilla we will still
|
||||
* be in the {@link #NOT_STARTED} phase, which means we must NOT send a reset packet. This is
|
||||
* handled by overriding the {@link #resetConnectionPhase(ConnectedPlayer)} in this element (it is
|
||||
* usually a no-op).</p>
|
||||
*/
|
||||
COMPLETE(null) {
|
||||
@Override
|
||||
@@ -180,16 +174,16 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable private final Integer packetToAdvanceOn;
|
||||
@Nullable
|
||||
private final Integer packetToAdvanceOn;
|
||||
|
||||
/**
|
||||
* Creates an instance of the {@link LegacyForgeHandshakeClientPhase}.
|
||||
*
|
||||
* @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 (inspecting
|
||||
* {@link #nextPhase()}. A null indicates there is no
|
||||
* further phase to transition to.
|
||||
* @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
|
||||
* (inspecting {@link #nextPhase()}. A null indicates there is no further
|
||||
* phase to transition to.
|
||||
*/
|
||||
LegacyForgeHandshakeClientPhase(Integer packetToAdvanceOn) {
|
||||
this.packetToAdvanceOn = packetToAdvanceOn;
|
||||
@@ -221,10 +215,9 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
/**
|
||||
* Handles the phase tasks.
|
||||
*
|
||||
* @param player The player
|
||||
* @param message The message to handle
|
||||
* @param player The player
|
||||
* @param message The message to handle
|
||||
* @param backendConn The backend connection to write to, if required.
|
||||
*
|
||||
* @return true if handled, false otherwise.
|
||||
*/
|
||||
boolean onHandle(ConnectedPlayer player,
|
||||
@@ -243,8 +236,7 @@ public enum LegacyForgeHandshakeClientPhase implements ClientConnectionPhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next phase, if any (will return self if we are at the end
|
||||
* of the handshake).
|
||||
* Gets the next phase, if any (will return self if we are at the end of the handshake).
|
||||
*
|
||||
* @return The next phase
|
||||
*/
|
||||
|
@@ -57,7 +57,7 @@ class LegacyForgeUtil {
|
||||
static List<ModInfo.Mod> readModList(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(message.getChannel().equals(FORGE_LEGACY_HANDSHAKE_CHANNEL),
|
||||
"message is not a FML HS plugin message");
|
||||
"message is not a FML HS plugin message");
|
||||
|
||||
ByteBuf contents = message.content().slice();
|
||||
byte discriminator = contents.readByte();
|
||||
|
@@ -22,7 +22,11 @@ import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Implements {@link ResourcePackInfo}.
|
||||
*/
|
||||
public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
|
||||
private final String url;
|
||||
private final @Nullable byte[] hash;
|
||||
private final boolean shouldForce;
|
||||
@@ -31,7 +35,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
private Origin originalOrigin;
|
||||
|
||||
private VelocityResourcePackInfo(String url, @Nullable byte[] hash, boolean shouldForce,
|
||||
@Nullable Component prompt, Origin origin) {
|
||||
@Nullable Component prompt, Origin origin) {
|
||||
this.url = url;
|
||||
this.hash = hash;
|
||||
this.shouldForce = shouldForce;
|
||||
@@ -77,20 +81,24 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new BuilderImpl(url)
|
||||
.setShouldForce(shouldForce)
|
||||
.setHash(hash)
|
||||
.setPrompt(prompt);
|
||||
.setShouldForce(shouldForce)
|
||||
.setHash(hash)
|
||||
.setPrompt(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder(String newUrl) {
|
||||
return new BuilderImpl(newUrl)
|
||||
.setShouldForce(shouldForce)
|
||||
.setHash(hash)
|
||||
.setPrompt(prompt);
|
||||
.setShouldForce(shouldForce)
|
||||
.setHash(hash)
|
||||
.setPrompt(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the builder for {@link ResourcePackInfo} instances.
|
||||
*/
|
||||
public static final class BuilderImpl implements ResourcePackInfo.Builder {
|
||||
|
||||
private final String url;
|
||||
private boolean shouldForce;
|
||||
private @Nullable byte[] hash;
|
||||
|
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.kyori.adventure.nbt.*;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.format.TextFormat;
|
||||
import net.kyori.adventure.translation.Translatable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
*/
|
||||
|
||||
// TODO Implement
|
||||
public class ChatData {
|
||||
/*
|
||||
private static final ListBinaryTag EMPTY_LIST_TAG = ListBinaryTag.empty();
|
||||
|
||||
|
||||
private final String identifier;
|
||||
private final int id;
|
||||
private final Map<>
|
||||
|
||||
|
||||
public static class Decoration implements Translatable {
|
||||
|
||||
private final List<String> parameters;
|
||||
private final List<TextFormat> style;
|
||||
private final String translationKey;
|
||||
|
||||
public List<String> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public List<TextFormat> getStyle() {
|
||||
return style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String translationKey() {
|
||||
return translationKey;
|
||||
}
|
||||
|
||||
public Decoration(List<String> parameters, List<TextFormat> style, String translationKey) {
|
||||
this.parameters = Preconditions.checkNotNull(parameters);
|
||||
this.style = Preconditions.checkNotNull(style);
|
||||
this.translationKey = Preconditions.checkNotNull(translationKey);
|
||||
Preconditions.checkArgument(translationKey.length() > 0);
|
||||
}
|
||||
|
||||
public static Decoration decodeRegistryEntry(CompoundBinaryTag toDecode) {
|
||||
ImmutableList.Builder<String> parameters = ImmutableList.builder();
|
||||
ListBinaryTag paramList = toDecode.getList("parameters", EMPTY_LIST_TAG);
|
||||
if (paramList != EMPTY_LIST_TAG) {
|
||||
paramList.forEach(binaryTag -> parameters.add(binaryTag.toString()));
|
||||
}
|
||||
|
||||
ImmutableList.Builder<TextFormat> style = ImmutableList.builder();
|
||||
CompoundBinaryTag styleList = toDecode.getCompound("style");
|
||||
for (String key : styleList.keySet()) {
|
||||
if ("color".equals(key)) {
|
||||
NamedTextColor color = Preconditions.checkNotNull(
|
||||
NamedTextColor.NAMES.value(styleList.getString(key)));
|
||||
style.add(color);
|
||||
} else {
|
||||
// Key is a Style option instead
|
||||
TextDecoration deco = TextDecoration.NAMES.value(key);
|
||||
// This wouldn't be here if it wasn't applied, but here it goes anyway:
|
||||
byte val = styleList.getByte(key);
|
||||
if (val != 0) {
|
||||
style.add(deco);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String translationKey = toDecode.getString("translation_key");
|
||||
|
||||
return new Decoration(parameters.build(), style.build(), translationKey);
|
||||
}
|
||||
|
||||
public void encodeRegistryEntry(CompoundBinaryTag )
|
||||
|
||||
}
|
||||
|
||||
public static enum Priority {
|
||||
SYSTEM,
|
||||
CHAT
|
||||
}
|
||||
*/
|
||||
}
|
@@ -22,7 +22,11 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a dimension sent to the client by the server.
|
||||
*/
|
||||
public final class DimensionData {
|
||||
|
||||
private static final String UNKNOWN_DIMENSION_ID = "velocity:unknown_dimension";
|
||||
|
||||
private final String registryIdentifier;
|
||||
@@ -53,41 +57,50 @@ public final class DimensionData {
|
||||
*
|
||||
* @param registryIdentifier the identifier for the dimension from the registry.
|
||||
* @param dimensionId the dimension ID contained in the registry (the "id" tag)
|
||||
* @param isNatural indicates if the dimension use natural world generation (e.g. overworld)
|
||||
* @param isNatural indicates if the dimension use natural world generation
|
||||
* (e.g. overworld)
|
||||
* @param ambientLight the light level the client sees without external lighting
|
||||
* @param isShrunk indicates if the world is shrunk, aka not the full 256 blocks (e.g. nether)
|
||||
* @param isShrunk indicates if the world is shrunk, aka not the full 256
|
||||
* blocks (e.g. nether)
|
||||
* @param isUltrawarm internal dimension warmth flag
|
||||
* @param hasCeiling indicates if the dimension has a ceiling layer
|
||||
* @param hasSkylight indicates if the dimension should display the sun
|
||||
* @param isPiglinSafe indicates if piglins should naturally zombify in this dimension
|
||||
* @param doBedsWork indicates if players should be able to sleep in beds in this dimension
|
||||
* @param doRespawnAnchorsWork indicates if player respawn points can be used in this dimension
|
||||
* @param isPiglinSafe indicates if piglins should naturally zombify in this
|
||||
* dimension
|
||||
* @param doBedsWork indicates if players should be able to sleep in beds in this
|
||||
* dimension
|
||||
* @param doRespawnAnchorsWork indicates if player respawn points can be used in this
|
||||
* dimension
|
||||
* @param hasRaids indicates if raids can be spawned in the dimension
|
||||
* @param logicalHeight the natural max height for the given dimension
|
||||
* @param burningBehaviourIdentifier the identifier for how burning blocks work in the dimension
|
||||
* @param fixedTime optional. If set to any game daytime value will deactivate time cycle
|
||||
* @param fixedTime optional. If set to any game daytime value will deactivate
|
||||
* time cycle
|
||||
* @param createDragonFight optional. Internal flag used in the end dimension
|
||||
* @param coordinateScale optional, unknown purpose
|
||||
* @param effects optional, unknown purpose
|
||||
* @param minY the world effective lowest build-level
|
||||
* @param height the world height above zero
|
||||
* @param monsterSpawnBlockLightLimit an integer controlling the block light needed to prevent monster spawns.
|
||||
* @param monsterSpawnLightLevel an int provider which is evaluated to find a value to compare the current
|
||||
* overall brightness with to determine if a monster should be allowed to spawn.
|
||||
* @param monsterSpawnBlockLightLimit an integer controlling the block light needed to prevent
|
||||
* monster spawns.
|
||||
* @param monsterSpawnLightLevel an int provider which is evaluated to find a value to
|
||||
* compare the current overall brightness with to determine if
|
||||
* a monster should be allowed to spawn.
|
||||
*/
|
||||
public DimensionData(String registryIdentifier,
|
||||
@Nullable Integer dimensionId,
|
||||
boolean isNatural,
|
||||
float ambientLight, boolean isShrunk, boolean isUltrawarm,
|
||||
boolean hasCeiling, boolean hasSkylight,
|
||||
boolean isPiglinSafe, boolean doBedsWork,
|
||||
boolean doRespawnAnchorsWork, boolean hasRaids,
|
||||
int logicalHeight, String burningBehaviourIdentifier,
|
||||
@Nullable Long fixedTime, @Nullable Boolean createDragonFight,
|
||||
@Nullable Double coordinateScale,
|
||||
@Nullable String effects,
|
||||
@Nullable Integer minY, @Nullable Integer height, @Nullable Integer monsterSpawnBlockLightLimit,
|
||||
@Nullable Integer monsterSpawnLightLevel) {
|
||||
@Nullable Integer dimensionId,
|
||||
boolean isNatural,
|
||||
float ambientLight, boolean isShrunk, boolean isUltrawarm,
|
||||
boolean hasCeiling, boolean hasSkylight,
|
||||
boolean isPiglinSafe, boolean doBedsWork,
|
||||
boolean doRespawnAnchorsWork, boolean hasRaids,
|
||||
int logicalHeight, String burningBehaviourIdentifier,
|
||||
@Nullable Long fixedTime, @Nullable Boolean createDragonFight,
|
||||
@Nullable Double coordinateScale,
|
||||
@Nullable String effects,
|
||||
@Nullable Integer minY, @Nullable Integer height,
|
||||
@Nullable Integer monsterSpawnBlockLightLimit,
|
||||
@Nullable Integer monsterSpawnLightLevel) {
|
||||
this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit;
|
||||
this.monsterSpawnLightLevel = monsterSpawnLightLevel;
|
||||
Preconditions.checkNotNull(
|
||||
@@ -198,19 +211,20 @@ public final class DimensionData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fresh {@link DimensionData} with the specified {@code registryIdentifier}
|
||||
* and {@code dimensionId}.
|
||||
* Returns a fresh {@link DimensionData} with the specified {@code registryIdentifier} and
|
||||
* {@code dimensionId}.
|
||||
*
|
||||
* @param registryIdentifier the identifier for the dimension from the registry
|
||||
* @param dimensionId optional, dimension ID
|
||||
* @return a new {@link DimensionData}
|
||||
*/
|
||||
public DimensionData annotateWith(String registryIdentifier,
|
||||
@Nullable Integer dimensionId) {
|
||||
@Nullable Integer dimensionId) {
|
||||
return new DimensionData(registryIdentifier, dimensionId, isNatural, ambientLight, isShrunk,
|
||||
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
|
||||
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, createDragonFight,
|
||||
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit, monsterSpawnLightLevel);
|
||||
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit,
|
||||
monsterSpawnLightLevel);
|
||||
}
|
||||
|
||||
public boolean isUnannotated() {
|
||||
@@ -226,7 +240,7 @@ public final class DimensionData {
|
||||
* @return game dimension data
|
||||
*/
|
||||
public static DimensionData decodeBaseCompoundTag(CompoundBinaryTag details,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
boolean isNatural = details.getBoolean("natural");
|
||||
float ambientLight = details.getFloat("ambient_light");
|
||||
boolean isShrunk = details.getBoolean("shrunk");
|
||||
@@ -249,10 +263,12 @@ public final class DimensionData {
|
||||
: null;
|
||||
Integer minY = details.keySet().contains("min_y") ? details.getInt("min_y") : null;
|
||||
Integer height = details.keySet().contains("height") ? details.getInt("height") : null;
|
||||
Integer monsterSpawnBlockLightLimit = details.keySet().contains("monster_spawn_block_light_limit")
|
||||
? details.getInt("monster_spawn_block_light_limit") : null;
|
||||
Integer monsterSpawnBlockLightLimit =
|
||||
details.keySet().contains("monster_spawn_block_light_limit")
|
||||
? details.getInt("monster_spawn_block_light_limit") : null;
|
||||
Integer monsterSpawnLightLevel =
|
||||
details.keySet().contains("monster_spawn_light_level") ? details.getInt("monster_spawn_block_light_limit") :
|
||||
details.keySet().contains("monster_spawn_light_level") ? details.getInt(
|
||||
"monster_spawn_block_light_limit") :
|
||||
null;
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) {
|
||||
Preconditions.checkNotNull(height,
|
||||
@@ -262,27 +278,30 @@ public final class DimensionData {
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) {
|
||||
Preconditions.checkNotNull(monsterSpawnBlockLightLimit,
|
||||
"DimensionData requires 'monster_spawn_block_light_limit' to be present for this version.");
|
||||
"DimensionData requires 'monster_spawn_block_light_limit' to be present"
|
||||
+ " for this version.");
|
||||
Preconditions.checkNotNull(monsterSpawnLightLevel,
|
||||
"DimensionData requires 'monster_spawn_light_level' to be present for this version.");
|
||||
"DimensionData requires 'monster_spawn_light_level' to be present"
|
||||
+ " for this version.");
|
||||
}
|
||||
return new DimensionData(
|
||||
UNKNOWN_DIMENSION_ID, null, isNatural, ambientLight, isShrunk,
|
||||
isUltrawarm, hasCeiling, hasSkylight, isPiglinSafe, doBedsWork, doRespawnAnchorsWork,
|
||||
hasRaids, logicalHeight, burningBehaviourIdentifier, fixedTime, hasEnderdragonFight,
|
||||
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit, monsterSpawnLightLevel);
|
||||
coordinateScale, effects, minY, height, monsterSpawnBlockLightLimit,
|
||||
monsterSpawnLightLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a given CompoundTag to a DimensionData instance. Assumes the data is part of a
|
||||
* dimension registry.
|
||||
* Parses a given CompoundTag to a DimensionData instance. Assumes the data is part of a dimension
|
||||
* registry.
|
||||
*
|
||||
* @param dimTag the compound from the registry to read
|
||||
* @param version the protocol version
|
||||
* @return game dimension data
|
||||
*/
|
||||
public static DimensionData decodeRegistryEntry(CompoundBinaryTag dimTag,
|
||||
ProtocolVersion version) {
|
||||
ProtocolVersion version) {
|
||||
String registryIdentifier = dimTag.getString("name");
|
||||
CompoundBinaryTag details;
|
||||
Integer dimensionId = null;
|
||||
|
@@ -20,6 +20,9 @@ package com.velocitypowered.proxy.connection.registry;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents basic information for a Minecraft dimension.
|
||||
*/
|
||||
public final class DimensionInfo {
|
||||
|
||||
private final String registryIdentifier;
|
||||
@@ -29,10 +32,12 @@ public final class DimensionInfo {
|
||||
|
||||
/**
|
||||
* Initializes a new {@link DimensionInfo} instance.
|
||||
*
|
||||
* @param registryIdentifier the identifier for the dimension from the registry
|
||||
* @param levelName the level name as displayed in the F3 menu and logs
|
||||
* @param isFlat if true will set world lighting below surface-level to not display fog
|
||||
* @param isDebugType if true constrains the world to the very limited debug-type world
|
||||
* @param levelName the level name as displayed in the F3 menu and logs
|
||||
* @param isFlat if true will set world lighting below surface-level to not display
|
||||
* fog
|
||||
* @param isDebugType if true constrains the world to the very limited debug-type world
|
||||
*/
|
||||
public DimensionInfo(String registryIdentifier, @Nullable String levelName,
|
||||
boolean isFlat, boolean isDebugType) {
|
||||
|
@@ -29,21 +29,24 @@ import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.nbt.ListBinaryTag;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a registry of dimensions sent to the client by the server.
|
||||
*/
|
||||
public final class DimensionRegistry {
|
||||
|
||||
private final Map<String, DimensionData> registeredDimensions;
|
||||
private final ImmutableSet<String> levelNames;
|
||||
|
||||
/**
|
||||
* Initializes a new {@link DimensionRegistry} instance.
|
||||
* This registry is required for 1.16+ clients/servers to communicate,
|
||||
* it constrains the dimension types and names the client can be sent
|
||||
* in a Respawn action (dimension change).
|
||||
* This WILL raise an IllegalArgumentException if the following is not met:
|
||||
* - At least one valid DimensionData instance is provided
|
||||
* - At least one valid world name is provided
|
||||
* Initializes a new {@link DimensionRegistry} instance. This registry is required for 1.16+
|
||||
* clients/servers to communicate, it constrains the dimension types and names the client can be
|
||||
* sent in a Respawn action (dimension change). This WILL raise an IllegalArgumentException if the
|
||||
* following is not met: - At least one valid DimensionData instance is provided - At least one
|
||||
* valid world name is provided
|
||||
*
|
||||
* @param registeredDimensions a populated {@link ImmutableSet} containing dimension data types
|
||||
* @param levelNames a populated {@link ImmutableSet} of the level (world) names the server offers
|
||||
* @param levelNames a populated {@link ImmutableSet} of the level (world) names the
|
||||
* server offers
|
||||
*/
|
||||
public DimensionRegistry(ImmutableSet<DimensionData> registeredDimensions,
|
||||
ImmutableSet<String> levelNames) {
|
||||
@@ -70,6 +73,7 @@ public final class DimensionRegistry {
|
||||
|
||||
/**
|
||||
* Returns the internal dimension data type as used by the game.
|
||||
*
|
||||
* @param dimensionIdentifier how the dimension is identified by the connection
|
||||
* @return game dimension data or null if not registered
|
||||
*/
|
||||
@@ -79,6 +83,7 @@ public final class DimensionRegistry {
|
||||
|
||||
/**
|
||||
* Checks a {@link DimensionInfo} against this registry.
|
||||
*
|
||||
* @param toValidate the {@link DimensionInfo} to validate
|
||||
* @return true: the dimension information is valid for this registry
|
||||
*/
|
||||
@@ -92,6 +97,7 @@ public final class DimensionRegistry {
|
||||
|
||||
/**
|
||||
* Encodes the stored Dimension registry as CompoundTag.
|
||||
*
|
||||
* @return the CompoundTag containing identifier:type mappings
|
||||
*/
|
||||
public ListBinaryTag encodeRegistry(ProtocolVersion version) {
|
||||
@@ -105,6 +111,7 @@ public final class DimensionRegistry {
|
||||
|
||||
/**
|
||||
* Decodes a CompoundTag storing a dimension registry.
|
||||
*
|
||||
* @param toParse CompoundTag containing a dimension registry
|
||||
*/
|
||||
public static ImmutableSet<DimensionData> fromGameData(ListBinaryTag toParse,
|
||||
|
@@ -22,6 +22,9 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
/**
|
||||
* Common messages that might be sent a client for various connection-related states.
|
||||
*/
|
||||
public class ConnectionMessages {
|
||||
|
||||
public static final TranslatableComponent ALREADY_CONNECTED = Component
|
||||
|
@@ -26,6 +26,9 @@ import javax.annotation.Nullable;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
/**
|
||||
* Common connection request results.
|
||||
*/
|
||||
public class ConnectionRequestResults {
|
||||
|
||||
private ConnectionRequestResults() {
|
||||
@@ -38,6 +41,7 @@ public class ConnectionRequestResults {
|
||||
|
||||
/**
|
||||
* Returns a plain result (one with a status but no reason).
|
||||
*
|
||||
* @param status the status to use
|
||||
* @param server the server to use
|
||||
* @return the result
|
||||
@@ -50,8 +54,9 @@ public class ConnectionRequestResults {
|
||||
|
||||
/**
|
||||
* Returns a disconnect result with a reason.
|
||||
*
|
||||
* @param component the reason for disconnecting from the server
|
||||
* @param server the server to use
|
||||
* @param server the server to use
|
||||
* @return the result
|
||||
*/
|
||||
public static Impl forDisconnect(Component component, RegisteredServer server) {
|
||||
@@ -68,6 +73,9 @@ public class ConnectionRequestResults {
|
||||
return new Impl(Status.SERVER_DISCONNECTED, deserialized, server, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base implementation.
|
||||
*/
|
||||
public static class Impl implements ConnectionRequestBuilder.Result {
|
||||
|
||||
private final Status status;
|
||||
@@ -100,6 +108,7 @@ public class ConnectionRequestResults {
|
||||
|
||||
/**
|
||||
* Returns whether or not it is safe to attempt a reconnect.
|
||||
*
|
||||
* @return whether we can try to reconnect
|
||||
*/
|
||||
public boolean isSafe() {
|
||||
|
@@ -32,7 +32,7 @@ public class ConnectionTypeImpl implements ConnectionType {
|
||||
private final BackendConnectionPhase initialBackendPhase;
|
||||
|
||||
public ConnectionTypeImpl(ClientConnectionPhase initialClientPhase,
|
||||
BackendConnectionPhase initialBackendPhase) {
|
||||
BackendConnectionPhase initialBackendPhase) {
|
||||
this.initialClientPhase = initialClientPhase;
|
||||
this.initialBackendPhase = initialBackendPhase;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class ConnectionTypeImpl implements ConnectionType {
|
||||
|
||||
@Override
|
||||
public GameProfile addGameProfileTokensIfRequired(GameProfile original,
|
||||
PlayerInfoForwarding forwardingType) {
|
||||
PlayerInfoForwarding forwardingType) {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,9 @@ import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Common utilities for handling server list ping results.
|
||||
*/
|
||||
public class ServerListPingHandler {
|
||||
|
||||
private final VelocityServer server;
|
||||
|
@@ -20,6 +20,10 @@ package com.velocitypowered.proxy.connection.util;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
|
||||
/**
|
||||
* Base internal interface for a {@link InboundConnection}.
|
||||
*/
|
||||
public interface VelocityInboundConnection extends InboundConnection {
|
||||
|
||||
MinecraftConnection getConnection();
|
||||
}
|
||||
|
@@ -48,6 +48,10 @@ import org.jline.reader.Candidate;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
|
||||
/**
|
||||
* Implements the Velocity console, including sending commands and being the recipient
|
||||
* of messages from plugins.
|
||||
*/
|
||||
public final class VelocityConsole extends SimpleTerminalConsole implements ConsoleCommandSource {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
|
||||
@@ -55,18 +59,19 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
|
||||
private final VelocityServer server;
|
||||
private PermissionFunction permissionFunction = ALWAYS_TRUE;
|
||||
private final @NotNull Pointers pointers = ConsoleCommandSource.super.pointers().toBuilder()
|
||||
.withDynamic(PermissionChecker.POINTER, this::getPermissionChecker)
|
||||
.withDynamic(Identity.LOCALE, () -> ClosestLocaleMatcher.INSTANCE
|
||||
.lookupClosest(Locale.getDefault()))
|
||||
.withStatic(FacetPointers.TYPE, Type.CONSOLE)
|
||||
.build();
|
||||
.withDynamic(PermissionChecker.POINTER, this::getPermissionChecker)
|
||||
.withDynamic(Identity.LOCALE, () -> ClosestLocaleMatcher.INSTANCE
|
||||
.lookupClosest(Locale.getDefault()))
|
||||
.withStatic(FacetPointers.TYPE, Type.CONSOLE)
|
||||
.build();
|
||||
|
||||
public VelocityConsole(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message, @NonNull MessageType messageType) {
|
||||
public void sendMessage(@NonNull Identity identity, @NonNull Component message,
|
||||
@NonNull MessageType messageType) {
|
||||
Component translated = GlobalTranslator.render(message, ClosestLocaleMatcher.INSTANCE
|
||||
.lookupClosest(Locale.getDefault()));
|
||||
logger.info(LegacyComponentSerializer.legacySection().serialize(translated));
|
||||
|
@@ -41,27 +41,30 @@ import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* Generic utilities for dealing with encryption operations in Minecraft.
|
||||
*/
|
||||
public enum EncryptionUtils {
|
||||
;
|
||||
|
||||
public static final Pair<String, String> PEM_RSA_PUBLIC_KEY_DESCRIPTOR =
|
||||
Pair.of("-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----");
|
||||
Pair.of("-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----");
|
||||
public static final Pair<String, String> PEM_RSA_PRIVATE_KEY_DESCRIPTOR =
|
||||
Pair.of("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
|
||||
Pair.of("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
|
||||
|
||||
public static final String SHA1_WITH_RSA = "SHA1withRSA";
|
||||
public static final String SHA256_WITH_RSA = "SHA256withRSA";
|
||||
|
||||
public static final QuietDecoderException INVALID_SIGNATURE
|
||||
= new QuietDecoderException("Incorrectly signed chat message");
|
||||
= new QuietDecoderException("Incorrectly signed chat message");
|
||||
public static final QuietDecoderException PREVIEW_SIGNATURE_MISSING
|
||||
= new QuietDecoderException("Unsigned chat message requested signed preview");
|
||||
= new QuietDecoderException("Unsigned chat message requested signed preview");
|
||||
public static final byte[] EMPTY = new byte[0];
|
||||
private static PublicKey YGGDRASIL_SESSION_KEY;
|
||||
private static KeyFactory RSA_KEY_FACTORY;
|
||||
|
||||
private static final Base64.Encoder MIME_SPECIAL_ENCODER
|
||||
= Base64.getMimeEncoder(76, "\n".getBytes(StandardCharsets.UTF_8));
|
||||
= Base64.getMimeEncoder(76, "\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
static {
|
||||
try {
|
||||
@@ -72,8 +75,9 @@ public enum EncryptionUtils {
|
||||
|
||||
try {
|
||||
byte[] bytes = ByteStreams.toByteArray(
|
||||
EncryptionUtils.class.getClassLoader().getResourceAsStream("yggdrasil_session_pubkey.der"));
|
||||
YGGDRASIL_SESSION_KEY = parseRsaPublicKey(bytes);
|
||||
EncryptionUtils.class.getClassLoader()
|
||||
.getResourceAsStream("yggdrasil_session_pubkey.der"));
|
||||
YGGDRASIL_SESSION_KEY = parseRsaPublicKey(bytes);
|
||||
} catch (IOException | NullPointerException err) {
|
||||
throw new RuntimeException(err);
|
||||
}
|
||||
@@ -87,12 +91,13 @@ public enum EncryptionUtils {
|
||||
* Verifies a key signature.
|
||||
*
|
||||
* @param algorithm the signature algorithm
|
||||
* @param base the public key to verify with
|
||||
* @param base the public key to verify with
|
||||
* @param signature the signature to verify against
|
||||
* @param toVerify the byte array(s) of data to verify
|
||||
* @param toVerify the byte array(s) of data to verify
|
||||
* @return validity of the signature
|
||||
*/
|
||||
public static boolean verifySignature(String algorithm, PublicKey base, byte[] signature, byte[]... toVerify) {
|
||||
public static boolean verifySignature(String algorithm, PublicKey base, byte[] signature,
|
||||
byte[]... toVerify) {
|
||||
Preconditions.checkArgument(toVerify.length > 0);
|
||||
try {
|
||||
Signature construct = Signature.getInstance(algorithm);
|
||||
@@ -110,8 +115,8 @@ public enum EncryptionUtils {
|
||||
* Generates a signature for input data.
|
||||
*
|
||||
* @param algorithm the signature algorithm
|
||||
* @param base the private key to sign with
|
||||
* @param toSign the byte array(s) of data to sign
|
||||
* @param base the private key to sign with
|
||||
* @param toSign the byte array(s) of data to sign
|
||||
* @return the generated signature
|
||||
*/
|
||||
public static byte[] generateSignature(String algorithm, PrivateKey base, byte[]... toSign) {
|
||||
@@ -153,7 +158,7 @@ public enum EncryptionUtils {
|
||||
/**
|
||||
* Parse a cer-encoded RSA key into its key bytes.
|
||||
*
|
||||
* @param toParse the cer-encoded key String
|
||||
* @param toParse the cer-encoded key String
|
||||
* @param descriptors the type of key
|
||||
* @return the parsed key bytes
|
||||
*/
|
||||
@@ -184,8 +189,8 @@ public enum EncryptionUtils {
|
||||
}
|
||||
|
||||
return encoder.first() + "\n"
|
||||
+ encodeUrlEncoded(toEncode.getEncoded()) + "\n"
|
||||
+ encoder.second() + "\n";
|
||||
+ encodeUrlEncoded(toEncode.getEncoded()) + "\n"
|
||||
+ encoder.second() + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +237,7 @@ public enum EncryptionUtils {
|
||||
* Decrypts an RSA message.
|
||||
*
|
||||
* @param keyPair the key pair to use
|
||||
* @param bytes the bytes of the encrypted message
|
||||
* @param bytes the bytes of the encrypted message
|
||||
* @return the decrypted message
|
||||
* @throws GeneralSecurityException if the message couldn't be decoded
|
||||
*/
|
||||
@@ -246,7 +251,7 @@ public enum EncryptionUtils {
|
||||
* Generates the server ID for the hasJoined endpoint.
|
||||
*
|
||||
* @param sharedSecret the shared secret between the client and the proxy
|
||||
* @param key the RSA public key
|
||||
* @param key the RSA public key
|
||||
* @return the server ID
|
||||
*/
|
||||
public static String generateServerId(byte[] sharedSecret, PublicKey key) {
|
||||
|
@@ -30,6 +30,9 @@ import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the contents of a {@link IdentifiedKey}.
|
||||
*/
|
||||
public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
|
||||
private final Revision revision;
|
||||
@@ -40,14 +43,16 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
private @MonotonicNonNull UUID holder;
|
||||
|
||||
public IdentifiedKeyImpl(Revision revision, byte[] keyBits, long expiry,
|
||||
byte[] signature) {
|
||||
this(revision, EncryptionUtils.parseRsaPublicKey(keyBits), Instant.ofEpochMilli(expiry), signature);
|
||||
byte[] signature) {
|
||||
this(revision, EncryptionUtils.parseRsaPublicKey(keyBits),
|
||||
Instant.ofEpochMilli(expiry), signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Identified key from data.
|
||||
*/
|
||||
public IdentifiedKeyImpl(Revision revision, PublicKey publicKey, Instant expiryTemporal, byte[] signature) {
|
||||
public IdentifiedKeyImpl(
|
||||
Revision revision, PublicKey publicKey, Instant expiryTemporal, byte[] signature) {
|
||||
this.revision = revision;
|
||||
this.publicKey = publicKey;
|
||||
this.expiryTemporal = expiryTemporal;
|
||||
@@ -85,8 +90,7 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uuid for this key.
|
||||
* Returns false if incorrect.
|
||||
* Sets the uuid for this key. Returns false if incorrect.
|
||||
*/
|
||||
public boolean internalAddHolder(UUID holder) {
|
||||
if (holder == null) {
|
||||
@@ -118,7 +122,8 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
long expires = expiryTemporal.toEpochMilli();
|
||||
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII);
|
||||
return EncryptionUtils.verifySignature(
|
||||
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
|
||||
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature,
|
||||
toVerify);
|
||||
} else {
|
||||
if (verify == null) {
|
||||
return null;
|
||||
@@ -131,14 +136,15 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
fixedDataSet.putLong(expiryTemporal.toEpochMilli());
|
||||
fixedDataSet.put(keyBytes);
|
||||
return EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA,
|
||||
EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
|
||||
EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyDataSignature(byte[] signature, byte[]... toVerify) {
|
||||
try {
|
||||
return EncryptionUtils.verifySignature(EncryptionUtils.SHA256_WITH_RSA, publicKey, signature, toVerify);
|
||||
return EncryptionUtils.verifySignature(EncryptionUtils.SHA256_WITH_RSA, publicKey, signature,
|
||||
toVerify);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
@@ -164,11 +170,12 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
|
||||
if (!(o instanceof IdentifiedKey)) {
|
||||
return false;
|
||||
}
|
||||
IdentifiedKey that = (IdentifiedKey) o; // This cannot be simplified because checkstyle doesn't like it.
|
||||
|
||||
IdentifiedKey that = (IdentifiedKey) o;
|
||||
|
||||
return Objects.equal(this.getSignedPublicKey(), that.getSignedPublicKey())
|
||||
&& Objects.equal(this.getExpiryTemporal(), that.getExpiryTemporal())
|
||||
&& Arrays.equals(this.getSignature(), that.getSignature())
|
||||
&& Objects.equal(this.getSigner(), that.getSigner());
|
||||
&& Objects.equal(this.getExpiryTemporal(), that.getExpiryTemporal())
|
||||
&& Arrays.equals(this.getSignature(), that.getSignature())
|
||||
&& Objects.equal(this.getSigner(), that.getSigner());
|
||||
}
|
||||
}
|
||||
|
@@ -15,12 +15,14 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package com.velocitypowered.proxy.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a signer and a signature.
|
||||
*/
|
||||
public class SignaturePair {
|
||||
|
||||
private final UUID signer;
|
||||
@@ -42,8 +44,8 @@ public class SignaturePair {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SignaturePair{"
|
||||
+ "signer=" + signer
|
||||
+ ", signature=" + Arrays.toString(signature)
|
||||
+ '}';
|
||||
+ "signer=" + signer
|
||||
+ ", signature=" + Arrays.toString(signature)
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,11 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a signed chat command.
|
||||
*/
|
||||
public class SignedChatCommand implements KeySigned {
|
||||
|
||||
private final String command;
|
||||
private final PublicKey signer;
|
||||
private final Instant expiry;
|
||||
@@ -43,9 +47,9 @@ public class SignedChatCommand implements KeySigned {
|
||||
* Create a signed command from data.
|
||||
*/
|
||||
public SignedChatCommand(String command, PublicKey signer, UUID sender,
|
||||
Instant expiry, Map<String, byte[]> signature, byte[] salt,
|
||||
boolean isPreviewSigned, SignaturePair[] previousSignatures,
|
||||
@Nullable SignaturePair lastSignature) {
|
||||
Instant expiry, Map<String, byte[]> signature, byte[] salt,
|
||||
boolean isPreviewSigned, SignaturePair[] previousSignatures,
|
||||
@Nullable SignaturePair lastSignature) {
|
||||
this.command = Preconditions.checkNotNull(command);
|
||||
this.signer = Preconditions.checkNotNull(signer);
|
||||
this.sender = Preconditions.checkNotNull(sender);
|
||||
|
@@ -23,10 +23,16 @@ import com.velocitypowered.api.event.EventHandler;
|
||||
import com.velocitypowered.api.event.EventTask;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Core class for invoking event handlers registered by plugins.
|
||||
*/
|
||||
public interface UntargetedEventHandler {
|
||||
|
||||
EventHandler<Object> buildHandler(Object targetInstance);
|
||||
|
||||
/**
|
||||
* Interface used for invoking listeners that return {@link EventTask}.
|
||||
*/
|
||||
interface EventTaskHandler extends UntargetedEventHandler {
|
||||
|
||||
@Nullable EventTask execute(Object targetInstance, Object event);
|
||||
@@ -37,6 +43,9 @@ public interface UntargetedEventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used for invoking listeners that return nothing.
|
||||
*/
|
||||
interface VoidHandler extends UntargetedEventHandler {
|
||||
|
||||
void execute(Object targetInstance, Object event);
|
||||
@@ -50,6 +59,9 @@ public interface UntargetedEventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used for invoking listeners that take a {@link Continuation} along with an event.
|
||||
*/
|
||||
interface WithContinuationHandler extends UntargetedEventHandler {
|
||||
|
||||
void execute(Object targetInstance, Object event, Continuation continuation);
|
||||
|
@@ -72,6 +72,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.lanternpowered.lmbda.LambdaFactory;
|
||||
import org.lanternpowered.lmbda.LambdaType;
|
||||
|
||||
/**
|
||||
* Implements the Velocity event handler.
|
||||
*/
|
||||
public class VelocityEventManager implements EventManager {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
||||
@@ -90,7 +93,8 @@ public class VelocityEventManager implements EventManager {
|
||||
private final ExecutorService asyncExecutor;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
private final ListMultimap<Class<?>, HandlerRegistration> handlersByType = ArrayListMultimap.create();
|
||||
private final ListMultimap<Class<?>, HandlerRegistration> handlersByType =
|
||||
ArrayListMultimap.create();
|
||||
private final LoadingCache<Class<?>, HandlersCache> handlersCache =
|
||||
Caffeine.newBuilder().build(this::bakeHandlers);
|
||||
|
||||
@@ -159,8 +163,7 @@ public class VelocityEventManager implements EventManager {
|
||||
*/
|
||||
ALWAYS,
|
||||
/**
|
||||
* The event will never run async, everything is handled on
|
||||
* the netty thread.
|
||||
* The event will never run async, everything is handled on the netty thread.
|
||||
*/
|
||||
NEVER
|
||||
}
|
||||
@@ -198,9 +201,8 @@ public class VelocityEventManager implements EventManager {
|
||||
/**
|
||||
* Creates an {@link UntargetedEventHandler} for the given {@link Method}. This essentially
|
||||
* implements the {@link UntargetedEventHandler} (or the no async task variant) to invoke the
|
||||
* target method. The implemented class is defined in the same package as the declaring class.
|
||||
* The {@link UntargetedEventHandler} interface must be public so the implementation can access
|
||||
* it.
|
||||
* target method. The implemented class is defined in the same package as the declaring class. The
|
||||
* {@link UntargetedEventHandler} interface must be public so the implementation can access it.
|
||||
*
|
||||
* @param method The method to generate an untargeted handler for
|
||||
* @return The untargeted handler
|
||||
@@ -364,7 +366,7 @@ public class VelocityEventManager implements EventManager {
|
||||
* Registers the listener for a given plugin.
|
||||
*
|
||||
* @param pluginContainer registering plugin
|
||||
* @param listener listener to register
|
||||
* @param listener listener to register
|
||||
*/
|
||||
public void registerInternally(final PluginContainer pluginContainer, final Object listener) {
|
||||
final Class<?> targetClass = listener.getClass();
|
||||
@@ -537,8 +539,8 @@ public class VelocityEventManager implements EventManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the task and returns whether the next one should be executed
|
||||
* immediately after this one without scheduling.
|
||||
* Executes the task and returns whether the next one should be executed immediately after this
|
||||
* one without scheduling.
|
||||
*/
|
||||
boolean execute() {
|
||||
state = TASK_STATE_EXECUTING;
|
||||
|
@@ -36,6 +36,9 @@ import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Backend channel initializer.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class BackendChannelInitializer extends ChannelInitializer<Channel> {
|
||||
|
||||
|
@@ -23,6 +23,9 @@ import java.util.function.Supplier;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Backend channel initializer holder.
|
||||
*/
|
||||
public class BackendChannelInitializerHolder implements Supplier<ChannelInitializer<Channel>> {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
|
||||
|
@@ -27,7 +27,7 @@ import com.velocitypowered.api.network.ListenerType;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver;
|
||||
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
|
||||
import com.velocitypowered.proxy.protocol.netty.GameSpyQueryHandler;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -49,6 +49,9 @@ import org.asynchttpclient.filter.FilterContext.FilterContextBuilder;
|
||||
import org.asynchttpclient.filter.RequestFilter;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Manages endpoints managed by Velocity, along with initializing the Netty event loop group.
|
||||
*/
|
||||
public final class ConnectionManager {
|
||||
|
||||
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20,
|
||||
@@ -144,14 +147,14 @@ public final class ConnectionManager {
|
||||
* Binds a GS4 listener to the specified {@code hostname} and {@code port}.
|
||||
*
|
||||
* @param hostname the hostname to bind to
|
||||
* @param port the port to bind to
|
||||
* @param port the port to bind to
|
||||
*/
|
||||
public void queryBind(final String hostname, final int port) {
|
||||
InetSocketAddress address = new InetSocketAddress(hostname, port);
|
||||
final Bootstrap bootstrap = new Bootstrap()
|
||||
.channelFactory(this.transportType.datagramChannelFactory)
|
||||
.group(this.workerGroup)
|
||||
.handler(new GS4QueryHandler(this.server))
|
||||
.handler(new GameSpyQueryHandler(this.server))
|
||||
.localAddress(address);
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
@@ -173,7 +176,6 @@ public final class ConnectionManager {
|
||||
* Creates a TCP {@link Bootstrap} using Velocity's event loops.
|
||||
*
|
||||
* @param group the event loop group to use. Use {@code null} for the default worker group.
|
||||
*
|
||||
* @return a new {@link Bootstrap}
|
||||
*/
|
||||
public Bootstrap createWorker(@Nullable EventLoopGroup group) {
|
||||
|
@@ -17,6 +17,9 @@
|
||||
|
||||
package com.velocitypowered.proxy.network;
|
||||
|
||||
/**
|
||||
* Constants used for the pipeline.
|
||||
*/
|
||||
public class Connections {
|
||||
|
||||
public static final String CIPHER_DECODER = "cipher-decoder";
|
||||
|
@@ -25,6 +25,7 @@ import io.netty.channel.Channel;
|
||||
* Represents a listener endpoint.
|
||||
*/
|
||||
public final class Endpoint {
|
||||
|
||||
private final Channel channel;
|
||||
private final ListenerType type;
|
||||
|
||||
|
@@ -41,6 +41,9 @@ import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Server channel initializer.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class ServerChannelInitializer extends ChannelInitializer<Channel> {
|
||||
|
||||
|
@@ -23,6 +23,9 @@ import java.util.function.Supplier;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Holder for the server channel initializer.
|
||||
*/
|
||||
public class ServerChannelInitializerHolder implements Supplier<ChannelInitializer<Channel>> {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
|
||||
@@ -39,6 +42,7 @@ public class ServerChannelInitializerHolder implements Supplier<ChannelInitializ
|
||||
|
||||
/**
|
||||
* Sets the channel initializer.
|
||||
*
|
||||
* @param initializer the new initializer to use
|
||||
* @deprecated Internal implementation detail
|
||||
*/
|
||||
|
@@ -35,6 +35,9 @@ import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Enumerates the supported transports for Velocity.
|
||||
*/
|
||||
public enum TransportType {
|
||||
NIO("NIO", NioServerSocketChannel::new,
|
||||
NioSocketChannel::new,
|
||||
@@ -76,6 +79,11 @@ public enum TransportType {
|
||||
return new VelocityNettyThreadFactory("Netty " + name + ' ' + type.toString() + " #%d");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the "best" transport to initialize.
|
||||
*
|
||||
* @return the transport to use
|
||||
*/
|
||||
public static TransportType bestType() {
|
||||
if (Boolean.getBoolean("velocity.disable-native-transport")) {
|
||||
return NIO;
|
||||
@@ -83,13 +91,22 @@ public enum TransportType {
|
||||
|
||||
if (Epoll.isAvailable()) {
|
||||
return EPOLL;
|
||||
} else {
|
||||
return NIO;
|
||||
}
|
||||
|
||||
return NIO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event loop group types.
|
||||
*/
|
||||
public enum Type {
|
||||
/**
|
||||
* Accepts connections and distributes them to workers.
|
||||
*/
|
||||
BOSS("Boss"),
|
||||
/**
|
||||
* Thread that handles connections.
|
||||
*/
|
||||
WORKER("Worker");
|
||||
|
||||
private final String name;
|
||||
|
@@ -36,6 +36,11 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An implementation of {@code InetNameResolver} that performs blocking DNS name lookups
|
||||
* in a separate thread, avoiding blocking the Netty threads for an extended period of time
|
||||
* and without the downsides of Netty's native DNS resolver.
|
||||
*/
|
||||
public final class SeparatePoolInetNameResolver extends InetNameResolver {
|
||||
|
||||
private final ExecutorService resolveExecutor;
|
||||
@@ -46,8 +51,8 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver {
|
||||
/**
|
||||
* Creates a new instance of {@code SeparatePoolInetNameResolver}.
|
||||
*
|
||||
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link
|
||||
* Future} returned by {@link #resolve(String)}
|
||||
* @param executor the {@link EventExecutor} which is used to notify the listeners of the
|
||||
* {@link Future} returned by {@link #resolve(String)}
|
||||
*/
|
||||
public SeparatePoolInetNameResolver(EventExecutor executor) {
|
||||
super(executor);
|
||||
|
@@ -26,6 +26,9 @@ import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* The per-plugin class loader.
|
||||
*/
|
||||
public class PluginClassLoader extends URLClassLoader {
|
||||
|
||||
private static final Set<PluginClassLoader> loaders = new CopyOnWriteArraySet<>();
|
||||
|
@@ -53,6 +53,9 @@ import java.util.Set;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Handles loading plugins and provides a registry for loaded plugins.
|
||||
*/
|
||||
public class VelocityPluginManager implements PluginManager {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class);
|
||||
@@ -73,6 +76,7 @@ public class VelocityPluginManager implements PluginManager {
|
||||
|
||||
/**
|
||||
* Loads all plugins from the specified {@code directory}.
|
||||
*
|
||||
* @param directory the directory to load from
|
||||
* @throws IOException if we could not open the directory
|
||||
*/
|
||||
@@ -137,8 +141,8 @@ public class VelocityPluginManager implements PluginManager {
|
||||
bind(CommandManager.class).toInstance(server.getCommandManager());
|
||||
for (PluginContainer container : pluginContainers.keySet()) {
|
||||
bind(PluginContainer.class)
|
||||
.annotatedWith(Names.named(container.getDescription().getId()))
|
||||
.toInstance(container);
|
||||
.annotatedWith(Names.named(container.getDescription().getId()))
|
||||
.toInstance(container);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -46,21 +46,20 @@ public interface PluginLoader {
|
||||
PluginDescription createPluginFromCandidate(PluginDescription candidate) throws Exception;
|
||||
|
||||
/**
|
||||
* Creates a {@link Module} for the provided {@link PluginContainer}
|
||||
* and verifies that the container's {@link PluginDescription} is correct.
|
||||
* Creates a {@link Module} for the provided {@link PluginContainer} and verifies that the
|
||||
* container's {@link PluginDescription} is correct.
|
||||
*
|
||||
* <p>Does not create an instance of the plugin.</p>
|
||||
*
|
||||
* @param container the plugin container
|
||||
* @return the module containing bindings specific to this plugin
|
||||
* @throws IllegalArgumentException If the {@link PluginDescription}
|
||||
* is missing the path
|
||||
* @throws IllegalArgumentException If the {@link PluginDescription} is missing the path
|
||||
*/
|
||||
Module createModule(PluginContainer container) throws Exception;
|
||||
|
||||
/**
|
||||
* Creates an instance of the plugin as specified by the
|
||||
* plugin's main class found in the {@link PluginDescription}.
|
||||
* Creates an instance of the plugin as specified by the plugin's main class found in the
|
||||
* {@link PluginDescription}.
|
||||
*
|
||||
* <p>The provided {@link Module modules} are used to create an
|
||||
* injector which is then used to create the plugin instance.</p>
|
||||
@@ -69,8 +68,8 @@ public interface PluginLoader {
|
||||
*
|
||||
* @param container the plugin container
|
||||
* @param modules the modules to be used when creating this plugin's injector
|
||||
* @throws IllegalStateException If the plugin instance could not be
|
||||
* created from the provided modules
|
||||
* @throws IllegalStateException If the plugin instance could not be created from the provided
|
||||
* modules
|
||||
*/
|
||||
void createPlugin(PluginContainer container, Module... modules) throws Exception;
|
||||
}
|
||||
|
@@ -21,6 +21,9 @@ import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Implements {@link PluginContainer}.
|
||||
*/
|
||||
public class VelocityPluginContainer implements PluginContainer {
|
||||
|
||||
private final PluginDescription description;
|
||||
|
@@ -31,6 +31,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Implements {@link PluginDescription}.
|
||||
*/
|
||||
public class VelocityPluginDescription implements PluginDescription {
|
||||
|
||||
private final String id;
|
||||
@@ -44,14 +47,15 @@ public class VelocityPluginDescription implements PluginDescription {
|
||||
|
||||
/**
|
||||
* Creates a new plugin description.
|
||||
* @param id the ID
|
||||
* @param name the name of the plugin
|
||||
* @param version the plugin version
|
||||
* @param description a description of the plugin
|
||||
* @param url the website for the plugin
|
||||
* @param authors the authors of this plugin
|
||||
*
|
||||
* @param id the ID
|
||||
* @param name the name of the plugin
|
||||
* @param version the plugin version
|
||||
* @param description a description of the plugin
|
||||
* @param url the website for the plugin
|
||||
* @param authors the authors of this plugin
|
||||
* @param dependencies the dependencies for this plugin
|
||||
* @param source the original source for the plugin
|
||||
* @param source the original source for the plugin
|
||||
*/
|
||||
public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
||||
@Nullable String description, @Nullable String url,
|
||||
|
@@ -46,6 +46,9 @@ import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
/**
|
||||
* Implements loading a Java plugin.
|
||||
*/
|
||||
public class JavaPluginLoader implements PluginLoader {
|
||||
|
||||
private final ProxyServer server;
|
||||
@@ -122,7 +125,7 @@ public class JavaPluginLoader implements PluginLoader {
|
||||
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException(
|
||||
"Got nothing from injector for plugin " + description.getId());
|
||||
"Got nothing from injector for plugin " + description.getId());
|
||||
}
|
||||
|
||||
((VelocityPluginContainer) container).setInstance(instance);
|
||||
|
@@ -36,7 +36,7 @@ class VelocityPluginModule implements Module {
|
||||
private final Path basePluginPath;
|
||||
|
||||
VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description,
|
||||
PluginContainer pluginContainer, Path basePluginPath) {
|
||||
PluginContainer pluginContainer, Path basePluginPath) {
|
||||
this.server = server;
|
||||
this.description = description;
|
||||
this.pluginContainer = pluginContainer;
|
||||
|
@@ -32,6 +32,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles sorting plugin dependencies into an order that satisfies all dependencies.
|
||||
*/
|
||||
public class PluginDependencyUtils {
|
||||
|
||||
private PluginDependencyUtils() {
|
||||
@@ -39,8 +42,8 @@ public class PluginDependencyUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to topographically sort all plugins for the proxy to load in dependency order using
|
||||
* a depth-first search.
|
||||
* Attempts to topographically sort all plugins for the proxy to load in dependency order using a
|
||||
* depth-first search.
|
||||
*
|
||||
* @param candidates the plugins to sort
|
||||
* @return the sorted list of plugins
|
||||
|
@@ -21,6 +21,9 @@ import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a Minecraft packet.
|
||||
*/
|
||||
public interface MinecraftPacket {
|
||||
|
||||
void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion);
|
||||
|
@@ -45,6 +45,9 @@ import net.kyori.adventure.nbt.BinaryTagIO;
|
||||
import net.kyori.adventure.nbt.CompoundBinaryTag;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
|
||||
/**
|
||||
* Utilities for writing and reading data in the Minecraft protocol.
|
||||
*/
|
||||
public enum ProtocolUtils {
|
||||
;
|
||||
|
||||
@@ -73,6 +76,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads a Minecraft-style VarInt from the specified {@code buf}.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the decoded VarInt
|
||||
*/
|
||||
@@ -89,6 +93,7 @@ public enum ProtocolUtils {
|
||||
* Reads a Minecraft-style VarInt from the specified {@code buf}. The difference between this
|
||||
* method and {@link #readVarInt(ByteBuf)} is that this function returns a sentinel value if the
|
||||
* varint is invalid.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the decoded VarInt, or {@code Integer.MIN_VALUE} if the varint is invalid
|
||||
*/
|
||||
@@ -107,6 +112,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Returns the exact byte size of {@code value} if it were encoded as a VarInt.
|
||||
*
|
||||
* @param value the value to encode
|
||||
* @return the byte size of {@code value} if encoded as a VarInt
|
||||
*/
|
||||
@@ -116,7 +122,8 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Writes a Minecraft-style VarInt to the specified {@code buf}.
|
||||
* @param buf the buffer to read from
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @param value the integer to write
|
||||
*/
|
||||
public static void writeVarInt(ByteBuf buf, int value) {
|
||||
@@ -157,7 +164,8 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Writes the specified {@code value} as a 21-bit Minecraft VarInt to the specified {@code buf}.
|
||||
* The upper 11 bits will be discarded.
|
||||
* @param buf the buffer to read from
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @param value the integer to write
|
||||
*/
|
||||
public static void write21BitVarInt(ByteBuf buf, int value) {
|
||||
@@ -173,6 +181,7 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Reads a VarInt length-prefixed UTF-8 string from the {@code buf}, making sure to not go over
|
||||
* {@code cap} size.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @param cap the maximum size of the string, in UTF-8 character length
|
||||
* @return the decoded string
|
||||
@@ -200,6 +209,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Writes the specified {@code str} to the {@code buf} with a VarInt prefix.
|
||||
*
|
||||
* @param buf the buffer to write to
|
||||
* @param str the string to write
|
||||
*/
|
||||
@@ -216,6 +226,7 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Reads a VarInt length-prefixed byte array from the {@code buf}, making sure to not go over
|
||||
* {@code cap} size.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @param cap the maximum size of the string, in UTF-8 character length
|
||||
* @return the byte array
|
||||
@@ -239,6 +250,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads an VarInt-prefixed array of VarInt integers from the {@code buf}.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return an array of integers
|
||||
*/
|
||||
@@ -254,6 +266,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads an UUID from the {@code buf}.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the UUID from the buffer
|
||||
*/
|
||||
@@ -270,6 +283,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads an UUID stored as an Integer Array from the {@code buf}.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the UUID from the buffer
|
||||
*/
|
||||
@@ -285,7 +299,8 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Writes an UUID as an Integer Array to the {@code buf}.
|
||||
* @param buf the buffer to write to
|
||||
*
|
||||
* @param buf the buffer to write to
|
||||
* @param uuid the UUID to write
|
||||
*/
|
||||
public static void writeUuidIntArray(ByteBuf buf, UUID uuid) {
|
||||
@@ -297,7 +312,8 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads a {@link net.kyori.adventure.nbt.CompoundBinaryTag} from the {@code buf}.
|
||||
* @param buf the buffer to read from
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @param reader the NBT reader to use
|
||||
* @return {@link net.kyori.adventure.nbt.CompoundBinaryTag} the CompoundTag from the buffer
|
||||
*/
|
||||
@@ -306,13 +322,14 @@ public enum ProtocolUtils {
|
||||
return reader.read((DataInput) new ByteBufInputStream(buf));
|
||||
} catch (IOException thrown) {
|
||||
throw new DecoderException(
|
||||
"Unable to parse NBT CompoundTag, full error: " + thrown.getMessage());
|
||||
"Unable to parse NBT CompoundTag, full error: " + thrown.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a CompoundTag to the {@code buf}.
|
||||
* @param buf the buffer to write to
|
||||
*
|
||||
* @param buf the buffer to write to
|
||||
* @param compoundTag the CompoundTag to write
|
||||
*/
|
||||
public static void writeCompoundTag(ByteBuf buf, CompoundBinaryTag compoundTag) {
|
||||
@@ -325,6 +342,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads a String array from the {@code buf}.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the String array from the buffer
|
||||
*/
|
||||
@@ -339,7 +357,8 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Writes a String Array to the {@code buf}.
|
||||
* @param buf the buffer to write to
|
||||
*
|
||||
* @param buf the buffer to write to
|
||||
* @param stringArray the array to write
|
||||
*/
|
||||
public static void writeStringArray(ByteBuf buf, String[] stringArray) {
|
||||
@@ -351,7 +370,8 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Writes a list of {@link com.velocitypowered.api.util.GameProfile.Property} to the buffer.
|
||||
* @param buf the buffer to write to
|
||||
*
|
||||
* @param buf the buffer to write to
|
||||
* @param properties the properties to serialize
|
||||
*/
|
||||
public static void writeProperties(ByteBuf buf, List<GameProfile.Property> properties) {
|
||||
@@ -371,6 +391,7 @@ public enum ProtocolUtils {
|
||||
|
||||
/**
|
||||
* Reads a list of {@link com.velocitypowered.api.util.GameProfile.Property} from the buffer.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
* @return the read properties
|
||||
*/
|
||||
@@ -433,8 +454,8 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Writes an byte array for legacy version 1.7 to the specified {@code buf}
|
||||
*
|
||||
* @param b array
|
||||
* @param buf buf
|
||||
* @param b array
|
||||
* @param buf buf
|
||||
* @param allowExtended forge
|
||||
*/
|
||||
public static void writeByteArray17(byte[] b, ByteBuf buf, boolean allowExtended) {
|
||||
@@ -457,8 +478,8 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Writes an {@link ByteBuf} for legacy version 1.7 to the specified {@code buf}
|
||||
*
|
||||
* @param b array
|
||||
* @param buf buf
|
||||
* @param b array
|
||||
* @param buf buf
|
||||
* @param allowExtended forge
|
||||
*/
|
||||
public static void writeByteBuf17(ByteBuf b, ByteBuf buf, boolean allowExtended) {
|
||||
@@ -497,7 +518,7 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Writes a Minecraft-style extended short to the specified {@code buf}.
|
||||
*
|
||||
* @param buf buf to write
|
||||
* @param buf buf to write
|
||||
* @param toWrite the extended short to write
|
||||
*/
|
||||
public static void writeExtendedForgeShort(ByteBuf buf, int toWrite) {
|
||||
@@ -524,8 +545,8 @@ public enum ProtocolUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate {@link GsonComponentSerializer} for the given protocol version. This
|
||||
* is used to constrain messages sent to older clients.
|
||||
* Returns the appropriate {@link GsonComponentSerializer} for the given protocol version. This is
|
||||
* used to constrain messages sent to older clients.
|
||||
*
|
||||
* @param version the protocol version used by the client.
|
||||
* @return the appropriate {@link GsonComponentSerializer}
|
||||
@@ -540,7 +561,7 @@ public enum ProtocolUtils {
|
||||
/**
|
||||
* Writes a players {@link IdentifiedKey} to the buffer.
|
||||
*
|
||||
* @param buf the buffer
|
||||
* @param buf the buffer
|
||||
* @param playerKey the key to write
|
||||
*/
|
||||
public static void writePlayerKey(ByteBuf buf, IdentifiedKey playerKey) {
|
||||
@@ -560,18 +581,15 @@ public enum ProtocolUtils {
|
||||
byte[] key = ProtocolUtils.readByteArray(buf);
|
||||
byte[] signature = ProtocolUtils.readByteArray(buf, 4096);
|
||||
IdentifiedKey.Revision revision = version.compareTo(ProtocolVersion.MINECRAFT_1_19) == 0
|
||||
? IdentifiedKey.Revision.GENERIC_V1 : IdentifiedKey.Revision.LINKED_V2;
|
||||
? IdentifiedKey.Revision.GENERIC_V1 : IdentifiedKey.Revision.LINKED_V2;
|
||||
return new IdentifiedKeyImpl(revision, key, expiry, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the direction in which a packet flows.
|
||||
*/
|
||||
public enum Direction {
|
||||
SERVERBOUND,
|
||||
CLIENTBOUND;
|
||||
|
||||
public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(StateRegistry state,
|
||||
ProtocolVersion version) {
|
||||
return (this == SERVERBOUND ? state.serverbound : state.clientbound)
|
||||
.getProtocolRegistry(version);
|
||||
}
|
||||
CLIENTBOUND
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,8 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9_4;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINIMUM_VERSION;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.SUPPORTED_VERSIONS;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.CLIENTBOUND;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.SERVERBOUND;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
|
||||
@@ -95,6 +97,9 @@ import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Registry of all Minecraft protocol states and the packets for each state.
|
||||
*/
|
||||
public enum StateRegistry {
|
||||
|
||||
HANDSHAKE {
|
||||
@@ -397,9 +402,17 @@ public enum StateRegistry {
|
||||
|
||||
public static final int STATUS_ID = 1;
|
||||
public static final int LOGIN_ID = 2;
|
||||
public final PacketRegistry clientbound = new PacketRegistry(Direction.CLIENTBOUND);
|
||||
public final PacketRegistry serverbound = new PacketRegistry(Direction.SERVERBOUND);
|
||||
protected final PacketRegistry clientbound = new PacketRegistry(CLIENTBOUND);
|
||||
protected final PacketRegistry serverbound = new PacketRegistry(SERVERBOUND);
|
||||
|
||||
public StateRegistry.PacketRegistry.ProtocolRegistry getProtocolRegistry(Direction direction,
|
||||
ProtocolVersion version) {
|
||||
return (direction == SERVERBOUND ? serverbound : clientbound).getProtocolRegistry(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Packet registry.
|
||||
*/
|
||||
public static class PacketRegistry {
|
||||
|
||||
private final Direction direction;
|
||||
@@ -431,7 +444,7 @@ public enum StateRegistry {
|
||||
}
|
||||
|
||||
<P extends MinecraftPacket> void register(Class<P> clazz, Supplier<P> packetSupplier,
|
||||
PacketMapping... mappings) {
|
||||
PacketMapping... mappings) {
|
||||
if (mappings.length == 0) {
|
||||
throw new IllegalArgumentException("At least one mapping must be provided.");
|
||||
}
|
||||
@@ -490,6 +503,9 @@ public enum StateRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protocol registry.
|
||||
*/
|
||||
public class ProtocolRegistry {
|
||||
|
||||
public final ProtocolVersion version;
|
||||
@@ -537,6 +553,9 @@ public enum StateRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packet mapping.
|
||||
*/
|
||||
public static final class PacketMapping {
|
||||
|
||||
private final int id;
|
||||
@@ -545,7 +564,7 @@ public enum StateRegistry {
|
||||
private final @Nullable ProtocolVersion lastValidProtocolVersion;
|
||||
|
||||
PacketMapping(int id, ProtocolVersion protocolVersion,
|
||||
ProtocolVersion lastValidProtocolVersion, boolean packetDecoding) {
|
||||
ProtocolVersion lastValidProtocolVersion, boolean packetDecoding) {
|
||||
this.id = id;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.lastValidProtocolVersion = lastValidProtocolVersion;
|
||||
@@ -604,7 +623,7 @@ public enum StateRegistry {
|
||||
* @return PacketMapping with the provided arguments
|
||||
*/
|
||||
private static PacketMapping map(int id, ProtocolVersion version,
|
||||
ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) {
|
||||
ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) {
|
||||
return new PacketMapping(id, version, lastValidProtocolVersion, encodeOnly);
|
||||
}
|
||||
|
||||
|
@@ -24,8 +24,8 @@ import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* A variation on {@link io.netty.handler.flow.FlowControlHandler} that explicitly holds messages
|
||||
* on {@code channelRead} and only releases them on an explicit read operation.
|
||||
* A variation on {@link io.netty.handler.flow.FlowControlHandler} that explicitly holds messages on
|
||||
* {@code channelRead} and only releases them on an explicit read operation.
|
||||
*/
|
||||
public class AutoReadHolderHandler extends ChannelDuplexHandler {
|
||||
|
||||
|
@@ -48,7 +48,10 @@ import java.util.stream.Collectors;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
/**
|
||||
* Implements the GameSpy protocol for Velocity.
|
||||
*/
|
||||
public class GameSpyQueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
|
||||
private static final short QUERY_MAGIC_FIRST = 0xFE;
|
||||
private static final short QUERY_MAGIC_SECOND = 0xFD;
|
||||
@@ -76,14 +79,15 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
private final SecureRandom random;
|
||||
private final VelocityServer server;
|
||||
|
||||
public GS4QueryHandler(VelocityServer server) {
|
||||
public GameSpyQueryHandler(VelocityServer server) {
|
||||
this.server = server;
|
||||
this.random = new SecureRandom();
|
||||
}
|
||||
|
||||
private QueryResponse createInitialResponse() {
|
||||
return QueryResponse.builder()
|
||||
.hostname(PlainTextComponentSerializer.plainText().serialize(server.getConfiguration().getMotd()))
|
||||
.hostname(
|
||||
PlainTextComponentSerializer.plainText().serialize(server.getConfiguration().getMotd()))
|
||||
.gameVersion(ProtocolVersion.SUPPORTED_VERSION_STRING)
|
||||
.map(server.getConfiguration().getQueryMap())
|
||||
.currentPlayers(server.getPlayerCount())
|
||||
@@ -264,7 +268,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
if (isBasic) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
StringBuilder pluginsString = new StringBuilder();
|
||||
pluginsString.append(serverVersion).append(':').append(' ');
|
||||
Iterator<QueryResponse.PluginInformation> iterator = plugins.iterator();
|
@@ -29,6 +29,9 @@ import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Decodes Minecraft 1.3-1.6.4 server ping requests.
|
||||
*/
|
||||
public class LegacyPingDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private static final String MC_1_6_CHANNEL = "MC|PingHost";
|
||||
|
@@ -24,6 +24,9 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Encodes {@code LegacyDisconnect} for Minecraft 1.3-1.6.4.
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user