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:
Andrew Steinborn
2023-01-01 17:53:37 -05:00
committed by GitHub
parent ffa4c95435
commit d72d707b1c
300 changed files with 2880 additions and 2169 deletions

View File

@@ -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
View 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.+")
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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(

View File

@@ -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));
}

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -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());

View File

@@ -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);
}

View File

@@ -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() + '\''
+ '}';
}
}

View File

@@ -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()) + '\''
+ '}';
}
}

View File

@@ -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);
}
}

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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());

View File

@@ -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);

View File

@@ -17,6 +17,9 @@
package com.velocitypowered.proxy.config;
/**
* Supported passthrough modes for ping passthrough.
*/
public enum PingPassthroughMode {
DISABLED,
MODS,

View File

@@ -17,6 +17,9 @@
package com.velocitypowered.proxy.config;
/**
* Supported player info forwarding methods.
*/
public enum PlayerInfoForwarding {
NONE,
LEGACY,

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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) {

View File

@@ -17,6 +17,9 @@
package com.velocitypowered.proxy.connection;
/**
* Marker interface for something that can be associated with a {@link MinecraftConnection}.
*/
public interface MinecraftConnectionAssociation {
}

View File

@@ -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() {

View File

@@ -17,6 +17,9 @@
package com.velocitypowered.proxy.connection;
/**
* Various useful constants.
*/
public class VelocityConstants {
private VelocityConstants() {

View File

@@ -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) {
}
}

View File

@@ -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

View File

@@ -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 ->

View File

@@ -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

View File

@@ -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);

View File

@@ -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,

View File

@@ -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.
*/

View File

@@ -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!");
}
}
}

View File

@@ -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() {

View File

@@ -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();

View File

@@ -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;
});
}
/**

View File

@@ -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(

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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";

View File

@@ -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();

View File

@@ -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
*/

View File

@@ -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();

View File

@@ -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;

View File

@@ -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
}
*/
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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

View File

@@ -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() {

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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));

View File

@@ -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) {

View File

@@ -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());
}
}

View File

@@ -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)
+ '}';
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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
*/

View File

@@ -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;

View File

@@ -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);

View File

@@ -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<>();

View File

@@ -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);
}
}
};

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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";

View File

@@ -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