geforkt von Mirrors/Velocity
Merge branch 'master' into plugin-message-event
# Conflicts: # proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java # proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java
Dieser Commit ist enthalten in:
Commit
5ff36f1ae7
@ -44,5 +44,4 @@ is not currently suitable for production usage. For development and 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.
|
||||
Forge support is currently not implemented, but Velocity will work with Forge's
|
||||
vanilla fallback mode.
|
||||
Forge is fully supported but mod compatibility may vary.
|
@ -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
|
||||
+ '}';
|
||||
}
|
||||
}
|
@ -3,8 +3,10 @@ package com.velocitypowered.api.event.connection;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@ -52,44 +54,59 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an "allowed/allowed with online mode/denied" result with a reason allowed for denial.
|
||||
* Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial.
|
||||
*/
|
||||
public static class PreLoginComponentResult extends ResultedEvent.ComponentResult {
|
||||
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
|
||||
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
|
||||
public static class PreLoginComponentResult implements ResultedEvent.Result {
|
||||
|
||||
private final boolean onlineMode;
|
||||
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult(Result.ALLOWED, null);
|
||||
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(Result.FORCE_ONLINE, null);
|
||||
private static final PreLoginComponentResult FORCE_OFFLINEMODE = new PreLoginComponentResult(Result.FORCE_OFFLINE, null);
|
||||
|
||||
/**
|
||||
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode.
|
||||
* @param allowedOnlineMode if true, online mode will be used for the connection
|
||||
*/
|
||||
private PreLoginComponentResult(boolean allowedOnlineMode) {
|
||||
super(true, null);
|
||||
this.onlineMode = allowedOnlineMode;
|
||||
private final Result result;
|
||||
private final Optional<Component> reason;
|
||||
|
||||
private PreLoginComponentResult(Result result, @Nullable Component reason) {
|
||||
this.result = result;
|
||||
this.reason = Optional.ofNullable(reason);
|
||||
}
|
||||
|
||||
private PreLoginComponentResult(@Nullable Component reason) {
|
||||
super(reason == null, reason);
|
||||
// Don't care about this
|
||||
this.onlineMode = false;
|
||||
@Override
|
||||
public boolean isAllowed() {
|
||||
return result != Result.DISALLOWED;
|
||||
}
|
||||
|
||||
public Optional<Component> getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public boolean isOnlineModeAllowed() {
|
||||
return this.onlineMode;
|
||||
return result == Result.FORCE_ONLINE;
|
||||
}
|
||||
|
||||
public boolean isForceOfflineMode() {
|
||||
return result == Result.FORCE_OFFLINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (isForceOfflineMode()) {
|
||||
return "allowed with force offline mode";
|
||||
}
|
||||
if (isOnlineModeAllowed()) {
|
||||
return "allowed with online mode";
|
||||
}
|
||||
|
||||
return super.toString();
|
||||
if (isAllowed()) {
|
||||
return "allowed";
|
||||
}
|
||||
if (reason.isPresent()) {
|
||||
return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get());
|
||||
}
|
||||
return "denied";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a result indicating the connection will be allowed through the proxy.
|
||||
* Returns a result indicating the connection will be allowed through
|
||||
* the proxy.
|
||||
* @return the allowed result
|
||||
*/
|
||||
public static PreLoginComponentResult allowed() {
|
||||
@ -97,23 +114,41 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a result indicating the connection will be allowed through the proxy, but the connection will be
|
||||
* forced to use online mode provided that the proxy is in offline mode. This acts similarly to {@link #allowed()}
|
||||
* on an online-mode proxy.
|
||||
* Returns a result indicating the connection will be allowed through
|
||||
* the proxy, but the connection will be forced to use online mode
|
||||
* provided that the proxy is in offline mode. This acts similarly to
|
||||
* {@link #allowed()} on an online-mode proxy.
|
||||
* @return the result
|
||||
*/
|
||||
public static PreLoginComponentResult forceOnlineMode() {
|
||||
return FORCE_ONLINEMODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a result indicating the connection will be allowed through
|
||||
* the proxy, but the connection will be forced to use offline mode even
|
||||
* when proxy running in online mode
|
||||
* @return the result
|
||||
*/
|
||||
public static PreLoginComponentResult forceOfflineMode() {
|
||||
return FORCE_OFFLINEMODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denies the login with the specified reason.
|
||||
* @param reason the reason for disallowing the connection
|
||||
* @return a new result
|
||||
*/
|
||||
public static PreLoginComponentResult denied(@NonNull Component reason) {
|
||||
public static PreLoginComponentResult denied(Component reason) {
|
||||
Preconditions.checkNotNull(reason, "reason");
|
||||
return new PreLoginComponentResult(reason);
|
||||
return new PreLoginComponentResult(Result.DISALLOWED, reason);
|
||||
}
|
||||
|
||||
private enum Result {
|
||||
ALLOWED,
|
||||
FORCE_ONLINE,
|
||||
FORCE_OFFLINE,
|
||||
DISALLOWED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,118 @@
|
||||
package com.velocitypowered.api.event.player;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.ResultedEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import net.kyori.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Fired when a player is kicked from a server. You may either allow Velocity to kick the player (with an optional reason
|
||||
* override) or redirect the player to a separate server.
|
||||
*/
|
||||
public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> {
|
||||
private final Player player;
|
||||
private final ServerInfo server;
|
||||
private final Component originalReason;
|
||||
private final boolean duringLogin;
|
||||
private ServerKickResult result;
|
||||
|
||||
public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
|
||||
this.duringLogin = duringLogin;
|
||||
this.result = new DisconnectPlayer(fancyReason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerKickResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResult(@NonNull ServerKickResult result) {
|
||||
this.result = Preconditions.checkNotNull(result, "result");
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public ServerInfo getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public Component getOriginalReason() {
|
||||
return originalReason;
|
||||
}
|
||||
|
||||
public boolean kickedDuringLogin() {
|
||||
return duringLogin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the base interface for {@link KickedFromServerEvent} results.
|
||||
*/
|
||||
public interface ServerKickResult extends ResultedEvent.Result {}
|
||||
|
||||
/**
|
||||
* Tells the proxy to disconnect the player with the specified reason.
|
||||
*/
|
||||
public static class DisconnectPlayer implements ServerKickResult {
|
||||
private final Component component;
|
||||
|
||||
private DisconnectPlayer(Component component) {
|
||||
this.component = Preconditions.checkNotNull(component, "component");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Component getReason() {
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DisconnectPlayer} with the specified reason.
|
||||
* @param reason the reason to use when disconnecting the player
|
||||
* @return the disconnect result
|
||||
*/
|
||||
public static DisconnectPlayer create(Component reason) {
|
||||
return new DisconnectPlayer(reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the proxy to redirect the player to another server. No messages will be sent from the proxy
|
||||
* when this result is used.
|
||||
*/
|
||||
public static class RedirectPlayer implements ServerKickResult {
|
||||
private final ServerInfo server;
|
||||
|
||||
private RedirectPlayer(ServerInfo server) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public ServerInfo getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new redirect result to forward the player to the specified {@code server}.
|
||||
* @param server the server to send the player to
|
||||
* @return the redirect result
|
||||
*/
|
||||
public static RedirectPlayer create(ServerInfo server) {
|
||||
return new RedirectPlayer(server);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,11 +14,13 @@ import java.util.Optional;
|
||||
*/
|
||||
public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> {
|
||||
private final Player player;
|
||||
private final ServerInfo originalServer;
|
||||
private ServerResult result;
|
||||
|
||||
public ServerPreConnectEvent(Player player, ServerResult result) {
|
||||
public ServerPreConnectEvent(Player player, ServerInfo originalServer) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.result = Preconditions.checkNotNull(result, "result");
|
||||
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
|
||||
this.result = ServerResult.allowed(originalServer);
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
this.result = Preconditions.checkNotNull(result, "result");
|
||||
}
|
||||
|
||||
public ServerInfo getOriginalServer() {
|
||||
return originalServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerPreConnectEvent{" +
|
||||
"player=" + player +
|
||||
", originalServer=" + originalServer +
|
||||
", result=" + result +
|
||||
'}';
|
||||
}
|
||||
@ -50,11 +57,11 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
private static final ServerResult DENIED = new ServerResult(false, null);
|
||||
|
||||
private final boolean allowed;
|
||||
private final ServerInfo info;
|
||||
private final ServerInfo server;
|
||||
|
||||
private ServerResult(boolean allowed, @Nullable ServerInfo info) {
|
||||
private ServerResult(boolean allowed, @Nullable ServerInfo server) {
|
||||
this.allowed = allowed;
|
||||
this.info = info;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -62,8 +69,8 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public Optional<ServerInfo> getInfo() {
|
||||
return Optional.ofNullable(info);
|
||||
public Optional<ServerInfo> getServer() {
|
||||
return Optional.ofNullable(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -71,7 +78,7 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
|
||||
if (!allowed) {
|
||||
return "denied";
|
||||
}
|
||||
return "allowed: connect to " + info.getName();
|
||||
return "allowed: connect to " + server.getName();
|
||||
}
|
||||
|
||||
public static ServerResult denied() {
|
||||
|
@ -4,7 +4,6 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import net.kyori.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
@ -17,12 +16,18 @@ public class ServerPing {
|
||||
private final Players players;
|
||||
private final Component description;
|
||||
private final @Nullable Favicon favicon;
|
||||
private final Modinfo modinfo;
|
||||
|
||||
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) {
|
||||
this(version, players, description, favicon, Modinfo.DEFAULT);
|
||||
}
|
||||
|
||||
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, @Nullable Modinfo modinfo) {
|
||||
this.version = Preconditions.checkNotNull(version, "version");
|
||||
this.players = players;
|
||||
this.description = Preconditions.checkNotNull(description, "description");
|
||||
this.favicon = favicon;
|
||||
this.modinfo = modinfo;
|
||||
}
|
||||
|
||||
public Version getVersion() {
|
||||
@ -41,6 +46,10 @@ public class ServerPing {
|
||||
return Optional.ofNullable(favicon);
|
||||
}
|
||||
|
||||
public Optional<Modinfo> getModinfo() {
|
||||
return Optional.ofNullable(modinfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerPing{" +
|
||||
@ -54,11 +63,19 @@ public class ServerPing {
|
||||
public Builder asBuilder() {
|
||||
Builder builder = new Builder();
|
||||
builder.version = version;
|
||||
builder.onlinePlayers = players.online;
|
||||
builder.maximumPlayers = players.max;
|
||||
builder.samplePlayers.addAll(players.sample);
|
||||
if (players != null) {
|
||||
builder.onlinePlayers = players.online;
|
||||
builder.maximumPlayers = players.max;
|
||||
builder.samplePlayers.addAll(players.sample);
|
||||
} else {
|
||||
builder.nullOutPlayers = true;
|
||||
}
|
||||
builder.description = description;
|
||||
builder.favicon = favicon;
|
||||
builder.nullOutModinfo = modinfo == null;
|
||||
if (modinfo != null) {
|
||||
builder.mods.addAll(modinfo.modList);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
@ -74,9 +91,11 @@ public class ServerPing {
|
||||
private int onlinePlayers;
|
||||
private int maximumPlayers;
|
||||
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
|
||||
private final List<Mod> mods = new ArrayList<>();
|
||||
private Component description;
|
||||
private Favicon favicon;
|
||||
private boolean nullOutPlayers;
|
||||
private boolean nullOutModinfo;
|
||||
|
||||
private Builder() {
|
||||
|
||||
@ -102,11 +121,26 @@ public class ServerPing {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder mods(Mod... mods) {
|
||||
this.mods.addAll(Arrays.asList(mods));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clearMods() {
|
||||
this.mods.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clearSamplePlayers() {
|
||||
this.samplePlayers.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder notModCompatible() {
|
||||
this.nullOutModinfo = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nullPlayers() {
|
||||
this.nullOutPlayers = true;
|
||||
return this;
|
||||
@ -123,7 +157,8 @@ public class ServerPing {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public Version getVersion() {
|
||||
@ -150,6 +185,10 @@ public class ServerPing {
|
||||
return favicon;
|
||||
}
|
||||
|
||||
public List<Mod> getMods() {
|
||||
return mods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Builder{" +
|
||||
@ -157,8 +196,11 @@ public class ServerPing {
|
||||
", onlinePlayers=" + onlinePlayers +
|
||||
", maximumPlayers=" + maximumPlayers +
|
||||
", samplePlayers=" + samplePlayers +
|
||||
", mods=" + mods +
|
||||
", description=" + description +
|
||||
", favicon=" + favicon +
|
||||
", nullOutPlayers=" + nullOutPlayers +
|
||||
", nullOutModinfo=" + nullOutModinfo +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -247,4 +289,25 @@ public class ServerPing {
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Modinfo {
|
||||
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of());
|
||||
|
||||
private final String type = "FML";
|
||||
private final List<Mod> modList;
|
||||
|
||||
public Modinfo(List<Mod> modList) {
|
||||
this.modList = ImmutableList.copyOf(modList);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mod {
|
||||
private final String id;
|
||||
private final String version;
|
||||
|
||||
public Mod(String id, String version) {
|
||||
this.id = Preconditions.checkNotNull(id, "id");
|
||||
this.version = Preconditions.checkNotNull(version, "version");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public final class GameProfile {
|
||||
'}';
|
||||
}
|
||||
|
||||
public final class Property {
|
||||
public final static class Property {
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String signature;
|
||||
|
15
build.gradle
15
build.gradle
@ -13,8 +13,8 @@ allprojects {
|
||||
// dependency versions
|
||||
junitVersion = '5.3.0-M1'
|
||||
slf4jVersion = '1.7.25'
|
||||
log4jVersion = '2.11.0'
|
||||
nettyVersion = '4.1.28.Final'
|
||||
log4jVersion = '2.11.1'
|
||||
nettyVersion = '4.1.29.Final'
|
||||
guavaVersion = '25.1-jre'
|
||||
|
||||
getCurrentBranchName = {
|
||||
@ -27,6 +27,17 @@ allprojects {
|
||||
return os.toString().trim()
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentShortRevision = {
|
||||
new ByteArrayOutputStream().withStream { os ->
|
||||
exec {
|
||||
executable = "git"
|
||||
args = ["rev-parse", "HEAD"]
|
||||
standardOutput = os
|
||||
}
|
||||
return os.toString().trim().substring(0, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
|
||||
|
@ -14,8 +14,11 @@ compileTestJava {
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
def buildNumber = System.getenv("BUILD_NUMBER") ?: "unknown"
|
||||
def version = "${project.version} (git-${project.ext.getCurrentShortRevision()}, build ${buildNumber})"
|
||||
|
||||
attributes 'Main-Class': 'com.velocitypowered.proxy.Velocity'
|
||||
attributes 'Implementation-Version': project.version
|
||||
attributes 'Implementation-Version': version
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ public class Velocity {
|
||||
|
||||
public static void main(String... args) {
|
||||
startTime = System.currentTimeMillis();
|
||||
logger.info("Booting up Velocity...");
|
||||
logger.info("Booting up Velocity {}...", Velocity.class.getPackage().getImplementationVersion());
|
||||
|
||||
VelocityServer server = new VelocityServer();
|
||||
server.start();
|
||||
|
@ -29,7 +29,7 @@ public class VelocityCommand implements Command {
|
||||
.append(TextComponent.of(" or the ").resetStyle())
|
||||
.append(TextComponent.builder("Velocity GitHub")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/astei/velocity"))
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/VelocityPowered/Velocity"))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
@ -48,7 +49,7 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
@Comment({
|
||||
"Should we forward IP addresses and other data to backend servers?",
|
||||
"Available options:",
|
||||
"- \"none\": No forwarding will be done. All players will appear to be Should we forward IP addresses and other data to backend servers?connecting from the proxy",
|
||||
"- \"none\": No forwarding will be done. All players will appear to be connecting from the proxy",
|
||||
" and will have offline-mode UUIDs.",
|
||||
"- \"legacy\": Forward player IPs and UUIDs in BungeeCord-compatible fashion. Use this if you run",
|
||||
" servers using Minecraft 1.12 or lower.",
|
||||
@ -62,6 +63,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
@ConfigKey("forwarding-secret")
|
||||
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@Comment("Announce whether or not your server supports Forge/FML. If you run a modded server, we suggest turning this on.")
|
||||
@ConfigKey("announce-forge")
|
||||
private boolean announceForge = false;
|
||||
|
||||
@Table("[servers]")
|
||||
private final Servers servers;
|
||||
|
||||
@ -83,12 +88,13 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
}
|
||||
|
||||
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
|
||||
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, Servers servers,
|
||||
Advanced advanced, Query query) {
|
||||
boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
|
||||
Servers servers, Advanced advanced, Query query) {
|
||||
this.bind = bind;
|
||||
this.motd = motd;
|
||||
this.showMaxPlayers = showMaxPlayers;
|
||||
this.onlineMode = onlineMode;
|
||||
this.announceForge = announceForge;
|
||||
this.playerInfoForwardingMode = playerInfoForwardingMode;
|
||||
this.forwardingSecret = forwardingSecret;
|
||||
this.servers = servers;
|
||||
@ -103,13 +109,13 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
if (bind.isEmpty()) {
|
||||
logger.error("'bind' option is empty.");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
try {
|
||||
AddressUtil.parseAddress(bind);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("'bind' option does not specify a valid IP address.", e);
|
||||
valid = false;
|
||||
} else {
|
||||
try {
|
||||
AddressUtil.parseAddress(bind);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("'bind' option does not specify a valid IP address.", e);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlineMode) {
|
||||
@ -118,11 +124,11 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
|
||||
switch (playerInfoForwardingMode) {
|
||||
case NONE:
|
||||
logger.info("Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs.");
|
||||
logger.warn("Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs.");
|
||||
break;
|
||||
case MODERN:
|
||||
if (forwardingSecret.length == 0) {
|
||||
logger.error("You don't have a forwarding secret set.");
|
||||
if (forwardingSecret == null || forwardingSecret.length == 0) {
|
||||
logger.error("You don't have a forwarding secret set. This is required for security.");
|
||||
valid = false;
|
||||
}
|
||||
break;
|
||||
@ -148,7 +154,7 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
|
||||
for (String s : servers.getAttemptConnectionOrder()) {
|
||||
if (!servers.getServers().containsKey(s)) {
|
||||
logger.error("Fallback server " + s + " doesn't exist!");
|
||||
logger.error("Fallback server " + s + " is not registered in your configuration!");
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
@ -165,18 +171,18 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
logger.error("Invalid compression level {}", advanced.compressionLevel);
|
||||
valid = false;
|
||||
} else if (advanced.compressionLevel == 0) {
|
||||
logger.warn("ALL packets going through the proxy are going to be uncompressed. This will increase bandwidth usage.");
|
||||
logger.warn("ALL packets going through the proxy will be uncompressed. This will increase bandwidth usage.");
|
||||
}
|
||||
|
||||
if (advanced.compressionThreshold < -1) {
|
||||
logger.error("Invalid compression threshold {}", advanced.compressionLevel);
|
||||
valid = false;
|
||||
} else if (advanced.compressionThreshold == 0) {
|
||||
logger.warn("ALL packets going through the proxy are going to be compressed. This may hurt performance.");
|
||||
logger.warn("ALL packets going through the proxy will be compressed. This will compromise throughput and increase CPU usage!");
|
||||
}
|
||||
|
||||
if (advanced.loginRatelimit < 0) {
|
||||
logger.error("Invalid login ratelimit {}", advanced.loginRatelimit);
|
||||
logger.error("Invalid login ratelimit {}ms", advanced.loginRatelimit);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
@ -217,7 +223,7 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
if (motd.startsWith("{")) {
|
||||
motdAsComponent = ComponentSerializers.JSON.deserialize(motd);
|
||||
} else {
|
||||
motdAsComponent = ComponentSerializers.LEGACY.deserialize(LegacyChatColorUtils.translate('&', motd));
|
||||
motdAsComponent = ComponentSerializers.LEGACY.deserialize(motd, '&');
|
||||
}
|
||||
}
|
||||
return motdAsComponent;
|
||||
@ -263,54 +269,34 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return favicon;
|
||||
}
|
||||
|
||||
private void setBind(String bind) {
|
||||
this.bind = bind;
|
||||
public boolean isAnnounceForge() {
|
||||
return announceForge;
|
||||
}
|
||||
|
||||
private void setMotd(String motd) {
|
||||
this.motd = motd;
|
||||
public int getConnectTimeout() {
|
||||
return advanced.getConnectionTimeout();
|
||||
}
|
||||
|
||||
private void setShowMaxPlayers(int showMaxPlayers) {
|
||||
this.showMaxPlayers = showMaxPlayers;
|
||||
}
|
||||
|
||||
private void setOnlineMode(boolean onlineMode) {
|
||||
this.onlineMode = onlineMode;
|
||||
}
|
||||
|
||||
private void setPlayerInfoForwardingMode(PlayerInfoForwarding playerInfoForwardingMode) {
|
||||
this.playerInfoForwardingMode = playerInfoForwardingMode;
|
||||
}
|
||||
|
||||
private void setForwardingSecret(byte[] forwardingSecret) {
|
||||
this.forwardingSecret = forwardingSecret;
|
||||
}
|
||||
|
||||
private void setMotdAsComponent(Component motdAsComponent) {
|
||||
this.motdAsComponent = motdAsComponent;
|
||||
}
|
||||
|
||||
private void setFavicon(Favicon favicon) {
|
||||
this.favicon = favicon;
|
||||
public int getReadTimeout() {
|
||||
return advanced.getReadTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return "VelocityConfiguration{"
|
||||
+ "bind='" + bind + '\''
|
||||
+ ", motd='" + motd + '\''
|
||||
+ ", showMaxPlayers=" + showMaxPlayers
|
||||
+ ", onlineMode=" + onlineMode
|
||||
+ ", playerInfoForwardingMode=" + playerInfoForwardingMode
|
||||
+ ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret)
|
||||
+ ", servers=" + servers
|
||||
+ ", advanced=" + advanced
|
||||
+ ", query=" + query
|
||||
+ ", motdAsComponent=" + motdAsComponent
|
||||
+ ", favicon=" + favicon
|
||||
+ '}';
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("configVersion", configVersion)
|
||||
.add("bind", bind)
|
||||
.add("motd", motd)
|
||||
.add("showMaxPlayers", showMaxPlayers)
|
||||
.add("onlineMode", onlineMode)
|
||||
.add("playerInfoForwardingMode", playerInfoForwardingMode)
|
||||
.add("forwardingSecret", forwardingSecret)
|
||||
.add("announceForge", announceForge)
|
||||
.add("servers", servers)
|
||||
.add("advanced", advanced)
|
||||
.add("query", query)
|
||||
.add("favicon", favicon)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static VelocityConfiguration read(Path path) throws IOException {
|
||||
@ -335,6 +321,7 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
toml.getString("motd", "&3A Velocity Server"),
|
||||
toml.getLong("show-max-players", 500L).intValue(),
|
||||
toml.getBoolean("online-mode", true),
|
||||
toml.getBoolean("announce-forge", false),
|
||||
PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding-mode", "MODERN").toUpperCase()),
|
||||
forwardingSecret,
|
||||
servers,
|
||||
@ -441,21 +428,23 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
"Disable by setting to 0"})
|
||||
@ConfigKey("login-ratelimit")
|
||||
private int loginRatelimit = 3000;
|
||||
@Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."})
|
||||
@ConfigKey("connection-timeout")
|
||||
private int connectionTimeout = 5000;
|
||||
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
|
||||
@ConfigKey("read-timeout")
|
||||
private int readTimeout = 30000;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
|
||||
private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) {
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.loginRatelimit = loginRatelimit;
|
||||
}
|
||||
|
||||
private Advanced(Toml toml) {
|
||||
if (toml != null) {
|
||||
this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue();
|
||||
this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
|
||||
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
|
||||
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
|
||||
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,33 +452,31 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return compressionThreshold;
|
||||
}
|
||||
|
||||
public void setCompressionThreshold(int compressionThreshold) {
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
}
|
||||
|
||||
public int getCompressionLevel() {
|
||||
return compressionLevel;
|
||||
}
|
||||
|
||||
public void setCompressionLevel(int compressionLevel) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
}
|
||||
|
||||
public int getLoginRatelimit() {
|
||||
return loginRatelimit;
|
||||
}
|
||||
|
||||
public void setLoginRatelimit(int loginRatelimit) {
|
||||
this.loginRatelimit = loginRatelimit;
|
||||
public int getConnectionTimeout() {
|
||||
return connectionTimeout;
|
||||
}
|
||||
|
||||
public int getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Advanced{"
|
||||
+ "compressionThreshold=" + compressionThreshold
|
||||
+ ", compressionLevel=" + compressionLevel
|
||||
+ ", loginRatelimit=" + loginRatelimit
|
||||
+ '}';
|
||||
return "Advanced{" +
|
||||
"compressionThreshold=" + compressionThreshold +
|
||||
", compressionLevel=" + compressionLevel +
|
||||
", loginRatelimit=" + loginRatelimit +
|
||||
", connectionTimeout=" + connectionTimeout +
|
||||
", readTimeout=" + readTimeout +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,18 +508,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return queryEnabled;
|
||||
}
|
||||
|
||||
public void setQueryEnabled(boolean queryEnabled) {
|
||||
this.queryEnabled = queryEnabled;
|
||||
}
|
||||
|
||||
public int getQueryPort() {
|
||||
return queryPort;
|
||||
}
|
||||
|
||||
public void setQueryPort(int queryPort) {
|
||||
this.queryPort = queryPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Query{"
|
||||
|
@ -45,7 +45,9 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
private MinecraftSessionHandler sessionHandler;
|
||||
private int protocolVersion;
|
||||
private MinecraftConnectionAssociation association;
|
||||
private boolean isLegacyForge;
|
||||
private final VelocityServer server;
|
||||
private boolean canSendLegacyFMLResetPacket = false;
|
||||
|
||||
public MinecraftConnection(Channel channel, VelocityServer server) {
|
||||
this.channel = channel;
|
||||
@ -105,6 +107,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.writabilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void write(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.writeAndFlush(msg, channel.voidPromise());
|
||||
@ -222,4 +231,20 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
public void setAssociation(MinecraftConnectionAssociation association) {
|
||||
this.association = association;
|
||||
}
|
||||
|
||||
public boolean isLegacyForge() {
|
||||
return isLegacyForge;
|
||||
}
|
||||
|
||||
public void setLegacyForge(boolean isForge) {
|
||||
this.isLegacyForge = isForge;
|
||||
}
|
||||
|
||||
public boolean canSendLegacyFMLResetPacket() {
|
||||
return canSendLegacyFMLResetPacket;
|
||||
}
|
||||
|
||||
public void setCanSendLegacyFMLResetPacket(boolean canSendLegacyFMLResetPacket) {
|
||||
this.canSendLegacyFMLResetPacket = isLegacyForge && canSendLegacyFMLResetPacket;
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,8 @@ public interface MinecraftSessionHandler {
|
||||
default void exception(Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
default void writabilityChanged() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,10 @@ public class VelocityConstants {
|
||||
}
|
||||
|
||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||
|
||||
public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS";
|
||||
public static final String FORGE_LEGACY_CHANNEL = "FML";
|
||||
public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP";
|
||||
|
||||
public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package com.velocitypowered.proxy.connection.backend;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
@ -30,7 +32,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (!connection.getPlayer().isActive()) {
|
||||
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
|
||||
// errors.
|
||||
connection.getMinecraftConnection().close();
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -42,6 +44,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
connection.getPlayer().getConnection().write(packet);
|
||||
} else if (packet instanceof Disconnect) {
|
||||
Disconnect original = (Disconnect) packet;
|
||||
connection.disconnect();
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original);
|
||||
} else if (packet instanceof JoinGame) {
|
||||
playerHandler.handleBackendJoinGame((JoinGame) packet);
|
||||
@ -67,6 +70,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connection.hasCompletedJoin() && pm.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
if (!connection.isLegacyForge()) {
|
||||
connection.setLegacyForge(true);
|
||||
|
||||
// We must always reset the handshake before a modded connection is established if
|
||||
// we haven't done so already.
|
||||
connection.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
||||
}
|
||||
|
||||
// Always forward these messages during login
|
||||
connection.getPlayer().getConnection().write(pm);
|
||||
return;
|
||||
}
|
||||
|
||||
PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), server.getChannelRegistrar().getFromId(pm.getChannel()),
|
||||
pm.getData());
|
||||
server.getEventManager().fire(event)
|
||||
@ -86,10 +103,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (!connection.getPlayer().isActive()) {
|
||||
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
|
||||
// errors.
|
||||
connection.getMinecraftConnection().close();
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
connection.getPlayer().getConnection().write(buf.retain());
|
||||
|
||||
if (connection.hasCompletedJoin()) {
|
||||
connection.getPlayer().getConnection().write(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,16 +117,29 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable);
|
||||
}
|
||||
|
||||
public VelocityServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (connection.isGracefulDisconnect()) {
|
||||
return;
|
||||
}
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), Disconnect.create(ConnectionMessages.UNEXPECTED_DISCONNECT));
|
||||
}
|
||||
|
||||
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||
ClientPlaySessionHandler playerHandler =
|
||||
(ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler();
|
||||
boolean isMCMessage;
|
||||
boolean isMCOrFMLMessage;
|
||||
if (connection.getMinecraftConnection().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
isMCMessage = message.getChannel().startsWith("MC|");
|
||||
String channel = message.getChannel();
|
||||
isMCOrFMLMessage = channel.startsWith("MC|") || channel.startsWith(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||
} else {
|
||||
isMCMessage = message.getChannel().startsWith("minecraft:");
|
||||
isMCOrFMLMessage = message.getChannel().startsWith("minecraft:");
|
||||
}
|
||||
return isMCMessage || playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) ||
|
||||
return isMCOrFMLMessage || playerHandler.getClientPluginMsgChannels().contains(message.getChannel()) ||
|
||||
server.getChannelRegistrar().registered(message.getChannel());
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer()));
|
||||
} else {
|
||||
// The previous server connection should become obsolete.
|
||||
// Before we remove it, if the server we are departing is modded, we must always reset the client state.
|
||||
if (existingConnection.isLegacyForge()) {
|
||||
connection.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
||||
}
|
||||
existingConnection.disconnect();
|
||||
}
|
||||
|
||||
@ -119,7 +123,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) {
|
||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) {
|
||||
ByteBuf dataToForward = Unpooled.buffer();
|
||||
ByteBuf finalData = Unpooled.buffer();
|
||||
try {
|
||||
|
@ -33,7 +33,6 @@ 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 {
|
||||
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
|
||||
@ -43,6 +42,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
private MinecraftConnection minecraftConnection;
|
||||
private boolean legacyForge = false;
|
||||
private boolean hasCompletedJoin = false;
|
||||
private boolean gracefulDisconnect = false;
|
||||
|
||||
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.serverInfo = target;
|
||||
@ -57,7 +59,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
|
||||
@ -107,6 +109,8 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion());
|
||||
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
|
||||
handshake.setServerAddress(createBungeeForwardingAddress());
|
||||
} else if (proxyPlayer.getConnection().isLegacyForge()) {
|
||||
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
|
||||
} else {
|
||||
handshake.setServerAddress(serverInfo.getAddress().getHostString());
|
||||
}
|
||||
@ -122,6 +126,12 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
minecraftConnection.write(login);
|
||||
}
|
||||
|
||||
public void writeIfJoined(PluginMessage message) {
|
||||
if (hasCompletedJoin) {
|
||||
minecraftConnection.write(message);
|
||||
}
|
||||
}
|
||||
|
||||
public MinecraftConnection getMinecraftConnection() {
|
||||
return minecraftConnection;
|
||||
}
|
||||
@ -136,8 +146,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
if (minecraftConnection != null) {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
gracefulDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -154,4 +167,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
message.setData(data);
|
||||
minecraftConnection.write(message);
|
||||
}
|
||||
|
||||
public boolean isLegacyForge() {
|
||||
return legacyForge;
|
||||
}
|
||||
|
||||
public void setLegacyForge(boolean modded) {
|
||||
legacyForge = modded;
|
||||
}
|
||||
|
||||
public boolean hasCompletedJoin() {
|
||||
return hasCompletedJoin;
|
||||
}
|
||||
|
||||
public void setHasCompletedJoin(boolean hasCompletedJoin) {
|
||||
this.hasCompletedJoin = hasCompletedJoin;
|
||||
}
|
||||
|
||||
public boolean isGracefulDisconnect() {
|
||||
return gracefulDisconnect;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package com.velocitypowered.proxy.connection.client;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
@ -23,7 +25,7 @@ import java.util.*;
|
||||
*/
|
||||
public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class);
|
||||
private static final int MAX_PLUGIN_CHANNELS = 128;
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private long lastPingID = -1;
|
||||
@ -31,6 +33,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private boolean spawned = false;
|
||||
private final List<UUID> serverBossBars = new ArrayList<>();
|
||||
private final Set<String> clientPluginMsgChannels = new HashSet<>();
|
||||
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
|
||||
private final VelocityServer server;
|
||||
|
||||
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
||||
@ -51,6 +54,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof KeepAlive) {
|
||||
KeepAlive keepAlive = (KeepAlive) packet;
|
||||
if (keepAlive.getRandomId() != lastPingID) {
|
||||
@ -60,6 +69,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
player.setPing(System.currentTimeMillis() - lastPingSent);
|
||||
resetPingData();
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof ClientSettings) {
|
||||
@ -106,7 +117,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
player.getConnection().write(response);
|
||||
} else {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e);
|
||||
@ -121,12 +132,22 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
// If we don't want to handle this packet, just forward it on.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
if (serverConnection.hasCompletedJoin()) {
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(buf.retain());
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverConnection.hasCompletedJoin()) {
|
||||
serverConnection.getMinecraftConnection().write(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -144,12 +165,31 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writabilityChanged() {
|
||||
VelocityServerConnection server = player.getConnectedServer();
|
||||
if (server != null) {
|
||||
boolean writable = player.getConnection().getChannel().isWritable();
|
||||
server.getMinecraftConnection().getChannel().config().setAutoRead(writable);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
||||
resetPingData(); // reset ping data;
|
||||
resetPingData(); // reset ping data
|
||||
if (!spawned) {
|
||||
// nothing special to do here
|
||||
// Nothing special to do with regards to spawning the player
|
||||
spawned = true;
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
|
||||
// We have something special to do for legacy Forge servers - during first connection the FML handshake
|
||||
// will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on
|
||||
// first switch.
|
||||
//
|
||||
// As we know that calling this branch only happens on first join, we set that if we are a Forge
|
||||
// client that we must reset on the next switch.
|
||||
//
|
||||
// The call will handle if the player is not a Forge player appropriately.
|
||||
player.getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
} else {
|
||||
// Ah, this is the meat and potatoes of the whole venture!
|
||||
//
|
||||
@ -193,17 +233,38 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
channel, toRegister));
|
||||
}
|
||||
|
||||
// If we had plugin messages queued during login/FML handshake, send them now.
|
||||
PluginMessage pm;
|
||||
while ((pm = loginPluginMessages.poll()) != null) {
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
|
||||
}
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
player.getConnectedServer().getMinecraftConnection().flush();
|
||||
player.getConnectedServer().setHasCompletedJoin(true);
|
||||
if (player.getConnectedServer().isLegacyForge()) {
|
||||
// We only need to indicate we can send a reset packet if we complete a handshake, that is,
|
||||
// logged onto a Forge server.
|
||||
//
|
||||
// The special case is if we log onto a Vanilla server as our first server, FML will treat this
|
||||
// as complete and **will** need a reset packet sending at some point. We will handle this
|
||||
// during initial player connection if the player is detected to be forge.
|
||||
//
|
||||
// This is why we use an if statement rather than the result of VelocityServerConnection#isLegacyForge()
|
||||
// because we don't want to set it false if this is a first connection to a Vanilla server.
|
||||
//
|
||||
// See LoginSessionHandler#handle for where the counterpart to this method is
|
||||
player.getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
}
|
||||
}
|
||||
|
||||
public List<UUID> getServerBossBars() {
|
||||
return serverBossBars;
|
||||
}
|
||||
|
||||
public void handleClientPluginMessage(PluginMessage packet) {
|
||||
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) {
|
||||
private void handleClientPluginMessage(PluginMessage packet) {
|
||||
if (PluginMessageUtil.isMCRegister(packet)) {
|
||||
List<String> actuallyRegistered = new ArrayList<>();
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
for (String channel : channels) {
|
||||
@ -220,28 +281,32 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
|
||||
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
|
||||
} else if (PluginMessageUtil.isMCUnregister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
clientPluginMsgChannels.removeAll(channels);
|
||||
}
|
||||
|
||||
if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
return;
|
||||
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
||||
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
// Always forward the FML handshake to the remote server.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else {
|
||||
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
|
||||
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
|
||||
// be sent once the JoinGame packet has been received by the proxy.
|
||||
loginPluginMessages.add(packet);
|
||||
}
|
||||
} else {
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(),
|
||||
server.getChannelRegistrar().getFromId(packet.getChannel()), packet.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
}
|
||||
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
|
||||
}
|
||||
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(),
|
||||
server.getChannelRegistrar().getFromId(packet.getChannel()), packet.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
}
|
||||
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
|
||||
}
|
||||
|
||||
public Set<String> getClientPluginMsgChannels() {
|
||||
|
@ -12,7 +12,7 @@ public class ClientSettingsWrapper implements PlayerSettings {
|
||||
private final SkinParts parts;
|
||||
private Locale locale = null;
|
||||
|
||||
public ClientSettingsWrapper(ClientSettings settings) {
|
||||
ClientSettingsWrapper(ClientSettings settings) {
|
||||
this.settings = settings;
|
||||
this.parts = new SkinParts((byte) settings.getSkinParts());
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
@ -14,6 +15,7 @@ import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
@ -36,6 +38,7 @@ import net.kyori.text.serializer.PlainComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
@ -190,7 +193,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + info.getName();
|
||||
}
|
||||
handleConnectionException(info, TextComponent.builder()
|
||||
handleConnectionException(info, null, TextComponent.builder()
|
||||
.content(userMessage + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(TextComponent.of(error, TextColor.WHITE))
|
||||
@ -202,17 +205,23 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason);
|
||||
handleConnectionException(info, disconnectReason, TextComponent.builder()
|
||||
.content("Kicked from " + info.getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
} else {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason);
|
||||
handleConnectionException(info, disconnectReason, TextComponent.builder()
|
||||
.content("Unable to connect to " + info.getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
}
|
||||
handleConnectionException(info, TextComponent.builder()
|
||||
.content("Unable to connect to " + info.getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Component disconnectReason) {
|
||||
private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) {
|
||||
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);;
|
||||
connectionInFlight = null;
|
||||
if (connectedServer == null) {
|
||||
// The player isn't yet connected to a server.
|
||||
@ -220,14 +229,29 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
if (nextServer.isPresent()) {
|
||||
createConnectionRequest(nextServer.get()).fireAndForget();
|
||||
} else {
|
||||
connection.closeWith(Disconnect.create(disconnectReason));
|
||||
connection.closeWith(Disconnect.create(friendlyReason));
|
||||
}
|
||||
} else if (connectedServer.getServerInfo().equals(info)) {
|
||||
// Already connected to the server being disconnected from.
|
||||
// TODO: ServerKickEvent
|
||||
connection.closeWith(Disconnect.create(disconnectReason));
|
||||
if (kickReason != null) {
|
||||
server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
|
||||
KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult();
|
||||
connection.closeWith(Disconnect.create(res.getReason()));
|
||||
} else if (event.getResult() instanceof KickedFromServerEvent.RedirectPlayer) {
|
||||
KickedFromServerEvent.RedirectPlayer res = (KickedFromServerEvent.RedirectPlayer) event.getResult();
|
||||
createConnectionRequest(res.getServer()).fireAndForget();
|
||||
} else {
|
||||
// In case someone gets creative, assume we want to disconnect the player.
|
||||
connection.closeWith(Disconnect.create(friendlyReason));
|
||||
}
|
||||
}, connection.getChannel().eventLoop());
|
||||
} else {
|
||||
connection.closeWith(Disconnect.create(friendlyReason));
|
||||
}
|
||||
} else {
|
||||
connection.write(Chat.create(disconnectReason));
|
||||
connection.write(Chat.create(friendlyReason));
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +280,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
// Otherwise, initiate the connection.
|
||||
ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer()));
|
||||
ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
|
||||
return server.getEventManager().fire(event)
|
||||
.thenCompose((newEvent) -> {
|
||||
if (!newEvent.getResult().isAllowed()) {
|
||||
@ -265,7 +289,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
);
|
||||
}
|
||||
|
||||
return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect();
|
||||
return new VelocityServerConnection(newEvent.getResult().getServer().get(), this, server).connect();
|
||||
});
|
||||
}
|
||||
|
||||
@ -276,6 +300,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
this.connectedServer = serverConnection;
|
||||
}
|
||||
|
||||
public void sendLegacyForgeHandshakeResetPacket() {
|
||||
if (connection.canSendLegacyFMLResetPacket()) {
|
||||
PluginMessage resetPacket = new PluginMessage();
|
||||
resetPacket.setChannel(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||
resetPacket.setData(VelocityConstants.FORGE_LEGACY_HANDSHAKE_RESET_DATA);
|
||||
connection.write(resetPacket);
|
||||
connection.setCanSendLegacyFMLResetPacket(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void close(TextComponent reason) {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
@ -70,9 +70,12 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_ reject Forge,
|
||||
// although Velocity does not yet support Forge.
|
||||
if (handshake.getServerAddress().contains("\0") && !handshake.getServerAddress().endsWith("\0FML\0")) {
|
||||
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13) and store that in the connection
|
||||
boolean isForge = handshake.getServerAddress().endsWith("\0FML\0");
|
||||
connection.setLegacyForge(isForge);
|
||||
|
||||
// Make sure legacy forwarding is not in use on this connection. Make sure that we do _not_ reject Forge
|
||||
if (handshake.getServerAddress().contains("\0") && !isForge) {
|
||||
connection.closeWith(Disconnect.create(TextComponent.of("Running Velocity behind Velocity is unsupported.")));
|
||||
return;
|
||||
}
|
||||
@ -105,6 +108,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
new ServerPing.Version(ProtocolConstants.MAXIMUM_GENERIC_VERSION, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
|
||||
configuration.getMotdComponent(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
ProxyPingEvent event = new ProxyPingEvent(new LegacyInboundConnection(connection), ping);
|
||||
|
@ -2,12 +2,14 @@ package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
@ -31,11 +33,14 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||
private static final String MOJANG_SERVER_AUTH_URL =
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
|
||||
@ -154,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) {
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
@ -176,6 +181,12 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
private void initializePlayer(GameProfile profile, boolean onlineMode) {
|
||||
if (inbound.isLegacyForge() && server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
|
||||
// We want to add the FML token to the properties
|
||||
List<GameProfile.Property> properties = new ArrayList<>(profile.getProperties());
|
||||
properties.add(new GameProfile.Property("forgeClient", "true", ""));
|
||||
profile = new GameProfile(profile.getId(), profile.getName(), properties);
|
||||
}
|
||||
GameProfileRequestEvent profileRequestEvent = new GameProfileRequestEvent(apiInbound, profile, onlineMode);
|
||||
|
||||
server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> {
|
||||
@ -235,7 +246,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> {
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -48,7 +48,8 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
|
||||
configuration.getMotdComponent(),
|
||||
configuration.getFavicon()
|
||||
configuration.getFavicon(),
|
||||
configuration.isAnnounceForge() ? ServerPing.Modinfo.DEFAULT : null
|
||||
);
|
||||
|
||||
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
|
||||
|
@ -7,6 +7,7 @@ public class ConnectionMessages {
|
||||
public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED);
|
||||
public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED);
|
||||
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error");
|
||||
public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?");
|
||||
|
||||
private ConnectionMessages() {
|
||||
throw new AssertionError();
|
||||
|
@ -16,11 +16,7 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
@ -48,6 +44,8 @@ import java.util.concurrent.TimeUnit;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
public final class ConnectionManager {
|
||||
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 16, 1 << 18);
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(ConnectionManager.class);
|
||||
|
||||
private final Set<Channel> endpoints = new HashSet<>();
|
||||
@ -72,11 +70,12 @@ public final class ConnectionManager {
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(this.transportType.serverSocketChannelClass)
|
||||
.group(this.bossGroup, this.workerGroup)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
|
||||
@ -126,7 +125,9 @@ public final class ConnectionManager {
|
||||
public Bootstrap createWorker() {
|
||||
return new Bootstrap()
|
||||
.channel(this.transportType.socketChannelClass)
|
||||
.group(this.workerGroup);
|
||||
.group(this.workerGroup)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
@ -13,7 +13,4 @@ public interface Connections {
|
||||
String MINECRAFT_DECODER = "minecraft-decoder";
|
||||
String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||
String READ_TIMEOUT = "read-timeout";
|
||||
|
||||
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
|
||||
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.ImmutableIntArray;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import io.netty.util.collection.IntObjectHashMap;
|
||||
@ -258,9 +259,18 @@ public enum StateRegistry {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder mappingAsString = new StringBuilder("{");
|
||||
for (Object2IntMap.Entry<Class<? extends MinecraftPacket>> entry : packetClassToId.object2IntEntrySet()) {
|
||||
mappingAsString.append(entry.getKey().getSimpleName()).append(" -> ")
|
||||
.append("0x")
|
||||
.append(Strings.padStart(Integer.toHexString(entry.getIntValue()), 2, '0'))
|
||||
.append(", ");
|
||||
}
|
||||
mappingAsString.setLength(mappingAsString.length() - 2);
|
||||
mappingAsString.append("}");
|
||||
return "ProtocolVersion{" +
|
||||
"id=" + id +
|
||||
", packetClassToId=" + packetClassToId +
|
||||
", packetClassToId=" + mappingAsString.toString() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -38,11 +38,11 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
packet.decode(msg, direction, protocolVersion.id);
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId), e);
|
||||
+ " Protocol " + protocolVersion.id + " State " + state + " ID " + Integer.toHexString(packetId), e);
|
||||
}
|
||||
if (msg.isReadable()) {
|
||||
throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId));
|
||||
+ " Protocol " + protocolVersion.id + " State " + state + " ID " + Integer.toHexString(packetId));
|
||||
}
|
||||
out.add(packet);
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -15,12 +17,31 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
in.markReaderIndex();
|
||||
int packetLength = ProtocolUtils.readVarInt(in);
|
||||
if (in.readableBytes() < packetLength) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
|
||||
byte[] lenBuf = new byte[3];
|
||||
for (int i = 0; i < lenBuf.length; i++) {
|
||||
if (!in.isReadable()) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
lenBuf[i] = in.readByte();
|
||||
if (lenBuf[i] > 0) {
|
||||
int packetLength = ProtocolUtils.readVarInt(Unpooled.wrappedBuffer(lenBuf));
|
||||
if (packetLength == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in.readableBytes() < packetLength) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(in.readRetainedSlice(packetLength));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
out.add(in.readRetainedSlice(packetLength));
|
||||
throw new CorruptedFrameException("VarInt too big");
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,20 @@ public enum PluginMessageUtil {
|
||||
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand");
|
||||
}
|
||||
|
||||
public static boolean isMCRegister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register");
|
||||
}
|
||||
|
||||
public static boolean isMCUnregister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister");
|
||||
}
|
||||
|
||||
public static List<String> getChannels(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(message.getChannel().equals("REGISTER") ||
|
||||
message.getChannel().equals("UNREGISTER") ||
|
||||
message.getChannel().equals("minecraft:register") ||
|
||||
message.getChannel().equals("minecraft:unregister"),
|
||||
"Unknown channel type " + message.getChannel());
|
||||
Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s",
|
||||
message.getChannel());
|
||||
String channels = new String(message.getData(), StandardCharsets.UTF_8);
|
||||
return ImmutableList.copyOf(channels.split("\0"));
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ -33,4 +34,36 @@ class RecordingThreadFactoryTest {
|
||||
Thread.sleep(10);
|
||||
assertEquals(0, factory.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cleanUpAfterExceptionThrown() throws Exception {
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch endThread = new CountDownLatch(1);
|
||||
CountDownLatch hasEnded = new CountDownLatch(1);
|
||||
RecordingThreadFactory factory = new RecordingThreadFactory((ThreadFactory) r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setUncaughtExceptionHandler((t1, e) -> hasEnded.countDown());
|
||||
return t;
|
||||
});
|
||||
factory.newThread(() -> {
|
||||
started.countDown();
|
||||
assertTrue(factory.currentlyInFactory());
|
||||
assertEquals(1, factory.size());
|
||||
try {
|
||||
endThread.await();
|
||||
} catch (InterruptedException e) {
|
||||
fail(e);
|
||||
}
|
||||
throw new RuntimeException("");
|
||||
}).start();
|
||||
started.await();
|
||||
assertFalse(factory.currentlyInFactory());
|
||||
assertEquals(1, factory.size());
|
||||
endThread.countDown();
|
||||
hasEnded.await();
|
||||
|
||||
// Wait a little bit to ensure the thread got shut down
|
||||
Thread.sleep(10);
|
||||
assertEquals(0, factory.size());
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren