3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-24 15:20:35 +01:00
Dieser Commit ist enthalten in:
Leymooo 2018-09-24 12:40:48 +03:00
Commit 46c02c9895
100 geänderte Dateien mit 1825 neuen und 1568 gelöschten Zeilen

180
.gitignore vendored
Datei anzeigen

@ -1,137 +1,87 @@
# Created by https://www.gitignore.io/api/java,gradle,intellij
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/**/compiler.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
.idea/
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
/out/
*.iml
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
.externalToolBuilders/
*.launch
.factorypath
.recommenders/
.apt_generated/
.project
.classpath
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Linux ###
*~
.fuse_hidden*
.directory
.Trash-*
.nfs*
# Editor-based Rest Client
.idea/httpRequests
### macOS ###
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
*.iml
modules.xml
.idea/misc.xml
*.ipr
.idea/vcs.xml
# Sonarlint plugin
.idea/sonarlint
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Eclipse #
**/.classpath
**/.project
**/.settings/
**/bin/
# NetBeans Gradle#
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.lnk
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
/build/
/out/
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
# End of https://www.gitignore.io/api/java,gradle,intellij
# Other trash
### Other trash ###
logs/
/velocity.toml
server-icon.png
/bin/
run/
plugins/

31
Jenkinsfile vendored
Datei anzeigen

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

Datei anzeigen

