3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +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 ### ### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm .idea/
# 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
*.iws *.iws
/out/
# IntelliJ *.iml
out/
# mpeltonen/sbt-idea plugin
.idea_modules/ .idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml atlassian-ide-plugin.xml
# Cursive Clojure plugin ### Eclipse ###
.idea/replstate.xml .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) ### Linux ###
com_crashlytics_export_strings.xml *~
crashlytics.properties .fuse_hidden*
crashlytics-build.properties .directory
fabric.properties .Trash-*
.nfs*
# Editor-based Rest Client ### macOS ###
.idea/httpRequests .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 ### ### NetBeans ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 nbproject/private/
build/
*.iml nbbuild/
modules.xml dist/
.idea/misc.xml nbdist/
*.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#
.nb-gradle/ .nb-gradle/
# Mobile Tools for Java (J2ME) ### Windows ###
.mtj.tmp/ # Windows thumbnail cache files
Thumbs.db
# Package Files # ehthumbs.db
*.jar ehthumbs_vista.db
*.war *.stackdump
*.nar [Dd]esktop.ini
*.ear $RECYCLE.BIN/
*.zip *.lnk
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Gradle ### ### Gradle ###
.gradle .gradle
build/ /build/
/out/
# Ignore Gradle GUI config
gradle-app.setting gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar !gradle-wrapper.jar
# Cache of project
.gradletasknamecache .gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 ### Other trash ###
# gradle/wrapper/gradle-wrapper.properties
# End of https://www.gitignore.io/api/java,gradle,intellij
# Other trash
logs/ logs/
/velocity.toml /velocity.toml
server-icon.png server-icon.png
/bin/ /bin/
run/ run/
plugins/

31
Jenkinsfile vendored
Datei anzeigen