@ -3,9 +3,8 @@
[![Build Status](https://img.shields.io/jenkins/s/https/ci.velocitypowered.com/job/velocity/job/master.svg)](https://ci.velocitypowered.com/job/velocity/job/master/)
[![Join our Discord](https://img.shields.io/discord/472484458856185878.svg?logo=discord&label=)](https://discord.gg/8cB9Bgf)
Velocity is a next-generation Minecraft: Java Edition proxy suite. It is
designed specifically for enhanced server support and scalability whilst
not compromising flexibility.
A Minecraft server proxy with unparalleled server support, scalability,
and flexibility.
Velocity is licensed under the MIT license for ultimate permissiveness
and expanding the pool of potential contributors and users.
@ -40,8 +39,7 @@ page.
## Status
Velocity is currently in an alpha state: it is prone to change at any time and
is not currently suitable for production usage. For development and testing
purposes, however, Velocity is fully-fledged and ready to go.
is currently only suitable for small servers and development/testing.
Velocity supports Minecraft 1.8-1.13.1, and has full support for Paper and Sponge.
Forge is fully supported but mod compatibility may vary.

Datei anzeigen

@ -19,7 +19,8 @@ import javax.tools.StandardLocation;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.Objects;
import java.util.Set;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor {

Datei anzeigen

@ -26,4 +26,19 @@ public interface Command {
default List<String> suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) {
return ImmutableList.of();
}
/**
* Tests to check if the {@code source} has permission to use this command
* with the provided {@code args}.
*
* <p>If this method returns false, the handling will be forwarded onto
* the players current server.</p>
*
* @param source the source of the command
* @param args the arguments for this command
* @return whether the source has permission
*/
default boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
return true;
}
}

Datei anzeigen

@ -0,0 +1,105 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
/**
* This event is fired when a plugin message is sent to the proxy, either from a client ({@link com.velocitypowered.api.proxy.Player})
* or a server ({@link com.velocitypowered.api.proxy.ServerConnection}).
*/
public class PluginMessageEvent implements ResultedEvent<PluginMessageEvent.ForwardResult> {
private final ChannelMessageSource source;
private final ChannelMessageSink target;
private final ChannelIdentifier identifier;
private final byte[] data;
private ForwardResult result;
public PluginMessageEvent(ChannelMessageSource source, ChannelMessageSink target, ChannelIdentifier identifier, byte[] data) {
this.source = Preconditions.checkNotNull(source, "source");
this.target = Preconditions.checkNotNull(target, "target");
this.identifier = Preconditions.checkNotNull(identifier, "identifier");
this.data = Preconditions.checkNotNull(data, "data");
this.result = ForwardResult.forward();
}
@Override
public ForwardResult getResult() {
return result;
}
@Override
public void setResult(@NonNull ForwardResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public ChannelMessageSource getSource() {
return source;
}
public ChannelMessageSink getTarget() {
return target;
}
public ChannelIdentifier getIdentifier() {
return identifier;
}
public byte[] getData() {
return Arrays.copyOf(data, data.length);
}
public ByteArrayDataInput dataAsDataStream() {
return ByteStreams.newDataInput(data);
}
@Override
public String toString() {
return "PluginMessageEvent{" +
"source=" + source +
", target=" + target +
", identifier=" + identifier +
", data=" + Arrays.toString(data) +
", result=" + result +
'}';
}
/**
* A result determining whether or not to forward this message on.
*/
public static class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true);
private static final ForwardResult DENIED = new ForwardResult(false);
private final boolean allowed;
private ForwardResult(boolean b) {
this.allowed = b;
}
@Override
public boolean isAllowed() {
return allowed;
}
@Override
public String toString() {
return allowed ? "forward to sink" : "handled message at proxy";
}
public static ForwardResult forward() {
return ALLOWED;
}
public static ForwardResult handled() {
return DENIED;
}
}
}

Datei anzeigen

@ -0,0 +1,28 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
/**
* This event is fired once the player has been successfully authenticated and
* fully initialized and player will be connected to server after this event
*/
public class PostLoginEvent {
private final Player player;
public PostLoginEvent(Player player) {
this.player = Preconditions.checkNotNull(player, "player");
}
public Player getPlayer() {
return player;
}
@Override
public String toString() {
return "PostLoginEvent{"
+ "player=" + player
+ '}';
}
}

Datei anzeigen

@ -3,12 +3,13 @@ package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/**
* This event is fired when a player has initiated a connection with the proxy but before the proxy authenticates the
* player with Mojang or before the player's proxy connection is fully established (for offline mode).
@ -52,44 +53,59 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
}
/**
* Represents an "allowed/allowed with online mode/denied" result with a reason allowed for denial.
* Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial.
*/
public static class PreLoginComponentResult extends ResultedEvent.ComponentResult {
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
public static class PreLoginComponentResult implements ResultedEvent.Result {
private final boolean onlineMode;
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult(Result.ALLOWED, null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(Result.FORCE_ONLINE, null);
private static final PreLoginComponentResult FORCE_OFFLINEMODE = new PreLoginComponentResult(Result.FORCE_OFFLINE, null);
/**
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode.
* @param allowedOnlineMode if true, online mode will be used for the connection
*/
private PreLoginComponentResult(boolean allowedOnlineMode) {
super(true, null);
this.onlineMode = allowedOnlineMode;
private final Result result;
private final Optional<Component> reason;
private PreLoginComponentResult(Result result, @Nullable Component reason) {
this.result = result;
this.reason = Optional.ofNullable(reason);
}
private PreLoginComponentResult(@Nullable Component reason) {
super(reason == null, reason);
// Don't care about this
this.onlineMode = false;
@Override
public boolean isAllowed() {
return result != Result.DISALLOWED;
}
public Optional<Component> getReason() {
return reason;
}
public boolean isOnlineModeAllowed() {
return this.onlineMode;
return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
}
@Override
public String toString() {
if (isForceOfflineMode()) {
return "allowed with force offline mode";
}
if (isOnlineModeAllowed()) {
return "allowed with online mode";
}
return super.toString();
if (isAllowed()) {
return "allowed";
}
if (reason.isPresent()) {
return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get());
}
return "denied";
}
/**
* Returns a result indicating the connection will be allowed through the proxy.
* Returns a result indicating the connection will be allowed through
* the proxy.
* @return the allowed result
*/
public static PreLoginComponentResult allowed() {
@ -97,23 +113,41 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
}
/**
* Returns a result indicating the connection will be allowed through the proxy, but the connection will be
* forced to use online mode provided that the proxy is in offline mode. This acts similarly to {@link #allowed()}
* on an online-mode proxy.
* Returns a result indicating the connection will be allowed through
* the proxy, but the connection will be forced to use online mode
* provided that the proxy is in offline mode. This acts similarly to
* {@link #allowed()} on an online-mode proxy.
* @return the result
*/
public static PreLoginComponentResult forceOnlineMode() {
return FORCE_ONLINEMODE;
}
/**
* Returns a result indicating the connection will be allowed through
* the proxy, but the connection will be forced to use offline mode even
* when proxy running in online mode
* @return the result
*/
public static PreLoginComponentResult forceOfflineMode() {
return FORCE_OFFLINEMODE;
}
/**
* Denies the login with the specified reason.
* @param reason the reason for disallowing the connection
* @return a new result
*/
public static PreLoginComponentResult denied(@NonNull Component reason) {
public static PreLoginComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new PreLoginComponentResult(reason);
return new PreLoginComponentResult(Result.DISALLOWED, reason);
}
private enum Result {
ALLOWED,
FORCE_ONLINE,
FORCE_OFFLINE,
DISALLOWED
}
}
}

Datei anzeigen

@ -1,10 +1,9 @@
package com.velocitypowered.api.event.player;
import com.velocitypowered.api.proxy.InboundConnection;
import org.checkerframework.checker.nullness.qual.Nullable;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.util.GameProfile;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in order to set up the

Datei anzeigen

@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -13,12 +13,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*/
public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> {
private final Player player;
private final ServerInfo server;
private final RegisteredServer server;
private final Component originalReason;
private final boolean duringLogin;
private ServerKickResult result;
public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) {
public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) {
this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server");
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return player;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}
@ -91,9 +91,9 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* when this result is used.
*/
public static class RedirectPlayer implements ServerKickResult {
private final ServerInfo server;
private final RegisteredServer server;
private RedirectPlayer(ServerInfo server) {
private RedirectPlayer(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "server");
}
@ -102,7 +102,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return false;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}
@ -111,7 +111,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* @param server the server to send the player to
* @return the redirect result
*/
public static RedirectPlayer create(ServerInfo server) {
public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server);
}
}

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.PlayerSettings;
public class PlayerSettingsChangedEvent {
private final Player player;

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
/**
* This event is fired once the player has successfully connected to the target server and the connection to the previous
@ -10,9 +10,9 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
*/
public class ServerConnectedEvent {
private final Player player;
private final ServerInfo server;
private final RegisteredServer server;
public ServerConnectedEvent(Player player, ServerInfo server) {
public ServerConnectedEvent(Player player, RegisteredServer server) {
this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server");
}
@ -21,7 +21,7 @@ public class ServerConnectedEvent {
return player;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}

Datei anzeigen

@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -14,11 +14,13 @@ import java.util.Optional;
*/
public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> {
private final Player player;
private final RegisteredServer originalServer;
private ServerResult result;
public ServerPreConnectEvent(Player player, ServerResult result) {
public ServerPreConnectEvent(Player player, RegisteredServer originalServer) {
this.player = Preconditions.checkNotNull(player, "player");
this.result = Preconditions.checkNotNull(result, "result");
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
this.result = ServerResult.allowed(originalServer);
}
public Player getPlayer() {
@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
this.result = Preconditions.checkNotNull(result, "result");
}
public RegisteredServer getOriginalServer() {
return originalServer;
}
@Override
public String toString() {
return "ServerPreConnectEvent{" +
"player=" + player +
", originalServer=" + originalServer +
", result=" + result +
'}';
}
@ -50,11 +57,11 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
private static final ServerResult DENIED = new ServerResult(false, null);
private final boolean allowed;
private final ServerInfo info;
private final RegisteredServer server;
private ServerResult(boolean allowed, @Nullable ServerInfo info) {
private ServerResult(boolean allowed, @Nullable RegisteredServer server) {
this.allowed = allowed;
this.info = info;
this.server = server;
}
@Override
@ -62,8 +69,8 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
return allowed;
}
public Optional<ServerInfo> getInfo() {
return Optional.ofNullable(info);
public Optional<RegisteredServer> getServer() {
return Optional.ofNullable(server);
}
@Override
@ -71,14 +78,14 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
if (!allowed) {
return "denied";
}
return "allowed: connect to " + info.getName();
return "allowed: connect to " + server.getServerInfo().getName();
}
public static ServerResult denied() {
return DENIED;
}
public static ServerResult allowed(ServerInfo server) {
public static ServerResult allowed(RegisteredServer server) {
Preconditions.checkNotNull(server, "server");
return new ServerResult(true, server);
}

Datei anzeigen

@ -29,5 +29,5 @@ public interface PermissionFunction {
* @param permission the permission
* @return the value the permission is set to
*/
@NonNull Tristate getPermissionSetting(@NonNull String permission);
@NonNull Tristate getPermissionValue(@NonNull String permission);
}

Datei anzeigen

@ -12,5 +12,15 @@ public interface PermissionSubject {
* @param permission the permission to check for
* @return whether or not the subject has the permission
*/
boolean hasPermission(@NonNull String permission);
default boolean hasPermission(@NonNull String permission) {
return getPermissionValue(permission).asBoolean();
}
/**
* Gets the subjects setting for a particular permission.
*
* @param permission the permission
* @return the value the permission is set to
*/
@NonNull Tristate getPermissionValue(@NonNull String permission);
}

Datei anzeigen

@ -1,7 +1,6 @@
package com.velocitypowered.api.plugin.meta;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;

Datei anzeigen

@ -1,29 +1,28 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/**
* Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection
* request is created using {@link Player#createConnectionRequest(ServerInfo)}.
* request is created using {@link Player#createConnectionRequest(RegisteredServer)}.
*/
public interface ConnectionRequestBuilder {
/**
* Returns the server that this connection request represents.
* @return the server this request will connect to
*/
@NonNull ServerInfo getServer();
RegisteredServer getServer();
/**
* Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user
* has logged on. No messages will be communicated to the client: the user is responsible for all error handling.
* @return a {@link CompletableFuture} representing the status of this connection
*/
@NonNull CompletableFuture<Result> connect();
CompletableFuture<Result> connect();
/**
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error

Datei anzeigen

@ -1,13 +1,15 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.util.title.Title;
import java.util.List;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -65,10 +67,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
/**
* Creates a new connection request so that the player can connect to another server.
* @param info the server to connect to
* @param server the server to connect to
* @return a new connection request
*/
ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info);
ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server);
/**
* Gets a game profile properties of player
@ -100,4 +102,17 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
* @param reason component with the reason
*/
void disconnect(Component reason);
/**
* Sends the specified title to the client.
* @param title the title to send
*/
void sendTitle(Title title);
/**
* Sends chat input onto the players current server as if they typed it
* into the client chat box.
* @param input the chat input to send
*/
void spoofChatInput(String input);
}

Datei anzeigen

@ -1,12 +1,14 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.Scheduler;
import net.kyori.text.Component;
import java.net.InetSocketAddress;
import java.util.Collection;
@ -31,6 +33,12 @@ public interface ProxyServer {
*/
Optional<Player> getPlayer(UUID uuid);
/**
* Broadcasts a message to all players currently online.
* @param component the message to send
*/
void broadcast(Component component);
/**
* Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players
* online.
@ -45,23 +53,24 @@ public interface ProxyServer {
int getPlayerCount();
/**
* Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive.
* Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive.
* @param name the name of the server
* @return the registered server, which may be empty
*/
Optional<ServerInfo> getServerInfo(String name);
Optional<RegisteredServer> getServer(String name);
/**
* Retrieves all {@link ServerInfo}s registered with this proxy.
* Retrieves all {@link RegisteredServer}s registered with this proxy.
* @return the servers registered with this proxy
*/
Collection<ServerInfo> getAllServers();
Collection<RegisteredServer> getAllServers();
/**
* Registers a server with this proxy. A server with this name should not already exist.
* @param server the server to register
* @return the newly registered server
*/
void registerServer(ServerInfo server);
RegisteredServer registerServer(ServerInfo server);
/**
* Unregisters this server from the proxy.

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
/**
@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi
* Returns the server that this connection is connected to.
* @return the server this connection is connected to
*/
RegisteredServer getServer();
/**
* Returns the server info for this connection.
* @return the server info for this connection
*/
ServerInfo getServerInfo();
/**

Datei anzeigen

@ -8,6 +8,7 @@ public interface ChannelMessageSink {
* Sends a plugin message to this target.
* @param identifier the channel identifier to send the message on
* @param data the data to send
* @return whether or not the message could be sent
*/
void sendPluginMessage(ChannelIdentifier identifier, byte[] data);
boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data);
}

Datei anzeigen

@ -1,16 +1,14 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from
* the client or the server.
* Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on.
*/
public interface ChannelRegistrar {
/**
* Registers the specified message handler to listen for plugin messages on the specified channels.
* @param handler the handler to register
* Registers the specified message identifiers to listen on for the
* @param identifiers the channel identifiers to register
*/
void register(MessageHandler handler, ChannelIdentifier... identifiers);
void register(ChannelIdentifier... identifiers);
/**
* Unregisters the handler for the specified channel.

Datei anzeigen

@ -1,15 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents from "which side" of the proxy the plugin message came from.
*/
public enum ChannelSide {
/**
* The plugin message came from a server that a client was connected to.
*/
FROM_SERVER,
/**
* The plugin message came from the client.
*/
FROM_CLIENT
}

Datei anzeigen

@ -1,28 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents a handler for handling plugin messages.
*/
public interface MessageHandler {
/**
* Handles an incoming plugin message.
* @param source the source of the plugin message
* @param side from where the plugin message originated
* @param identifier the channel on which the message was sent
* @param data the data inside the plugin message
* @return a {@link ForwardStatus} indicating whether or not to forward this plugin message on
*/
ForwardStatus handle(ChannelMessageSource source, ChannelSide side, ChannelIdentifier identifier, byte[] data);
enum ForwardStatus {
/**
* Forwards this plugin message on to the client or server, depending on the {@link ChannelSide} it originated
* from.
*/
FORWARD,
/**
* Discard the plugin message and do not forward it on.
*/
HANDLED
}
}

Datei anzeigen

@ -10,7 +10,7 @@ import java.util.regex.Pattern;
* Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use.
*/
public final class MinecraftChannelIdentifier implements ChannelIdentifier {
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+", Pattern.CASE_INSENSITIVE);
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
private final String namespace;
private final String name;

Datei anzeigen

@ -0,0 +1,30 @@
package com.velocitypowered.api.proxy.server;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
/**
* Represents a server that has been registered with the proxy.
*/
public interface RegisteredServer extends ChannelMessageSink {
/**
* Returns the {@link ServerInfo} for this server.
* @return the server info
*/
ServerInfo getServerInfo();
/**
* Returns a list of all the players currently connected to this server on this proxy.
* @return the players on this proxy
*/
Collection<Player> getPlayersConnected();
/**
* Attempts to ping the remote server and return the server list ping result.
* @return the server ping result from the server
*/
CompletableFuture<ServerPing> ping();
}

Datei anzeigen

@ -16,13 +16,13 @@ public class ServerPing {
private final Players players;
private final Component description;
private final @Nullable Favicon favicon;
private final Modinfo modinfo;
private final ModInfo modinfo;
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) {
this(version, players, description, favicon, Modinfo.DEFAULT);
this(version, players, description, favicon, ModInfo.DEFAULT);
}
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, @Nullable Modinfo modinfo) {
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, ServerPing.@Nullable ModInfo modinfo) {
this.version = Preconditions.checkNotNull(version, "version");
this.players = players;
this.description = Preconditions.checkNotNull(description, "description");
@ -46,7 +46,7 @@ public class ServerPing {
return Optional.ofNullable(favicon);
}
public Optional<Modinfo> getModinfo() {
public Optional<ModInfo> getModinfo() {
return Optional.ofNullable(modinfo);
}
@ -74,6 +74,7 @@ public class ServerPing {
builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null;
if (modinfo != null) {
builder.modType = modinfo.type;
builder.mods.addAll(modinfo.modList);
}
return builder;
@ -91,6 +92,7 @@ public class ServerPing {
private int onlinePlayers;
private int maximumPlayers;
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType;
private final List<Mod> mods = new ArrayList<>();
private Component description;
private Favicon favicon;
@ -121,6 +123,11 @@ public class ServerPing {
return this;
}
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(Mod... mods) {
this.mods.addAll(Arrays.asList(mods));
return this;
@ -158,7 +165,7 @@ public class ServerPing {
public ServerPing build() {
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon,
nullOutModinfo ? null : new Modinfo(mods));
nullOutModinfo ? null : new ModInfo(modType, mods));
}
public Version getVersion() {
@ -185,6 +192,10 @@ public class ServerPing {
return favicon;
}
public String getModType() {
return modType;
}
public List<Mod> getMods() {
return mods;
}
@ -196,6 +207,7 @@ public class ServerPing {
", onlinePlayers=" + onlinePlayers +
", maximumPlayers=" + maximumPlayers +
", samplePlayers=" + samplePlayers +
", modType=" + modType +
", mods=" + mods +
", description=" + description +
", favicon=" + favicon +
@ -290,15 +302,24 @@ public class ServerPing {
}
}
public static class Modinfo {
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of());
public static class ModInfo {
public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
private final String type = "FML";
private final String type;
private final List<Mod> modList;
public Modinfo(List<Mod> modList) {
public ModInfo(String type, List<Mod> modList) {
this.type = Preconditions.checkNotNull(type, "type");
this.modList = ImmutableList.copyOf(modList);
}
public String getType() {
return type;
}
public List<Mod> getMods() {
return modList;
}
}
public static class Mod {
@ -309,5 +330,13 @@ public class ServerPing {
this.id = Preconditions.checkNotNull(id, "id");
this.version = Preconditions.checkNotNull(version, "version");
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
}
}

Datei anzeigen

@ -24,7 +24,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder delay(int time, TimeUnit unit);
TaskBuilder delay(long time, TimeUnit unit);
/**
* Specifies that the task should continue running after waiting for the specified amount, until it is cancelled.
@ -32,7 +32,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder repeat(int time, TimeUnit unit);
TaskBuilder repeat(long time, TimeUnit unit);
/**
* Clears the delay on this task.

Datei anzeigen

@ -1,66 +0,0 @@
package com.velocitypowered.api.util;
import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.regex.Pattern;
/**
* LegacyChatColorUtils contains utilities for handling legacy Minecraft color codes. Generally, you should prefer
* JSON-based components, but for convenience Velocity provides a limited set of tools to handle Minecraft color codes.
*/
public class LegacyChatColorUtils {
private LegacyChatColorUtils() {
throw new AssertionError();
}
/**
* Represents the legacy Minecraft format character, the section symbol.
*/
public static final char FORMAT_CHAR = '\u00a7';
/**
* Translates a string with Minecraft color codes prefixed with a different character than the section symbol into
* a string that uses the section symbol.
* @param originalChar the char the color codes are prefixed by
* @param text the text to translate
* @return the translated text
*/
public static String translate(char originalChar, @NonNull String text) {
Preconditions.checkNotNull(text, "text");
char[] textChars = text.toCharArray();
int foundSectionIdx = -1;
for (int i = 0; i < textChars.length; i++) {
char textChar = textChars[i];
if (textChar == originalChar) {
foundSectionIdx = i;
continue;
}
if (foundSectionIdx >= 0) {
textChar = Character.toLowerCase(textChar);
if ((textChar >= 'a' && textChar <= 'f') || (textChar >= '0' && textChar <= '9') ||
(textChar >= 'l' && textChar <= 'o' || textChar == 'r')) {
textChars[foundSectionIdx] = FORMAT_CHAR;
}
foundSectionIdx = -1;
}
}
return new String(textChars);
}
/**
* A regex that matches all Minecraft color codes and removes them.
*/
private static final Pattern CHAT_COLOR_MATCHER = Pattern.compile("(?i)" + Character.toString(FORMAT_CHAR) + "[0-9A-FL-OR]");
/**
* Removes all Minecraft color codes from the string.
* @param text the text to remove color codes from
* @return a new String without Minecraft color codes
*/
public static String removeFormatting(@NonNull String text) {
Preconditions.checkNotNull(text, "text");
return CHAT_COLOR_MATCHER.matcher(text).replaceAll("");
}
}

Datei anzeigen

@ -0,0 +1,236 @@
package com.velocitypowered.api.util.title;
import com.google.common.base.Preconditions;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a "full" title, including all components. This class is immutable.
*/
public class TextTitle implements Title {
private final Component title;
private final Component subtitle;
private final int stay;
private final int fadeIn;
private final int fadeOut;
private final boolean resetBeforeSend;
private TextTitle(Builder builder) {
this.title = builder.title;
this.subtitle = builder.subtitle;
this.stay = builder.stay;
this.fadeIn = builder.fadeIn;
this.fadeOut = builder.fadeOut;
this.resetBeforeSend = builder.resetBeforeSend;
}
/**
* Returns the main title this title has, if any.
* @return the main title of this title
*/
public Optional<Component> getTitle() {
return Optional.ofNullable(title);
}
/**
* Returns the subtitle this title has, if any.
* @return the subtitle
*/
public Optional<Component> getSubtitle() {
return Optional.ofNullable(subtitle);
}
/**
* Returns the number of ticks this title will stay up.
* @return how long the title will stay, in ticks
*/
public int getStay() {
return stay;
}
/**
* Returns the number of ticks over which this title will fade in.
* @return how long the title will fade in, in ticks
*/
public int getFadeIn() {
return fadeIn;
}
/**
* Returns the number of ticks over which this title will fade out.
* @return how long the title will fade out, in ticks
*/
public int getFadeOut() {
return fadeOut;
}
/**
* Returns whether or not a reset packet will be sent before this title is sent. By default, unless explicitly
* disabled, this is enabled by default.
* @return whether or not a reset packet will be sent before this title is sent
*/
public boolean isResetBeforeSend() {
return resetBeforeSend;
}
/**
* Determines whether or not this title has times set on it. If none are set, it will update the previous title
* set on the client.
* @return whether or not this title has times set on it
*/
public boolean areTimesSet() {
return stay != 0 || fadeIn != 0 || fadeOut != 0;
}
/**
* Creates a new builder from the contents of this title so that it may be changed.
* @return a builder instance with the contents of this title
*/
public Builder toBuilder() {
return new Builder(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TextTitle textTitle = (TextTitle) o;
return stay == textTitle.stay &&
fadeIn == textTitle.fadeIn &&
fadeOut == textTitle.fadeOut &&
resetBeforeSend == textTitle.resetBeforeSend &&
Objects.equals(title, textTitle.title) &&
Objects.equals(subtitle, textTitle.subtitle);
}
@Override
public String toString() {
return "TextTitle{" +
"title=" + title +
", subtitle=" + subtitle +
", stay=" + stay +
", fadeIn=" + fadeIn +
", fadeOut=" + fadeOut +
", resetBeforeSend=" + resetBeforeSend +
'}';
}
@Override
public int hashCode() {
return Objects.hash(title, subtitle, stay, fadeIn, fadeOut, resetBeforeSend);
}
/**
* Creates a new builder for constructing titles.
* @return a builder for constructing titles
*/
public static Builder builder() {
return new Builder();
}
public static class Builder {
private @Nullable Component title;
private @Nullable Component subtitle;
private int stay;
private int fadeIn;
private int fadeOut;
private boolean resetBeforeSend = true;
private Builder() {}
private Builder(TextTitle copy) {
this.title = copy.title;
this.subtitle = copy.subtitle;
this.stay = copy.stay;
this.fadeIn = copy.fadeIn;
this.fadeOut = copy.fadeOut;
this.resetBeforeSend = copy.resetBeforeSend;
}
public Builder title(Component title) {
this.title = Preconditions.checkNotNull(title, "title");
return this;
}
public Builder clearTitle() {
this.title = null;
return this;
}
public Builder subtitle(Component subtitle) {
this.subtitle = Preconditions.checkNotNull(subtitle, "subtitle");
return this;
}
public Builder clearSubtitle() {
this.subtitle = null;
return this;
}
public Builder stay(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
this.stay = ticks;
return this;
}
public Builder fadeIn(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
this.fadeIn = ticks;
return this;
}
public Builder fadeOut(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
this.fadeOut = ticks;
return this;
}
public Builder resetBeforeSend(boolean b) {
this.resetBeforeSend = b;
return this;
}
public Component getTitle() {
return title;
}
public Component getSubtitle() {
return subtitle;
}
public int getStay() {
return stay;
}
public int getFadeIn() {
return fadeIn;
}
public int getFadeOut() {
return fadeOut;
}
public boolean isResetBeforeSend() {
return resetBeforeSend;
}
public TextTitle build() {
return new TextTitle(this);
}
@Override
public String toString() {
return "Builder{" +
"title=" + title +
", subtitle=" + subtitle +
", stay=" + stay +
", fadeIn=" + fadeIn +
", fadeOut=" + fadeOut +
", resetBeforeSend=" + resetBeforeSend +
'}';
}
}
}

Datei anzeigen

@ -0,0 +1,7 @@
package com.velocitypowered.api.util.title;
/**
* Represents a title that can be sent to a Minecraft client.
*/
public interface Title {
}

Datei anzeigen

@ -0,0 +1,50 @@
package com.velocitypowered.api.util.title;
/**
* Provides special-purpose titles.
*/
public class Titles {
private Titles() {
throw new AssertionError();
}
private static final Title RESET = new Title() {
@Override
public String toString() {
return "reset title";
}
};
private static final Title HIDE = new Title() {
@Override
public String toString() {
return "hide title";
}
};
/**
* Returns a title that, when sent to the client, will cause all title data to be reset and any existing title to be
* hidden.
* @return the reset title
*/
public static Title reset() {
return RESET;
}
/**
* Returns a title that, when sent to the client, will cause any existing title to be hidden. The title may be
* restored by a {@link TextTitle} with no title or subtitle (only a time).
* @return the hide title
*/
public static Title hide() {
return HIDE;
}
/**
* Returns a builder for {@link TextTitle}s.
* @return a builder for text titles
*/
public static TextTitle.Builder text() {
return TextTitle.builder();
}
}

Datei anzeigen

@ -0,0 +1,4 @@
/**
* Provides data structures for creating and manipulating titles.
*/
package com.velocitypowered.api.util.title;

Datei anzeigen

@ -1,62 +0,0 @@
package com.velocitypowered.api.util;
import com.velocitypowered.api.util.LegacyChatColorUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LegacyChatColorUtilsTest {
private static final String NON_FORMATTED = "Velocity";
private static final String FORMATTED = "\u00a7cVelocity";
private static final String FORMATTED_MULTIPLE = "\u00a7c\u00a7lVelocity";
private static final String FORMATTED_MULTIPLE_VARIED = "\u00a7c\u00a7lVelo\u00a7a\u00a7mcity";
private static final String INVALID = "\u00a7gVelocity";
private static final String RAW_SECTION = "\u00a7";
@Test
void removeFormattingNonFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(NON_FORMATTED));
}
@Test
void removeFormattingFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED));
}
@Test
void removeFormattingFormattedMultiple() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE));
}
@Test
void removeFormattingFormattedMultipleVaried() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE_VARIED));
}
@Test
void removeFormattingInvalidFormat() {
assertEquals(INVALID, LegacyChatColorUtils.removeFormatting(INVALID));
}
@Test
void removeFormattingRawSection() {
assertEquals(RAW_SECTION, LegacyChatColorUtils.removeFormatting(RAW_SECTION));
}
@Test
void translate() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('&', "&cVelocity"));
}
@Test
void translateMultiple() {
assertEquals(FORMATTED_MULTIPLE, LegacyChatColorUtils.translate('&', "&c&lVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('&', "&c&lVelo&a&mcity"));
}
@Test
void translateDifferentChar() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('$', "$cVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('$', "$c$lVelo$a$mcity"));
}
}

Datei anzeigen

@ -8,7 +8,7 @@ import java.util.function.Supplier;
public class NativeCodeLoader<T> implements Supplier<T> {
private final List<Variant<T>> variants;
private Variant<T> selected;
private volatile Variant<T> selected;
public NativeCodeLoader(List<Variant<T>> variants) {
this.variants = ImmutableList.copyOf(variants);
@ -16,36 +16,41 @@ public class NativeCodeLoader<T> implements Supplier<T> {
@Override
public T get() {
if (selected == null) {
selected = select();
}
return selected.object;
return tryLoad().object;
}
private Variant<T> select() {
for (Variant<T> variant : variants) {
T got = variant.get();
if (got == null) {
continue;
}
return variant;
private Variant<T> tryLoad() {
if (selected != null) {
return selected;
}
synchronized (this) {
if (selected != null) {
return selected;
}
for (Variant<T> variant : variants) {
T got = variant.get();
if (got == null) {
continue;
}
selected = variant;
return selected;
}
throw new IllegalArgumentException("Can't find any suitable variants");
}
throw new IllegalArgumentException("Can't find any suitable variants");
}
public String getLoadedVariant() {
if (selected == null) {
selected = select();
}
return selected.name;
return tryLoad().name;
}
static class Variant<T> {
private boolean available;
private volatile boolean available;
private final Runnable setup;
private final String name;
private final T object;
private boolean hasBeenSetup = false;
private volatile boolean hasBeenSetup = false;
Variant(BooleanSupplier available, Runnable setup, String name, T object) {
this.available = available.getAsBoolean();
@ -54,27 +59,34 @@ public class NativeCodeLoader<T> implements Supplier<T> {
this.object = object;
}
private void setup() {
if (available && !hasBeenSetup) {
try {
setup.run();
hasBeenSetup = true;
} catch (Exception e) {
available = false;
public T get() {
if (!available) {
return null;
}
// Make sure setup happens only once
if (!hasBeenSetup) {
synchronized (this) {
// We change availability if need be below, may as well check it again here for sanity.
if (!available) {
return null;
}
// Okay, now try the setup if we haven't done so yet.
if (!hasBeenSetup) {
try {
setup.run();
hasBeenSetup = true;
return object;
} catch (Exception e) {
available = false;
return null;
}
}
}
}
}
public T get() {
if (!hasBeenSetup) {
setup();
}
if (available) {
return object;
}
return null;
return object;
}
}

Datei anzeigen

@ -3,10 +3,8 @@ package com.velocitypowered.natives.util;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import java.io.IOException;

Datei anzeigen

@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import java.util.Random;
import java.util.function.Supplier;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;

Datei anzeigen

@ -1,6 +1,5 @@
package com.velocitypowered.proxy;
import com.velocitypowered.proxy.console.VelocityConsole;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -28,6 +27,6 @@ public class Velocity {
double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
new VelocityConsole(server).start();
server.getConsoleCommandSource().start();
}
}

Datei anzeigen

@ -4,40 +4,43 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.command.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.config.AnnotatedConfig;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.messages.VelocityChannelRegistrar;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.plugin.VelocityEventManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.scheduler.VelocityScheduler;
import com.velocitypowered.proxy.server.ServerMap;
import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.Ratelimiter;
import com.velocitypowered.proxy.util.ServerMap;
import io.netty.bootstrap.Bootstrap;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.ComponentSerializers;
import net.kyori.text.serializer.GsonComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.net.InetSocketAddress;
import java.nio.file.Files;
@ -61,7 +64,7 @@ public class VelocityServer implements ProxyServer {
private VelocityConfiguration configuration;
private NettyHttpClient httpClient;
private KeyPair serverKeyPair;
private final ServerMap servers = new ServerMap();
private final ServerMap servers = new ServerMap(this);
private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = false;
@ -69,17 +72,7 @@ public class VelocityServer implements ProxyServer {
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private final CommandSource consoleCommandSource = new CommandSource() {
@Override
public void sendMessage(Component component) {
logger.info(ComponentSerializers.LEGACY.serialize(component));
}
@Override
public boolean hasPermission(String permission) {
return true;
}
};
private final VelocityConsole console = new VelocityConsole(this);
private Ratelimiter ipAttemptLimiter;
private VelocityEventManager eventManager;
private VelocityScheduler scheduler;
@ -144,6 +137,9 @@ public class VelocityServer implements ProxyServer {
// issues) and there is almost no chance ExecutionException will be thrown.
}
// init console permissions after plugins are loaded
console.setupPermissions();
this.cm.bind(configuration.getBind());
if (configuration.isQueryEnabled()) {
@ -252,6 +248,15 @@ public class VelocityServer implements ProxyServer {
return Optional.ofNullable(connectionsByUuid.get(uuid));
}
@Override
public void broadcast(Component component) {
Preconditions.checkNotNull(component, "component");
Chat chat = Chat.createClientbound(component);
for (ConnectedPlayer player : connectionsByUuid.values()) {
player.getConnection().write(chat);
}
}
@Override
public Collection<Player> getAllPlayers() {
return ImmutableList.copyOf(connectionsByUuid.values());
@ -263,19 +268,19 @@ public class VelocityServer implements ProxyServer {
}
@Override
public Optional<ServerInfo> getServerInfo(String name) {
public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "name");
return servers.getServer(name);
}
@Override
public Collection<ServerInfo> getAllServers() {
public Collection<RegisteredServer> getAllServers() {
return servers.getAllServers();
}
@Override
public void registerServer(ServerInfo server) {
servers.register(server);
public RegisteredServer registerServer(ServerInfo server) {
return servers.register(server);
}
@Override
@ -284,8 +289,8 @@ public class VelocityServer implements ProxyServer {
}
@Override
public CommandSource getConsoleCommandSource() {
return consoleCommandSource;
public VelocityConsole getConsoleCommandSource() {
return console;
}
@Override

Datei anzeigen

@ -3,14 +3,17 @@ package com.velocitypowered.proxy.command;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Optional;
@ -34,7 +37,7 @@ public class ServerCommand implements Command {
if (args.length == 1) {
// Trying to connect to a server.
String serverName = args[0];
Optional<ServerInfo> toConnect = server.getServerInfo(serverName);
Optional<RegisteredServer> toConnect = server.getServer(serverName);
if (!toConnect.isPresent()) {
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
return;
@ -48,17 +51,19 @@ public class ServerCommand implements Command {
// Assemble the list of servers as components
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
List<ServerInfo> infos = ImmutableList.copyOf(server.getAllServers());
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
for (int i = 0; i < infos.size(); i++) {
ServerInfo serverInfo = infos.get(i);
TextComponent infoComponent = TextComponent.of(serverInfo.getName());
if (serverInfo.getName().equals(currentServer)) {
RegisteredServer rs = infos.get(i);
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
String playersText = rs.getPlayersConnected().size() + " player(s) online";
if (rs.getServerInfo().getName().equals(currentServer)) {
infoComponent = infoComponent.color(TextColor.GREEN)
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Currently connected to this server")));
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
TextComponent.of("Currently connected to this server\n" + playersText)));
} else {
infoComponent = infoComponent.color(TextColor.GRAY)
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName()))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server")));
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName()))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
}
serverListBuilder.append(infoComponent);
if (i != infos.size() - 1) {
@ -74,15 +79,20 @@ public class ServerCommand implements Command {
public List<String> suggest(CommandSource source, String[] currentArgs) {
if (currentArgs.length == 0) {
return server.getAllServers().stream()
.map(ServerInfo::getName)
.map(rs -> rs.getServerInfo().getName())
.collect(Collectors.toList());
} else if (currentArgs.length == 1) {
return server.getAllServers().stream()
.map(ServerInfo::getName)
.map(rs -> rs.getServerInfo().getName())
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList());
} else {
return ImmutableList.of();
}
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
}
}

Datei anzeigen

@ -5,6 +5,7 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ShutdownCommand implements Command {
private final VelocityServer server;
@ -21,4 +22,9 @@ public class ShutdownCommand implements Command {
}
server.shutdown();
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
return source == server.getConsoleCommandSource();
}
}

Datei anzeigen

@ -2,10 +2,12 @@ package com.velocitypowered.proxy.command;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
public class VelocityCommand implements Command {
@Override
@ -37,4 +39,9 @@ public class VelocityCommand implements Command {
source.sendMessage(velocityInfo);
source.sendMessage(velocityWebsite);
}
@Override
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
}
}

Datei anzeigen

@ -1,10 +1,9 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import java.util.*;
import java.util.stream.Collectors;
@ -47,6 +46,10 @@ public class VelocityCommandManager implements CommandManager {
}
try {
if (!command.hasPermission(source, actualArgs)) {
return false;
}
command.execute(source, actualArgs);
return true;
} catch (Exception e) {
@ -63,23 +66,29 @@ public class VelocityCommandManager implements CommandManager {
return Optional.empty();
}
String command = split[0];
String alias = split[0];
if (split.length == 1) {
return Optional.of(commands.keySet().stream()
.filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length()))
return Optional.of(commands.entrySet().stream()
.filter(ent -> ent.getKey().regionMatches(true, 0, alias, 0, alias.length()))
.filter(ent -> ent.getValue().hasPermission(source, new String[0]))
.map(ent -> "/" + ent.getKey())
.collect(Collectors.toList()));
}
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
Command executor = commands.get(command);
if (executor == null) {
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
return Optional.empty();
}
try {
return Optional.of(executor.suggest(source, actualArgs));
if (!command.hasPermission(source, actualArgs)) {
return Optional.empty();
}
return Optional.of(command.suggest(source, actualArgs));
} catch (Exception e) {
throw new RuntimeException("Unable to invoke suggestions for command " + command + " for " + source, e);
throw new RuntimeException("Unable to invoke suggestions for command " + alias + " for " + source, e);
}
}
}

Datei anzeigen

@ -1,5 +1,8 @@
package com.velocitypowered.proxy.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Only for simple configs

Datei anzeigen

@ -5,10 +5,9 @@ import com.google.common.collect.ImmutableMap;
import com.moandjiezana.toml.Toml;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.api.util.LegacyChatColorUtils;
import io.netty.buffer.ByteBufUtil;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.Reader;
@ -17,12 +16,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.logging.log4j.Logger;
import java.util.*;
public class VelocityConfiguration extends AnnotatedConfig {
@ -273,6 +267,14 @@ public class VelocityConfiguration extends AnnotatedConfig {
return announceForge;
}
public int getConnectTimeout() {
return advanced.getConnectionTimeout();
}
public int getReadTimeout() {
return advanced.getReadTimeout();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@ -420,21 +422,23 @@ public class VelocityConfiguration extends AnnotatedConfig {
"Disable by setting to 0"})
@ConfigKey("login-ratelimit")
private int loginRatelimit = 3000;
@Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."})
@ConfigKey("connection-timeout")
private int connectionTimeout = 5000;
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
@ConfigKey("read-timeout")
private int readTimeout = 30000;
private Advanced() {
}
private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) {
this.compressionThreshold = compressionThreshold;
this.compressionLevel = compressionLevel;
this.loginRatelimit = loginRatelimit;
}
private Advanced(Toml toml) {
if (toml != null) {
this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue();
this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
}
}
@ -442,33 +446,31 @@ public class VelocityConfiguration extends AnnotatedConfig {
return compressionThreshold;
}
public void setCompressionThreshold(int compressionThreshold) {
this.compressionThreshold = compressionThreshold;
}
public int getCompressionLevel() {
return compressionLevel;
}
public void setCompressionLevel(int compressionLevel) {
this.compressionLevel = compressionLevel;
}
public int getLoginRatelimit() {
return loginRatelimit;
}
public void setLoginRatelimit(int loginRatelimit) {
this.loginRatelimit = loginRatelimit;
public int getConnectionTimeout() {
return connectionTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
@Override
public String toString() {
return "Advanced{"
+ "compressionThreshold=" + compressionThreshold
+ ", compressionLevel=" + compressionLevel
+ ", loginRatelimit=" + loginRatelimit
+ '}';
return "Advanced{" +
"compressionThreshold=" + compressionThreshold +
", compressionLevel=" + compressionLevel +
", loginRatelimit=" + loginRatelimit +
", connectionTimeout=" + connectionTimeout +
", readTimeout=" + readTimeout +
'}';
}
}
@ -500,18 +502,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
return queryEnabled;
}
public void setQueryEnabled(boolean queryEnabled) {
this.queryEnabled = queryEnabled;
}
public int getQueryPort() {
return queryPort;
}
public void setQueryPort(int queryPort) {
this.queryPort = queryPort;
}
@Override
public String toString() {
return "Query{"

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.proxy.protocol.netty.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER;
import static com.velocitypowered.proxy.network.Connections.CIPHER_ENCODER;
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_DECODER;
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_ENCODER;
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
import static com.velocitypowered.proxy.network.Connections.*;
/**
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
@ -99,8 +91,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (association != null) {
logger.error("{}: exception encountered", association, cause);
} else {
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
}
ctx.close();

Datei anzeigen

@ -8,6 +8,8 @@ public class VelocityConstants {
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS";
public static final String FORGE_LEGACY_CHANNEL = "FML";
public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP";
public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
}

Datei anzeigen

@ -1,15 +1,16 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide;
import com.velocitypowered.api.proxy.messages.MessageHandler;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf;
@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void activated() {
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo()));
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer()));
connection.getServer().addPlayer(connection.getPlayer());
}
@Override
@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors.
connection.getMinecraftConnection().close();
connection.disconnect();
return;
}
@ -40,11 +42,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
(ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler();
if (packet instanceof KeepAlive) {
// Forward onto the player
playerHandler.setLastPing(((KeepAlive) packet).getRandomId());
connection.setLastPingId(((KeepAlive) packet).getRandomId());
connection.getPlayer().getConnection().write(packet);
} else if (packet instanceof Disconnect) {
Disconnect original = (Disconnect) packet;
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original);
connection.disconnect();
connection.getPlayer().handleConnectionException(connection.getServer(), original);
} else if (packet instanceof JoinGame) {
playerHandler.handleBackendJoinGame((JoinGame) packet);
} else if (packet instanceof BossBar) {
@ -83,11 +86,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return;
}
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection,
ChannelSide.FROM_SERVER, pm);
if (status == MessageHandler.ForwardStatus.FORWARD) {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel());
if (id == null) {
connection.getPlayer().getConnection().write(pm);
} else {
PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), id, pm.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
connection.getPlayer().getConnection().write(pm);
}
}, connection.getMinecraftConnection().getChannel().eventLoop());
}
} else if (packet instanceof TabCompleteResponse) {
playerHandler.handleTabCompleteResponse((TabCompleteResponse) packet);
} else if (connection.hasCompletedJoin()) {
// Just forward the packet on. We don't have anything to handle at this time.
connection.getPlayer().getConnection().write(packet);
@ -99,7 +111,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors.
connection.getMinecraftConnection().close();
connection.disconnect();
return;
}
@ -110,7 +122,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void exception(Throwable throwable) {
connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable);
connection.getPlayer().handleConnectionException(connection.getServer(), throwable);
}
public VelocityServer getServer() {
return server;
}
@Override
public void disconnected() {
connection.getServer().removePlayer(connection.getPlayer());
if (!connection.isGracefulDisconnect()) {
connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create(
ConnectionMessages.UNEXPECTED_DISCONNECT));
}
}
private boolean canForwardPluginMessage(PluginMessage message) {

Datei anzeigen

@ -1,18 +1,18 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.kyori.text.TextComponent;
@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
if (existingConnection == null) {
// Strap on the play session handler
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer()));
// This is for legacy Forge servers - during first connection the FML handshake will transition to complete regardless
// Thus, we need to ensure that a reset packet is ALWAYS sent on first switch.
//
// The call will handle if the player is not a Forge player appropriately.
connection.getPlayer().getConnection().setCanSendLegacyFMLResetPacket(true);
} else {
// The previous server connection should become obsolete.
// Before we remove it, if the server we are departing is modded, we must always reset the client state.

Datei anzeigen

@ -4,9 +4,14 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import io.netty.channel.*;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey;
@ -27,27 +31,24 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static com.velocitypowered.proxy.VelocityServer.GSON;
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
import static com.velocitypowered.proxy.network.Connections.HANDLER;
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
import static com.velocitypowered.proxy.network.Connections.SERVER_READ_TIMEOUT_SECONDS;
import static com.velocitypowered.proxy.network.Connections.*;
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
AttributeKey.newInstance("connection-notification-result");
private final ServerInfo serverInfo;
private final VelocityRegisteredServer registeredServer;
private final ConnectedPlayer proxyPlayer;
private final VelocityServer server;
private MinecraftConnection minecraftConnection;
private boolean legacyForge = false;
private boolean hasCompletedJoin = false;
private boolean gracefulDisconnect = false;
private long lastPingId;
private long lastPingSent;
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.serverInfo = target;
public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.registeredServer = registeredServer;
this.proxyPlayer = proxyPlayer;
this.server = server;
}
@ -55,12 +56,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
server.initializeGenericBootstrap()
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
@ -73,7 +73,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
ch.pipeline().addLast(HANDLER, connection);
}
})
.connect(serverInfo.getAddress())
.connect(registeredServer.getServerInfo().getAddress())
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
@ -95,7 +95,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
return serverInfo.getAddress().getHostString() + "\0" +
return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
proxyPlayer.getProfile().getId() + "\0" +
GSON.toJson(proxyPlayer.getProfile().getProperties());
@ -113,9 +113,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
} else if (proxyPlayer.getConnection().isLegacyForge()) {
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
} else {
handshake.setServerAddress(serverInfo.getAddress().getHostString());
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
}
handshake.setPort(serverInfo.getAddress().getPort());
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
minecraftConnection.write(handshake);
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
@ -127,12 +127,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
minecraftConnection.write(login);
}
public void writeIfJoined(PluginMessage message) {
if (hasCompletedJoin) {
minecraftConnection.write(message);
}
}
public MinecraftConnection getMinecraftConnection() {
return minecraftConnection;
}
@Override
public VelocityRegisteredServer getServer() {
return registeredServer;
}
@Override
public ServerInfo getServerInfo() {
return serverInfo;
return registeredServer.getServerInfo();
}
@Override
@ -141,23 +153,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
}
public void disconnect() {
minecraftConnection.close();
minecraftConnection = null;
if (minecraftConnection != null) {
minecraftConnection.close();
minecraftConnection = null;
gracefulDisconnect = true;
}
}
@Override
public String toString() {
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName();
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
}
@Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId());
message.setData(data);
minecraftConnection.write(message);
return true;
}
public boolean isLegacyForge() {
@ -175,4 +191,25 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public void setHasCompletedJoin(boolean hasCompletedJoin) {
this.hasCompletedJoin = hasCompletedJoin;
}
public boolean isGracefulDisconnect() {
return gracefulDisconnect;
}
public long getLastPingId() {
return lastPingId;
}
public long getLastPingSent() {
return lastPingSent;
}
public void setLastPingId(long lastPingId) {
this.lastPingId = lastPingId;
this.lastPingSent = System.currentTimeMillis();
}
public void resetLastPingId() {
this.lastPingId = -1;
}
}

Datei anzeigen

@ -1,14 +1,15 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide;
import com.velocitypowered.api.proxy.messages.MessageHandler;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.ThrowableUtils;
import io.netty.buffer.ByteBuf;
@ -28,12 +29,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private static final int MAX_PLUGIN_CHANNELS = 1024;
private final ConnectedPlayer player;
private long lastPingID = -1;
private long lastPingSent = -1;
private boolean spawned = false;
private final List<UUID> serverBossBars = new ArrayList<>();
private final Set<String> clientPluginMsgChannels = new HashSet<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server;
private TabCompleteRequest outstandingTabComplete;
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
this.player = player;
@ -53,16 +54,22 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public void handle(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (packet instanceof KeepAlive) {
KeepAlive keepAlive = (KeepAlive) packet;
if (keepAlive.getRandomId() != lastPingID) {
if (keepAlive.getRandomId() != serverConnection.getLastPingId()) {
// The last keep alive we got was probably from a different server. Let's ignore it, and hope the next
// ping is alright.
return;
}
player.setPing(System.currentTimeMillis() - lastPingSent);
resetPingData();
player.getConnectedServer().getMinecraftConnection().write(packet);
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
serverConnection.getMinecraftConnection().write(packet);
serverConnection.resetLastPingId();
return;
}
@ -92,31 +99,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
if (packet instanceof TabCompleteRequest) {
TabCompleteRequest req = (TabCompleteRequest) packet;
int lastSpace = req.getCommand().indexOf(' ');
if (!req.isAssumeCommand() && lastSpace != -1) {
String command = req.getCommand().substring(1);
try {
Optional<List<String>> offers = server.getCommandManager().offerSuggestions(player, command);
if (offers.isPresent()) {
TabCompleteResponse response = new TabCompleteResponse();
response.setTransactionId(req.getTransactionId());
response.setStart(lastSpace);
response.setLength(req.getCommand().length() - lastSpace);
for (String s : offers.get()) {
response.getOffers().add(new TabCompleteResponse.Offer(s, null));
}
player.getConnection().write(response);
} else {
player.getConnectedServer().getMinecraftConnection().write(packet);
}
} catch (Exception e) {
logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e);
}
return;
}
// Record the request so that the outstanding request can be augmented later.
outstandingTabComplete = (TabCompleteRequest) packet;
serverConnection.getMinecraftConnection().write(packet);
}
if (packet instanceof PluginMessage) {
@ -125,15 +110,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
// If we don't want to handle this packet, just forward it on.
if (player.getConnectedServer().hasCompletedJoin()) {
player.getConnectedServer().getMinecraftConnection().write(packet);
if (serverConnection.hasCompletedJoin()) {
serverConnection.getMinecraftConnection().write(packet);
}
}
@Override
public void handleUnknown(ByteBuf buf) {
if (player.getConnectedServer().hasCompletedJoin()) {
player.getConnectedServer().getMinecraftConnection().write(buf.retain());
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (serverConnection.hasCompletedJoin()) {
serverConnection.getMinecraftConnection().write(buf.retain());
}
}
@ -162,11 +153,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
public void handleBackendJoinGame(JoinGame joinGame) {
resetPingData(); // reset ping data;
if (!spawned) {
// nothing special to do here
// Nothing special to do with regards to spawning the player
spawned = true;
player.getConnection().delayedWrite(joinGame);
// We have something special to do for legacy Forge servers - during first connection the FML handshake
// will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on
// first switch.
//
// As we know that calling this branch only happens on first join, we set that if we are a Forge
// client that we must reset on the next switch.
//
// The call will handle if the player is not a Forge player appropriately.
player.getConnection().setCanSendLegacyFMLResetPacket(true);
} else {
// Ah, this is the meat and potatoes of the whole venture!
//
@ -210,6 +210,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
channel, toRegister));
}
// If we had plugin messages queued during login/FML handshake, send them now.
PluginMessage pm;
while ((pm = loginPluginMessages.poll()) != null) {
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
}
// Clear any title from the previous server.
player.getConnection().delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
// Flush everything
player.getConnection().flush();
player.getConnectedServer().getMinecraftConnection().flush();
@ -234,8 +243,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return serverBossBars;
}
public void handleClientPluginMessage(PluginMessage packet) {
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) {
private void handleClientPluginMessage(PluginMessage packet) {
if (PluginMessageUtil.isMCRegister(packet)) {
List<String> actuallyRegistered = new ArrayList<>();
List<String> channels = PluginMessageUtil.getChannels(packet);
for (String channel : channels) {
@ -252,31 +261,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
}
return;
}
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
} else if (PluginMessageUtil.isMCUnregister(packet)) {
List<String> channels = PluginMessageUtil.getChannels(packet);
clientPluginMsgChannels.removeAll(channels);
}
if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getMinecraftConnection().write(packet);
} else if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
return;
}
if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
// Ensure that the messages are forwarded
player.getConnectedServer().getMinecraftConnection().write(packet);
return;
}
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player,
ChannelSide.FROM_CLIENT, packet);
if (status == MessageHandler.ForwardStatus.FORWARD) {
// We're going to forward on the original packet.
player.getConnectedServer().getMinecraftConnection().write(packet);
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
// Always forward the FML handshake to the remote server.
player.getConnectedServer().getMinecraftConnection().write(packet);
} else {
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
// be sent once the JoinGame packet has been received by the proxy.
loginPluginMessages.add(packet);
}
} else {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
player.getConnectedServer().getMinecraftConnection().write(packet);
} else {
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData());
server.getEventManager().fire(event)
.thenAcceptAsync(pme -> {
if (pme.getResult().isAllowed()) {
player.getConnectedServer().getMinecraftConnection().write(packet);
}
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
}
}
}
@ -284,13 +297,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return clientPluginMsgChannels;
}
public void setLastPing(long lastPing) {
this.lastPingID = lastPing;
this.lastPingSent = System.currentTimeMillis();
}
private void resetPingData() {
this.lastPingID = -1;
this.lastPingSent = -1;
public void handleTabCompleteResponse(TabCompleteResponse response) {
if (outstandingTabComplete != null) {
if (!outstandingTabComplete.isAssumeCommand()) {
String command = outstandingTabComplete.getCommand().substring(1);
try {
Optional<List<String>> offers = server.getCommandManager().offerSuggestions(player, command);
offers.ifPresent(strings -> response.getOffers().addAll(strings));
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(),
command, e);
}
outstandingTabComplete = null;
}
player.getConnection().write(response);
}
}
}

Datei anzeigen

@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.player.SkinParts;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import java.util.Locale;
public class ClientSettingsWrapper implements PlayerSettings {

Datei anzeigen

@ -7,28 +7,29 @@ import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.PermissionProvider;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.util.title.TextTitle;
import com.velocitypowered.api.util.title.Title;
import com.velocitypowered.api.util.title.Titles;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.util.ThrowableUtils;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent;
@ -143,11 +144,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
byte pos = (byte) position.ordinal();
String json;
if (position == MessagePosition.ACTION_BAR) {
// Due to issues with action bar packets, we'll need to convert the text message into a legacy message
// and then inject the legacy text into a component... yuck!
JsonObject object = new JsonObject();
object.addProperty("text", ComponentSerializers.LEGACY.serialize(component));
json = VelocityServer.GSON.toJson(object);
if (getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_11) {
// We can use the title packet instead.
TitlePacket pkt = new TitlePacket();
pkt.setAction(TitlePacket.SET_ACTION_BAR);
pkt.setComponent(ComponentSerializers.JSON.serialize(component));
connection.write(pkt);
return;
} else {
// Due to issues with action bar packets, we'll need to convert the text message into a legacy message
// and then inject the legacy text into a component... yuck!
JsonObject object = new JsonObject();
object.addProperty("text", ComponentSerializers.LEGACY.serialize(component));
json = VelocityServer.GSON.toJson(object);
}
} else {
json = ComponentSerializers.JSON.serialize(component);
}
@ -159,8 +169,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) {
return new ConnectionRequestBuilderImpl(info);
public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
return new ConnectionRequestBuilderImpl(server);
}
@Override
@ -191,56 +201,103 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.closeWith(Disconnect.create(reason));
}
@Override
public void sendTitle(Title title) {
Preconditions.checkNotNull(title, "title");
if (title.equals(Titles.reset())) {
connection.write(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
} else if (title.equals(Titles.hide())) {
connection.write(TitlePacket.hideForProtocolVersion(connection.getProtocolVersion()));
} else if (title instanceof TextTitle) {
TextTitle tt = (TextTitle) title;
if (tt.isResetBeforeSend()) {
connection.delayedWrite(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
}
if (tt.getTitle().isPresent()) {
TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_TITLE);
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getTitle().get()));
connection.delayedWrite(titlePkt);
}
if (tt.getSubtitle().isPresent()) {
TitlePacket titlePkt = new TitlePacket();
titlePkt.setAction(TitlePacket.SET_SUBTITLE);
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getSubtitle().get()));
connection.delayedWrite(titlePkt);
}
if (tt.areTimesSet()) {
TitlePacket timesPkt = TitlePacket.timesForProtocolVersion(connection.getProtocolVersion());
timesPkt.setFadeIn(tt.getFadeIn());
timesPkt.setStay(tt.getStay());
timesPkt.setFadeOut(tt.getFadeOut());
connection.delayedWrite(timesPkt);
}
connection.flush();
} else {
throw new IllegalArgumentException("Unknown title class " + title.getClass().getName());
}
}
public VelocityServerConnection getConnectedServer() {
return connectedServer;
}
public void handleConnectionException(ServerInfo info, Throwable throwable) {
public void handleConnectionException(RegisteredServer server, Throwable throwable) {
String error = ThrowableUtils.briefDescription(throwable);
String userMessage;
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
userMessage = "Exception in server " + info.getName();
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
userMessage = "Exception in server " + server.getServerInfo().getName();
} else {
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
userMessage = "Exception connecting to server " + info.getName();
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable);
userMessage = "Exception connecting to server " + server.getServerInfo().getName();
}
handleConnectionException(info, null, TextComponent.builder()
handleConnectionException(server, null, TextComponent.builder()
.content(userMessage + ": ")
.color(TextColor.RED)
.append(TextComponent.of(error, TextColor.WHITE))
.build());
}
public void handleConnectionException(ServerInfo info, Disconnect disconnect) {
public void handleConnectionException(RegisteredServer server, Disconnect disconnect) {
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason);
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason, TextComponent.builder()
.content("Kicked from " + server.getServerInfo().getName() + ": ")
.color(TextColor.RED)
.append(disconnectReason)
.build());
} else {
logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason);
logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason);
handleConnectionException(server, disconnectReason, TextComponent.builder()
.content("Unable to connect to " + server.getServerInfo().getName() + ": ")
.color(TextColor.RED)
.append(disconnectReason)
.build());
}
handleConnectionException(info, disconnectReason, TextComponent.builder()
.content("Unable to connect to " + info.getName() + ": ")
.color(TextColor.RED)
.append(disconnectReason)
.build());
}
private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) {
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);;
private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) {
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo());
connectionInFlight = null;
if (connectedServer == null) {
// The player isn't yet connected to a server.
Optional<ServerInfo> nextServer = getNextServerToTry();
Optional<RegisteredServer> nextServer = getNextServerToTry();
if (nextServer.isPresent()) {
createConnectionRequest(nextServer.get()).fireAndForget();
} else {
connection.closeWith(Disconnect.create(friendlyReason));
}
} else if (connectedServer.getServerInfo().equals(info)) {
} else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) {
// Already connected to the server being disconnected from.
if (kickReason != null) {
server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason))
server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason))
.thenAcceptAsync(event -> {
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult();
@ -257,11 +314,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.closeWith(Disconnect.create(friendlyReason));
}
} else {
connection.write(Chat.create(friendlyReason));
connection.write(Chat.createClientbound(friendlyReason));
}
}
Optional<ServerInfo> getNextServerToTry() {
Optional<RegisteredServer> getNextServerToTry() {
List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder();
if (tryIndex >= serversToTry.size()) {
return Optional.empty();
@ -272,21 +329,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return server.getServers().getServer(toTryName);
}
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
private Optional<ConnectionRequestBuilder.Status> checkServer(RegisteredServer server) {
Preconditions.checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server.");
if (connectionInFlight != null) {
return CompletableFuture.completedFuture(
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS)
);
return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS);
}
if (connectedServer != null && connectedServer.getServer().equals(server)) {
return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED);
}
return Optional.empty();
}
if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) {
return CompletableFuture.completedFuture(
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED)
);
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(request.getServer());
if (initialCheck.isPresent()) {
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get()));
}
// Otherwise, initiate the connection.
ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer()));
ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
return server.getEventManager().fire(event)
.thenCompose((newEvent) -> {
if (!newEvent.getResult().isAllowed()) {
@ -295,7 +356,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
);
}
return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect();
RegisteredServer rs = newEvent.getResult().getServer().get();
Optional<ConnectionRequestBuilder.Status> lastCheck = checkServer(rs);
if (lastCheck.isPresent()) {
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get()));
}
return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect();
});
}
@ -336,30 +402,37 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
}
@Override
public boolean hasPermission(String permission) {
return permissionFunction.getPermissionSetting(permission).asBoolean();
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
return permissionFunction.getPermissionValue(permission);
}
@Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId());
message.setData(data);
connection.write(message);
return true;
}
@Override
public void spoofChatInput(String input) {
Preconditions.checkArgument(input.length() <= Chat.MAX_SERVERBOUND_MESSAGE_LENGTH, "input cannot be greater than " + Chat.MAX_SERVERBOUND_MESSAGE_LENGTH + " characters in length");
connectedServer.getMinecraftConnection().write(Chat.createServerbound(input));
}
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final ServerInfo info;
private final RegisteredServer server;
ConnectionRequestBuilderImpl(ServerInfo info) {
this.info = Preconditions.checkNotNull(info, "info");
ConnectionRequestBuilderImpl(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "info");
}
@Override
public ServerInfo getServer() {
return info;
public RegisteredServer getServer() {
return server;
}
@Override
@ -372,7 +445,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connect()
.whenCompleteAsync((status, throwable) -> {
if (throwable != null) {
handleConnectionException(info, throwable);
handleConnectionException(server, throwable);
return;
}
@ -387,7 +460,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// Ignored; the plugin probably already handled this.
break;
case SERVER_DISCONNECTED:
handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
break;
}
}, connection.getChannel().eventLoop());

Datei anzeigen

@ -5,12 +5,12 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
private final ConnectedPlayer player;

Datei anzeigen

@ -2,22 +2,23 @@ package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.util.EncryptionUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
@ -39,6 +40,7 @@ import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
public class LoginSessionHandler implements MinecraftSessionHandler {
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
private static final String MOJANG_SERVER_AUTH_URL =
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
@ -157,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
return;
}
if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) {
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
// Request encryption.
EncryptionRequest request = generateRequest();
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
@ -218,7 +220,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
}
private void handleProxyLogin(ConnectedPlayer player) {
Optional<ServerInfo> toTry = player.getNextServerToTry();
Optional<RegisteredServer> toTry = player.getNextServerToTry();
if (!toTry.isPresent()) {
player.close(TextComponent.of("No available servers", TextColor.RED));
return;
@ -244,7 +246,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
logger.info("{} has connected", player);
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
player.createConnectionRequest(toTry.get()).fireAndForget();
server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
}
@Override

Datei anzeigen

@ -4,16 +4,16 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.StatusPing;
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
@ -49,7 +49,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
configuration.getMotdComponent(),
configuration.getFavicon(),
configuration.isAnnounceForge() ? ServerPing.Modinfo.DEFAULT : null
configuration.isAnnounceForge() ? ServerPing.ModInfo.DEFAULT : null
);
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);

Datei anzeigen

@ -7,6 +7,7 @@ public class ConnectionMessages {
public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED);
public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED);
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error");
public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?");
private ConnectionMessages() {
throw new AssertionError();

Datei anzeigen

@ -1,28 +1,57 @@
package com.velocitypowered.proxy.console;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import net.kyori.text.serializer.ComponentSerializers;
import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.jline.reader.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jline.reader.Candidate;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import java.util.List;
import java.util.Optional;
public final class VelocityConsole extends SimpleTerminalConsole {
public final class VelocityConsole extends SimpleTerminalConsole implements CommandSource {
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
private final VelocityServer server;
private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE;
public VelocityConsole(VelocityServer server) {
this.server = server;
}
@Override
public void sendMessage(Component component) {
logger.info(ComponentSerializers.LEGACY.serialize(component));
}
@Override
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
return this.permissionFunction.getPermissionValue(permission);
}
public void setupPermissions() {
PermissionsSetupEvent event = new PermissionsSetupEvent(this, s -> PermissionFunction.ALWAYS_TRUE);
this.server.getEventManager().fire(event).join(); // this is called on startup, we can safely #join
this.permissionFunction = event.createFunction(this);
}
@Override
protected LineReader buildReader(LineReaderBuilder builder) {
return super.buildReader(builder
.appName("Velocity")
.completer((reader, parsedLine, list) -> {
Optional<List<String>> o = server.getCommandManager().offerSuggestions(server.getConsoleCommandSource(), parsedLine.line());
Optional<List<String>> o = this.server.getCommandManager().offerSuggestions(this, parsedLine.line());
o.ifPresent(offers -> {
for (String offer : offers) {
list.add(new Candidate(offer));
@ -39,8 +68,8 @@ public final class VelocityConsole extends SimpleTerminalConsole {
@Override
protected void runCommand(String command) {
if (!this.server.getCommandManager().execute(this.server.getConsoleCommandSource(), command)) {
server.getConsoleCommandSource().sendMessage(TextComponent.of("Command not found.", TextColor.RED));
if (!this.server.getCommandManager().execute(this, command)) {
sendMessage(TextComponent.of("Command not found.", TextColor.RED));
}
}

Datei anzeigen

@ -2,10 +2,10 @@ package com.velocitypowered.proxy.messages;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.messages.*;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import java.util.Collection;
import java.util.Map;
@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class VelocityChannelRegistrar implements ChannelRegistrar {
private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class);
private final Map<String, MessageHandler> handlers = new ConcurrentHashMap<>();
private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>();
@Override
public void register(MessageHandler handler, ChannelIdentifier... identifiers) {
public void register(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) {
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier,
"identifier is unknown");
}
for (ChannelIdentifier identifier : identifiers) {
handlers.put(identifier.getId(), handler);
identifierMap.put(identifier.getId(), identifier);
}
}
public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) {
MessageHandler handler = handlers.get(message.getChannel());
ChannelIdentifier identifier = identifierMap.get(message.getChannel());
if (handler == null || identifier == null) {
return MessageHandler.ForwardStatus.FORWARD;
}
try {
return handler.handle(source, side, identifier, message.getData());
} catch (Exception e) {
logger.info("Unable to handle plugin message on channel {} for {}", message.getChannel(), source);
// In case of doubt, do not forward the message on.
return MessageHandler.ForwardStatus.HANDLED;
}
}
@Override
public void unregister(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) {
@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
}
for (ChannelIdentifier identifier : identifiers) {
handlers.remove(identifier.getId());
identifierMap.remove(identifier.getId());
}
}
@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
public boolean registered(String id) {
return identifierMap.containsKey(id);
}
public ChannelIdentifier getFromId(String id) {
return identifierMap.get(id);
}
}

Datei anzeigen

@ -7,21 +7,11 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.epoll.*;
import io.netty.channel.kqueue.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
@ -75,7 +65,7 @@ public final class ConnectionManager {
@Override
protected void initChannel(final Channel ch) {
ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
@ -125,7 +115,9 @@ public final class ConnectionManager {
public Bootstrap createWorker() {
return new Bootstrap()
.channel(this.transportType.socketChannelClass)
.group(this.workerGroup);
.group(this.workerGroup)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout());
}
public void shutdown() {

Datei anzeigen

@ -13,7 +13,4 @@ public interface Connections {
String MINECRAFT_DECODER = "minecraft-decoder";
String MINECRAFT_ENCODER = "minecraft-encoder";
String READ_TIMEOUT = "read-timeout";
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
}

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.proxy.network.http;
import com.velocitypowered.proxy.VelocityServer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.Channel;
import io.netty.channel.pool.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext;

Datei anzeigen

@ -13,7 +13,10 @@ import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory;
import net.kyori.event.EventSubscriber;
import net.kyori.event.PostResult;
import net.kyori.event.SimpleEventBus;
import net.kyori.event.method.*;
import net.kyori.event.method.EventExecutor;
import net.kyori.event.method.MethodScanner;
import net.kyori.event.method.MethodSubscriptionAdapter;
import net.kyori.event.method.SimpleMethodSubscriptionAdapter;
import net.kyori.event.method.asm.ASMEventExecutorFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class VelocityEventManager implements EventManager {
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.plugin;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -2,7 +2,9 @@ package com.velocitypowered.proxy.plugin.loader;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.velocitypowered.api.plugin.*;
import com.velocitypowered.api.plugin.InvalidPluginException;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -8,7 +8,6 @@ import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Datei anzeigen

@ -8,7 +8,7 @@ import io.netty.util.collection.IntObjectMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*;
import java.util.Objects;
import java.util.function.Supplier;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
@ -23,7 +23,7 @@ public enum StateRegistry {
},
STATUS {
{
SERVERBOUND.register(StatusRequest.class, StatusRequest::new,
SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
genericMappings(0x00));
SERVERBOUND.register(StatusPing.class, StatusPing::new,
genericMappings(0x01));
@ -40,8 +40,7 @@ public enum StateRegistry {
map(0x14, MINECRAFT_1_8, false),
map(0x01, MINECRAFT_1_9, false),
map(0x02, MINECRAFT_1_12, false),
map(0x01, MINECRAFT_1_12_1, false),
map(0x05, MINECRAFT_1_13, false));
map(0x01, MINECRAFT_1_12_1, false));
SERVERBOUND.register(Chat.class, Chat::new,
map(0x01, MINECRAFT_1_8, false),
map(0x02, MINECRAFT_1_9, false),
@ -77,10 +76,9 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_12, true),
map(0x0E, MINECRAFT_1_13, true));
CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new,
map(0x3A, MINECRAFT_1_8, true),
map(0x0E, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_12, true),
map(0x10, MINECRAFT_1_13, true));
map(0x3A, MINECRAFT_1_8, false),
map(0x0E, MINECRAFT_1_9, false),
map(0x0E, MINECRAFT_1_12, false));
CLIENTBOUND.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_8, false),
map(0x18, MINECRAFT_1_9, false),
@ -114,30 +112,12 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_12, true),
map(0x4A, MINECRAFT_1_12_1, true),
map(0x4E, MINECRAFT_1_13, true));
CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new,
map(0x3D, MINECRAFT_1_8, true),
map(0x38, MINECRAFT_1_9, true),
map(0x3A, MINECRAFT_1_12, true),
map(0x3B, MINECRAFT_1_12_1, true),
map(0x3E, MINECRAFT_1_13, true));
CLIENTBOUND.register(ScoreboardObjective.class, ScoreboardObjective::new,
map(0x3B, MINECRAFT_1_8, true),
map(0x3F, MINECRAFT_1_9, true),
map(0x41, MINECRAFT_1_12, true),
map(0x42, MINECRAFT_1_12_1, true),
map(0x45, MINECRAFT_1_13, true));
CLIENTBOUND.register(ScoreboardTeam.class, ScoreboardTeam::new,
map(0x3E, MINECRAFT_1_8, true),
map(0x41, MINECRAFT_1_9, true),
map(0x43, MINECRAFT_1_12, true),
map(0x44, MINECRAFT_1_12_1, true),
map(0x47, MINECRAFT_1_13, true));
CLIENTBOUND.register(ScoreboardSetScore.class, ScoreboardSetScore::new,
map(0x3C, MINECRAFT_1_8, true),
map(0x42, MINECRAFT_1_9, true),
map(0x44, MINECRAFT_1_12, true),
map(0x45, MINECRAFT_1_12_1, true),
map(0x48, MINECRAFT_1_13, true));
CLIENTBOUND.register(TitlePacket.class, TitlePacket::new,
map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_12, true),
map(0x48, MINECRAFT_1_12_1, true),
map(0x4B, MINECRAFT_1_13, true));
}
},
LOGIN {

Datei anzeigen

@ -101,9 +101,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
queryResponse.writeByte(QUERY_TYPE_STAT);
queryResponse.writeInt(sessionId);
// Fetch information
Collection<Player> players = server.getAllPlayers();
// Start writing the response
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
@ -114,12 +111,14 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
responseWriter.write("plugins", "");
responseWriter.write("map", "Velocity");
responseWriter.write("numplayers", players.size());
responseWriter.write("numplayers", server.getPlayerCount());
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
responseWriter.writePlayers(players);
if (!responseWriter.isBasic) {
responseWriter.writePlayers(server.getAllPlayers());
}
break;
}
@ -132,6 +131,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
ctx.writeAndFlush(responsePacket);
} catch (Exception e) {
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
// count.
responsePacket.release();
}
}

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

Datei anzeigen

@ -1,7 +1,10 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.*;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException;

Datei anzeigen

@ -4,7 +4,6 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
@ -10,6 +10,8 @@ import net.kyori.text.serializer.ComponentSerializers;
public class Chat implements MinecraftPacket {
public static final byte CHAT = (byte) 0;
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
private String message;
private byte type;
@ -61,12 +63,16 @@ public class Chat implements MinecraftPacket {
}
}
public static Chat create(Component component) {
return create(component, CHAT);
public static Chat createClientbound(Component component) {
return createClientbound(component, CHAT);
}
public static Chat create(Component component, byte type) {
public static Chat createClientbound(Component component, byte type) {
Preconditions.checkNotNull(component, "component");
return new Chat(ComponentSerializers.JSON.serialize(component), type);
}
public static Chat createServerbound(String message) {
return new Chat(message, CHAT);
}
}

Datei anzeigen

@ -1,8 +1,8 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializer;
import net.kyori.text.serializer.ComponentSerializers;
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
public class HeaderAndFooter implements MinecraftPacket {
private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}");

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,47 +0,0 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class ScoreboardDisplay implements MinecraftPacket {
private byte position;
private String displayName;
public byte getPosition() {
return position;
}
public void setPosition(byte position) {
this.position = position;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
@Override
public String toString() {
return "ScoreboardDisplay{" +
"position=" + position +
", displayName='" + displayName + '\'' +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
this.position = buf.readByte();
this.displayName = ProtocolUtils.readString(buf, 16);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
buf.writeByte(position);
ProtocolUtils.writeString(buf, displayName);
}
}

Datei anzeigen

@ -1,93 +0,0 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.ScoreboardProtocolUtil;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
public class ScoreboardObjective implements MinecraftPacket {
public static final byte ADD = (byte) 0;
public static final byte REMOVE = (byte) 1;
public static final byte CHANGE = (byte) 2;
private String id;
private byte mode;
private Component displayName;
private ObjectiveMode type;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public byte getMode() {
return mode;
}
public void setMode(byte mode) {
this.mode = mode;
}
public Component getDisplayName() {
return displayName;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public ObjectiveMode getType() {
return type;
}
public void setType(ObjectiveMode type) {
this.type = type;
}
@Override
public String toString() {
return "ScoreboardObjective{" +
"id='" + id + '\'' +
", mode=" + mode +
", displayName='" + displayName + '\'' +
", type='" + type + '\'' +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
this.id = ProtocolUtils.readString(buf, 16);
this.mode = buf.readByte();
if (this.mode != REMOVE) {
this.displayName = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
this.type = ScoreboardProtocolUtil.getMode(ProtocolUtils.readVarInt(buf));
} else {
this.type = ScoreboardProtocolUtil.getMode(ProtocolUtils.readString(buf));
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, id);
buf.writeByte(mode);
if (this.mode != REMOVE) {
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, displayName);
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
ProtocolUtils.writeVarInt(buf, type.ordinal());
} else {
ProtocolUtils.writeString(buf, type.name().toLowerCase());
}
}
}
public enum ObjectiveMode {
INTEGER,
HEARTS
}
}

Datei anzeigen

@ -1,77 +0,0 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class ScoreboardSetScore implements MinecraftPacket {
public static final byte CHANGE = (byte) 0;
public static final byte REMOVE = (byte) 1;
private String entity;
private byte action;
private String objective;
private int value;
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public byte getAction() {
return action;
}
public void setAction(byte action) {
this.action = action;
}
public String getObjective() {
return objective;
}
public void setObjective(String objective) {
this.objective = objective;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public String toString() {
return "ScoreboardSetScore{" +
"entity='" + entity + '\'' +
", action=" + action +
", objective='" + objective + '\'' +
", value=" + value +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
this.entity = ProtocolUtils.readString(buf, 40);
this.action = buf.readByte();
this.objective = ProtocolUtils.readString(buf, 16);
if (this.action != REMOVE) {
value = ProtocolUtils.readVarInt(buf);
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, entity);
buf.writeByte(action);
ProtocolUtils.writeString(buf, objective);
if (this.action != REMOVE) {
ProtocolUtils.writeVarInt(buf, value);
}
}
}

Datei anzeigen

@ -1,215 +0,0 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import java.util.ArrayList;
import java.util.List;
public class ScoreboardTeam implements MinecraftPacket {
public static final byte ADD = (byte) 0;
public static final byte REMOVE = (byte) 1;
public static final byte UPDATE = (byte) 2;
public static final byte ADD_PLAYER = (byte) 3;
public static final byte REMOVE_PLAYER = (byte) 4;
private String id;
private byte mode;
private Component displayName;
private Component prefix;
private Component suffix;
private byte flags;
private String nameTagVisibility;
private String collisionRule;
private int color;
private List<String> entities;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public byte getMode() {
return mode;
}
public void setMode(byte mode) {
this.mode = mode;
}
public Component getDisplayName() {
return displayName;
}
public void setDisplayName(Component displayName) {
this.displayName = displayName;
}
public Component getPrefix() {
return prefix;
}
public void setPrefix(Component prefix) {
this.prefix = prefix;
}
public Component getSuffix() {
return suffix;
}
public void setSuffix(Component suffix) {
this.suffix = suffix;
}
public byte getFlags() {
return flags;
}
public void setFlags(byte flags) {
this.flags = flags;
}
public String getNameTagVisibility() {
return nameTagVisibility;
}
public void setNameTagVisibility(String nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
}
public String getCollisionRule() {
return collisionRule;
}
public void setCollisionRule(String collisionRule) {
this.collisionRule = collisionRule;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public List<String> getEntities() {
return entities;
}
public void setEntities(List<String> entities) {
this.entities = entities;
}
@Override
public String toString() {
return "ScoreboardTeam{" +
"id='" + id + '\'' +
", mode=" + mode +
", displayName='" + displayName + '\'' +
", prefix='" + prefix + '\'' +
", suffix='" + suffix + '\'' +
", flags=" + flags +
", nameTagVisibility='" + nameTagVisibility + '\'' +
", collisionRule='" + collisionRule + '\'' +
", color=" + color +
", entities=" + entities +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
this.id = ProtocolUtils.readString(buf, 16);
this.mode = buf.readByte();
switch (mode) {
case ADD:
case UPDATE:
this.displayName = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2) {
this.prefix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
this.suffix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
}
this.flags = buf.readByte();
this.nameTagVisibility = ProtocolUtils.readString(buf, 32);
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
this.collisionRule = ProtocolUtils.readString(buf, 32);
}
this.color = protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ? buf.readByte() :
ProtocolUtils.readVarInt(buf);
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
this.prefix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
this.suffix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
}
if (mode == ADD) {
this.entities = readEntities(buf);
}
break;
case REMOVE: // remove
break;
case ADD_PLAYER: // add player
case REMOVE_PLAYER: // remove player
this.entities = readEntities(buf);
break;
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, id);
buf.writeByte(mode);
switch (mode) {
case ADD:
case UPDATE:
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, displayName);
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2) {
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix);
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, suffix);
}
buf.writeByte(flags);
ProtocolUtils.writeString(buf, nameTagVisibility);
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
ProtocolUtils.writeString(buf, collisionRule);
}
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix);
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, suffix);
} else {
buf.writeByte(color);
}
if (mode == ADD) {
writeEntities(buf, entities);
}
break;
case REMOVE:
break;
case ADD_PLAYER:
case REMOVE_PLAYER:
writeEntities(buf, entities);
break;
}
}
private static List<String> readEntities(ByteBuf buf) {
List<String> entities = new ArrayList<>();
int size = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < size; i++) {
entities.add(ProtocolUtils.readString(buf, 40));
}
return entities;
}
private static void writeEntities(ByteBuf buf, List<String> entities) {
ProtocolUtils.writeVarInt(buf, entities.size());
for (String entity : entities) {
ProtocolUtils.writeString(buf, entity);
}
}
}

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,10 +1,16 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf;
public class StatusRequest implements MinecraftPacket {
public static final StatusRequest INSTANCE = new StatusRequest();
private StatusRequest() {
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
@ -14,4 +20,9 @@ public class StatusRequest implements MinecraftPacket {
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
}
@Override
public String toString() {
return "StatusRequest";
}
}

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -9,20 +9,11 @@ import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_1
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
public class TabCompleteRequest implements MinecraftPacket {
private int transactionId;
private String command;
private boolean assumeCommand;
private boolean hasPosition;
private long position;
public int getTransactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
public String getCommand() {
return command;
}
@ -58,8 +49,7 @@ public class TabCompleteRequest implements MinecraftPacket {
@Override
public String toString() {
return "TabCompleteRequest{" +
"transactionId=" + transactionId +
", command='" + command + '\'' +
"command='" + command + '\'' +
", assumeCommand=" + assumeCommand +
", hasPosition=" + hasPosition +
", position=" + position +
@ -68,35 +58,25 @@ public class TabCompleteRequest implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) {
this.transactionId = ProtocolUtils.readVarInt(buf);
this.command = ProtocolUtils.readString(buf);
} else {
this.command = ProtocolUtils.readString(buf);
if (protocolVersion >= MINECRAFT_1_9) {
this.assumeCommand = buf.readBoolean();
}
this.hasPosition = buf.readBoolean();
if (hasPosition) {
this.position = buf.readLong();
}
this.command = ProtocolUtils.readString(buf);
if (protocolVersion >= MINECRAFT_1_9) {
this.assumeCommand = buf.readBoolean();
}
this.hasPosition = buf.readBoolean();
if (hasPosition) {
this.position = buf.readLong();
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) {
ProtocolUtils.writeVarInt(buf, transactionId);
ProtocolUtils.writeString(buf, command);
} else {
ProtocolUtils.writeString(buf, command);
if (protocolVersion >= MINECRAFT_1_9) {
buf.writeBoolean(assumeCommand);
}
buf.writeBoolean(hasPosition);
if (hasPosition) {
buf.writeLong(position);
}
ProtocolUtils.writeString(buf, command);
if (protocolVersion >= MINECRAFT_1_9) {
buf.writeBoolean(assumeCommand);
}
buf.writeBoolean(hasPosition);
if (hasPosition) {
buf.writeLong(position);
}
}
}

Datei anzeigen

@ -4,125 +4,37 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13;
public class TabCompleteResponse implements MinecraftPacket {
private int transactionId;
private int start;
private int length;
private final List<Offer> offers = new ArrayList<>();
private final List<String> offers = new ArrayList<>();
public int getTransactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public List<Offer> getOffers() {
public List<String> getOffers() {
return offers;
}
@Override
public String toString() {
return "TabCompleteResponse{" +
"transactionId=" + transactionId +
", start=" + start +
", length=" + length +
", offers=" + offers +
"offers=" + offers +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) {
this.transactionId = ProtocolUtils.readVarInt(buf);
this.start = ProtocolUtils.readVarInt(buf);
this.length = ProtocolUtils.readVarInt(buf);
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
String entry = ProtocolUtils.readString(buf);
Component component = buf.readBoolean() ? ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)) :
null;
offers.add(new Offer(entry, component));
}
} else {
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
offers.add(new Offer(ProtocolUtils.readString(buf), null));
}
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
offers.add(ProtocolUtils.readString(buf));
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) {
ProtocolUtils.writeVarInt(buf, transactionId);
ProtocolUtils.writeVarInt(buf, start);
ProtocolUtils.writeVarInt(buf, length);
ProtocolUtils.writeVarInt(buf, offers.size());
for (Offer offer : offers) {
ProtocolUtils.writeString(buf, offer.entry);
buf.writeBoolean(offer.tooltip != null);
if (offer.tooltip != null) {
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(offer.tooltip));
}
}
} else {
ProtocolUtils.writeVarInt(buf, offers.size());
for (Offer offer : offers) {
ProtocolUtils.writeString(buf, offer.entry);
}
}
}
public static class Offer {
private final String entry;
private final Component tooltip;
public Offer(String entry, @Nullable Component tooltip) {
this.entry = entry;
this.tooltip = tooltip;
}
public String getEntry() {
return entry;
}
public @Nullable Component getTooltip() {
return tooltip;
}
@Override
public String toString() {
return "Offer{" +
"entry='" + entry + '\'' +
", tooltip=" + tooltip +
'}';
ProtocolUtils.writeVarInt(buf, offers.size());
for (String offer : offers) {
ProtocolUtils.writeString(buf, offer);
}
}
}

Datei anzeigen

@ -0,0 +1,137 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class TitlePacket implements MinecraftPacket {
public static final int SET_TITLE = 0;
public static final int SET_SUBTITLE = 1;
public static final int SET_ACTION_BAR = 2;
public static final int SET_TIMES = 3;
public static final int SET_TIMES_OLD = 2;
public static final int HIDE = 4;
public static final int HIDE_OLD = 3;
public static final int RESET = 5;
public static final int RESET_OLD = 4;
private int action;
private String component;
private int fadeIn;
private int stay;
private int fadeOut;
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeVarInt(buf, action);
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_11) {
// 1.11+ shifted the action enum by 1 to handle the action bar
switch (action) {
case SET_TITLE:
case SET_SUBTITLE:
case SET_ACTION_BAR:
ProtocolUtils.writeString(buf, component);
break;
case SET_TIMES:
buf.writeInt(fadeIn);
buf.writeInt(stay);
buf.writeInt(fadeOut);
break;
case HIDE:
case RESET:
break;
}
} else {
switch (action) {
case SET_TITLE:
case SET_SUBTITLE:
ProtocolUtils.writeString(buf, component);
break;
case SET_TIMES_OLD:
buf.writeInt(fadeIn);
buf.writeInt(stay);
buf.writeInt(fadeOut);
break;
case HIDE_OLD:
case RESET_OLD:
break;
}
}
}
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public int getFadeIn() {
return fadeIn;
}
public void setFadeIn(int fadeIn) {
this.fadeIn = fadeIn;
}
public int getStay() {
return stay;
}
public void setStay(int stay) {
this.stay = stay;
}
public int getFadeOut() {
return fadeOut;
}
public void setFadeOut(int fadeOut) {
this.fadeOut = fadeOut;
}
public static TitlePacket hideForProtocolVersion(int protocolVersion) {
TitlePacket packet = new TitlePacket();
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.HIDE : TitlePacket.HIDE_OLD);
return packet;
}
public static TitlePacket resetForProtocolVersion(int protocolVersion) {
TitlePacket packet = new TitlePacket();
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.RESET : TitlePacket.RESET_OLD);
return packet;
}
public static TitlePacket timesForProtocolVersion(int protocolVersion) {
TitlePacket packet = new TitlePacket();
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.SET_TIMES : TitlePacket.SET_TIMES_OLD);
return packet;
}
@Override
public String toString() {
return "TitlePacket{" +
"action=" + action +
", component='" + component + '\'' +
", fadeIn=" + fadeIn +
", stay=" + stay +
", fadeOut=" + fadeOut +
'}';
}
}

Datei anzeigen

@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.nio.charset.StandardCharsets;
@ -20,13 +19,20 @@ public enum PluginMessageUtil {
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand");
}
public static boolean isMCRegister(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register");
}
public static boolean isMCUnregister(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister");
}
public static List<String> getChannels(PluginMessage message) {
Preconditions.checkNotNull(message, "message");
Preconditions.checkArgument(message.getChannel().equals("REGISTER") ||
message.getChannel().equals("UNREGISTER") ||
message.getChannel().equals("minecraft:register") ||
message.getChannel().equals("minecraft:unregister"),
"Unknown channel type " + message.getChannel());
Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s",
message.getChannel());
String channels = new String(message.getData(), StandardCharsets.UTF_8);
return ImmutableList.copyOf(channels.split("\0"));
}

Datei anzeigen

@ -1,24 +0,0 @@
package com.velocitypowered.proxy.protocol.util;
import com.velocitypowered.proxy.protocol.packet.ScoreboardObjective;
public class ScoreboardProtocolUtil {
private ScoreboardProtocolUtil() {
throw new AssertionError();
}
public static ScoreboardObjective.ObjectiveMode getMode(String mode) {
return ScoreboardObjective.ObjectiveMode.valueOf(mode.toUpperCase());
}
public static ScoreboardObjective.ObjectiveMode getMode(int enumVal) {
switch (enumVal) {
case 0:
return ScoreboardObjective.ObjectiveMode.INTEGER;
case 1:
return ScoreboardObjective.ObjectiveMode.HEARTS;
default:
throw new IllegalStateException("Unknown mode " + enumVal);
}
}
}

Datei anzeigen

@ -16,7 +16,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class VelocityScheduler implements Scheduler {
private final PluginManager pluginManager;
@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler {
}
@Override
public TaskBuilder delay(int time, TimeUnit unit) {
public TaskBuilder delay(long time, TimeUnit unit) {
this.delay = unit.toMillis(time);
return this;
}
@Override
public TaskBuilder repeat(int time, TimeUnit unit) {
public TaskBuilder repeat(long time, TimeUnit unit) {
this.repeat = unit.toMillis(time);
return this;
}
@ -91,62 +90,9 @@ public class VelocityScheduler implements Scheduler {
@Override
public ScheduledTask schedule() {
if (delay == 0 && repeat == 0) {
// A special purpose, simplified implementation
VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable);
tasksByPlugin.put(plugin, task);
taskService.execute(task);
return task;
} else {
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
tasksByPlugin.put(plugin, task);
return task;
}
}
}
private class VelocityImmediatelyScheduledTask implements ScheduledTask, Runnable {
private final Object plugin;
private final Runnable runnable;
private final AtomicReference<TaskStatus> status;
private Thread taskThread;
private VelocityImmediatelyScheduledTask(Object plugin, Runnable runnable) {
this.plugin = plugin;
this.runnable = runnable;
this.status = new AtomicReference<>(TaskStatus.SCHEDULED);
}
@Override
public Object plugin() {
return plugin;
}
@Override
public TaskStatus status() {
return status.get();
}
@Override
public void cancel() {
if (status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.CANCELLED)) {
if (taskThread != null) {
taskThread.interrupt();
}
}
}
@Override
public void run() {
taskThread = Thread.currentThread();
try {
runnable.run();
} catch (Exception e) {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
}
status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.FINISHED);
taskThread = null;
tasksByPlugin.remove(plugin, this);
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
tasksByPlugin.put(plugin, task);
return task;
}
}
@ -154,6 +100,7 @@ public class VelocityScheduler implements Scheduler {
private final Object plugin;
private final Runnable runnable;
private ScheduledFuture<?> future;
private volatile Thread currentTaskThread;
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
this.plugin = plugin;
@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler {
public void cancel() {
if (future != null) {
future.cancel(false);
Thread cur = currentTaskThread;
if (cur != null) {
cur.interrupt();
}
onFinish();
}
}
@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler {
@Override
public void run() {
taskService.execute(() -> {
currentTaskThread = Thread.currentThread();
try {
runnable.run();
} catch (Exception e) {
@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
}
}
currentTaskThread = null;
});
}

Datei anzeigen

@ -0,0 +1,49 @@
package com.velocitypowered.proxy.server;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.VelocityServer;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ServerMap {
private final VelocityServer server;
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
public ServerMap(VelocityServer server) {
this.server = server;
}
public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "server");
String lowerName = name.toLowerCase(Locale.US);
return Optional.ofNullable(servers.get(lowerName));
}
public Collection<RegisteredServer> getAllServers() {
return ImmutableList.copyOf(servers.values());
}
public RegisteredServer register(ServerInfo serverInfo) {
Preconditions.checkNotNull(serverInfo, "serverInfo");
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo);
Preconditions.checkArgument(servers.putIfAbsent(lowerName, rs) == null, "Server with name %s already registered", serverInfo.getName());
return rs;
}
public void unregister(ServerInfo serverInfo) {
Preconditions.checkNotNull(serverInfo, "serverInfo");
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
RegisteredServer rs = servers.get(lowerName);
Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName());
Preconditions.checkArgument(rs.getServerInfo().equals(serverInfo), "Trying to remove server %s with differing information", serverInfo.getName());
Preconditions.checkState(servers.remove(lowerName, rs), "Server with name %s replaced whilst unregistering", serverInfo.getName());
}
}

Datei anzeigen

@ -0,0 +1,112 @@
package com.velocitypowered.proxy.server;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.server.ping.PingSessionHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import static com.velocitypowered.proxy.network.Connections.*;
public class VelocityRegisteredServer implements RegisteredServer {
private final VelocityServer server;
private final ServerInfo serverInfo;
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) {
this.server = server;
this.serverInfo = serverInfo;
}
@Override
public ServerInfo getServerInfo() {
return serverInfo;
}
@Override
public Collection<Player> getPlayersConnected() {
return ImmutableList.copyOf(players);
}
@Override
public CompletableFuture<ServerPing> ping() {
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
server.initializeGenericBootstrap()
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND));
MinecraftConnection connection = new MinecraftConnection(ch, server);
connection.setState(StateRegistry.HANDSHAKE);
ch.pipeline().addLast(HANDLER, connection);
}
})
.connect(serverInfo.getAddress())
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
conn.setSessionHandler(new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn));
} else {
pingFuture.completeExceptionally(future.cause());
}
}
});
return pingFuture;
}
public void addPlayer(ConnectedPlayer player) {
players.add(player);
}
public void removePlayer(ConnectedPlayer player) {
players.remove(player);
}
@Override
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
for (ConnectedPlayer player : players) {
if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) {
ServerConnection connection = player.getConnectedServer();
return connection.sendPluginMessage(identifier, data);
}
}
return false;
}
@Override
public String toString() {
return "registered server: " + serverInfo;
}
}

Datei anzeigen

@ -0,0 +1,68 @@
package com.velocitypowered.proxy.server.ping;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
public class PingSessionHandler implements MinecraftSessionHandler {
private final CompletableFuture<ServerPing> result;
private final RegisteredServer server;
private final MinecraftConnection connection;
private boolean completed = false;
public PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server, MinecraftConnection connection) {
this.result = result;
this.server = server;
this.connection = connection;
}
@Override
public void activated() {
Handshake handshake = new Handshake();
handshake.setNextStatus(StateRegistry.STATUS_ID);
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
connection.write(handshake);
connection.setState(StateRegistry.STATUS);
connection.write(StatusRequest.INSTANCE);
}
@Override
public void handle(MinecraftPacket packet) {
Preconditions.checkState(packet instanceof StatusResponse, "Did not get status response back from connection");
// All good!
completed = true;
connection.close();
ServerPing ping = VelocityServer.GSON.fromJson(((StatusResponse) packet).getStatus(), ServerPing.class);
result.complete(ping);
}
@Override
public void disconnected() {
if (!completed) {
result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
}
}
@Override
public void exception(Throwable throwable) {
completed = true;
result.completeExceptionally(throwable);
}
}

Datei anzeigen

@ -1,56 +0,0 @@
package com.velocitypowered.proxy.util;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ServerMap {
private final Map<String, ServerInfo> servers = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Optional<ServerInfo> getServer(String server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.toLowerCase(Locale.US);
lock.readLock().lock();
try {
return Optional.ofNullable(servers.get(lowerName));
} finally {
lock.readLock().unlock();
}
}
public Collection<ServerInfo> getAllServers() {
lock.readLock().lock();
try {
return ImmutableList.copyOf(servers.values());
} finally {
lock.readLock().unlock();
}
}
public void register(ServerInfo server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.getName().toLowerCase(Locale.US);
lock.writeLock().lock();
try {
Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName());
} finally {
lock.writeLock().unlock();
}
}
public void unregister(ServerInfo server) {
Preconditions.checkNotNull(server, "server");
String lowerName = server.getName().toLowerCase(Locale.US);
lock.writeLock().lock();
try {
Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!");
} finally {
lock.writeLock().unlock();
}
}
}

Datei anzeigen

@ -6,7 +6,6 @@ import com.google.common.collect.MapMaker;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
/**

Datei anzeigen

@ -3,9 +3,7 @@ package com.velocitypowered.proxy.protocol;
import com.velocitypowered.proxy.protocol.packet.Handshake;
import org.junit.jupiter.api.Test;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
import static org.junit.jupiter.api.Assertions.*;
class PacketRegistryTest {

Datei anzeigen

@ -9,7 +9,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
class VelocitySchedulerTest {
// TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep()

Datei anzeigen

@ -7,7 +7,8 @@ import java.net.InetAddress;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RatelimiterTest {

Datei anzeigen

@ -1,31 +1,34 @@
package com.velocitypowered.proxy.util;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.server.ServerMap;
import org.junit.jupiter.api.Test;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class ServerMapTest {
private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565);
@Test
void respectsCaseInsensitivity() {
ServerMap map = new ServerMap();
ServerMap map = new ServerMap(null);
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
map.register(info);
RegisteredServer connection = map.register(info);
assertEquals(Optional.of(info), map.getServer("TestServer"));
assertEquals(Optional.of(info), map.getServer("testserver"));
assertEquals(Optional.of(info), map.getServer("TESTSERVER"));
assertEquals(Optional.of(connection), map.getServer("TestServer"));
assertEquals(Optional.of(connection), map.getServer("testserver"));
assertEquals(Optional.of(connection), map.getServer("TESTSERVER"));
}
@Test
void rejectsRepeatedRegisterAttempts() {
ServerMap map = new ServerMap();
ServerMap map = new ServerMap(null);
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
map.register(info);