@ -1,36 +1,33 @@
pipeline { pipeline {
agent { agent none
docker {
image 'velocitypowered/openjdk8-plus-git:slim'
args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw -v javadoc:/javadoc'
}
}
stages { stages {
stage('Build') { stage('Build') {
agent {
docker {
image 'velocitypowered/openjdk8-plus-git:slim'
args '-v gradle-cache:/root/.gradle:rw'
}
}
steps { steps {
sh './gradlew build' sh './gradlew build --no-daemon'
archiveArtifacts 'proxy/build/libs/*-all.jar,api/build/libs/*-all.jar' archiveArtifacts 'proxy/build/libs/*-all.jar,api/build/libs/*-all.jar'
} }
} }
stage('Deploy Artifacts') { stage('Deploy') {
when { when {
expression { expression {
GIT_BRANCH = sh(returnStdout: true, script: 'git rev-parse --abbrev-ref HEAD').trim() GIT_BRANCH = sh(returnStdout: true, script: 'git rev-parse --abbrev-ref HEAD').trim()
return GIT_BRANCH == 'master' return GIT_BRANCH == 'master'
} }
} }
steps { agent {
sh 'export MAVEN_DEPLOYMENT=true; ./gradlew publish' docker {
} image 'velocitypowered/openjdk8-plus-git:slim'
} args '-v gradle-cache:/root/.gradle:rw -v maven-repo:/maven-repo:rw -v javadoc:/javadoc:rw'
stage('Deploy Javadoc') {
when {
expression {
GIT_BRANCH = sh(returnStdout: true, script: 'git rev-parse --abbrev-ref HEAD').trim()
return GIT_BRANCH == 'master'
} }
} }
steps { steps {
sh 'export MAVEN_DEPLOYMENT=true; ./gradlew publish --no-daemon'
sh 'rsync -av --delete ./api/build/docs/javadoc/ /javadoc' 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/) [![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) [![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 A Minecraft server proxy with unparalleled server support, scalability,
designed specifically for enhanced server support and scalability whilst and flexibility.
not compromising flexibility.
Velocity is licensed under the MIT license for ultimate permissiveness Velocity is licensed under the MIT license for ultimate permissiveness
and expanding the pool of potential contributors and users. and expanding the pool of potential contributors and users.
@ -40,8 +39,7 @@ page.
## Status ## Status
Velocity is currently in an alpha state: it is prone to change at any time and 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 is currently only suitable for small servers and development/testing.
purposes, however, Velocity is fully-fledged and ready to go.
Velocity supports Minecraft 1.8-1.13.1, and has full support for Paper and Sponge. 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. 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.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.*; import java.util.Objects;
import java.util.Set;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"}) @SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor { public class PluginAnnotationProcessor extends AbstractProcessor {

Datei anzeigen

@ -26,4 +26,19 @@ public interface Command {
default List<String> suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) { default List<String> suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) {
return ImmutableList.of(); 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.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; 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 * 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). * 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 { public static class PreLoginComponentResult implements ResultedEvent.Result {
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
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);
/** private final Result result;
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode. private final Optional<Component> reason;
* @param allowedOnlineMode if true, online mode will be used for the connection
*/ private PreLoginComponentResult(Result result, @Nullable Component reason) {
private PreLoginComponentResult(boolean allowedOnlineMode) { this.result = result;
super(true, null); this.reason = Optional.ofNullable(reason);
this.onlineMode = allowedOnlineMode;
} }
private PreLoginComponentResult(@Nullable Component reason) { @Override
super(reason == null, reason); public boolean isAllowed() {
// Don't care about this return result != Result.DISALLOWED;
this.onlineMode = false; }
public Optional<Component> getReason() {
return reason;
} }
public boolean isOnlineModeAllowed() { public boolean isOnlineModeAllowed() {
return this.onlineMode; return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
} }
@Override @Override
public String toString() { public String toString() {
if (isForceOfflineMode()) {
return "allowed with force offline mode";
}
if (isOnlineModeAllowed()) { if (isOnlineModeAllowed()) {
return "allowed with online mode"; return "allowed with online mode";
} }
if (isAllowed()) {
return super.toString(); 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 * @return the allowed result
*/ */
public static PreLoginComponentResult allowed() { 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 * Returns a result indicating the connection will be allowed through
* forced to use online mode provided that the proxy is in offline mode. This acts similarly to {@link #allowed()} * the proxy, but the connection will be forced to use online mode
* on an online-mode proxy. * provided that the proxy is in offline mode. This acts similarly to
* {@link #allowed()} on an online-mode proxy.
* @return the result * @return the result
*/ */
public static PreLoginComponentResult forceOnlineMode() { public static PreLoginComponentResult forceOnlineMode() {
return FORCE_ONLINEMODE; 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. * Denies the login with the specified reason.
* @param reason the reason for disallowing the connection * @param reason the reason for disallowing the connection
* @return a new result * @return a new result
*/ */
public static PreLoginComponentResult denied(@NonNull Component reason) { public static PreLoginComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "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; 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.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.util.GameProfile; 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 * 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.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player; 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 net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; 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> { public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> {
private final Player player; private final Player player;
private final ServerInfo server; private final RegisteredServer server;
private final Component originalReason; private final Component originalReason;
private final boolean duringLogin; private final boolean duringLogin;
private ServerKickResult result; 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.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason"); this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return player; return player;
} }
public ServerInfo getServer() { public RegisteredServer getServer() {
return server; return server;
} }
@ -91,9 +91,9 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* when this result is used. * when this result is used.
*/ */
public static class RedirectPlayer implements ServerKickResult { 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"); this.server = Preconditions.checkNotNull(server, "server");
} }
@ -102,7 +102,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return false; return false;
} }
public ServerInfo getServer() { public RegisteredServer getServer() {
return server; return server;
} }
@ -111,7 +111,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* @param server the server to send the player to * @param server the server to send the player to
* @return the redirect result * @return the redirect result
*/ */
public static RedirectPlayer create(ServerInfo server) { public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server); return new RedirectPlayer(server);
} }
} }

Datei anzeigen

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

Datei anzeigen

@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player; 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 * 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 { public class ServerConnectedEvent {
private final Player player; 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.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server"); this.server = Preconditions.checkNotNull(server, "server");
} }
@ -21,7 +21,7 @@ public class ServerConnectedEvent {
return player; return player;
} }
public ServerInfo getServer() { public RegisteredServer getServer() {
return server; return server;
} }

Datei anzeigen

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

Datei anzeigen

@ -29,5 +29,5 @@ public interface PermissionFunction {
* @param permission the permission * @param permission the permission
* @return the value the permission is set to * @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 * @param permission the permission to check for
* @return whether or not the subject has the permission * @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; package com.velocitypowered.api.plugin.meta;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;

Datei anzeigen

@ -1,29 +1,28 @@
package com.velocitypowered.api.proxy; 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 net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection * 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 { public interface ConnectionRequestBuilder {
/** /**
* Returns the server that this connection request represents. * Returns the server that this connection request represents.
* @return the server this request will connect to * @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 * 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. * 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 * @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 * 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; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource; 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.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; 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.GameProfile;
import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.util.title.Title;
import java.util.List; import java.util.List;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; 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. * 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 * @return a new connection request
*/ */
ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info); ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server);
/** /**
* Gets a game profile properties of player * Gets a game profile properties of player
@ -100,4 +102,17 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
* @param reason component with the reason * @param reason component with the reason
*/ */
void disconnect(Component 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; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar; 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.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.Scheduler;
import net.kyori.text.Component;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
@ -31,6 +33,12 @@ public interface ProxyServer {
*/ */
Optional<Player> getPlayer(UUID uuid); 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 * Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players
* online. * online.
@ -45,23 +53,24 @@ public interface ProxyServer {
int getPlayerCount(); 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 * @param name the name of the server
* @return the registered server, which may be empty * @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 * @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. * Registers a server with this proxy. A server with this name should not already exist.
* @param server the server to register * @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. * 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.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; 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. * Returns the server that this connection is connected to.
* @return the server 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(); ServerInfo getServerInfo();
/** /**

Datei anzeigen

@ -8,6 +8,7 @@ public interface ChannelMessageSink {
* Sends a plugin message to this target. * Sends a plugin message to this target.
* @param identifier the channel identifier to send the message on * @param identifier the channel identifier to send the message on
* @param data the data to send * @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; package com.velocitypowered.api.proxy.messages;
/** /**
* Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from * Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on.
* the client or the server.
*/ */
public interface ChannelRegistrar { public interface ChannelRegistrar {
/** /**
* Registers the specified message handler to listen for plugin messages on the specified channels. * Registers the specified message identifiers to listen on for the
* @param handler the handler to register
* @param identifiers the channel identifiers to register * @param identifiers the channel identifiers to register
*/ */
void register(MessageHandler handler, ChannelIdentifier... identifiers); void register(ChannelIdentifier... identifiers);
/** /**
* Unregisters the handler for the specified channel. * 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. * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use.
*/ */
public final class MinecraftChannelIdentifier implements ChannelIdentifier { 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 namespace;
private final String name; 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 Players players;
private final Component description; private final Component description;
private final @Nullable Favicon favicon; 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) { 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.version = Preconditions.checkNotNull(version, "version");
this.players = players; this.players = players;
this.description = Preconditions.checkNotNull(description, "description"); this.description = Preconditions.checkNotNull(description, "description");
@ -46,7 +46,7 @@ public class ServerPing {
return Optional.ofNullable(favicon); return Optional.ofNullable(favicon);
} }
public Optional<Modinfo> getModinfo() { public Optional<ModInfo> getModinfo() {
return Optional.ofNullable(modinfo); return Optional.ofNullable(modinfo);
} }
@ -74,6 +74,7 @@ public class ServerPing {
builder.favicon = favicon; builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null; builder.nullOutModinfo = modinfo == null;
if (modinfo != null) { if (modinfo != null) {
builder.modType = modinfo.type;
builder.mods.addAll(modinfo.modList); builder.mods.addAll(modinfo.modList);
} }
return builder; return builder;
@ -91,6 +92,7 @@ public class ServerPing {
private int onlinePlayers; private int onlinePlayers;
private int maximumPlayers; private int maximumPlayers;
private final List<SamplePlayer> samplePlayers = new ArrayList<>(); private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType;
private final List<Mod> mods = new ArrayList<>(); private final List<Mod> mods = new ArrayList<>();
private Component description; private Component description;
private Favicon favicon; private Favicon favicon;
@ -121,6 +123,11 @@ public class ServerPing {
return this; return this;
} }
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(Mod... mods) { public Builder mods(Mod... mods) {
this.mods.addAll(Arrays.asList(mods)); this.mods.addAll(Arrays.asList(mods));
return this; return this;
@ -158,7 +165,7 @@ public class ServerPing {
public ServerPing build() { public ServerPing build() {
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon, 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() { public Version getVersion() {
@ -185,6 +192,10 @@ public class ServerPing {
return favicon; return favicon;
} }
public String getModType() {
return modType;
}
public List<Mod> getMods() { public List<Mod> getMods() {
return mods; return mods;
} }
@ -196,6 +207,7 @@ public class ServerPing {
", onlinePlayers=" + onlinePlayers + ", onlinePlayers=" + onlinePlayers +
", maximumPlayers=" + maximumPlayers + ", maximumPlayers=" + maximumPlayers +
", samplePlayers=" + samplePlayers + ", samplePlayers=" + samplePlayers +
", modType=" + modType +
", mods=" + mods + ", mods=" + mods +
", description=" + description + ", description=" + description +
", favicon=" + favicon + ", favicon=" + favicon +
@ -290,15 +302,24 @@ public class ServerPing {
} }
} }
public static class Modinfo { public static class ModInfo {
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of()); public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
private final String type = "FML"; private final String type;
private final List<Mod> modList; 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); this.modList = ImmutableList.copyOf(modList);
} }
public String getType() {
return type;
}
public List<Mod> getMods() {
return modList;
}
} }
public static class Mod { public static class Mod {
@ -309,5 +330,13 @@ public class ServerPing {
this.id = Preconditions.checkNotNull(id, "id"); this.id = Preconditions.checkNotNull(id, "id");
this.version = Preconditions.checkNotNull(version, "version"); 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} * @param unit the unit of time for {@code time}
* @return this builder, for chaining * @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. * 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} * @param unit the unit of time for {@code time}
* @return this builder, for chaining * @return this builder, for chaining
*/ */
TaskBuilder repeat(int time, TimeUnit unit); TaskBuilder repeat(long time, TimeUnit unit);
/** /**
* Clears the delay on this task. * 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> { public class NativeCodeLoader<T> implements Supplier<T> {
private final List<Variant<T>> variants; private final List<Variant<T>> variants;
private Variant<T> selected; private volatile Variant<T> selected;
public NativeCodeLoader(List<Variant<T>> variants) { public NativeCodeLoader(List<Variant<T>> variants) {
this.variants = ImmutableList.copyOf(variants); this.variants = ImmutableList.copyOf(variants);
@ -16,36 +16,41 @@ public class NativeCodeLoader<T> implements Supplier<T> {
@Override @Override
public T get() { public T get() {
if (selected == null) { return tryLoad().object;
selected = select();
}
return selected.object;
} }
private Variant<T> select() { private Variant<T> tryLoad() {
for (Variant<T> variant : variants) { if (selected != null) {
T got = variant.get(); return selected;
if (got == null) { }
continue;
} synchronized (this) {
return variant; 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() { public String getLoadedVariant() {
if (selected == null) { return tryLoad().name;
selected = select();
}
return selected.name;
} }
static class Variant<T> { static class Variant<T> {
private boolean available; private volatile boolean available;
private final Runnable setup; private final Runnable setup;
private final String name; private final String name;
private final T object; private final T object;
private boolean hasBeenSetup = false; private volatile boolean hasBeenSetup = false;
Variant(BooleanSupplier available, Runnable setup, String name, T object) { Variant(BooleanSupplier available, Runnable setup, String name, T object) {
this.available = available.getAsBoolean(); this.available = available.getAsBoolean();
@ -54,27 +59,34 @@ public class NativeCodeLoader<T> implements Supplier<T> {
this.object = object; this.object = object;
} }
private void setup() { public T get() {
if (available && !hasBeenSetup) { if (!available) {
try { return null;
setup.run(); }
hasBeenSetup = true;
} catch (Exception e) { // Make sure setup happens only once
available = false; 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() { return object;
if (!hasBeenSetup) {
setup();
}
if (available) {
return object;
}
return null;
} }
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,6 +1,5 @@
package com.velocitypowered.proxy; package com.velocitypowered.proxy;
import com.velocitypowered.proxy.console.VelocityConsole;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -28,6 +27,6 @@ public class Velocity {
double bootTime = (System.currentTimeMillis() - startTime) / 1000d; double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime)); 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.common.collect.ImmutableList;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; 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.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.server.ServerInfo; 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.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand; import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand; 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.command.VelocityCommandManager;
import com.velocitypowered.proxy.config.AnnotatedConfig; 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.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.plugin.VelocityEventManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.plugin.VelocityPluginManager; 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.scheduler.VelocityScheduler;
import com.velocitypowered.proxy.server.ServerMap;
import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.Ratelimiter; import com.velocitypowered.proxy.util.Ratelimiter;
import com.velocitypowered.proxy.util.ServerMap;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.serializer.ComponentSerializers;
import net.kyori.text.serializer.GsonComponentSerializer; import net.kyori.text.serializer.GsonComponentSerializer;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
@ -61,7 +64,7 @@ public class VelocityServer implements ProxyServer {
private VelocityConfiguration configuration; private VelocityConfiguration configuration;
private NettyHttpClient httpClient; private NettyHttpClient httpClient;
private KeyPair serverKeyPair; private KeyPair serverKeyPair;
private final ServerMap servers = new ServerMap(); private final ServerMap servers = new ServerMap(this);
private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = 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<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>(); private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private final CommandSource consoleCommandSource = new CommandSource() { private final VelocityConsole console = new VelocityConsole(this);
@Override
public void sendMessage(Component component) {
logger.info(ComponentSerializers.LEGACY.serialize(component));
}
@Override
public boolean hasPermission(String permission) {
return true;
}
};
private Ratelimiter ipAttemptLimiter; private Ratelimiter ipAttemptLimiter;
private VelocityEventManager eventManager; private VelocityEventManager eventManager;
private VelocityScheduler scheduler; private VelocityScheduler scheduler;
@ -144,6 +137,9 @@ public class VelocityServer implements ProxyServer {
// issues) and there is almost no chance ExecutionException will be thrown. // 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()); this.cm.bind(configuration.getBind());
if (configuration.isQueryEnabled()) { if (configuration.isQueryEnabled()) {
@ -252,6 +248,15 @@ public class VelocityServer implements ProxyServer {
return Optional.ofNullable(connectionsByUuid.get(uuid)); 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 @Override
public Collection<Player> getAllPlayers() { public Collection<Player> getAllPlayers() {
return ImmutableList.copyOf(connectionsByUuid.values()); return ImmutableList.copyOf(connectionsByUuid.values());
@ -263,19 +268,19 @@ public class VelocityServer implements ProxyServer {
} }
@Override @Override
public Optional<ServerInfo> getServerInfo(String name) { public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "name"); Preconditions.checkNotNull(name, "name");
return servers.getServer(name); return servers.getServer(name);
} }
@Override @Override
public Collection<ServerInfo> getAllServers() { public Collection<RegisteredServer> getAllServers() {
return servers.getAllServers(); return servers.getAllServers();
} }
@Override @Override
public void registerServer(ServerInfo server) { public RegisteredServer registerServer(ServerInfo server) {
servers.register(server); return servers.register(server);
} }
@Override @Override
@ -284,8 +289,8 @@ public class VelocityServer implements ProxyServer {
} }
@Override @Override
public CommandSource getConsoleCommandSource() { public VelocityConsole getConsoleCommandSource() {
return consoleCommandSource; return console;
} }
@Override @Override

Datei anzeigen

@ -3,14 +3,17 @@ package com.velocitypowered.proxy.command;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent; import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent; import net.kyori.text.event.HoverEvent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -34,7 +37,7 @@ public class ServerCommand implements Command {
if (args.length == 1) { if (args.length == 1) {
// Trying to connect to a server. // Trying to connect to a server.
String serverName = args[0]; String serverName = args[0];
Optional<ServerInfo> toConnect = server.getServerInfo(serverName); Optional<RegisteredServer> toConnect = server.getServer(serverName);
if (!toConnect.isPresent()) { if (!toConnect.isPresent()) {
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
return; return;
@ -48,17 +51,19 @@ public class ServerCommand implements Command {
// Assemble the list of servers as components // Assemble the list of servers as components
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW); 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++) { for (int i = 0; i < infos.size(); i++) {
ServerInfo serverInfo = infos.get(i); RegisteredServer rs = infos.get(i);
TextComponent infoComponent = TextComponent.of(serverInfo.getName()); TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
if (serverInfo.getName().equals(currentServer)) { String playersText = rs.getPlayersConnected().size() + " player(s) online";
if (rs.getServerInfo().getName().equals(currentServer)) {
infoComponent = infoComponent.color(TextColor.GREEN) 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 { } else {
infoComponent = infoComponent.color(TextColor.GRAY) infoComponent = infoComponent.color(TextColor.GRAY)
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName())) .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"))); .hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
} }
serverListBuilder.append(infoComponent); serverListBuilder.append(infoComponent);
if (i != infos.size() - 1) { if (i != infos.size() - 1) {
@ -74,15 +79,20 @@ public class ServerCommand implements Command {
public List<String> suggest(CommandSource source, String[] currentArgs) { public List<String> suggest(CommandSource source, String[] currentArgs) {
if (currentArgs.length == 0) { if (currentArgs.length == 0) {
return server.getAllServers().stream() return server.getAllServers().stream()
.map(ServerInfo::getName) .map(rs -> rs.getServerInfo().getName())
.collect(Collectors.toList()); .collect(Collectors.toList());
} else if (currentArgs.length == 1) { } else if (currentArgs.length == 1) {
return server.getAllServers().stream() return server.getAllServers().stream()
.map(ServerInfo::getName) .map(rs -> rs.getServerInfo().getName())
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length())) .filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} else { } else {
return ImmutableList.of(); 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 com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ShutdownCommand implements Command { public class ShutdownCommand implements Command {
private final VelocityServer server; private final VelocityServer server;
@ -21,4 +22,9 @@ public class ShutdownCommand implements Command {
} }
server.shutdown(); 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.Command;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent; import net.kyori.text.event.ClickEvent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
public class VelocityCommand implements Command { public class VelocityCommand implements Command {
@Override @Override
@ -37,4 +39,9 @@ public class VelocityCommand implements Command {
source.sendMessage(velocityInfo); source.sendMessage(velocityInfo);
source.sendMessage(velocityWebsite); 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; package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -47,6 +46,10 @@ public class VelocityCommandManager implements CommandManager {
} }
try { try {
if (!command.hasPermission(source, actualArgs)) {
return false;
}
command.execute(source, actualArgs); command.execute(source, actualArgs);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
@ -63,23 +66,29 @@ public class VelocityCommandManager implements CommandManager {
return Optional.empty(); return Optional.empty();
} }
String command = split[0]; String alias = split[0];
if (split.length == 1) { if (split.length == 1) {
return Optional.of(commands.keySet().stream() return Optional.of(commands.entrySet().stream()
.filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length())) .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())); .collect(Collectors.toList()));
} }
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length); String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
Command executor = commands.get(command); Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (executor == null) { if (command == null) {
return Optional.empty(); return Optional.empty();
} }
try { 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) { } 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; package com.velocitypowered.proxy.config;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/** /**
* Only for simple configs * Only for simple configs

Datei anzeigen

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

Datei anzeigen

@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.proxy.protocol.netty.*; import com.velocitypowered.proxy.protocol.netty.*;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER; import static com.velocitypowered.proxy.network.Connections.*;
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;
/** /**
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft * 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) { if (association != null) {
logger.error("{}: exception encountered", association, cause); logger.error("{}: exception encountered", association, cause);
} else {
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
} }
ctx.close(); 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 VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS"; 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 }; 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; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MessageHandler;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void activated() { 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 @Override
@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) { if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors. // errors.
connection.getMinecraftConnection().close(); connection.disconnect();
return; return;
} }
@ -40,11 +42,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
(ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler(); (ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler();
if (packet instanceof KeepAlive) { if (packet instanceof KeepAlive) {
// Forward onto the player // Forward onto the player
playerHandler.setLastPing(((KeepAlive) packet).getRandomId()); connection.setLastPingId(((KeepAlive) packet).getRandomId());
connection.getPlayer().getConnection().write(packet); connection.getPlayer().getConnection().write(packet);
} else if (packet instanceof Disconnect) { } else if (packet instanceof Disconnect) {
Disconnect original = (Disconnect) packet; Disconnect original = (Disconnect) packet;
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original); connection.disconnect();
connection.getPlayer().handleConnectionException(connection.getServer(), original);
} else if (packet instanceof JoinGame) { } else if (packet instanceof JoinGame) {
playerHandler.handleBackendJoinGame((JoinGame) packet); playerHandler.handleBackendJoinGame((JoinGame) packet);
} else if (packet instanceof BossBar) { } else if (packet instanceof BossBar) {
@ -83,11 +86,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return; return;
} }
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection, ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel());
ChannelSide.FROM_SERVER, pm); if (id == null) {
if (status == MessageHandler.ForwardStatus.FORWARD) {
connection.getPlayer().getConnection().write(pm); 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()) { } else if (connection.hasCompletedJoin()) {
// Just forward the packet on. We don't have anything to handle at this time. // Just forward the packet on. We don't have anything to handle at this time.
connection.getPlayer().getConnection().write(packet); connection.getPlayer().getConnection().write(packet);
@ -99,7 +111,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (!connection.getPlayer().isActive()) { if (!connection.getPlayer().isActive()) {
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location" // Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
// errors. // errors.
connection.getMinecraftConnection().close(); connection.disconnect();
return; return;
} }
@ -110,7 +122,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void exception(Throwable throwable) { 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) { private boolean canForwardPluginMessage(PluginMessage message) {

Datei anzeigen

@ -1,18 +1,18 @@
package com.velocitypowered.proxy.connection.backend; package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
if (existingConnection == null) { if (existingConnection == null) {
// Strap on the play session handler // Strap on the play session handler
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer())); 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 { } else {
// The previous server connection should become obsolete. // 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. // 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.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 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.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolConstants; 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.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.Handshake;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.packet.ServerLogin; import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.protocol.StateRegistry; import io.netty.channel.Channel;
import com.velocitypowered.api.proxy.server.ServerInfo; import io.netty.channel.ChannelFuture;
import com.velocitypowered.proxy.VelocityServer; import io.netty.channel.ChannelFutureListener;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.*;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
@ -27,27 +31,24 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.velocitypowered.proxy.VelocityServer.GSON; import static com.velocitypowered.proxy.VelocityServer.GSON;
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER; import static com.velocitypowered.proxy.network.Connections.*;
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;
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection { public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER = static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
AttributeKey.newInstance("connection-notification-result"); AttributeKey.newInstance("connection-notification-result");
private final ServerInfo serverInfo; private final VelocityRegisteredServer registeredServer;
private final ConnectedPlayer proxyPlayer; private final ConnectedPlayer proxyPlayer;
private final VelocityServer server; private final VelocityServer server;
private MinecraftConnection minecraftConnection; private MinecraftConnection minecraftConnection;
private boolean legacyForge = false; private boolean legacyForge = false;
private boolean hasCompletedJoin = false; private boolean hasCompletedJoin = false;
private boolean gracefulDisconnect = false;
private long lastPingId;
private long lastPingSent;
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.serverInfo = target; this.registeredServer = registeredServer;
this.proxyPlayer = proxyPlayer; this.proxyPlayer = proxyPlayer;
this.server = server; this.server = server;
} }
@ -55,12 +56,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public CompletableFuture<ConnectionRequestBuilder.Result> connect() { public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>(); CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
server.initializeGenericBootstrap() server.initializeGenericBootstrap()
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() { .handler(new ChannelInitializer<Channel>() {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ch.pipeline() 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_DECODER, new MinecraftVarintFrameDecoder())
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND)) .addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
@ -73,7 +73,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
ch.pipeline().addLast(HANDLER, connection); ch.pipeline().addLast(HANDLER, connection);
} }
}) })
.connect(serverInfo.getAddress()) .connect(registeredServer.getServerInfo().getAddress())
.addListener(new ChannelFutureListener() { .addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { 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, // 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 // 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). // 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.getRemoteAddress().getHostString() + "\0" +
proxyPlayer.getProfile().getId() + "\0" + proxyPlayer.getProfile().getId() + "\0" +
GSON.toJson(proxyPlayer.getProfile().getProperties()); GSON.toJson(proxyPlayer.getProfile().getProperties());
@ -113,9 +113,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
} else if (proxyPlayer.getConnection().isLegacyForge()) { } else if (proxyPlayer.getConnection().isLegacyForge()) {
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0"); handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
} else { } 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); minecraftConnection.write(handshake);
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion(); int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
@ -127,12 +127,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
minecraftConnection.write(login); minecraftConnection.write(login);
} }
public void writeIfJoined(PluginMessage message) {
if (hasCompletedJoin) {
minecraftConnection.write(message);
}
}
public MinecraftConnection getMinecraftConnection() { public MinecraftConnection getMinecraftConnection() {
return minecraftConnection; return minecraftConnection;
} }
@Override
public VelocityRegisteredServer getServer() {
return registeredServer;
}
@Override
public ServerInfo getServerInfo() { public ServerInfo getServerInfo() {
return serverInfo; return registeredServer.getServerInfo();
} }
@Override @Override
@ -141,23 +153,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
} }
public void disconnect() { public void disconnect() {
minecraftConnection.close(); if (minecraftConnection != null) {
minecraftConnection = null; minecraftConnection.close();
minecraftConnection = null;
gracefulDisconnect = true;
}
} }
@Override @Override
public String toString() { public String toString() {
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName(); return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
} }
@Override @Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data"); Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage(); PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId()); message.setChannel(identifier.getId());
message.setData(data); message.setData(data);
minecraftConnection.write(message); minecraftConnection.write(message);
return true;
} }
public boolean isLegacyForge() { public boolean isLegacyForge() {
@ -175,4 +191,25 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public void setHasCompletedJoin(boolean hasCompletedJoin) { public void setHasCompletedJoin(boolean hasCompletedJoin) {
this.hasCompletedJoin = 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; package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.proxy.messages.ChannelSide; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.proxy.messages.MessageHandler; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer; 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.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.proxy.util.ThrowableUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -28,12 +29,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private static final int MAX_PLUGIN_CHANNELS = 1024; private static final int MAX_PLUGIN_CHANNELS = 1024;
private final ConnectedPlayer player; private final ConnectedPlayer player;
private long lastPingID = -1;
private long lastPingSent = -1;
private boolean spawned = false; private boolean spawned = false;
private final List<UUID> serverBossBars = new ArrayList<>(); private final List<UUID> serverBossBars = new ArrayList<>();
private final Set<String> clientPluginMsgChannels = new HashSet<>(); private final Set<String> clientPluginMsgChannels = new HashSet<>();
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
private final VelocityServer server; private final VelocityServer server;
private TabCompleteRequest outstandingTabComplete;
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) { public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
this.player = player; this.player = player;
@ -53,16 +54,22 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void handle(MinecraftPacket packet) { public void handle(MinecraftPacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection == null) {
// No server connection yet, probably transitioning.
return;
}
if (packet instanceof KeepAlive) { if (packet instanceof KeepAlive) {
KeepAlive keepAlive = (KeepAlive) packet; 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 // The last keep alive we got was probably from a different server. Let's ignore it, and hope the next
// ping is alright. // ping is alright.
return; return;
} }
player.setPing(System.currentTimeMillis() - lastPingSent); player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
resetPingData(); serverConnection.getMinecraftConnection().write(packet);
player.getConnectedServer().getMinecraftConnection().write(packet); serverConnection.resetLastPingId();
return; return;
} }
@ -92,31 +99,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
if (packet instanceof TabCompleteRequest) { if (packet instanceof TabCompleteRequest) {
TabCompleteRequest req = (TabCompleteRequest) packet; // Record the request so that the outstanding request can be augmented later.
int lastSpace = req.getCommand().indexOf(' '); outstandingTabComplete = (TabCompleteRequest) packet;
if (!req.isAssumeCommand() && lastSpace != -1) { serverConnection.getMinecraftConnection().write(packet);
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;
}
} }
if (packet instanceof PluginMessage) { 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 we don't want to handle this packet, just forward it on.
if (player.getConnectedServer().hasCompletedJoin()) { if (serverConnection.hasCompletedJoin()) {
player.getConnectedServer().getMinecraftConnection().write(packet); serverConnection.getMinecraftConnection().write(packet);
} }
} }
@Override @Override
public void handleUnknown(ByteBuf buf) { public void handleUnknown(ByteBuf buf) {
if (player.getConnectedServer().hasCompletedJoin()) { VelocityServerConnection serverConnection = player.getConnectedServer();
player.getConnectedServer().getMinecraftConnection().write(buf.retain()); 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) { public void handleBackendJoinGame(JoinGame joinGame) {
resetPingData(); // reset ping data;
if (!spawned) { if (!spawned) {
// nothing special to do here // Nothing special to do with regards to spawning the player
spawned = true; spawned = true;
player.getConnection().delayedWrite(joinGame); 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 { } else {
// Ah, this is the meat and potatoes of the whole venture! // Ah, this is the meat and potatoes of the whole venture!
// //
@ -210,6 +210,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
channel, toRegister)); 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 // Flush everything
player.getConnection().flush(); player.getConnection().flush();
player.getConnectedServer().getMinecraftConnection().flush(); player.getConnectedServer().getMinecraftConnection().flush();
@ -234,8 +243,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return serverBossBars; return serverBossBars;
} }
public void handleClientPluginMessage(PluginMessage packet) { private void handleClientPluginMessage(PluginMessage packet) {
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) { if (PluginMessageUtil.isMCRegister(packet)) {
List<String> actuallyRegistered = new ArrayList<>(); List<String> actuallyRegistered = new ArrayList<>();
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
for (String channel : channels) { for (String channel : channels) {
@ -252,31 +261,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered); PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket); player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
} }
} else if (PluginMessageUtil.isMCUnregister(packet)) {
return;
}
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
List<String> channels = PluginMessageUtil.getChannels(packet); List<String> channels = PluginMessageUtil.getChannels(packet);
clientPluginMsgChannels.removeAll(channels); clientPluginMsgChannels.removeAll(channels);
} player.getConnectedServer().getMinecraftConnection().write(packet);
} else if (PluginMessageUtil.isMCBrand(packet)) {
if (PluginMessageUtil.isMCBrand(packet)) {
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet)); player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
return; } 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.
if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) { player.getConnectedServer().getMinecraftConnection().write(packet);
// Ensure that the messages are forwarded } else {
player.getConnectedServer().getMinecraftConnection().write(packet); // The client is trying to send messages too early. This is primarily caused by mods, but it's further
return; // 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);
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player, }
ChannelSide.FROM_CLIENT, packet); } else {
if (status == MessageHandler.ForwardStatus.FORWARD) { ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
// We're going to forward on the original packet. if (id == null) {
player.getConnectedServer().getMinecraftConnection().write(packet); 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; return clientPluginMsgChannels;
} }
public void setLastPing(long lastPing) { public void handleTabCompleteResponse(TabCompleteResponse response) {
this.lastPingID = lastPing; if (outstandingTabComplete != null) {
this.lastPingSent = System.currentTimeMillis(); if (!outstandingTabComplete.isAssumeCommand()) {
} String command = outstandingTabComplete.getCommand().substring(1);
try {
private void resetPingData() { Optional<List<String>> offers = server.getCommandManager().offerSuggestions(player, command);
this.lastPingID = -1; offers.ifPresent(strings -> response.getOffers().addAll(strings));
this.lastPingSent = -1; } 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.PlayerSettings;
import com.velocitypowered.api.proxy.player.SkinParts; import com.velocitypowered.api.proxy.player.SkinParts;
import com.velocitypowered.proxy.protocol.packet.ClientSettings; import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import java.util.Locale; import java.util.Locale;
public class ClientSettingsWrapper implements PlayerSettings { 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.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.PermissionProvider; 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.ConnectionRequestBuilder;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 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.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.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.VelocityConstants; 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.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.Chat; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
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.util.ThrowableUtils; 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.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent; import net.kyori.text.TranslatableComponent;
@ -143,11 +144,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
byte pos = (byte) position.ordinal(); byte pos = (byte) position.ordinal();
String json; String json;
if (position == MessagePosition.ACTION_BAR) { if (position == MessagePosition.ACTION_BAR) {
// Due to issues with action bar packets, we'll need to convert the text message into a legacy message if (getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_11) {
// and then inject the legacy text into a component... yuck! // We can use the title packet instead.
JsonObject object = new JsonObject(); TitlePacket pkt = new TitlePacket();
object.addProperty("text", ComponentSerializers.LEGACY.serialize(component)); pkt.setAction(TitlePacket.SET_ACTION_BAR);
json = VelocityServer.GSON.toJson(object); 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 { } else {
json = ComponentSerializers.JSON.serialize(component); json = ComponentSerializers.JSON.serialize(component);
} }
@ -159,8 +169,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
@Override @Override
public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) { public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
return new ConnectionRequestBuilderImpl(info); return new ConnectionRequestBuilderImpl(server);
} }
@Override @Override
@ -191,56 +201,103 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.closeWith(Disconnect.create(reason)); 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() { public VelocityServerConnection getConnectedServer() {
return connectedServer; return connectedServer;
} }
public void handleConnectionException(ServerInfo info, Throwable throwable) { public void handleConnectionException(RegisteredServer server, Throwable throwable) {
String error = ThrowableUtils.briefDescription(throwable); String error = ThrowableUtils.briefDescription(throwable);
String userMessage; String userMessage;
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
userMessage = "Exception in server " + info.getName(); userMessage = "Exception in server " + server.getServerInfo().getName();
} else { } else {
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable); logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable);
userMessage = "Exception connecting to server " + info.getName(); userMessage = "Exception connecting to server " + server.getServerInfo().getName();
} }
handleConnectionException(info, null, TextComponent.builder() handleConnectionException(server, null, TextComponent.builder()
.content(userMessage + ": ") .content(userMessage + ": ")
.color(TextColor.RED) .color(TextColor.RED)
.append(TextComponent.of(error, TextColor.WHITE)) .append(TextComponent.of(error, TextColor.WHITE))
.build()); .build());
} }
public void handleConnectionException(ServerInfo info, Disconnect disconnect) { public void handleConnectionException(RegisteredServer server, Disconnect disconnect) {
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason()); Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) { if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason); 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 { } 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) { private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) {
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);; boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo());
connectionInFlight = null; connectionInFlight = null;
if (connectedServer == null) { if (connectedServer == null) {
// The player isn't yet connected to a server. // The player isn't yet connected to a server.
Optional<ServerInfo> nextServer = getNextServerToTry(); Optional<RegisteredServer> nextServer = getNextServerToTry();
if (nextServer.isPresent()) { if (nextServer.isPresent()) {
createConnectionRequest(nextServer.get()).fireAndForget(); createConnectionRequest(nextServer.get()).fireAndForget();
} else { } else {
connection.closeWith(Disconnect.create(friendlyReason)); 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. // Already connected to the server being disconnected from.
if (kickReason != null) { 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 -> { .thenAcceptAsync(event -> {
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) { if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult(); KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult();
@ -257,11 +314,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connection.closeWith(Disconnect.create(friendlyReason)); connection.closeWith(Disconnect.create(friendlyReason));
} }
} else { } else {
connection.write(Chat.create(friendlyReason)); connection.write(Chat.createClientbound(friendlyReason));
} }
} }
Optional<ServerInfo> getNextServerToTry() { Optional<RegisteredServer> getNextServerToTry() {
List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder(); List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder();
if (tryIndex >= serversToTry.size()) { if (tryIndex >= serversToTry.size()) {
return Optional.empty(); return Optional.empty();
@ -272,21 +329,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return server.getServers().getServer(toTryName); 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) { if (connectionInFlight != null) {
return CompletableFuture.completedFuture( return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS);
ConnectionRequestResults.plainResult(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())) { private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
return CompletableFuture.completedFuture( Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(request.getServer());
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED) if (initialCheck.isPresent()) {
); return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get()));
} }
// Otherwise, initiate the connection. // 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) return server.getEventManager().fire(event)
.thenCompose((newEvent) -> { .thenCompose((newEvent) -> {
if (!newEvent.getResult().isAllowed()) { 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 @Override
public boolean hasPermission(String permission) { public @NonNull Tristate getPermissionValue(@NonNull String permission) {
return permissionFunction.getPermissionSetting(permission).asBoolean(); return permissionFunction.getPermissionValue(permission);
} }
@Override @Override
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) { public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data"); Preconditions.checkNotNull(data, "data");
PluginMessage message = new PluginMessage(); PluginMessage message = new PluginMessage();
message.setChannel(identifier.getId()); message.setChannel(identifier.getId());
message.setData(data); message.setData(data);
connection.write(message); 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 class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final ServerInfo info; private final RegisteredServer server;
ConnectionRequestBuilderImpl(ServerInfo info) { ConnectionRequestBuilderImpl(RegisteredServer server) {
this.info = Preconditions.checkNotNull(info, "info"); this.server = Preconditions.checkNotNull(server, "info");
} }
@Override @Override
public ServerInfo getServer() { public RegisteredServer getServer() {
return info; return server;
} }
@Override @Override
@ -372,7 +445,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
connect() connect()
.whenCompleteAsync((status, throwable) -> { .whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
handleConnectionException(info, throwable); handleConnectionException(server, throwable);
return; return;
} }
@ -387,7 +460,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
// Ignored; the plugin probably already handled this. // Ignored; the plugin probably already handled this.
break; break;
case SERVER_DISCONNECTED: 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; break;
} }
}, connection.getChannel().eventLoop()); }, 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.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent; import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.PlayerInfoForwarding;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,28 +1,57 @@
package com.velocitypowered.proxy.console; 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 com.velocitypowered.proxy.VelocityServer;
import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import net.kyori.text.serializer.ComponentSerializers;
import net.minecrell.terminalconsole.SimpleTerminalConsole; 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.List;
import java.util.Optional; 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 final VelocityServer server;
private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE;
public VelocityConsole(VelocityServer server) { public VelocityConsole(VelocityServer server) {
this.server = 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 @Override
protected LineReader buildReader(LineReaderBuilder builder) { protected LineReader buildReader(LineReaderBuilder builder) {
return super.buildReader(builder return super.buildReader(builder
.appName("Velocity") .appName("Velocity")
.completer((reader, parsedLine, list) -> { .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 -> { o.ifPresent(offers -> {
for (String offer : offers) { for (String offer : offers) {
list.add(new Candidate(offer)); list.add(new Candidate(offer));
@ -39,8 +68,8 @@ public final class VelocityConsole extends SimpleTerminalConsole {
@Override @Override
protected void runCommand(String command) { protected void runCommand(String command) {
if (!this.server.getCommandManager().execute(this.server.getConsoleCommandSource(), command)) { if (!this.server.getCommandManager().execute(this, command)) {
server.getConsoleCommandSource().sendMessage(TextComponent.of("Command not found.", TextColor.RED)); 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.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.messages.*; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import org.apache.logging.log4j.LogManager; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import org.apache.logging.log4j.Logger; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class VelocityChannelRegistrar implements ChannelRegistrar { 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<>(); private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>();
@Override @Override
public void register(MessageHandler handler, ChannelIdentifier... identifiers) { public void register(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier, Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier,
"identifier is unknown"); "identifier is unknown");
} }
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
handlers.put(identifier.getId(), handler);
identifierMap.put(identifier.getId(), identifier); 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 @Override
public void unregister(ChannelIdentifier... identifiers) { public void unregister(ChannelIdentifier... identifiers) {
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
} }
for (ChannelIdentifier identifier : identifiers) { for (ChannelIdentifier identifier : identifiers) {
handlers.remove(identifier.getId());
identifierMap.remove(identifier.getId()); identifierMap.remove(identifier.getId());
} }
} }
@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
public boolean registered(String id) { public boolean registered(String id) {
return identifierMap.containsKey(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.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler; import com.velocitypowered.proxy.protocol.netty.*;
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 io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*; import io.netty.channel.*;
import io.netty.channel.epoll.Epoll; import io.netty.channel.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.kqueue.*; import io.netty.channel.kqueue.*;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
@ -75,7 +65,7 @@ public final class ConnectionManager {
@Override @Override
protected void initChannel(final Channel ch) { protected void initChannel(final Channel ch) {
ch.pipeline() 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(LEGACY_PING_DECODER, new LegacyPingDecoder())
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE) .addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
@ -125,7 +115,9 @@ public final class ConnectionManager {
public Bootstrap createWorker() { public Bootstrap createWorker() {
return new Bootstrap() return new Bootstrap()
.channel(this.transportType.socketChannelClass) .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() { public void shutdown() {

Datei anzeigen

@ -13,7 +13,4 @@ public interface Connections {
String MINECRAFT_DECODER = "minecraft-decoder"; String MINECRAFT_DECODER = "minecraft-decoder";
String MINECRAFT_ENCODER = "minecraft-encoder"; String MINECRAFT_ENCODER = "minecraft-encoder";
String READ_TIMEOUT = "read-timeout"; 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 com.velocitypowered.proxy.VelocityServer;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*; import io.netty.channel.Channel;
import io.netty.channel.pool.*; import io.netty.channel.pool.*;
import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext; 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.EventSubscriber;
import net.kyori.event.PostResult; import net.kyori.event.PostResult;
import net.kyori.event.SimpleEventBus; 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 net.kyori.event.method.asm.ASMEventExecutorFactory;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.ArrayList;
import java.util.concurrent.*; 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 { public class VelocityEventManager implements EventManager {
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class); private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.plugin; package com.velocitypowered.proxy.plugin;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.api.plugin.meta.PluginDependency;
import com.velocitypowered.proxy.VelocityServer; 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.Guice;
import com.google.inject.Injector; 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.plugin.meta.PluginDependency;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer; 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.PluginManager;
import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.proxy.VelocityServer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*; import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*; import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
@ -23,7 +23,7 @@ public enum StateRegistry {
}, },
STATUS { STATUS {
{ {
SERVERBOUND.register(StatusRequest.class, StatusRequest::new, SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
genericMappings(0x00)); genericMappings(0x00));
SERVERBOUND.register(StatusPing.class, StatusPing::new, SERVERBOUND.register(StatusPing.class, StatusPing::new,
genericMappings(0x01)); genericMappings(0x01));
@ -40,8 +40,7 @@ public enum StateRegistry {
map(0x14, MINECRAFT_1_8, false), map(0x14, MINECRAFT_1_8, false),
map(0x01, MINECRAFT_1_9, false), map(0x01, MINECRAFT_1_9, false),
map(0x02, MINECRAFT_1_12, false), map(0x02, MINECRAFT_1_12, false),
map(0x01, MINECRAFT_1_12_1, false), map(0x01, MINECRAFT_1_12_1, false));
map(0x05, MINECRAFT_1_13, false));
SERVERBOUND.register(Chat.class, Chat::new, SERVERBOUND.register(Chat.class, Chat::new,
map(0x01, MINECRAFT_1_8, false), map(0x01, MINECRAFT_1_8, false),
map(0x02, MINECRAFT_1_9, false), map(0x02, MINECRAFT_1_9, false),
@ -77,10 +76,9 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_12, true), map(0x0F, MINECRAFT_1_12, true),
map(0x0E, MINECRAFT_1_13, true)); map(0x0E, MINECRAFT_1_13, true));
CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new, CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new,
map(0x3A, MINECRAFT_1_8, true), map(0x3A, MINECRAFT_1_8, false),
map(0x0E, MINECRAFT_1_9, true), map(0x0E, MINECRAFT_1_9, false),
map(0x0E, MINECRAFT_1_12, true), map(0x0E, MINECRAFT_1_12, false));
map(0x10, MINECRAFT_1_13, true));
CLIENTBOUND.register(PluginMessage.class, PluginMessage::new, CLIENTBOUND.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_8, false), map(0x3F, MINECRAFT_1_8, false),
map(0x18, MINECRAFT_1_9, false), map(0x18, MINECRAFT_1_9, false),
@ -114,30 +112,12 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_12, true), map(0x49, MINECRAFT_1_12, true),
map(0x4A, MINECRAFT_1_12_1, true), map(0x4A, MINECRAFT_1_12_1, true),
map(0x4E, MINECRAFT_1_13, true)); map(0x4E, MINECRAFT_1_13, true));
CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new, CLIENTBOUND.register(TitlePacket.class, TitlePacket::new,
map(0x3D, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_8, true),
map(0x38, MINECRAFT_1_9, true), map(0x45, MINECRAFT_1_9, true),
map(0x3A, MINECRAFT_1_12, true), map(0x47, MINECRAFT_1_12, true),
map(0x3B, MINECRAFT_1_12_1, true), map(0x48, MINECRAFT_1_12_1, true),
map(0x3E, MINECRAFT_1_13, true)); map(0x4B, 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));
} }
}, },
LOGIN { LOGIN {

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,7 +1,10 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions; 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.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException; 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.buffer.ByteBuf;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List; import java.util.List;

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,7 +1,7 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; 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; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;

Datei anzeigen

@ -1,10 +1,16 @@
package com.velocitypowered.proxy.protocol.packet; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
public class StatusRequest implements MinecraftPacket { public class StatusRequest implements MinecraftPacket {
public static final StatusRequest INSTANCE = new StatusRequest();
private StatusRequest() {
}
@Override @Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { 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) { 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; package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; 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; import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
public class TabCompleteRequest implements MinecraftPacket { public class TabCompleteRequest implements MinecraftPacket {
private int transactionId;
private String command; private String command;
private boolean assumeCommand; private boolean assumeCommand;
private boolean hasPosition; private boolean hasPosition;
private long position; private long position;
public int getTransactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
public String getCommand() { public String getCommand() {
return command; return command;
} }
@ -58,8 +49,7 @@ public class TabCompleteRequest implements MinecraftPacket {
@Override @Override
public String toString() { public String toString() {
return "TabCompleteRequest{" + return "TabCompleteRequest{" +
"transactionId=" + transactionId + "command='" + command + '\'' +
", command='" + command + '\'' +
", assumeCommand=" + assumeCommand + ", assumeCommand=" + assumeCommand +
", hasPosition=" + hasPosition + ", hasPosition=" + hasPosition +
", position=" + position + ", position=" + position +
@ -68,35 +58,25 @@ public class TabCompleteRequest implements MinecraftPacket {
@Override @Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) { this.command = ProtocolUtils.readString(buf);
this.transactionId = ProtocolUtils.readVarInt(buf); if (protocolVersion >= MINECRAFT_1_9) {
this.command = ProtocolUtils.readString(buf); this.assumeCommand = buf.readBoolean();
} else { }
this.command = ProtocolUtils.readString(buf); this.hasPosition = buf.readBoolean();
if (protocolVersion >= MINECRAFT_1_9) { if (hasPosition) {
this.assumeCommand = buf.readBoolean(); this.position = buf.readLong();
}
this.hasPosition = buf.readBoolean();
if (hasPosition) {
this.position = buf.readLong();
}
} }
} }
@Override @Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) { ProtocolUtils.writeString(buf, command);
ProtocolUtils.writeVarInt(buf, transactionId); if (protocolVersion >= MINECRAFT_1_9) {
ProtocolUtils.writeString(buf, command); buf.writeBoolean(assumeCommand);
} else { }
ProtocolUtils.writeString(buf, command); buf.writeBoolean(hasPosition);
if (protocolVersion >= MINECRAFT_1_9) { if (hasPosition) {
buf.writeBoolean(assumeCommand); buf.writeLong(position);
}
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.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; 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.ArrayList;
import java.util.List; import java.util.List;
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13;
public class TabCompleteResponse implements MinecraftPacket { public class TabCompleteResponse implements MinecraftPacket {
private int transactionId; private final List<String> offers = new ArrayList<>();
private int start;
private int length;
private final List<Offer> offers = new ArrayList<>();
public int getTransactionId() { public List<String> getOffers() {
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() {
return offers; return offers;
} }
@Override @Override
public String toString() { public String toString() {
return "TabCompleteResponse{" + return "TabCompleteResponse{" +
"transactionId=" + transactionId + "offers=" + offers +
", start=" + start +
", length=" + length +
", offers=" + offers +
'}'; '}';
} }
@Override @Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) { int offersAvailable = ProtocolUtils.readVarInt(buf);
this.transactionId = ProtocolUtils.readVarInt(buf); for (int i = 0; i < offersAvailable; i++) {
this.start = ProtocolUtils.readVarInt(buf); offers.add(ProtocolUtils.readString(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));
}
} }
} }
@Override @Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
if (protocolVersion >= MINECRAFT_1_13) { ProtocolUtils.writeVarInt(buf, offers.size());
ProtocolUtils.writeVarInt(buf, transactionId); for (String offer : offers) {
ProtocolUtils.writeVarInt(buf, start); ProtocolUtils.writeString(buf, offer);
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 +
'}';
} }
} }
} }

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.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -20,13 +19,20 @@ public enum PluginMessageUtil {
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand"); 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) { public static List<String> getChannels(PluginMessage message) {
Preconditions.checkNotNull(message, "message"); Preconditions.checkNotNull(message, "message");
Preconditions.checkArgument(message.getChannel().equals("REGISTER") || Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s",
message.getChannel().equals("UNREGISTER") || message.getChannel());
message.getChannel().equals("minecraft:register") ||
message.getChannel().equals("minecraft:unregister"),
"Unknown channel type " + message.getChannel());
String channels = new String(message.getData(), StandardCharsets.UTF_8); String channels = new String(message.getData(), StandardCharsets.UTF_8);
return ImmutableList.copyOf(channels.split("\0")); 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.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
public class VelocityScheduler implements Scheduler { public class VelocityScheduler implements Scheduler {
private final PluginManager pluginManager; private final PluginManager pluginManager;
@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler {
} }
@Override @Override
public TaskBuilder delay(int time, TimeUnit unit) { public TaskBuilder delay(long time, TimeUnit unit) {
this.delay = unit.toMillis(time); this.delay = unit.toMillis(time);
return this; return this;
} }
@Override @Override
public TaskBuilder repeat(int time, TimeUnit unit) { public TaskBuilder repeat(long time, TimeUnit unit) {
this.repeat = unit.toMillis(time); this.repeat = unit.toMillis(time);
return this; return this;
} }
@ -91,62 +90,9 @@ public class VelocityScheduler implements Scheduler {
@Override @Override
public ScheduledTask schedule() { public ScheduledTask schedule() {
if (delay == 0 && repeat == 0) { VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
// A special purpose, simplified implementation tasksByPlugin.put(plugin, task);
VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable); return task;
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);
} }
} }
@ -154,6 +100,7 @@ public class VelocityScheduler implements Scheduler {
private final Object plugin; private final Object plugin;
private final Runnable runnable; private final Runnable runnable;
private ScheduledFuture<?> future; private ScheduledFuture<?> future;
private volatile Thread currentTaskThread;
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) { private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
this.plugin = plugin; this.plugin = plugin;
@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler {
public void cancel() { public void cancel() {
if (future != null) { if (future != null) {
future.cancel(false); future.cancel(false);
Thread cur = currentTaskThread;
if (cur != null) {
cur.interrupt();
}
onFinish(); onFinish();
} }
} }
@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler {
@Override @Override
public void run() { public void run() {
taskService.execute(() -> { taskService.execute(() -> {
currentTaskThread = Thread.currentThread();
try { try {
runnable.run(); runnable.run();
} catch (Exception e) { } catch (Exception e) {
@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler {
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin); 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.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
/** /**

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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