@@ -1,29 +1,29 @@
|
||||
package com.velocitypowered.proxy;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class Velocity {
|
||||
private static final Logger logger = LogManager.getLogger(Velocity.class);
|
||||
|
||||
static {
|
||||
// We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient.
|
||||
// Force AWT to work with its head chopped off.
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
}
|
||||
private static final Logger logger = LogManager.getLogger(Velocity.class);
|
||||
|
||||
public static void main(String... args) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
static {
|
||||
// We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient.
|
||||
// Force AWT to work with its head chopped off.
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
}
|
||||
|
||||
VelocityServer server = new VelocityServer();
|
||||
server.start();
|
||||
public static void main(String... args) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread"));
|
||||
VelocityServer server = new VelocityServer();
|
||||
server.start();
|
||||
|
||||
double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
|
||||
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
|
||||
server.getConsoleCommandSource().start();
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread"));
|
||||
|
||||
double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
|
||||
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
|
||||
server.getConsoleCommandSource().start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ 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.util.VelocityChannelRegistrar;
|
||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||
import com.velocitypowered.proxy.network.http.NettyHttpClient;
|
||||
import com.velocitypowered.proxy.plugin.VelocityEventManager;
|
||||
@@ -36,356 +35,367 @@ import com.velocitypowered.proxy.server.ServerMap;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.util.Ratelimiter;
|
||||
import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyPair;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
public class VelocityServer implements ProxyServer {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
||||
.registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer())
|
||||
.create();
|
||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
||||
.registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer())
|
||||
.create();
|
||||
|
||||
private @MonotonicNonNull ConnectionManager cm;
|
||||
private @MonotonicNonNull VelocityConfiguration configuration;
|
||||
private @MonotonicNonNull NettyHttpClient httpClient;
|
||||
private @MonotonicNonNull KeyPair serverKeyPair;
|
||||
private @MonotonicNonNull ServerMap servers;
|
||||
private final VelocityCommandManager commandManager = new VelocityCommandManager();
|
||||
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
|
||||
private boolean shutdown = false;
|
||||
private @MonotonicNonNull VelocityPluginManager pluginManager;
|
||||
private @MonotonicNonNull ConnectionManager cm;
|
||||
private @MonotonicNonNull VelocityConfiguration configuration;
|
||||
private @MonotonicNonNull NettyHttpClient httpClient;
|
||||
private @MonotonicNonNull KeyPair serverKeyPair;
|
||||
private @MonotonicNonNull ServerMap servers;
|
||||
private final VelocityCommandManager commandManager = new VelocityCommandManager();
|
||||
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
|
||||
private boolean shutdown = false;
|
||||
private @MonotonicNonNull VelocityPluginManager pluginManager;
|
||||
|
||||
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
|
||||
private @MonotonicNonNull VelocityConsole console;
|
||||
private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
|
||||
private @MonotonicNonNull VelocityEventManager eventManager;
|
||||
private @MonotonicNonNull VelocityScheduler scheduler;
|
||||
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
|
||||
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
|
||||
private @MonotonicNonNull VelocityConsole console;
|
||||
private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
|
||||
private @MonotonicNonNull VelocityEventManager eventManager;
|
||||
private @MonotonicNonNull VelocityScheduler scheduler;
|
||||
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
|
||||
|
||||
public KeyPair getServerKeyPair() {
|
||||
if (serverKeyPair == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return serverKeyPair;
|
||||
public KeyPair getServerKeyPair() {
|
||||
if (serverKeyPair == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return serverKeyPair;
|
||||
}
|
||||
|
||||
public VelocityConfiguration getConfiguration() {
|
||||
VelocityConfiguration cfg = this.configuration;
|
||||
if (cfg == null) {
|
||||
throw new IllegalStateException("Configuration not initialized!");
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyVersion getVersion() {
|
||||
Package pkg = VelocityServer.class.getPackage();
|
||||
String implName;
|
||||
String implVersion;
|
||||
String implVendor;
|
||||
if (pkg != null) {
|
||||
implName = MoreObjects.firstNonNull(pkg.getImplementationTitle(), "Velocity");
|
||||
implVersion = MoreObjects.firstNonNull(pkg.getImplementationVersion(), "<unknown>");
|
||||
implVendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), "Velocity Contributors");
|
||||
} else {
|
||||
implName = "Velocity";
|
||||
implVersion = "<unknown>";
|
||||
implVendor = "Velocity Contributors";
|
||||
}
|
||||
|
||||
public VelocityConfiguration getConfiguration() {
|
||||
VelocityConfiguration cfg = this.configuration;
|
||||
if (cfg == null) {
|
||||
throw new IllegalStateException("Configuration not initialized!");
|
||||
}
|
||||
return cfg;
|
||||
return new ProxyVersion(implName, implVendor, implVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityCommandManager getCommandManager() {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@EnsuresNonNull({"serverKeyPair", "servers", "pluginManager", "eventManager", "scheduler",
|
||||
"console", "cm", "configuration"})
|
||||
public void start() {
|
||||
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
|
||||
|
||||
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
|
||||
pluginManager = new VelocityPluginManager(this);
|
||||
eventManager = new VelocityEventManager(pluginManager);
|
||||
scheduler = new VelocityScheduler(pluginManager);
|
||||
console = new VelocityConsole(this);
|
||||
cm = new ConnectionManager(this);
|
||||
servers = new ServerMap(this);
|
||||
|
||||
cm.logChannelInformation();
|
||||
|
||||
// Initialize commands first
|
||||
commandManager.register(new VelocityCommand(this), "velocity");
|
||||
commandManager.register(new ServerCommand(this), "server");
|
||||
commandManager.register(new ShutdownCommand(this), "shutdown", "end");
|
||||
|
||||
try {
|
||||
Path configPath = Paths.get("velocity.toml");
|
||||
configuration = VelocityConfiguration.read(configPath);
|
||||
|
||||
if (!configuration.validate()) {
|
||||
logger.error(
|
||||
"Your configuration is invalid. Velocity will refuse to start up until the errors are resolved.");
|
||||
LogManager.shutdown();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
AnnotatedConfig
|
||||
.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e);
|
||||
LogManager.shutdown();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProxyVersion getVersion() {
|
||||
Package pkg = VelocityServer.class.getPackage();
|
||||
String implName;
|
||||
String implVersion;
|
||||
String implVendor;
|
||||
if (pkg != null) {
|
||||
implName = MoreObjects.firstNonNull(pkg.getImplementationTitle(), "Velocity");
|
||||
implVersion = MoreObjects.firstNonNull(pkg.getImplementationVersion(), "<unknown>");
|
||||
implVendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), "Velocity Contributors");
|
||||
} else {
|
||||
implName = "Velocity";
|
||||
implVersion = "<unknown>";
|
||||
implVendor = "Velocity Contributors";
|
||||
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
|
||||
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
|
||||
}
|
||||
|
||||
ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit());
|
||||
httpClient = new NettyHttpClient(this);
|
||||
loadPlugins();
|
||||
|
||||
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
|
||||
// to fully initialize before we accept any connections to the server.
|
||||
eventManager.fire(new ProxyInitializeEvent()).join();
|
||||
|
||||
// init console permissions after plugins are loaded
|
||||
console.setupPermissions();
|
||||
|
||||
this.cm.bind(configuration.getBind());
|
||||
|
||||
if (configuration.isQueryEnabled()) {
|
||||
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresNonNull({"pluginManager", "eventManager"})
|
||||
private void loadPlugins() {
|
||||
logger.info("Loading plugins...");
|
||||
|
||||
try {
|
||||
Path pluginPath = Paths.get("plugins");
|
||||
|
||||
if (!pluginPath.toFile().exists()) {
|
||||
Files.createDirectory(pluginPath);
|
||||
} else {
|
||||
if (!pluginPath.toFile().isDirectory()) {
|
||||
logger.warn("Plugin location {} is not a directory, continuing without loading plugins",
|
||||
pluginPath);
|
||||
return;
|
||||
}
|
||||
|
||||
return new ProxyVersion(implName, implVendor, implVersion);
|
||||
pluginManager.loadPlugins(pluginPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Couldn't load plugins", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityCommandManager getCommandManager() {
|
||||
return commandManager;
|
||||
// Register the plugin main classes so that we may proceed with firing the proxy initialize event
|
||||
for (PluginContainer plugin : pluginManager.getPlugins()) {
|
||||
Optional<?> instance = plugin.getInstance();
|
||||
if (instance.isPresent()) {
|
||||
eventManager.register(instance.get(), instance.get());
|
||||
}
|
||||
}
|
||||
|
||||
@EnsuresNonNull({"serverKeyPair", "servers", "pluginManager", "eventManager", "scheduler", "console", "cm", "configuration"})
|
||||
public void start() {
|
||||
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
|
||||
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
|
||||
}
|
||||
|
||||
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
|
||||
pluginManager = new VelocityPluginManager(this);
|
||||
eventManager = new VelocityEventManager(pluginManager);
|
||||
scheduler = new VelocityScheduler(pluginManager);
|
||||
console = new VelocityConsole(this);
|
||||
cm = new ConnectionManager(this);
|
||||
servers = new ServerMap(this);
|
||||
public Bootstrap initializeGenericBootstrap() {
|
||||
if (cm == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return this.cm.createWorker();
|
||||
}
|
||||
|
||||
cm.logChannelInformation();
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
// Initialize commands first
|
||||
commandManager.register(new VelocityCommand(this), "velocity");
|
||||
commandManager.register(new ServerCommand(this), "server");
|
||||
commandManager.register(new ShutdownCommand(this), "shutdown", "end");
|
||||
|
||||
try {
|
||||
Path configPath = Paths.get("velocity.toml");
|
||||
configuration = VelocityConfiguration.read(configPath);
|
||||
|
||||
if (!configuration.validate()) {
|
||||
logger.error("Your configuration is invalid. Velocity will refuse to start up until the errors are resolved.");
|
||||
LogManager.shutdown();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e);
|
||||
LogManager.shutdown();
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
|
||||
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
|
||||
}
|
||||
|
||||
ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit());
|
||||
httpClient = new NettyHttpClient(this);
|
||||
loadPlugins();
|
||||
|
||||
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
|
||||
// to fully initialize before we accept any connections to the server.
|
||||
eventManager.fire(new ProxyInitializeEvent()).join();
|
||||
|
||||
// init console permissions after plugins are loaded
|
||||
console.setupPermissions();
|
||||
|
||||
this.cm.bind(configuration.getBind());
|
||||
|
||||
if (configuration.isQueryEnabled()) {
|
||||
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
|
||||
}
|
||||
public void shutdown() {
|
||||
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@RequiresNonNull({"pluginManager", "eventManager"})
|
||||
private void loadPlugins() {
|
||||
logger.info("Loading plugins...");
|
||||
if (!shutdownInProgress.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
logger.info("Shutting down the proxy...");
|
||||
|
||||
try {
|
||||
Path pluginPath = Paths.get("plugins");
|
||||
|
||||
if (!pluginPath.toFile().exists()) {
|
||||
Files.createDirectory(pluginPath);
|
||||
} else {
|
||||
if (!pluginPath.toFile().isDirectory()) {
|
||||
logger.warn("Plugin location {} is not a directory, continuing without loading plugins", pluginPath);
|
||||
return;
|
||||
}
|
||||
|
||||
pluginManager.loadPlugins(pluginPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Couldn't load plugins", e);
|
||||
}
|
||||
|
||||
// Register the plugin main classes so that we may proceed with firing the proxy initialize event
|
||||
for (PluginContainer plugin : pluginManager.getPlugins()) {
|
||||
Optional<?> instance = plugin.getInstance();
|
||||
if (instance.isPresent()) {
|
||||
eventManager.register(instance.get(), instance.get());
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
|
||||
for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) {
|
||||
player.close(TextComponent.of("Proxy shutting down."));
|
||||
}
|
||||
|
||||
public Bootstrap initializeGenericBootstrap() {
|
||||
if (cm == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return this.cm.createWorker();
|
||||
this.cm.shutdown();
|
||||
|
||||
eventManager.fire(new ProxyShutdownEvent());
|
||||
try {
|
||||
if (!eventManager.shutdown() || !scheduler.shutdown()) {
|
||||
logger.error("Your plugins took over 10 seconds to shut down.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Not much we can do about this...
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
public NettyHttpClient getHttpClient() {
|
||||
if (httpClient == null) {
|
||||
throw new IllegalStateException("HTTP client not initialized");
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
if (!shutdownInProgress.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
logger.info("Shutting down the proxy...");
|
||||
|
||||
for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) {
|
||||
player.close(TextComponent.of("Proxy shutting down."));
|
||||
}
|
||||
|
||||
this.cm.shutdown();
|
||||
|
||||
eventManager.fire(new ProxyShutdownEvent());
|
||||
try {
|
||||
if (!eventManager.shutdown() || !scheduler.shutdown()) {
|
||||
logger.error("Your plugins took over 10 seconds to shut down.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Not much we can do about this...
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
shutdown = true;
|
||||
public Ratelimiter getIpAttemptLimiter() {
|
||||
if (ipAttemptLimiter == null) {
|
||||
throw new IllegalStateException("Ratelimiter not initialized");
|
||||
}
|
||||
return ipAttemptLimiter;
|
||||
}
|
||||
|
||||
public NettyHttpClient getHttpClient() {
|
||||
if (httpClient == null) {
|
||||
throw new IllegalStateException("HTTP client not initialized");
|
||||
}
|
||||
return httpClient;
|
||||
public boolean registerConnection(ConnectedPlayer connection) {
|
||||
String lowerName = connection.getUsername().toLowerCase(Locale.US);
|
||||
if (connectionsByName.putIfAbsent(lowerName, connection) != null) {
|
||||
return false;
|
||||
}
|
||||
if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) {
|
||||
connectionsByName.remove(lowerName, connection);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Ratelimiter getIpAttemptLimiter() {
|
||||
if (ipAttemptLimiter == null) {
|
||||
throw new IllegalStateException("Ratelimiter not initialized");
|
||||
}
|
||||
return ipAttemptLimiter;
|
||||
}
|
||||
public void unregisterConnection(ConnectedPlayer connection) {
|
||||
connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection);
|
||||
connectionsByUuid.remove(connection.getUniqueId(), connection);
|
||||
}
|
||||
|
||||
public boolean registerConnection(ConnectedPlayer connection) {
|
||||
String lowerName = connection.getUsername().toLowerCase(Locale.US);
|
||||
if (connectionsByName.putIfAbsent(lowerName, connection) != null) {
|
||||
return false;
|
||||
}
|
||||
if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) {
|
||||
connectionsByName.remove(lowerName, connection);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public Optional<Player> getPlayer(String username) {
|
||||
Preconditions.checkNotNull(username, "username");
|
||||
return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US)));
|
||||
}
|
||||
|
||||
public void unregisterConnection(ConnectedPlayer connection) {
|
||||
connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection);
|
||||
connectionsByUuid.remove(connection.getUniqueId(), connection);
|
||||
}
|
||||
@Override
|
||||
public Optional<Player> getPlayer(UUID uuid) {
|
||||
Preconditions.checkNotNull(uuid, "uuid");
|
||||
return Optional.ofNullable(connectionsByUuid.get(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getPlayer(String username) {
|
||||
Preconditions.checkNotNull(username, "username");
|
||||
return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US)));
|
||||
@Override
|
||||
public void broadcast(Component component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
Chat chat = Chat.createClientbound(component);
|
||||
for (ConnectedPlayer player : connectionsByUuid.values()) {
|
||||
player.getConnection().write(chat);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getPlayer(UUID uuid) {
|
||||
Preconditions.checkNotNull(uuid, "uuid");
|
||||
return Optional.ofNullable(connectionsByUuid.get(uuid));
|
||||
}
|
||||
@Override
|
||||
public Collection<Player> getAllPlayers() {
|
||||
return ImmutableList.copyOf(connectionsByUuid.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast(Component component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
Chat chat = Chat.createClientbound(component);
|
||||
for (ConnectedPlayer player : connectionsByUuid.values()) {
|
||||
player.getConnection().write(chat);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public int getPlayerCount() {
|
||||
return connectionsByUuid.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getAllPlayers() {
|
||||
return ImmutableList.copyOf(connectionsByUuid.values());
|
||||
@Override
|
||||
public Optional<RegisteredServer> getServer(String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.getServer(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPlayerCount() {
|
||||
return connectionsByUuid.size();
|
||||
@Override
|
||||
public Collection<RegisteredServer> getAllServers() {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.getAllServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RegisteredServer> getServer(String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.getServer(name);
|
||||
@Override
|
||||
public RegisteredServer registerServer(ServerInfo server) {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.register(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RegisteredServer> getAllServers() {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.getAllServers();
|
||||
@Override
|
||||
public void unregisterServer(ServerInfo server) {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
servers.unregister(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisteredServer registerServer(ServerInfo server) {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.register(server);
|
||||
@Override
|
||||
public VelocityConsole getConsoleCommandSource() {
|
||||
if (console == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return console;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterServer(ServerInfo server) {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
servers.unregister(server);
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
if (pluginManager == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityConsole getConsoleCommandSource() {
|
||||
if (console == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return console;
|
||||
@Override
|
||||
public EventManager getEventManager() {
|
||||
if (eventManager == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return eventManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
if (pluginManager == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return pluginManager;
|
||||
@Override
|
||||
public VelocityScheduler getScheduler() {
|
||||
if (scheduler == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventManager getEventManager() {
|
||||
if (eventManager == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return eventManager;
|
||||
}
|
||||
@Override
|
||||
public VelocityChannelRegistrar getChannelRegistrar() {
|
||||
return channelRegistrar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityScheduler getScheduler() {
|
||||
if (scheduler == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityChannelRegistrar getChannelRegistrar() {
|
||||
return channelRegistrar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getBoundAddress() {
|
||||
if (configuration == null) {
|
||||
throw new IllegalStateException("No configuration"); // even though you'll never get the chance... heh, heh
|
||||
}
|
||||
return configuration.getBind();
|
||||
@Override
|
||||
public InetSocketAddress getBoundAddress() {
|
||||
if (configuration == null) {
|
||||
throw new IllegalStateException(
|
||||
"No configuration"); // even though you'll never get the chance... heh, heh
|
||||
}
|
||||
return configuration.getBind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,90 +9,96 @@ import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.event.ClickEvent;
|
||||
import net.kyori.text.event.HoverEvent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ServerCommand implements Command {
|
||||
private final ProxyServer server;
|
||||
|
||||
public ServerCommand(ProxyServer server) {
|
||||
this.server = server;
|
||||
private final ProxyServer server;
|
||||
|
||||
public ServerCommand(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (!(source instanceof Player)) {
|
||||
source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (!(source instanceof Player)) {
|
||||
source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
Player player = (Player) source;
|
||||
if (args.length == 1) {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (!toConnect.isPresent()) {
|
||||
player.sendMessage(
|
||||
TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) source;
|
||||
if (args.length == 1) {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (!toConnect.isPresent()) {
|
||||
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
player.createConnectionRequest(toConnect.get()).fireAndForget();
|
||||
} else {
|
||||
String currentServer = player.getCurrentServer().map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName)
|
||||
.orElse("<unknown>");
|
||||
player.sendMessage(TextComponent
|
||||
.of("You are currently connected to " + currentServer + ".", TextColor.YELLOW));
|
||||
|
||||
player.createConnectionRequest(toConnect.get()).fireAndForget();
|
||||
// Assemble the list of servers as components
|
||||
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ")
|
||||
.color(TextColor.YELLOW);
|
||||
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
|
||||
for (int i = 0; i < infos.size(); i++) {
|
||||
RegisteredServer rs = infos.get(i);
|
||||
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
|
||||
String playersText = rs.getPlayersConnected().size() + " player(s) online";
|
||||
if (rs.getServerInfo().getName().equals(currentServer)) {
|
||||
infoComponent = infoComponent.color(TextColor.GREEN)
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
TextComponent.of("Currently connected to this server\n" + playersText)));
|
||||
} else {
|
||||
String currentServer = player.getCurrentServer().map(ServerConnection::getServerInfo).map(ServerInfo::getName)
|
||||
.orElse("<unknown>");
|
||||
player.sendMessage(TextComponent.of("You are currently connected to " + currentServer + ".", TextColor.YELLOW));
|
||||
|
||||
// Assemble the list of servers as components
|
||||
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
|
||||
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
|
||||
for (int i = 0; i < infos.size(); i++) {
|
||||
RegisteredServer rs = infos.get(i);
|
||||
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
|
||||
String playersText = rs.getPlayersConnected().size() + " player(s) online";
|
||||
if (rs.getServerInfo().getName().equals(currentServer)) {
|
||||
infoComponent = infoComponent.color(TextColor.GREEN)
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
TextComponent.of("Currently connected to this server\n" + playersText)));
|
||||
} else {
|
||||
infoComponent = infoComponent.color(TextColor.GRAY)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName()))
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
|
||||
}
|
||||
serverListBuilder.append(infoComponent);
|
||||
if (i != infos.size() - 1) {
|
||||
serverListBuilder.append(TextComponent.of(", ", TextColor.GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
player.sendMessage(serverListBuilder.build());
|
||||
infoComponent = infoComponent.color(TextColor.GRAY)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND,
|
||||
"/server " + rs.getServerInfo().getName()))
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
TextComponent.of("Click to connect to this server\n" + playersText)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.collect(Collectors.toList());
|
||||
} else if (currentArgs.length == 1) {
|
||||
return server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return ImmutableList.of();
|
||||
serverListBuilder.append(infoComponent);
|
||||
if (i != infos.size() - 1) {
|
||||
serverListBuilder.append(TextComponent.of(", ", TextColor.GRAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
|
||||
player.sendMessage(serverListBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.collect(Collectors.toList());
|
||||
} else if (currentArgs.length == 1) {
|
||||
return server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,23 +8,25 @@ import net.kyori.text.format.TextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public class ShutdownCommand implements Command {
|
||||
private final VelocityServer server;
|
||||
|
||||
public ShutdownCommand(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
private final VelocityServer server;
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (source != server.getConsoleCommandSource()) {
|
||||
source.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
server.shutdown();
|
||||
}
|
||||
public ShutdownCommand(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
return source == server.getConsoleCommandSource();
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (source != server.getConsoleCommandSource()) {
|
||||
source
|
||||
.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
return source == server.getConsoleCommandSource();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,125 +7,130 @@ import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.util.ProxyVersion;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.event.ClickEvent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import net.kyori.text.format.TextDecoration;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VelocityCommand implements Command {
|
||||
private final Map<String, Command> subcommands;
|
||||
|
||||
public VelocityCommand(ProxyServer server) {
|
||||
this.subcommands = ImmutableMap.<String, Command>builder()
|
||||
.put("version", new Info(server))
|
||||
.build();
|
||||
private final Map<String, Command> subcommands;
|
||||
|
||||
public VelocityCommand(ProxyServer server) {
|
||||
this.subcommands = ImmutableMap.<String, Command>builder()
|
||||
.put("version", new Info(server))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void usage(CommandSource source) {
|
||||
String commandText = "/velocity <" + String.join("|", subcommands.keySet()) + ">";
|
||||
source.sendMessage(TextComponent.of(commandText, TextColor.RED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length == 0) {
|
||||
usage(source);
|
||||
return;
|
||||
}
|
||||
|
||||
private void usage(CommandSource source) {
|
||||
String commandText = "/velocity <" + String.join("|", subcommands.keySet()) + ">";
|
||||
source.sendMessage(TextComponent.of(commandText, TextColor.RED));
|
||||
Command command = subcommands.get(args[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
usage(source);
|
||||
return;
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
command.execute(source, actualArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return ImmutableList.copyOf(subcommands.keySet());
|
||||
}
|
||||
|
||||
if (currentArgs.length == 1) {
|
||||
return subcommands.keySet().stream()
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(currentArgs, 1, currentArgs.length);
|
||||
return command.suggest(source, actualArgs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length == 0) {
|
||||
return true;
|
||||
}
|
||||
Command command = subcommands.get(args[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
return true;
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
return command.hasPermission(source, actualArgs);
|
||||
}
|
||||
|
||||
private static class Info implements Command {
|
||||
|
||||
private final ProxyServer server;
|
||||
|
||||
private Info(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length == 0) {
|
||||
usage(source);
|
||||
return;
|
||||
}
|
||||
ProxyVersion version = server.getVersion();
|
||||
|
||||
Command command = subcommands.get(args[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
usage(source);
|
||||
return;
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
command.execute(source, actualArgs);
|
||||
}
|
||||
TextComponent velocity = TextComponent.builder(version.getName() + " ")
|
||||
.decoration(TextDecoration.BOLD, true)
|
||||
.color(TextColor.DARK_AQUA)
|
||||
.append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false))
|
||||
.build();
|
||||
TextComponent copyright = TextComponent
|
||||
.of("Copyright 2018 " + version.getVendor() + ". " + version.getName()
|
||||
+ " is freely licensed under the terms of the " +
|
||||
"MIT License.");
|
||||
source.sendMessage(velocity);
|
||||
source.sendMessage(copyright);
|
||||
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return ImmutableList.copyOf(subcommands.keySet());
|
||||
}
|
||||
|
||||
if (currentArgs.length == 1) {
|
||||
return subcommands.keySet().stream()
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(currentArgs, 1, currentArgs.length);
|
||||
return command.suggest(source, actualArgs);
|
||||
if (version.getName().equals("Velocity")) {
|
||||
TextComponent velocityWebsite = TextComponent.builder()
|
||||
.content("Visit the ")
|
||||
.append(TextComponent.builder("Velocity website")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(
|
||||
new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com"))
|
||||
.build())
|
||||
.append(TextComponent.of(" or the ").resetStyle())
|
||||
.append(TextComponent.builder("Velocity GitHub")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL,
|
||||
"https://github.com/VelocityPowered/Velocity"))
|
||||
.build())
|
||||
.build();
|
||||
source.sendMessage(velocityWebsite);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
if (args.length == 0) {
|
||||
return true;
|
||||
}
|
||||
Command command = subcommands.get(args[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
return true;
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
return command.hasPermission(source, actualArgs);
|
||||
}
|
||||
|
||||
private static class Info implements Command {
|
||||
private final ProxyServer server;
|
||||
|
||||
private Info(ProxyServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String @NonNull [] args) {
|
||||
ProxyVersion version = server.getVersion();
|
||||
|
||||
TextComponent velocity = TextComponent.builder(version.getName() + " ")
|
||||
.decoration(TextDecoration.BOLD, true)
|
||||
.color(TextColor.DARK_AQUA)
|
||||
.append(TextComponent.of(version.getVersion()).decoration(TextDecoration.BOLD, false))
|
||||
.build();
|
||||
TextComponent copyright = TextComponent.of("Copyright 2018 " + version.getVendor() + ". " + version.getName() + " is freely licensed under the terms of the " +
|
||||
"MIT License.");
|
||||
source.sendMessage(velocity);
|
||||
source.sendMessage(copyright);
|
||||
|
||||
if (version.getName().equals("Velocity")) {
|
||||
TextComponent velocityWebsite = TextComponent.builder()
|
||||
.content("Visit the ")
|
||||
.append(TextComponent.builder("Velocity website")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com"))
|
||||
.build())
|
||||
.append(TextComponent.of(" or the ").resetStyle())
|
||||
.append(TextComponent.builder("Velocity GitHub")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/VelocityPowered/Velocity"))
|
||||
.build())
|
||||
.build();
|
||||
source.sendMessage(velocityWebsite);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
|
||||
}
|
||||
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,100 +5,106 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandManager;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class VelocityCommandManager implements CommandManager {
|
||||
private final Map<String, Command> commands = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void register(@NonNull final Command command, final String... aliases) {
|
||||
Preconditions.checkNotNull(aliases, "aliases");
|
||||
Preconditions.checkNotNull(command, "executor");
|
||||
for (int i = 0, length = aliases.length; i < length; i++) {
|
||||
final String alias = aliases[i];
|
||||
Preconditions.checkNotNull(aliases, "alias at index %s", i);
|
||||
this.commands.put(alias.toLowerCase(Locale.ENGLISH), command);
|
||||
}
|
||||
private final Map<String, Command> commands = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void register(@NonNull final Command command, final String... aliases) {
|
||||
Preconditions.checkNotNull(aliases, "aliases");
|
||||
Preconditions.checkNotNull(command, "executor");
|
||||
for (int i = 0, length = aliases.length; i < length; i++) {
|
||||
final String alias = aliases[i];
|
||||
Preconditions.checkNotNull(aliases, "alias at index %s", i);
|
||||
this.commands.put(alias.toLowerCase(Locale.ENGLISH), command);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NonNull final String alias) {
|
||||
Preconditions.checkNotNull(alias, "name");
|
||||
this.commands.remove(alias.toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(@NonNull CommandSource source, @NonNull String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "invoker");
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
|
||||
String[] split = cmdLine.split(" ", -1);
|
||||
if (split.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NonNull final String alias) {
|
||||
Preconditions.checkNotNull(alias, "name");
|
||||
this.commands.remove(alias.toLowerCase(Locale.ENGLISH));
|
||||
String alias = split[0];
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
|
||||
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (command == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(@NonNull CommandSource source, @NonNull String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "invoker");
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
try {
|
||||
if (!command.hasPermission(source, actualArgs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] split = cmdLine.split(" ", -1);
|
||||
if (split.length == 0) {
|
||||
return false;
|
||||
}
|
||||
command.execute(source, actualArgs);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
|
||||
}
|
||||
}
|
||||
|
||||
String alias = split[0];
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
|
||||
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (command == null) {
|
||||
return false;
|
||||
}
|
||||
public boolean hasCommand(String command) {
|
||||
return commands.containsKey(command);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!command.hasPermission(source, actualArgs)) {
|
||||
return false;
|
||||
}
|
||||
public List<String> offerSuggestions(CommandSource source, String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "source");
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
|
||||
command.execute(source, actualArgs);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
|
||||
}
|
||||
String[] split = cmdLine.split(" ", -1);
|
||||
if (split.length == 0) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public boolean hasCommand(String command) {
|
||||
return commands.containsKey(command);
|
||||
String alias = split[0];
|
||||
if (split.length == 1) {
|
||||
List<String> availableCommands = new ArrayList<>();
|
||||
for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
||||
if (entry.getKey().regionMatches(true, 0, alias, 0, alias.length()) &&
|
||||
entry.getValue().hasPermission(source, new String[0])) {
|
||||
availableCommands.add("/" + entry.getKey());
|
||||
}
|
||||
}
|
||||
return availableCommands;
|
||||
}
|
||||
|
||||
public List<String> offerSuggestions(CommandSource source, String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "source");
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
|
||||
String[] split = cmdLine.split(" ", -1);
|
||||
if (split.length == 0) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
String alias = split[0];
|
||||
if (split.length == 1) {
|
||||
List<String> availableCommands = new ArrayList<>();
|
||||
for (Map.Entry<String, Command> entry : commands.entrySet()) {
|
||||
if (entry.getKey().regionMatches(true, 0, alias, 0, alias.length()) &&
|
||||
entry.getValue().hasPermission(source, new String[0])) {
|
||||
availableCommands.add("/" + entry.getKey());
|
||||
}
|
||||
}
|
||||
return availableCommands;
|
||||
}
|
||||
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
|
||||
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (command == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!command.hasPermission(source, actualArgs)) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
return command.suggest(source, actualArgs);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to invoke suggestions for command " + alias + " for " + source, e);
|
||||
}
|
||||
@SuppressWarnings("nullness")
|
||||
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
|
||||
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (command == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!command.hasPermission(source, actualArgs)) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
return command.suggest(source, actualArgs);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"Unable to invoke suggestions for command " + alias + " for " + source, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -19,206 +16,221 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Simple annotation and fields based TOML configuration serializer
|
||||
*/
|
||||
public abstract class AnnotatedConfig {
|
||||
private static final Logger logger = LogManager.getLogger(AnnotatedConfig.class);
|
||||
|
||||
public static Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
private static final Logger logger = LogManager.getLogger(AnnotatedConfig.class);
|
||||
|
||||
/**
|
||||
* Indicates that a field is a table
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface Table {
|
||||
String value();
|
||||
}
|
||||
public static Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a comment
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface Comment {
|
||||
String[] value();
|
||||
}
|
||||
/**
|
||||
* Indicates that a field is a table
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface Table {
|
||||
|
||||
/**
|
||||
* How field will be named in config
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface ConfigKey {
|
||||
String value();
|
||||
}
|
||||
String value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a field is a map and we need to save all map data to
|
||||
* config
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface IsMap {}
|
||||
/**
|
||||
* Creates a comment
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface Comment {
|
||||
|
||||
/**
|
||||
* Indicates that a field is a string converted to byte[]
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface StringAsBytes {}
|
||||
String[] value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a field should be skipped
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface Ignore {}
|
||||
/**
|
||||
* How field will be named in config
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface ConfigKey {
|
||||
|
||||
/**
|
||||
* Dumps this configuration to list of strings using {@link #dumpConfig(Object)}
|
||||
* @return configuration dump
|
||||
*/
|
||||
public List<String> dumpConfig() {
|
||||
return dumpConfig(this);
|
||||
}
|
||||
String value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates TOML configuration from supplied <pre>dumpable</pre> object.
|
||||
*
|
||||
* @param dumpable object which is going to be dumped
|
||||
* @throws RuntimeException if reading field value(s) fail
|
||||
* @return string list of configuration file lines
|
||||
*/
|
||||
private static List<String> dumpConfig(Object dumpable) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
try {
|
||||
for (Field field : dumpable.getClass().getDeclaredFields()) {
|
||||
// Skip fields with @Ignore annotation
|
||||
if (field.getAnnotation(Ignore.class) != null) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Indicates that a field is a map and we need to save all map data to config
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface IsMap {
|
||||
|
||||
// Make field accessible
|
||||
field.setAccessible(true);
|
||||
}
|
||||
|
||||
// Add comments
|
||||
Comment comment = field.getAnnotation(Comment.class);
|
||||
if (comment != null) {
|
||||
for (String line : comment.value()) {
|
||||
lines.add("# " + line);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Indicates that a field is a string converted to byte[]
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
public @interface StringAsBytes {
|
||||
|
||||
// Get a key name for config
|
||||
ConfigKey key = field.getAnnotation(ConfigKey.class);
|
||||
String name = safeKey(key == null ? field.getName() : key.value()); // Use field name if @ConfigKey annotation is not present
|
||||
}
|
||||
|
||||
// Check if field is table.
|
||||
Table table = field.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
lines.add(table.value()); // Write [name]
|
||||
lines.addAll(dumpConfig(field.get(dumpable))); // Dump fields of table
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Indicates that a field should be skipped
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface Ignore {
|
||||
|
||||
if (field.getAnnotation(IsMap.class) != null) { // Check if field is a map
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) field.get(dumpable);
|
||||
for (Entry<String, ?> entry : map.entrySet()) {
|
||||
lines.add(safeKey(entry.getKey()) + " = " + serialize(entry.getValue())); // Save map data
|
||||
}
|
||||
lines.add(""); // Add empty line
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Object value = field.get(dumpable);
|
||||
/**
|
||||
* Dumps this configuration to list of strings using {@link #dumpConfig(Object)}
|
||||
*
|
||||
* @return configuration dump
|
||||
*/
|
||||
public List<String> dumpConfig() {
|
||||
return dumpConfig(this);
|
||||
}
|
||||
|
||||
// Check if field is a byte[] representation of a string
|
||||
if (field.getAnnotation(StringAsBytes.class) != null) {
|
||||
value = new String((byte[]) value, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Save field to config
|
||||
lines.add(name + " = " + serialize(value));
|
||||
lines.add(""); // Add empty line
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException | SecurityException e) {
|
||||
throw new RuntimeException("Could not dump configuration", e);
|
||||
/**
|
||||
* Creates TOML configuration from supplied <pre>dumpable</pre> object.
|
||||
*
|
||||
* @param dumpable object which is going to be dumped
|
||||
* @return string list of configuration file lines
|
||||
* @throws RuntimeException if reading field value(s) fail
|
||||
*/
|
||||
private static List<String> dumpConfig(Object dumpable) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
try {
|
||||
for (Field field : dumpable.getClass().getDeclaredFields()) {
|
||||
// Skip fields with @Ignore annotation
|
||||
if (field.getAnnotation(Ignore.class) != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return lines;
|
||||
// Make field accessible
|
||||
field.setAccessible(true);
|
||||
|
||||
// Add comments
|
||||
Comment comment = field.getAnnotation(Comment.class);
|
||||
if (comment != null) {
|
||||
for (String line : comment.value()) {
|
||||
lines.add("# " + line);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a key name for config
|
||||
ConfigKey key = field.getAnnotation(ConfigKey.class);
|
||||
String name = safeKey(key == null ? field.getName()
|
||||
: key.value()); // Use field name if @ConfigKey annotation is not present
|
||||
|
||||
// Check if field is table.
|
||||
Table table = field.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
lines.add(table.value()); // Write [name]
|
||||
lines.addAll(dumpConfig(field.get(dumpable))); // Dump fields of table
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.getAnnotation(IsMap.class) != null) { // Check if field is a map
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) field.get(dumpable);
|
||||
for (Entry<String, ?> entry : map.entrySet()) {
|
||||
lines.add(
|
||||
safeKey(entry.getKey()) + " = " + serialize(entry.getValue())); // Save map data
|
||||
}
|
||||
lines.add(""); // Add empty line
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = field.get(dumpable);
|
||||
|
||||
// Check if field is a byte[] representation of a string
|
||||
if (field.getAnnotation(StringAsBytes.class) != null) {
|
||||
value = new String((byte[]) value, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Save field to config
|
||||
lines.add(name + " = " + serialize(value));
|
||||
lines.add(""); // Add empty line
|
||||
}
|
||||
} catch (IllegalAccessException | IllegalArgumentException | SecurityException e) {
|
||||
throw new RuntimeException("Could not dump configuration", e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes <pre>value</pre> so it could be parsed by TOML specification
|
||||
*
|
||||
* @param value object to serialize
|
||||
* @return Serialized object
|
||||
*/
|
||||
private static String serialize(Object value) {
|
||||
if (value instanceof List) {
|
||||
List<?> listValue = (List<?>) value;
|
||||
if (listValue.isEmpty()) {
|
||||
return "[]";
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
StringBuilder m = new StringBuilder();
|
||||
m.append("[");
|
||||
/**
|
||||
* Serializes <pre>value</pre> so it could be parsed by TOML specification
|
||||
*
|
||||
* @param value object to serialize
|
||||
* @return Serialized object
|
||||
*/
|
||||
private static String serialize(Object value) {
|
||||
if (value instanceof List) {
|
||||
List<?> listValue = (List<?>) value;
|
||||
if (listValue.isEmpty()) {
|
||||
return "[]";
|
||||
}
|
||||
|
||||
for (Object obj : listValue) {
|
||||
m.append(System.lineSeparator()).append(" ").append(serialize(obj)).append(",");
|
||||
}
|
||||
StringBuilder m = new StringBuilder();
|
||||
m.append("[");
|
||||
|
||||
m.deleteCharAt(m.length() - 1).append(System.lineSeparator()).append("]");
|
||||
return m.toString();
|
||||
}
|
||||
for (Object obj : listValue) {
|
||||
m.append(System.lineSeparator()).append(" ").append(serialize(obj)).append(",");
|
||||
}
|
||||
|
||||
if (value instanceof Enum) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
String stringValue = (String) value;
|
||||
if (stringValue.isEmpty()) {
|
||||
return "\"\"";
|
||||
}
|
||||
return "\"" + stringValue.replace("\n", "\\n") + "\"";
|
||||
}
|
||||
|
||||
return value != null ? value.toString() : "null";
|
||||
m.deleteCharAt(m.length() - 1).append(System.lineSeparator()).append("]");
|
||||
return m.toString();
|
||||
}
|
||||
|
||||
private static String safeKey(String key) {
|
||||
if(key.contains(".") && !(key.indexOf('"') == 0 && key.lastIndexOf('"') == (key.length() - 1))) {
|
||||
return '"' + key + '"';
|
||||
}
|
||||
return key;
|
||||
if (value instanceof Enum) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes list of strings to file
|
||||
*
|
||||
* @param lines list of strings to write
|
||||
* @param to Path of file where lines should be written
|
||||
* @throws IOException if error occurred during writing
|
||||
* @throws IllegalArgumentException if <pre>lines</pre> is empty list
|
||||
*/
|
||||
public static void saveConfig(List<String> lines, Path to) throws IOException {
|
||||
if (lines.isEmpty()) {
|
||||
throw new IllegalArgumentException("lines cannot be empty");
|
||||
}
|
||||
|
||||
Path temp = to.getParent().resolve(to.getFileName().toString() + "__tmp");
|
||||
Files.write(temp, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
|
||||
try {
|
||||
Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
if (value instanceof String) {
|
||||
String stringValue = (String) value;
|
||||
if (stringValue.isEmpty()) {
|
||||
return "\"\"";
|
||||
}
|
||||
return "\"" + stringValue.replace("\n", "\\n") + "\"";
|
||||
}
|
||||
|
||||
return value != null ? value.toString() : "null";
|
||||
}
|
||||
|
||||
private static String safeKey(String key) {
|
||||
if (key.contains(".") && !(key.indexOf('"') == 0 && key.lastIndexOf('"') == (key.length()
|
||||
- 1))) {
|
||||
return '"' + key + '"';
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes list of strings to file
|
||||
*
|
||||
* @param lines list of strings to write
|
||||
* @param to Path of file where lines should be written
|
||||
* @throws IOException if error occurred during writing
|
||||
* @throws IllegalArgumentException if <pre>lines</pre> is empty list
|
||||
*/
|
||||
public static void saveConfig(List<String> lines, Path to) throws IOException {
|
||||
if (lines.isEmpty()) {
|
||||
throw new IllegalArgumentException("lines cannot be empty");
|
||||
}
|
||||
|
||||
Path temp = to.getParent().resolve(to.getFileName().toString() + "__tmp");
|
||||
Files.write(temp, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
|
||||
try {
|
||||
Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
public enum PlayerInfoForwarding {
|
||||
NONE,
|
||||
LEGACY,
|
||||
MODERN
|
||||
NONE,
|
||||
LEGACY,
|
||||
MODERN
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,14 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.CIPHER_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
@@ -9,278 +18,288 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.*;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftCipherEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftCompressDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftCompressEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.handler.codec.haproxy.HAProxyMessage;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
/**
|
||||
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
|
||||
* protocol mechanics.
|
||||
* A utility class to make working with the pipeline a little less painful and transparently handles
|
||||
* certain Minecraft protocol mechanics.
|
||||
*/
|
||||
public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
private static final Logger logger = LogManager.getLogger(MinecraftConnection.class);
|
||||
|
||||
private final Channel channel;
|
||||
private SocketAddress remoteAddress;
|
||||
private StateRegistry state;
|
||||
private @Nullable MinecraftSessionHandler sessionHandler;
|
||||
private int protocolVersion;
|
||||
private int nextProtocolVersion;
|
||||
private @Nullable MinecraftConnectionAssociation association;
|
||||
private boolean isLegacyForge;
|
||||
private final VelocityServer server;
|
||||
private boolean canSendLegacyFMLResetPacket = false;
|
||||
private static final Logger logger = LogManager.getLogger(MinecraftConnection.class);
|
||||
|
||||
public MinecraftConnection(Channel channel, VelocityServer server) {
|
||||
this.channel = channel;
|
||||
this.remoteAddress = channel.remoteAddress();
|
||||
this.server = server;
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
private final Channel channel;
|
||||
private SocketAddress remoteAddress;
|
||||
private StateRegistry state;
|
||||
private @Nullable MinecraftSessionHandler sessionHandler;
|
||||
private int protocolVersion;
|
||||
private int nextProtocolVersion;
|
||||
private @Nullable MinecraftConnectionAssociation association;
|
||||
private boolean isLegacyForge;
|
||||
private final VelocityServer server;
|
||||
private boolean canSendLegacyFMLResetPacket = false;
|
||||
|
||||
public MinecraftConnection(Channel channel, VelocityServer server) {
|
||||
this.channel = channel;
|
||||
this.remoteAddress = channel.remoteAddress();
|
||||
this.server = server;
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.connected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.connected();
|
||||
if (association != null) {
|
||||
logger.info("{} has connected", association);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.disconnected();
|
||||
}
|
||||
|
||||
if (association != null) {
|
||||
logger.info("{} has disconnected", association);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (sessionHandler == null) {
|
||||
// No session handler available, do nothing
|
||||
ReferenceCountUtil.release(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg instanceof MinecraftPacket) {
|
||||
if (sessionHandler.beforeHandle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftPacket pkt = (MinecraftPacket) msg;
|
||||
if (!pkt.handle(sessionHandler)) {
|
||||
sessionHandler.handleGeneric((MinecraftPacket) msg);
|
||||
}
|
||||
} else if (msg instanceof HAProxyMessage) {
|
||||
if (sessionHandler.beforeHandle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HAProxyMessage proxyMessage = (HAProxyMessage) msg;
|
||||
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
|
||||
proxyMessage.sourcePort());
|
||||
} else if (msg instanceof ByteBuf) {
|
||||
try {
|
||||
if (sessionHandler.beforeHandle()) {
|
||||
return;
|
||||
}
|
||||
sessionHandler.handleUnknown((ByteBuf) msg);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (association != null) {
|
||||
logger.info("{} has connected", association);
|
||||
}
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (ctx.channel().isActive()) {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.exception(cause);
|
||||
}
|
||||
|
||||
if (association != null) {
|
||||
logger.error("{}: exception encountered", association, cause);
|
||||
}
|
||||
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.writabilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public EventLoop eventLoop() {
|
||||
return channel.eventLoop();
|
||||
}
|
||||
|
||||
public void write(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.writeAndFlush(msg, channel.voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
public void delayedWrite(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.write(msg, channel.voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (channel.isActive()) {
|
||||
channel.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeWith(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (channel.isActive()) {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return !channel.isActive();
|
||||
}
|
||||
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
public StateRegistry getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
|
||||
this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.nextProtocolVersion = protocolVersion;
|
||||
if (protocolVersion != ProtocolConstants.LEGACY) {
|
||||
this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion);
|
||||
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion);
|
||||
} else {
|
||||
// Legacy handshake handling
|
||||
this.channel.pipeline().remove(MINECRAFT_ENCODER);
|
||||
this.channel.pipeline().remove(MINECRAFT_DECODER);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MinecraftSessionHandler getSessionHandler() {
|
||||
return sessionHandler;
|
||||
}
|
||||
|
||||
public void setSessionHandler(MinecraftSessionHandler sessionHandler) {
|
||||
if (this.sessionHandler != null) {
|
||||
this.sessionHandler.deactivated();
|
||||
}
|
||||
this.sessionHandler = sessionHandler;
|
||||
sessionHandler.activated();
|
||||
}
|
||||
|
||||
private void ensureOpen() {
|
||||
Preconditions.checkState(!isClosed(), "Connection is closed.");
|
||||
}
|
||||
|
||||
public void setCompressionThreshold(int threshold) {
|
||||
ensureOpen();
|
||||
|
||||
if (threshold == -1) {
|
||||
channel.pipeline().remove(COMPRESSION_DECODER);
|
||||
channel.pipeline().remove(COMPRESSION_ENCODER);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.disconnected();
|
||||
}
|
||||
int level = server.getConfiguration().getCompressionLevel();
|
||||
VelocityCompressor compressor = Natives.compressor.get().create(level);
|
||||
MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor);
|
||||
MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor);
|
||||
|
||||
if (association != null) {
|
||||
logger.info("{} has disconnected", association);
|
||||
}
|
||||
}
|
||||
channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder);
|
||||
channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (sessionHandler == null) {
|
||||
// No session handler available, do nothing
|
||||
ReferenceCountUtil.release(msg);
|
||||
return;
|
||||
}
|
||||
public void enableEncryption(byte[] secret) throws GeneralSecurityException {
|
||||
ensureOpen();
|
||||
|
||||
if (msg instanceof MinecraftPacket) {
|
||||
if (sessionHandler.beforeHandle()) {
|
||||
return;
|
||||
}
|
||||
SecretKey key = new SecretKeySpec(secret, "AES");
|
||||
|
||||
MinecraftPacket pkt = (MinecraftPacket) msg;
|
||||
if (!pkt.handle(sessionHandler)) {
|
||||
sessionHandler.handleGeneric((MinecraftPacket) msg);
|
||||
}
|
||||
} else if (msg instanceof HAProxyMessage) {
|
||||
if (sessionHandler.beforeHandle()) {
|
||||
return;
|
||||
}
|
||||
VelocityCipherFactory factory = Natives.cipher.get();
|
||||
VelocityCipher decryptionCipher = factory.forDecryption(key);
|
||||
VelocityCipher encryptionCipher = factory.forEncryption(key);
|
||||
channel.pipeline()
|
||||
.addBefore(FRAME_DECODER, CIPHER_DECODER, new MinecraftCipherDecoder(decryptionCipher));
|
||||
channel.pipeline()
|
||||
.addBefore(FRAME_ENCODER, CIPHER_ENCODER, new MinecraftCipherEncoder(encryptionCipher));
|
||||
}
|
||||
|
||||
HAProxyMessage proxyMessage = (HAProxyMessage) msg;
|
||||
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), proxyMessage.sourcePort());
|
||||
} else if (msg instanceof ByteBuf) {
|
||||
try {
|
||||
if (sessionHandler.beforeHandle()) {
|
||||
return;
|
||||
}
|
||||
sessionHandler.handleUnknown((ByteBuf) msg);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Nullable
|
||||
public MinecraftConnectionAssociation getAssociation() {
|
||||
return association;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (ctx.channel().isActive()) {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.exception(cause);
|
||||
}
|
||||
public void setAssociation(MinecraftConnectionAssociation association) {
|
||||
this.association = association;
|
||||
}
|
||||
|
||||
if (association != null) {
|
||||
logger.error("{}: exception encountered", association, cause);
|
||||
}
|
||||
public boolean isLegacyForge() {
|
||||
return isLegacyForge;
|
||||
}
|
||||
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
public void setLegacyForge(boolean isForge) {
|
||||
this.isLegacyForge = isForge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
||||
if (sessionHandler != null) {
|
||||
sessionHandler.writabilityChanged();
|
||||
}
|
||||
}
|
||||
public boolean canSendLegacyFMLResetPacket() {
|
||||
return canSendLegacyFMLResetPacket;
|
||||
}
|
||||
|
||||
public EventLoop eventLoop() {
|
||||
return channel.eventLoop();
|
||||
}
|
||||
public void setCanSendLegacyFMLResetPacket(boolean canSendLegacyFMLResetPacket) {
|
||||
this.canSendLegacyFMLResetPacket = isLegacyForge && canSendLegacyFMLResetPacket;
|
||||
}
|
||||
|
||||
public void write(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.writeAndFlush(msg, channel.voidPromise());
|
||||
}
|
||||
}
|
||||
public int getNextProtocolVersion() {
|
||||
return this.nextProtocolVersion;
|
||||
}
|
||||
|
||||
public void delayedWrite(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.write(msg, channel.voidPromise());
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (channel.isActive()) {
|
||||
channel.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void closeWith(Object msg) {
|
||||
if (channel.isActive()) {
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (channel.isActive()) {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return !channel.isActive();
|
||||
}
|
||||
|
||||
public SocketAddress getRemoteAddress() {
|
||||
return remoteAddress;
|
||||
}
|
||||
|
||||
public StateRegistry getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
|
||||
this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.nextProtocolVersion = protocolVersion;
|
||||
if (protocolVersion != ProtocolConstants.LEGACY) {
|
||||
this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion);
|
||||
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion);
|
||||
} else {
|
||||
// Legacy handshake handling
|
||||
this.channel.pipeline().remove(MINECRAFT_ENCODER);
|
||||
this.channel.pipeline().remove(MINECRAFT_DECODER);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MinecraftSessionHandler getSessionHandler() {
|
||||
return sessionHandler;
|
||||
}
|
||||
|
||||
public void setSessionHandler(MinecraftSessionHandler sessionHandler) {
|
||||
if (this.sessionHandler != null) {
|
||||
this.sessionHandler.deactivated();
|
||||
}
|
||||
this.sessionHandler = sessionHandler;
|
||||
sessionHandler.activated();
|
||||
}
|
||||
|
||||
private void ensureOpen() {
|
||||
Preconditions.checkState(!isClosed(), "Connection is closed.");
|
||||
}
|
||||
|
||||
public void setCompressionThreshold(int threshold) {
|
||||
ensureOpen();
|
||||
|
||||
if (threshold == -1) {
|
||||
channel.pipeline().remove(COMPRESSION_DECODER);
|
||||
channel.pipeline().remove(COMPRESSION_ENCODER);
|
||||
return;
|
||||
}
|
||||
|
||||
int level = server.getConfiguration().getCompressionLevel();
|
||||
VelocityCompressor compressor = Natives.compressor.get().create(level);
|
||||
MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor);
|
||||
MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor);
|
||||
|
||||
channel.pipeline().addBefore(MINECRAFT_DECODER, COMPRESSION_DECODER, decoder);
|
||||
channel.pipeline().addBefore(MINECRAFT_ENCODER, COMPRESSION_ENCODER, encoder);
|
||||
}
|
||||
|
||||
public void enableEncryption(byte[] secret) throws GeneralSecurityException {
|
||||
ensureOpen();
|
||||
|
||||
SecretKey key = new SecretKeySpec(secret, "AES");
|
||||
|
||||
VelocityCipherFactory factory = Natives.cipher.get();
|
||||
VelocityCipher decryptionCipher = factory.forDecryption(key);
|
||||
VelocityCipher encryptionCipher = factory.forEncryption(key);
|
||||
channel.pipeline().addBefore(FRAME_DECODER, CIPHER_DECODER, new MinecraftCipherDecoder(decryptionCipher));
|
||||
channel.pipeline().addBefore(FRAME_ENCODER, CIPHER_ENCODER, new MinecraftCipherEncoder(encryptionCipher));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MinecraftConnectionAssociation getAssociation() {
|
||||
return association;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getNextProtocolVersion() {
|
||||
return this.nextProtocolVersion;
|
||||
}
|
||||
|
||||
public void setNextProtocolVersion(int nextProtocolVersion) {
|
||||
this.nextProtocolVersion = nextProtocolVersion;
|
||||
}
|
||||
public void setNextProtocolVersion(int nextProtocolVersion) {
|
||||
this.nextProtocolVersion = nextProtocolVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
public interface MinecraftConnectionAssociation {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,70 +1,173 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface MinecraftSessionHandler {
|
||||
default boolean beforeHandle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default void handleGeneric(MinecraftPacket packet) {
|
||||
default boolean beforeHandle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
default void handleGeneric(MinecraftPacket packet) {
|
||||
|
||||
default void handleUnknown(ByteBuf buf) {
|
||||
}
|
||||
|
||||
}
|
||||
default void handleUnknown(ByteBuf buf) {
|
||||
|
||||
default void connected() {
|
||||
}
|
||||
|
||||
}
|
||||
default void connected() {
|
||||
|
||||
default void disconnected() {
|
||||
}
|
||||
|
||||
}
|
||||
default void disconnected() {
|
||||
|
||||
default void activated() {
|
||||
}
|
||||
|
||||
}
|
||||
default void activated() {
|
||||
|
||||
default void deactivated() {
|
||||
}
|
||||
|
||||
}
|
||||
default void deactivated() {
|
||||
|
||||
default void exception(Throwable throwable) {
|
||||
}
|
||||
|
||||
}
|
||||
default void exception(Throwable throwable) {
|
||||
|
||||
default void writabilityChanged() {
|
||||
}
|
||||
|
||||
}
|
||||
default void writabilityChanged() {
|
||||
|
||||
default boolean handle(BossBar packet) { return false; }
|
||||
default boolean handle(Chat packet) { return false; }
|
||||
default boolean handle(ClientSettings packet) { return false; }
|
||||
default boolean handle(Disconnect packet) { return false; }
|
||||
default boolean handle(EncryptionRequest packet) { return false; }
|
||||
default boolean handle(EncryptionResponse packet) { return false; }
|
||||
default boolean handle(Handshake packet) { return false; }
|
||||
default boolean handle(HeaderAndFooter packet) { return false; }
|
||||
default boolean handle(JoinGame packet) { return false; }
|
||||
default boolean handle(KeepAlive packet) { return false; }
|
||||
default boolean handle(LegacyHandshake packet) { return false; }
|
||||
default boolean handle(LegacyPing packet) { return false; }
|
||||
default boolean handle(LoginPluginMessage packet) { return false; }
|
||||
default boolean handle(LoginPluginResponse packet) { return false; }
|
||||
default boolean handle(PluginMessage packet) { return false; }
|
||||
default boolean handle(Respawn packet) { return false; }
|
||||
default boolean handle(ServerLogin packet) { return false; }
|
||||
default boolean handle(ServerLoginSuccess packet) { return false; }
|
||||
default boolean handle(SetCompression packet) { return false; }
|
||||
default boolean handle(StatusPing packet) { return false; }
|
||||
default boolean handle(StatusRequest packet) { return false; }
|
||||
default boolean handle(StatusResponse packet) { return false; }
|
||||
default boolean handle(TabCompleteRequest packet) { return false; }
|
||||
default boolean handle(TabCompleteResponse packet) { return false; }
|
||||
default boolean handle(TitlePacket packet) { return false; }
|
||||
default boolean handle(PlayerListItem packet) { return false; }
|
||||
}
|
||||
|
||||
default boolean handle(BossBar packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(Chat packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(ClientSettings packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(Disconnect packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(EncryptionRequest packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(EncryptionResponse packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(Handshake packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(HeaderAndFooter packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(JoinGame packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(KeepAlive packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(LegacyHandshake packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(LegacyPing packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(LoginPluginMessage packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(LoginPluginResponse packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(PluginMessage packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(Respawn packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(ServerLogin packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(ServerLoginSuccess packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(SetCompression packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(StatusPing packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(StatusRequest packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(StatusResponse packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(TabCompleteRequest packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(TabCompleteResponse packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(TitlePacket packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean handle(PlayerListItem packet) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
public class VelocityConstants {
|
||||
private VelocityConstants() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||
public static final int FORWARDING_VERSION = 1;
|
||||
private VelocityConstants() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||
public static final int FORWARDING_VERSION = 1;
|
||||
|
||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
}
|
||||
|
||||
@@ -10,167 +10,179 @@ import com.velocitypowered.proxy.connection.forge.ForgeConstants;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private final VelocityServer server;
|
||||
private final VelocityServerConnection serverConn;
|
||||
private final ClientPlaySessionHandler playerSessionHandler;
|
||||
|
||||
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
|
||||
this.server = server;
|
||||
this.serverConn = serverConn;
|
||||
private final VelocityServer server;
|
||||
private final VelocityServerConnection serverConn;
|
||||
private final ClientPlaySessionHandler playerSessionHandler;
|
||||
|
||||
MinecraftSessionHandler psh = serverConn.getPlayer().getConnection().getSessionHandler();
|
||||
if (!(psh instanceof ClientPlaySessionHandler)) {
|
||||
throw new IllegalStateException("Initializing BackendPlaySessionHandler with no backing client play session handler!");
|
||||
}
|
||||
this.playerSessionHandler = (ClientPlaySessionHandler) psh;
|
||||
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
|
||||
this.server = server;
|
||||
this.serverConn = serverConn;
|
||||
|
||||
MinecraftSessionHandler psh = serverConn.getPlayer().getConnection().getSessionHandler();
|
||||
if (!(psh instanceof ClientPlaySessionHandler)) {
|
||||
throw new IllegalStateException(
|
||||
"Initializing BackendPlaySessionHandler with no backing client play session handler!");
|
||||
}
|
||||
this.playerSessionHandler = (ClientPlaySessionHandler) psh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
serverConn.getServer().addPlayer(serverConn.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforeHandle() {
|
||||
if (!serverConn.getPlayer().isActive()) {
|
||||
// Obsolete connection
|
||||
serverConn.disconnect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(KeepAlive packet) {
|
||||
serverConn.setLastPingId(packet.getRandomId());
|
||||
return false; // forwards on
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Disconnect packet) {
|
||||
serverConn.disconnect();
|
||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(JoinGame packet) {
|
||||
playerSessionHandler.handleBackendJoinGame(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(BossBar packet) {
|
||||
if (packet.getAction() == BossBar.ADD) {
|
||||
playerSessionHandler.getServerBossBars().add(packet.getUuid());
|
||||
} else if (packet.getAction() == BossBar.REMOVE) {
|
||||
playerSessionHandler.getServerBossBars().remove(packet.getUuid());
|
||||
}
|
||||
return false; // forward
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(PluginMessage packet) {
|
||||
MinecraftConnection smc = serverConn.getConnection();
|
||||
if (smc == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
serverConn.getServer().addPlayer(serverConn.getPlayer());
|
||||
if (!canForwardPluginMessage(packet)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforeHandle() {
|
||||
if (!serverConn.getPlayer().isActive()) {
|
||||
// Obsolete connection
|
||||
serverConn.disconnect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
serverConn.getPlayer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(KeepAlive packet) {
|
||||
serverConn.setLastPingId(packet.getRandomId());
|
||||
return false; // forwards on
|
||||
if (!serverConn.hasCompletedJoin() && packet.getChannel()
|
||||
.equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
if (!serverConn.isLegacyForge()) {
|
||||
serverConn.setLegacyForge(true);
|
||||
|
||||
// We must always reset the handshake before a modded connection is established if
|
||||
// we haven't done so already.
|
||||
serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
||||
}
|
||||
|
||||
// Always forward these messages during login.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Disconnect packet) {
|
||||
serverConn.disconnect();
|
||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet);
|
||||
return true;
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(JoinGame packet) {
|
||||
playerSessionHandler.handleBackendJoinGame(packet);
|
||||
return true;
|
||||
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id,
|
||||
packet.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
smc.write(packet);
|
||||
}
|
||||
}, smc.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(TabCompleteResponse packet) {
|
||||
playerSessionHandler.handleTabCompleteResponse(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(PlayerListItem packet) {
|
||||
serverConn.getPlayer().getTabList().processBackendPacket(packet);
|
||||
return false; //Forward packet to player
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
serverConn.getPlayer().getConnection().write(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
serverConn.getPlayer().getConnection().write(buf.retain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), throwable);
|
||||
}
|
||||
|
||||
public VelocityServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
serverConn.getServer().removePlayer(serverConn.getPlayer());
|
||||
if (!serverConn.isGracefulDisconnect()) {
|
||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), Disconnect.create(
|
||||
ConnectionMessages.UNEXPECTED_DISCONNECT));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(BossBar packet) {
|
||||
if (packet.getAction() == BossBar.ADD) {
|
||||
playerSessionHandler.getServerBossBars().add(packet.getUuid());
|
||||
} else if (packet.getAction() == BossBar.REMOVE) {
|
||||
playerSessionHandler.getServerBossBars().remove(packet.getUuid());
|
||||
}
|
||||
return false; // forward
|
||||
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||
MinecraftConnection mc = serverConn.getConnection();
|
||||
if (mc == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(PluginMessage packet) {
|
||||
MinecraftConnection smc = serverConn.getConnection();
|
||||
if (smc == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!canForwardPluginMessage(packet)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
serverConn.getPlayer().getConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!serverConn.hasCompletedJoin() && packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
if (!serverConn.isLegacyForge()) {
|
||||
serverConn.setLegacyForge(true);
|
||||
|
||||
// We must always reset the handshake before a modded connection is established if
|
||||
// we haven't done so already.
|
||||
serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
||||
}
|
||||
|
||||
// Always forward these messages during login.
|
||||
return false;
|
||||
}
|
||||
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, packet.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
smc.write(packet);
|
||||
}
|
||||
}, smc.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(TabCompleteResponse packet) {
|
||||
playerSessionHandler.handleTabCompleteResponse(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(PlayerListItem packet) {
|
||||
serverConn.getPlayer().getTabList().processBackendPacket(packet);
|
||||
return false; //Forward packet to player
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
serverConn.getPlayer().getConnection().write(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
serverConn.getPlayer().getConnection().write(buf.retain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), throwable);
|
||||
}
|
||||
|
||||
public VelocityServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
serverConn.getServer().removePlayer(serverConn.getPlayer());
|
||||
if (!serverConn.isGracefulDisconnect()) {
|
||||
serverConn.getPlayer().handleConnectionException(serverConn.getServer(), Disconnect.create(
|
||||
ConnectionMessages.UNEXPECTED_DISCONNECT));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||
MinecraftConnection mc = serverConn.getConnection();
|
||||
if (mc == null) {
|
||||
return false;
|
||||
}
|
||||
boolean isMCOrFMLMessage;
|
||||
if (mc.getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
String channel = message.getChannel();
|
||||
isMCOrFMLMessage = channel.startsWith("MC|") || channel.startsWith(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||
} else {
|
||||
isMCOrFMLMessage = message.getChannel().startsWith("minecraft:");
|
||||
}
|
||||
return isMCOrFMLMessage || playerSessionHandler.getClientPluginMsgChannels().contains(message.getChannel()) ||
|
||||
server.getChannelRegistrar().registered(message.getChannel());
|
||||
boolean isMCOrFMLMessage;
|
||||
if (mc.getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
String channel = message.getChannel();
|
||||
isMCOrFMLMessage = channel.startsWith("MC|") || channel
|
||||
.startsWith(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||
} else {
|
||||
isMCOrFMLMessage = message.getChannel().startsWith("minecraft:");
|
||||
}
|
||||
return isMCOrFMLMessage || playerSessionHandler.getClientPluginMsgChannels()
|
||||
.contains(message.getChannel()) ||
|
||||
server.getChannelRegistrar().registered(message.getChannel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,156 +13,168 @@ import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.text.TextComponent;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import net.kyori.text.TextComponent;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
private final VelocityServer server;
|
||||
private final VelocityServerConnection serverConn;
|
||||
private final CompletableFuture<ConnectionRequestBuilder.Result> resultFuture;
|
||||
private boolean informationForwarded;
|
||||
|
||||
LoginSessionHandler(VelocityServer server, VelocityServerConnection serverConn,
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> resultFuture) {
|
||||
this.server = server;
|
||||
this.serverConn = serverConn;
|
||||
this.resultFuture = resultFuture;
|
||||
private final VelocityServer server;
|
||||
private final VelocityServerConnection serverConn;
|
||||
private final CompletableFuture<ConnectionRequestBuilder.Result> resultFuture;
|
||||
private boolean informationForwarded;
|
||||
|
||||
LoginSessionHandler(VelocityServer server, VelocityServerConnection serverConn,
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> resultFuture) {
|
||||
this.server = server;
|
||||
this.serverConn = serverConn;
|
||||
this.resultFuture = resultFuture;
|
||||
}
|
||||
|
||||
private MinecraftConnection ensureMinecraftConnection() {
|
||||
MinecraftConnection mc = serverConn.getConnection();
|
||||
if (mc == null) {
|
||||
throw new IllegalStateException("Not connected to backend server!");
|
||||
}
|
||||
return mc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(EncryptionRequest packet) {
|
||||
throw new IllegalStateException("Backend server is online-mode!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPluginMessage packet) {
|
||||
MinecraftConnection mc = ensureMinecraftConnection();
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet
|
||||
.getChannel()
|
||||
.equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(true);
|
||||
response.setId(packet.getId());
|
||||
response.setData(createForwardingData(configuration.getForwardingSecret(),
|
||||
serverConn.getPlayer().getRemoteAddress().getHostString(),
|
||||
serverConn.getPlayer().getProfile()));
|
||||
mc.write(response);
|
||||
informationForwarded = true;
|
||||
} else {
|
||||
// Don't understand
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(false);
|
||||
response.setId(packet.getId());
|
||||
response.setData(Unpooled.EMPTY_BUFFER);
|
||||
mc.write(response);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Disconnect packet) {
|
||||
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet));
|
||||
serverConn.disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(SetCompression packet) {
|
||||
ensureMinecraftConnection().setCompressionThreshold(packet.getThreshold());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ServerLoginSuccess packet) {
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
||||
&& !informationForwarded) {
|
||||
resultFuture.complete(ConnectionRequestResults.forDisconnect(
|
||||
TextComponent
|
||||
.of("Your server did not send a forwarding request to the proxy. Is it set up correctly?")));
|
||||
serverConn.disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
private MinecraftConnection ensureMinecraftConnection() {
|
||||
MinecraftConnection mc = serverConn.getConnection();
|
||||
if (mc == null) {
|
||||
throw new IllegalStateException("Not connected to backend server!");
|
||||
}
|
||||
return mc;
|
||||
// The player has been logged on to the backend server.
|
||||
MinecraftConnection smc = ensureMinecraftConnection();
|
||||
smc.setState(StateRegistry.PLAY);
|
||||
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
||||
if (existingConnection == null) {
|
||||
// Strap on the play session handler
|
||||
serverConn.getPlayer().getConnection()
|
||||
.setSessionHandler(new ClientPlaySessionHandler(server, serverConn.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()) {
|
||||
serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
||||
}
|
||||
existingConnection.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(EncryptionRequest packet) {
|
||||
throw new IllegalStateException("Backend server is online-mode!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPluginMessage packet) {
|
||||
MinecraftConnection mc = ensureMinecraftConnection();
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet.getChannel()
|
||||
.equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(true);
|
||||
response.setId(packet.getId());
|
||||
response.setData(createForwardingData(configuration.getForwardingSecret(),
|
||||
serverConn.getPlayer().getRemoteAddress().getHostString(),
|
||||
serverConn.getPlayer().getProfile()));
|
||||
mc.write(response);
|
||||
informationForwarded = true;
|
||||
} else {
|
||||
// Don't understand
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(false);
|
||||
response.setId(packet.getId());
|
||||
response.setData(Unpooled.EMPTY_BUFFER);
|
||||
mc.write(response);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Disconnect packet) {
|
||||
resultFuture.complete(ConnectionRequestResults.forDisconnect(packet));
|
||||
serverConn.disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(SetCompression packet) {
|
||||
ensureMinecraftConnection().setCompressionThreshold(packet.getThreshold());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ServerLoginSuccess packet) {
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && !informationForwarded) {
|
||||
resultFuture.complete(ConnectionRequestResults.forDisconnect(
|
||||
TextComponent.of("Your server did not send a forwarding request to the proxy. Is it set up correctly?")));
|
||||
serverConn.disconnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
// The player has been logged on to the backend server.
|
||||
MinecraftConnection smc = ensureMinecraftConnection();
|
||||
smc.setState(StateRegistry.PLAY);
|
||||
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
|
||||
if (existingConnection == null) {
|
||||
// Strap on the play session handler
|
||||
serverConn.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, serverConn.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()) {
|
||||
serverConn.getPlayer().sendLegacyForgeHandshakeResetPacket();
|
||||
}
|
||||
existingConnection.disconnect();
|
||||
}
|
||||
|
||||
smc.getChannel().config().setAutoRead(false);
|
||||
server.getEventManager().fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
|
||||
.whenCompleteAsync((x, error) -> {
|
||||
resultFuture.complete(ConnectionRequestResults.SUCCESSFUL);
|
||||
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
|
||||
serverConn.getPlayer().setConnectedServer(serverConn);
|
||||
smc.getChannel().config().setAutoRead(true);
|
||||
}, smc.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
resultFuture.completeExceptionally(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
resultFuture.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) {
|
||||
ByteBuf dataToForward = Unpooled.buffer();
|
||||
ByteBuf finalData = Unpooled.buffer();
|
||||
try {
|
||||
ProtocolUtils.writeVarInt(dataToForward, VelocityConstants.FORWARDING_VERSION);
|
||||
ProtocolUtils.writeString(dataToForward, address);
|
||||
ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid());
|
||||
ProtocolUtils.writeString(dataToForward, profile.getName());
|
||||
ProtocolUtils.writeProperties(dataToForward, profile.getProperties());
|
||||
|
||||
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes());
|
||||
byte[] sig = mac.doFinal();
|
||||
finalData.writeBytes(sig);
|
||||
finalData.writeBytes(dataToForward);
|
||||
return finalData;
|
||||
} catch (InvalidKeyException e) {
|
||||
finalData.release();
|
||||
throw new RuntimeException("Unable to authenticate data", e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never happen
|
||||
finalData.release();
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
dataToForward.release();
|
||||
}
|
||||
smc.getChannel().config().setAutoRead(false);
|
||||
server.getEventManager()
|
||||
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer()))
|
||||
.whenCompleteAsync((x, error) -> {
|
||||
resultFuture.complete(ConnectionRequestResults.SUCCESSFUL);
|
||||
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
|
||||
serverConn.getPlayer().setConnectedServer(serverConn);
|
||||
smc.getChannel().config().setAutoRead(true);
|
||||
}, smc.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
resultFuture.completeExceptionally(throwable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
resultFuture
|
||||
.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
|
||||
GameProfile profile) {
|
||||
ByteBuf dataToForward = Unpooled.buffer();
|
||||
ByteBuf finalData = Unpooled.buffer();
|
||||
try {
|
||||
ProtocolUtils.writeVarInt(dataToForward, VelocityConstants.FORWARDING_VERSION);
|
||||
ProtocolUtils.writeString(dataToForward, address);
|
||||
ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid());
|
||||
ProtocolUtils.writeString(dataToForward, profile.getName());
|
||||
ProtocolUtils.writeProperties(dataToForward, profile.getProperties());
|
||||
|
||||
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes());
|
||||
byte[] sig = mac.doFinal();
|
||||
finalData.writeBytes(sig);
|
||||
finalData.writeBytes(dataToForward);
|
||||
return finalData;
|
||||
} catch (InvalidKeyException e) {
|
||||
finalData.release();
|
||||
throw new RuntimeException("Unable to authenticate data", e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never happen
|
||||
finalData.release();
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
dataToForward.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.HANDLER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
@@ -25,193 +33,196 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
|
||||
private final VelocityRegisteredServer registeredServer;
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
private @Nullable MinecraftConnection connection;
|
||||
private boolean legacyForge = false;
|
||||
private boolean hasCompletedJoin = false;
|
||||
private boolean gracefulDisconnect = false;
|
||||
private long lastPingId;
|
||||
private long lastPingSent;
|
||||
|
||||
public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.registeredServer = registeredServer;
|
||||
this.proxyPlayer = proxyPlayer;
|
||||
this.server = server;
|
||||
private final VelocityRegisteredServer registeredServer;
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
private @Nullable MinecraftConnection connection;
|
||||
private boolean legacyForge = false;
|
||||
private boolean hasCompletedJoin = false;
|
||||
private boolean gracefulDisconnect = false;
|
||||
private long lastPingId;
|
||||
private long lastPingSent;
|
||||
|
||||
public VelocityServerConnection(VelocityRegisteredServer registeredServer,
|
||||
ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.registeredServer = registeredServer;
|
||||
this.proxyPlayer = proxyPlayer;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> result = 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 mc = new MinecraftConnection(ch, server);
|
||||
mc.setState(StateRegistry.HANDSHAKE);
|
||||
mc.setAssociation(VelocityServerConnection.this);
|
||||
ch.pipeline().addLast(HANDLER, mc);
|
||||
}
|
||||
})
|
||||
.connect(registeredServer.getServerInfo().getAddress())
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
connection = future.channel().pipeline().get(MinecraftConnection.class);
|
||||
|
||||
// This is guaranteed not to be null, but Checker Framework is whining about it anyway
|
||||
if (connection == null) {
|
||||
throw new VerifyException("MinecraftConnection not injected into pipeline");
|
||||
}
|
||||
|
||||
// Kick off the connection process
|
||||
connection.setSessionHandler(
|
||||
new LoginSessionHandler(server, VelocityServerConnection.this, result));
|
||||
startHandshake();
|
||||
} else {
|
||||
result.completeExceptionally(future.cause());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private String createBungeeForwardingAddress() {
|
||||
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
|
||||
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
|
||||
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
|
||||
return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getProfile().getId() + "\0" +
|
||||
GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||
}
|
||||
|
||||
private void startHandshake() {
|
||||
MinecraftConnection mc = connection;
|
||||
if (mc == null) {
|
||||
throw new IllegalStateException("No connection established!");
|
||||
}
|
||||
|
||||
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> result = 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));
|
||||
PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode();
|
||||
|
||||
MinecraftConnection mc = new MinecraftConnection(ch, server);
|
||||
mc.setState(StateRegistry.HANDSHAKE);
|
||||
mc.setAssociation(VelocityServerConnection.this);
|
||||
ch.pipeline().addLast(HANDLER, mc);
|
||||
}
|
||||
})
|
||||
.connect(registeredServer.getServerInfo().getAddress())
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
connection = future.channel().pipeline().get(MinecraftConnection.class);
|
||||
// Initiate a handshake.
|
||||
Handshake handshake = new Handshake();
|
||||
handshake.setNextStatus(StateRegistry.LOGIN_ID);
|
||||
handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion());
|
||||
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
|
||||
handshake.setServerAddress(createBungeeForwardingAddress());
|
||||
} else if (proxyPlayer.getConnection().isLegacyForge()) {
|
||||
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
|
||||
} else {
|
||||
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
|
||||
}
|
||||
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
|
||||
mc.write(handshake);
|
||||
|
||||
// This is guaranteed not to be null, but Checker Framework is whining about it anyway
|
||||
if (connection == null) {
|
||||
throw new VerifyException("MinecraftConnection not injected into pipeline");
|
||||
}
|
||||
int protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion();
|
||||
mc.setProtocolVersion(protocolVersion);
|
||||
mc.setState(StateRegistry.LOGIN);
|
||||
mc.write(new ServerLogin(proxyPlayer.getUsername()));
|
||||
}
|
||||
|
||||
// Kick off the connection process
|
||||
connection.setSessionHandler(new LoginSessionHandler(server, VelocityServerConnection.this, result));
|
||||
startHandshake();
|
||||
} else {
|
||||
result.completeExceptionally(future.cause());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
@Nullable
|
||||
public MinecraftConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityRegisteredServer getServer() {
|
||||
return registeredServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return registeredServer.getServerInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectedPlayer getPlayer() {
|
||||
return proxyPlayer;
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
connection = null;
|
||||
gracefulDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer
|
||||
.getServerInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
|
||||
MinecraftConnection mc = connection;
|
||||
if (mc == null) {
|
||||
throw new IllegalStateException("Not connected to a server!");
|
||||
}
|
||||
|
||||
private String createBungeeForwardingAddress() {
|
||||
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
|
||||
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
|
||||
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
|
||||
return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getProfile().getId() + "\0" +
|
||||
GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||
}
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(identifier.getId());
|
||||
message.setData(data);
|
||||
mc.write(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void startHandshake() {
|
||||
MinecraftConnection mc = connection;
|
||||
if (mc == null) {
|
||||
throw new IllegalStateException("No connection established!");
|
||||
}
|
||||
public boolean isLegacyForge() {
|
||||
return legacyForge;
|
||||
}
|
||||
|
||||
PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode();
|
||||
public void setLegacyForge(boolean modded) {
|
||||
legacyForge = modded;
|
||||
}
|
||||
|
||||
// Initiate a handshake.
|
||||
Handshake handshake = new Handshake();
|
||||
handshake.setNextStatus(StateRegistry.LOGIN_ID);
|
||||
handshake.setProtocolVersion(proxyPlayer.getConnection().getNextProtocolVersion());
|
||||
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
|
||||
handshake.setServerAddress(createBungeeForwardingAddress());
|
||||
} else if (proxyPlayer.getConnection().isLegacyForge()) {
|
||||
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
|
||||
} else {
|
||||
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
|
||||
}
|
||||
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
|
||||
mc.write(handshake);
|
||||
public boolean hasCompletedJoin() {
|
||||
return hasCompletedJoin;
|
||||
}
|
||||
|
||||
int protocolVersion = proxyPlayer.getConnection().getNextProtocolVersion();
|
||||
mc.setProtocolVersion(protocolVersion);
|
||||
mc.setState(StateRegistry.LOGIN);
|
||||
mc.write(new ServerLogin(proxyPlayer.getUsername()));
|
||||
}
|
||||
public void setHasCompletedJoin(boolean hasCompletedJoin) {
|
||||
this.hasCompletedJoin = hasCompletedJoin;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MinecraftConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
public boolean isGracefulDisconnect() {
|
||||
return gracefulDisconnect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityRegisteredServer getServer() {
|
||||
return registeredServer;
|
||||
}
|
||||
public long getLastPingId() {
|
||||
return lastPingId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return registeredServer.getServerInfo();
|
||||
}
|
||||
public long getLastPingSent() {
|
||||
return lastPingSent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectedPlayer getPlayer() {
|
||||
return proxyPlayer;
|
||||
}
|
||||
public void setLastPingId(long lastPingId) {
|
||||
this.lastPingId = lastPingId;
|
||||
this.lastPingSent = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
connection = null;
|
||||
gracefulDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
|
||||
MinecraftConnection mc = connection;
|
||||
if (mc == null) {
|
||||
throw new IllegalStateException("Not connected to a server!");
|
||||
}
|
||||
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(identifier.getId());
|
||||
message.setData(data);
|
||||
mc.write(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
public void resetLastPingId() {
|
||||
this.lastPingId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,357 +12,388 @@ import com.velocitypowered.proxy.connection.forge.ForgeConstants;
|
||||
import com.velocitypowered.proxy.connection.forge.ForgeUtil;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Handles communication with the connected Minecraft client. This is effectively the primary nerve center that
|
||||
* joins backend servers with players.
|
||||
* Handles communication with the connected Minecraft client. This is effectively the primary nerve
|
||||
* center that joins backend servers with players.
|
||||
*/
|
||||
public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class);
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private boolean spawned = false;
|
||||
private final List<UUID> serverBossBars = new ArrayList<>();
|
||||
private final Set<String> clientPluginMsgChannels = new HashSet<>();
|
||||
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
|
||||
private final VelocityServer server;
|
||||
private @Nullable TabCompleteRequest outstandingTabComplete;
|
||||
private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class);
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
|
||||
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
this.server = server;
|
||||
private final ConnectedPlayer player;
|
||||
private boolean spawned = false;
|
||||
private final List<UUID> serverBossBars = new ArrayList<>();
|
||||
private final Set<String> clientPluginMsgChannels = new HashSet<>();
|
||||
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
|
||||
private final VelocityServer server;
|
||||
private @Nullable TabCompleteRequest outstandingTabComplete;
|
||||
|
||||
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
PluginMessage register = PluginMessageUtil.constructChannelsPacket(player.getProtocolVersion(),
|
||||
server.getChannelRegistrar().getModernChannelIds());
|
||||
player.getConnection().write(register);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(KeepAlive packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) {
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null) {
|
||||
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
|
||||
smc.write(packet);
|
||||
serverConnection.resetLastPingId();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
PluginMessage register = PluginMessageUtil.constructChannelsPacket(player.getProtocolVersion(), server.getChannelRegistrar().getModernChannelIds());
|
||||
player.getConnection().write(register);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(ClientSettings packet) {
|
||||
player.setPlayerSettings(packet);
|
||||
return false; // will forward onto the handleGeneric below, which will write the packet to the remote server
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(KeepAlive packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection != null && packet.getRandomId() == serverConnection.getLastPingId()) {
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null) {
|
||||
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
|
||||
@Override
|
||||
public boolean handle(Chat packet) {
|
||||
String msg = packet.getMessage();
|
||||
if (msg.startsWith("/")) {
|
||||
try {
|
||||
if (!server.getCommandManager().execute(player, msg.substring(1))) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger
|
||||
.info("Exception occurred while running command for {}", player.getProfile().getName(),
|
||||
e);
|
||||
player.sendMessage(
|
||||
TextComponent.of("An error occurred while running this command.", TextColor.RED));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
return true;
|
||||
}
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc == null) {
|
||||
return true;
|
||||
}
|
||||
PlayerChatEvent event = new PlayerChatEvent(player, msg);
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
PlayerChatEvent.ChatResult chatResult = pme.getResult();
|
||||
if (chatResult.isAllowed()) {
|
||||
Optional<String> eventMsg = pme.getResult().getMessage();
|
||||
if (eventMsg.isPresent()) {
|
||||
smc.write(Chat.createServerbound(eventMsg.get()));
|
||||
} else {
|
||||
smc.write(packet);
|
||||
serverConnection.resetLastPingId();
|
||||
}
|
||||
}
|
||||
}, smc.eventLoop());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(TabCompleteRequest packet) {
|
||||
// Record the request so that the outstanding request can be augmented later.
|
||||
if (!packet.isAssumeCommand() && packet.getCommand().startsWith("/")) {
|
||||
int spacePos = packet.getCommand().indexOf(' ');
|
||||
if (spacePos > 0) {
|
||||
String cmd = packet.getCommand().substring(1, spacePos);
|
||||
if (server.getCommandManager().hasCommand(cmd)) {
|
||||
List<String> suggestions = server.getCommandManager()
|
||||
.offerSuggestions(player, packet.getCommand().substring(1));
|
||||
if (!suggestions.isEmpty()) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.getOffers().addAll(suggestions);
|
||||
player.getConnection().write(resp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
outstandingTabComplete = packet;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ClientSettings packet) {
|
||||
player.setPlayerSettings(packet);
|
||||
return false; // will forward onto the handleGeneric below, which will write the packet to the remote server
|
||||
}
|
||||
@Override
|
||||
public boolean handle(PluginMessage packet) {
|
||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
||||
if (serverConn != null && backendConn != null) {
|
||||
if (PluginMessageUtil.isMCRegister(packet)) {
|
||||
List<String> actuallyRegistered = new ArrayList<>();
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
for (String channel : channels) {
|
||||
if (clientPluginMsgChannels.size() >= MAX_PLUGIN_CHANNELS &&
|
||||
!clientPluginMsgChannels.contains(channel)) {
|
||||
throw new IllegalStateException("Too many plugin message channels registered");
|
||||
}
|
||||
if (clientPluginMsgChannels.add(channel)) {
|
||||
actuallyRegistered.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Chat packet) {
|
||||
String msg = packet.getMessage();
|
||||
if (msg.startsWith("/")) {
|
||||
try {
|
||||
if (!server.getCommandManager().execute(player, msg.substring(1))) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("Exception occurred while running command for {}", player.getProfile().getName(), e);
|
||||
player.sendMessage(TextComponent.of("An error occurred while running this command.", TextColor.RED));
|
||||
return true;
|
||||
if (!actuallyRegistered.isEmpty()) {
|
||||
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(backendConn
|
||||
.getProtocolVersion(), actuallyRegistered);
|
||||
backendConn.write(newRegisterPacket);
|
||||
}
|
||||
} else if (PluginMessageUtil.isMCUnregister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
clientPluginMsgChannels.removeAll(channels);
|
||||
backendConn.write(packet);
|
||||
} else if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
backendConn.write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
} else if (backendConn.isLegacyForge() && !serverConn.hasCompletedJoin()) {
|
||||
if (packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
if (!player.getModInfo().isPresent()) {
|
||||
List<ModInfo.Mod> mods = ForgeUtil.readModList(packet);
|
||||
if (!mods.isEmpty()) {
|
||||
player.setModInfo(new ModInfo("FML", mods));
|
||||
}
|
||||
}
|
||||
|
||||
// Always forward the FML handshake to the remote server.
|
||||
backendConn.write(packet);
|
||||
} else {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
return true;
|
||||
}
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc == null) {
|
||||
return true;
|
||||
}
|
||||
PlayerChatEvent event = new PlayerChatEvent(player, msg);
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
PlayerChatEvent.ChatResult chatResult = pme.getResult();
|
||||
if (chatResult.isAllowed()) {
|
||||
Optional<String> eventMsg = pme.getResult().getMessage();
|
||||
if (eventMsg.isPresent()) {
|
||||
smc.write(Chat.createServerbound(eventMsg.get()));
|
||||
} else {
|
||||
smc.write(packet);
|
||||
}
|
||||
}
|
||||
}, smc.eventLoop());
|
||||
// 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);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(TabCompleteRequest packet) {
|
||||
// Record the request so that the outstanding request can be augmented later.
|
||||
if (!packet.isAssumeCommand() && packet.getCommand().startsWith("/")) {
|
||||
int spacePos = packet.getCommand().indexOf(' ');
|
||||
if (spacePos > 0) {
|
||||
String cmd = packet.getCommand().substring(1, spacePos);
|
||||
if (server.getCommandManager().hasCommand(cmd)) {
|
||||
List<String> suggestions = server.getCommandManager().offerSuggestions(player, packet.getCommand().substring(1));
|
||||
if (!suggestions.isEmpty()) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.getOffers().addAll(suggestions);
|
||||
player.getConnection().write(resp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
outstandingTabComplete = packet;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(PluginMessage packet) {
|
||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
|
||||
if (serverConn != null && backendConn != null) {
|
||||
if (PluginMessageUtil.isMCRegister(packet)) {
|
||||
List<String> actuallyRegistered = new ArrayList<>();
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
for (String channel : channels) {
|
||||
if (clientPluginMsgChannels.size() >= MAX_PLUGIN_CHANNELS &&
|
||||
!clientPluginMsgChannels.contains(channel)) {
|
||||
throw new IllegalStateException("Too many plugin message channels registered");
|
||||
}
|
||||
if (clientPluginMsgChannels.add(channel)) {
|
||||
actuallyRegistered.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
if (!actuallyRegistered.isEmpty()) {
|
||||
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(backendConn
|
||||
.getProtocolVersion(), actuallyRegistered);
|
||||
backendConn.write(newRegisterPacket);
|
||||
}
|
||||
} else if (PluginMessageUtil.isMCUnregister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
clientPluginMsgChannels.removeAll(channels);
|
||||
backendConn.write(packet);
|
||||
} else if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
backendConn.write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
} else if (backendConn.isLegacyForge() && !serverConn.hasCompletedJoin()) {
|
||||
if (packet.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
if (!player.getModInfo().isPresent()) {
|
||||
List<ModInfo.Mod> mods = ForgeUtil.readModList(packet);
|
||||
if (!mods.isEmpty()) {
|
||||
player.setModInfo(new ModInfo("FML", mods));
|
||||
}
|
||||
}
|
||||
|
||||
// Always forward the FML handshake to the remote server.
|
||||
backendConn.write(packet);
|
||||
} else {
|
||||
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
|
||||
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
|
||||
// be sent once the JoinGame packet has been received by the proxy.
|
||||
loginPluginMessages.add(packet);
|
||||
}
|
||||
} else {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
backendConn.write(packet);
|
||||
} else {
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, packet.getData());
|
||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet),
|
||||
backendConn.eventLoop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null && serverConnection.hasCompletedJoin()) {
|
||||
smc.write(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null && serverConnection.hasCompletedJoin()) {
|
||||
smc.write(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
player.teardown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
player.close(TextComponent.builder()
|
||||
.content("An exception occurred in your connection: ")
|
||||
.color(TextColor.RED)
|
||||
.append(TextComponent.of(ThrowableUtils.briefDescription(throwable), TextColor.WHITE))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writabilityChanged() {
|
||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||
if (serverConn != null) {
|
||||
boolean writable = player.getConnection().getChannel().isWritable();
|
||||
MinecraftConnection smc = serverConn.getConnection();
|
||||
if (smc != null) {
|
||||
smc.getChannel().config().setAutoRead(writable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||
if (serverConn == null) {
|
||||
throw new IllegalStateException("No server connection for " + player + ", but JoinGame packet received");
|
||||
}
|
||||
MinecraftConnection serverMc = serverConn.getConnection();
|
||||
if (serverMc == null) {
|
||||
throw new IllegalStateException("Server connection for " + player + " is disconnected, but JoinGame packet received");
|
||||
}
|
||||
|
||||
if (!spawned) {
|
||||
// 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 {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
backendConn.write(packet);
|
||||
} else {
|
||||
// Clear tab list to avoid duplicate entries
|
||||
player.getTabList().clearAll();
|
||||
|
||||
// In order to handle switching to another server, you will need to send three packets:
|
||||
//
|
||||
// - The join game packet from the backend server
|
||||
// - A respawn packet with a different dimension
|
||||
// - Another respawn with the correct dimension
|
||||
//
|
||||
// The two respawns with different dimensions are required, otherwise the client gets confused.
|
||||
//
|
||||
// Most notably, by having the client accept the join game packet, we can work around the need to perform
|
||||
// entity ID rewrites, eliminating potential issues from rewriting packets and improving compatibility with
|
||||
// mods.
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
||||
player.getConnection().delayedWrite(new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType()));
|
||||
player.getConnection().delayedWrite(new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType()));
|
||||
}
|
||||
|
||||
// Remove old boss bars. These don't get cleared when sending JoinGame so we need to track these.
|
||||
for (UUID serverBossBar : serverBossBars) {
|
||||
BossBar deletePacket = new BossBar();
|
||||
deletePacket.setUuid(serverBossBar);
|
||||
deletePacket.setAction(BossBar.REMOVE);
|
||||
player.getConnection().delayedWrite(deletePacket);
|
||||
}
|
||||
serverBossBars.clear();
|
||||
|
||||
// Tell the server about this client's plugin message channels.
|
||||
int serverVersion = serverMc.getProtocolVersion();
|
||||
Collection<String> toRegister = new HashSet<>(clientPluginMsgChannels);
|
||||
if (serverVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
toRegister.addAll(server.getChannelRegistrar().getModernChannelIds());
|
||||
} else {
|
||||
toRegister.addAll(server.getChannelRegistrar().getIdsForLegacyConnections());
|
||||
}
|
||||
if (!toRegister.isEmpty()) {
|
||||
serverMc.delayedWrite(PluginMessageUtil.constructChannelsPacket(serverVersion, toRegister));
|
||||
}
|
||||
|
||||
// If we had plugin messages queued during login/FML handshake, send them now.
|
||||
PluginMessage pm;
|
||||
while ((pm = loginPluginMessages.poll()) != null) {
|
||||
serverMc.delayedWrite(pm);
|
||||
}
|
||||
|
||||
// Clear any title from the previous server.
|
||||
player.getConnection().delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
serverMc.flush();
|
||||
serverConn.setHasCompletedJoin(true);
|
||||
if (serverConn.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);
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
|
||||
packet.getData());
|
||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> backendConn.write(packet),
|
||||
backendConn.eventLoop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<UUID> getServerBossBars() {
|
||||
return serverBossBars;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
public Set<String> getClientPluginMsgChannels() {
|
||||
return clientPluginMsgChannels;
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null && serverConnection.hasCompletedJoin()) {
|
||||
smc.write(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
public void handleTabCompleteResponse(TabCompleteResponse response) {
|
||||
if (outstandingTabComplete != null) {
|
||||
if (!outstandingTabComplete.isAssumeCommand()) {
|
||||
String command = outstandingTabComplete.getCommand().substring(1);
|
||||
try {
|
||||
response.getOffers().addAll(server.getCommandManager().offerSuggestions(player, command));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(),
|
||||
command, e);
|
||||
}
|
||||
outstandingTabComplete = null;
|
||||
}
|
||||
player.getConnection().write(response);
|
||||
MinecraftConnection smc = serverConnection.getConnection();
|
||||
if (smc != null && serverConnection.hasCompletedJoin()) {
|
||||
smc.write(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
player.teardown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
player.close(TextComponent.builder()
|
||||
.content("An exception occurred in your connection: ")
|
||||
.color(TextColor.RED)
|
||||
.append(TextComponent.of(ThrowableUtils.briefDescription(throwable), TextColor.WHITE))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writabilityChanged() {
|
||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||
if (serverConn != null) {
|
||||
boolean writable = player.getConnection().getChannel().isWritable();
|
||||
MinecraftConnection smc = serverConn.getConnection();
|
||||
if (smc != null) {
|
||||
smc.getChannel().config().setAutoRead(writable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
||||
VelocityServerConnection serverConn = player.getConnectedServer();
|
||||
if (serverConn == null) {
|
||||
throw new IllegalStateException(
|
||||
"No server connection for " + player + ", but JoinGame packet received");
|
||||
}
|
||||
MinecraftConnection serverMc = serverConn.getConnection();
|
||||
if (serverMc == null) {
|
||||
throw new IllegalStateException(
|
||||
"Server connection for " + player + " is disconnected, but JoinGame packet received");
|
||||
}
|
||||
|
||||
if (!spawned) {
|
||||
// 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 {
|
||||
// Clear tab list to avoid duplicate entries
|
||||
player.getTabList().clearAll();
|
||||
|
||||
// In order to handle switching to another server, you will need to send three packets:
|
||||
//
|
||||
// - The join game packet from the backend server
|
||||
// - A respawn packet with a different dimension
|
||||
// - Another respawn with the correct dimension
|
||||
//
|
||||
// The two respawns with different dimensions are required, otherwise the client gets confused.
|
||||
//
|
||||
// Most notably, by having the client accept the join game packet, we can work around the need to perform
|
||||
// entity ID rewrites, eliminating potential issues from rewriting packets and improving compatibility with
|
||||
// mods.
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(),
|
||||
joinGame.getLevelType()));
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
|
||||
joinGame.getLevelType()));
|
||||
}
|
||||
|
||||
// Remove old boss bars. These don't get cleared when sending JoinGame so we need to track these.
|
||||
for (UUID serverBossBar : serverBossBars) {
|
||||
BossBar deletePacket = new BossBar();
|
||||
deletePacket.setUuid(serverBossBar);
|
||||
deletePacket.setAction(BossBar.REMOVE);
|
||||
player.getConnection().delayedWrite(deletePacket);
|
||||
}
|
||||
serverBossBars.clear();
|
||||
|
||||
// Tell the server about this client's plugin message channels.
|
||||
int serverVersion = serverMc.getProtocolVersion();
|
||||
Collection<String> toRegister = new HashSet<>(clientPluginMsgChannels);
|
||||
if (serverVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
toRegister.addAll(server.getChannelRegistrar().getModernChannelIds());
|
||||
} else {
|
||||
toRegister.addAll(server.getChannelRegistrar().getIdsForLegacyConnections());
|
||||
}
|
||||
if (!toRegister.isEmpty()) {
|
||||
serverMc.delayedWrite(PluginMessageUtil.constructChannelsPacket(serverVersion, toRegister));
|
||||
}
|
||||
|
||||
// If we had plugin messages queued during login/FML handshake, send them now.
|
||||
PluginMessage pm;
|
||||
while ((pm = loginPluginMessages.poll()) != null) {
|
||||
serverMc.delayedWrite(pm);
|
||||
}
|
||||
|
||||
// Clear any title from the previous server.
|
||||
player.getConnection()
|
||||
.delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
serverMc.flush();
|
||||
serverConn.setHasCompletedJoin(true);
|
||||
if (serverConn.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 Set<String> getClientPluginMsgChannels() {
|
||||
return clientPluginMsgChannels;
|
||||
}
|
||||
|
||||
public void handleTabCompleteResponse(TabCompleteResponse response) {
|
||||
if (outstandingTabComplete != null) {
|
||||
if (!outstandingTabComplete.isAssumeCommand()) {
|
||||
String command = outstandingTabComplete.getCommand().substring(1);
|
||||
try {
|
||||
response.getOffers().addAll(server.getCommandManager().offerSuggestions(player, command));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for {} for command '{}'",
|
||||
player.getUsername(),
|
||||
command, e);
|
||||
}
|
||||
outstandingTabComplete = null;
|
||||
}
|
||||
player.getConnection().write(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,58 +3,59 @@ package com.velocitypowered.proxy.connection.client;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.player.SkinParts;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
import java.util.Locale;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ClientSettingsWrapper implements PlayerSettings {
|
||||
static final PlayerSettings DEFAULT = new ClientSettingsWrapper(new ClientSettings("en_US", (byte) 10, 0, true, (short)127, 1));
|
||||
|
||||
private final ClientSettings settings;
|
||||
private final SkinParts parts;
|
||||
private @Nullable Locale locale;
|
||||
|
||||
ClientSettingsWrapper(ClientSettings settings) {
|
||||
this.settings = settings;
|
||||
this.parts = new SkinParts((byte) settings.getSkinParts());
|
||||
}
|
||||
static final PlayerSettings DEFAULT = new ClientSettingsWrapper(
|
||||
new ClientSettings("en_US", (byte) 10, 0, true, (short) 127, 1));
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
if (locale == null) {
|
||||
locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-"));
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
private final ClientSettings settings;
|
||||
private final SkinParts parts;
|
||||
private @Nullable Locale locale;
|
||||
|
||||
@Override
|
||||
public byte getViewDistance() {
|
||||
return settings.getViewDistance();
|
||||
}
|
||||
ClientSettingsWrapper(ClientSettings settings) {
|
||||
this.settings = settings;
|
||||
this.parts = new SkinParts((byte) settings.getSkinParts());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatMode getChatMode() {
|
||||
int chat = settings.getChatVisibility();
|
||||
if (chat < 0 || chat > 2) {
|
||||
return ChatMode.SHOWN;
|
||||
}
|
||||
return ChatMode.values()[chat];
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
if (locale == null) {
|
||||
locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-"));
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChatColors() {
|
||||
return settings.isChatColors();
|
||||
}
|
||||
@Override
|
||||
public byte getViewDistance() {
|
||||
return settings.getViewDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkinParts getSkinParts() {
|
||||
return parts;
|
||||
@Override
|
||||
public ChatMode getChatMode() {
|
||||
int chat = settings.getChatVisibility();
|
||||
if (chat < 0 || chat > 2) {
|
||||
return ChatMode.SHOWN;
|
||||
}
|
||||
return ChatMode.values()[chat];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChatColors() {
|
||||
return settings.isChatColors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkinParts getSkinParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MainHand getMainHand() {
|
||||
return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MainHand getMainHand() {
|
||||
return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,144 +14,159 @@ 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.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyDisconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyHandshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.LegacyPingResponse;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final VelocityServer server;
|
||||
|
||||
public HandshakeSessionHandler(MinecraftConnection connection, VelocityServer server) {
|
||||
this.connection = Preconditions.checkNotNull(connection, "connection");
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LegacyPing packet) {
|
||||
connection.setProtocolVersion(ProtocolConstants.LEGACY);
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
ServerPing ping = new ServerPing(
|
||||
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);
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
// The disconnect packet is the same as the server response one.
|
||||
LegacyPingResponse response = LegacyPingResponse.from(event.getPing());
|
||||
connection.closeWith(LegacyDisconnect.fromPingResponse(response));
|
||||
}, connection.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LegacyHandshake packet) {
|
||||
connection.closeWith(LegacyDisconnect
|
||||
.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Handshake handshake) {
|
||||
InitialInboundConnection ic = new InitialInboundConnection(connection,
|
||||
cleanVhost(handshake.getServerAddress()), handshake);
|
||||
switch (handshake.getNextStatus()) {
|
||||
case StateRegistry.STATUS_ID:
|
||||
connection.setState(StateRegistry.STATUS);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
case StateRegistry.LOGIN_ID:
|
||||
connection.setState(StateRegistry.LOGIN);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
|
||||
if (!ProtocolConstants.isSupported(handshake.getProtocolVersion())) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
|
||||
return true;
|
||||
}
|
||||
|
||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||
connection.closeWith(
|
||||
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 true;
|
||||
}
|
||||
|
||||
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 and lower,
|
||||
// otherwise IP information will never get forwarded.
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
||||
&& handshake.getProtocolVersion() <
|
||||
ProtocolConstants.MINECRAFT_1_13) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
|
||||
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private String cleanVhost(String hostname) {
|
||||
int zeroIdx = hostname.indexOf('\0');
|
||||
return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
// Unknown packet received. Better to close the connection.
|
||||
connection.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
// Unknown packet received. Better to close the connection.
|
||||
connection.close();
|
||||
}
|
||||
|
||||
private static class LegacyInboundConnection implements InboundConnection {
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final VelocityServer server;
|
||||
|
||||
public HandshakeSessionHandler(MinecraftConnection connection, VelocityServer server) {
|
||||
this.connection = Preconditions.checkNotNull(connection, "connection");
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
private LegacyInboundConnection(MinecraftConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LegacyPing packet) {
|
||||
connection.setProtocolVersion(ProtocolConstants.LEGACY);
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
ServerPing ping = new ServerPing(
|
||||
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);
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
// The disconnect packet is the same as the server response one.
|
||||
LegacyPingResponse response = LegacyPingResponse.from(event.getPing());
|
||||
connection.closeWith(LegacyDisconnect.fromPingResponse(response));
|
||||
}, connection.eventLoop());
|
||||
return true;
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LegacyHandshake packet) {
|
||||
connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED)));
|
||||
return true;
|
||||
public Optional<InetSocketAddress> getVirtualHost() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(Handshake handshake) {
|
||||
InitialInboundConnection ic = new InitialInboundConnection(connection, cleanVhost(handshake.getServerAddress()), handshake);
|
||||
switch (handshake.getNextStatus()) {
|
||||
case StateRegistry.STATUS_ID:
|
||||
connection.setState(StateRegistry.STATUS);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
case StateRegistry.LOGIN_ID:
|
||||
connection.setState(StateRegistry.LOGIN);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
|
||||
if (!ProtocolConstants.isSupported(handshake.getProtocolVersion())) {
|
||||
connection.closeWith(Disconnect.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
|
||||
return true;
|
||||
}
|
||||
|
||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||
connection.closeWith(Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 true;
|
||||
}
|
||||
|
||||
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2 and lower,
|
||||
// otherwise IP information will never get forwarded.
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && handshake.getProtocolVersion() <
|
||||
ProtocolConstants.MINECRAFT_1_13) {
|
||||
connection.closeWith(Disconnect.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
|
||||
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private String cleanVhost(String hostname) {
|
||||
int zeroIdx = hostname.indexOf('\0');
|
||||
return zeroIdx == -1 ? hostname : hostname.substring(0, zeroIdx);
|
||||
public boolean isActive() {
|
||||
return !connection.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
// Unknown packet received. Better to close the connection.
|
||||
connection.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
// Unknown packet received. Better to close the connection.
|
||||
connection.close();
|
||||
}
|
||||
|
||||
private static class LegacyInboundConnection implements InboundConnection {
|
||||
private final MinecraftConnection connection;
|
||||
|
||||
private LegacyInboundConnection(MinecraftConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InetSocketAddress> getVirtualHost() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return !connection.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return 0;
|
||||
}
|
||||
public int getProtocolVersion() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,21 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
|
||||
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
||||
private final ConnectedPlayer player;
|
||||
|
||||
InitialConnectSessionHandler(ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
private final ConnectedPlayer player;
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
// No-op: will never handle packets
|
||||
}
|
||||
InitialConnectSessionHandler(ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
// the user cancelled the login process
|
||||
player.teardown();
|
||||
}
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
// No-op: will never handle packets
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
// the user cancelled the login process
|
||||
player.teardown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,38 +3,39 @@ package com.velocitypowered.proxy.connection.client;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
class InitialInboundConnection implements InboundConnection {
|
||||
private final MinecraftConnection connection;
|
||||
private final String cleanedAddress;
|
||||
private final Handshake handshake;
|
||||
|
||||
InitialInboundConnection(MinecraftConnection connection, String cleanedAddress, Handshake handshake) {
|
||||
this.connection = connection;
|
||||
this.cleanedAddress = cleanedAddress;
|
||||
this.handshake = handshake;
|
||||
}
|
||||
private final MinecraftConnection connection;
|
||||
private final String cleanedAddress;
|
||||
private final Handshake handshake;
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) connection.getRemoteAddress();
|
||||
}
|
||||
InitialInboundConnection(MinecraftConnection connection, String cleanedAddress,
|
||||
Handshake handshake) {
|
||||
this.connection = connection;
|
||||
this.cleanedAddress = cleanedAddress;
|
||||
this.handshake = handshake;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InetSocketAddress> getVirtualHost() {
|
||||
return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort()));
|
||||
}
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return connection.getChannel().isActive();
|
||||
}
|
||||
@Override
|
||||
public Optional<InetSocketAddress> getVirtualHost() {
|
||||
return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return connection.getProtocolVersion();
|
||||
}
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return connection.getChannel().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return connection.getProtocolVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
@@ -17,18 +19,18 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -39,246 +41,266 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
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";
|
||||
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";
|
||||
|
||||
private final VelocityServer server;
|
||||
private final MinecraftConnection inbound;
|
||||
private final InboundConnection apiInbound;
|
||||
private @MonotonicNonNull ServerLogin login;
|
||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
||||
private int playerInfoId;
|
||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||
private final VelocityServer server;
|
||||
private final MinecraftConnection inbound;
|
||||
private final InboundConnection apiInbound;
|
||||
private @MonotonicNonNull ServerLogin login;
|
||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
||||
private int playerInfoId;
|
||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||
|
||||
public LoginSessionHandler(VelocityServer server, MinecraftConnection inbound, InboundConnection apiInbound) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
this.apiInbound = Preconditions.checkNotNull(apiInbound, "apiInbound");
|
||||
public LoginSessionHandler(VelocityServer server, MinecraftConnection inbound,
|
||||
InboundConnection apiInbound) {
|
||||
this.server = Preconditions.checkNotNull(server, "server");
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
this.apiInbound = Preconditions.checkNotNull(apiInbound, "apiInbound");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ServerLogin packet) {
|
||||
this.login = packet;
|
||||
if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
playerInfoId = ThreadLocalRandom.current().nextInt();
|
||||
inbound.write(
|
||||
new LoginPluginMessage(playerInfoId, VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL,
|
||||
Unpooled.EMPTY_BUFFER));
|
||||
} else {
|
||||
beginPreLogin();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPluginResponse packet) {
|
||||
if (packet.getId() == playerInfoId) {
|
||||
if (packet.isSuccess()) {
|
||||
// Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening.
|
||||
inbound.closeWith(Disconnect.create(
|
||||
TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED)
|
||||
));
|
||||
} else {
|
||||
// Proceed with the regular login process.
|
||||
beginPreLogin();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(EncryptionResponse packet) {
|
||||
ServerLogin login = this.login;
|
||||
if (login == null) {
|
||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ServerLogin packet) {
|
||||
this.login = packet;
|
||||
if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
playerInfoId = ThreadLocalRandom.current().nextInt();
|
||||
inbound.write(new LoginPluginMessage(playerInfoId, VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL,
|
||||
Unpooled.EMPTY_BUFFER));
|
||||
} else {
|
||||
beginPreLogin();
|
||||
}
|
||||
return true;
|
||||
if (verify.length == 0) {
|
||||
throw new IllegalStateException("No EncryptionRequest packet sent yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPluginResponse packet) {
|
||||
if (packet.getId() == playerInfoId) {
|
||||
if (packet.isSuccess()) {
|
||||
// Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening.
|
||||
inbound.closeWith(Disconnect.create(
|
||||
TextComponent.of("Running Velocity behind Velocity isn't supported.", TextColor.RED)
|
||||
));
|
||||
try {
|
||||
KeyPair serverKeyPair = server.getServerKeyPair();
|
||||
byte[] decryptedVerifyToken = EncryptionUtils
|
||||
.decryptRsa(serverKeyPair, packet.getVerifyToken());
|
||||
if (!Arrays.equals(verify, decryptedVerifyToken)) {
|
||||
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
|
||||
}
|
||||
|
||||
byte[] decryptedSharedSecret = EncryptionUtils
|
||||
.decryptRsa(serverKeyPair, packet.getSharedSecret());
|
||||
String serverId = EncryptionUtils
|
||||
.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
|
||||
|
||||
String playerIp = ((InetSocketAddress) inbound.getRemoteAddress()).getHostString();
|
||||
server.getHttpClient()
|
||||
.get(new URL(
|
||||
String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
|
||||
.thenAcceptAsync(profileResponse -> {
|
||||
if (inbound.isClosed()) {
|
||||
// The player disconnected after we authenticated them.
|
||||
return;
|
||||
}
|
||||
|
||||
// Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption is
|
||||
// enabled.
|
||||
try {
|
||||
inbound.enableEncryption(decryptedSharedSecret);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (profileResponse.getCode() == 200) {
|
||||
// All went well, initialize the session.
|
||||
initializePlayer(
|
||||
VelocityServer.GSON.fromJson(profileResponse.getBody(), GameProfile.class), true);
|
||||
} else if (profileResponse.getCode() == 204) {
|
||||
// Apparently an offline-mode user logged onto this online-mode proxy. The client has enabled
|
||||
// encryption, so we need to do that as well.
|
||||
logger.warn("An offline-mode client ({} from {}) tried to connect!",
|
||||
login.getUsername(), playerIp);
|
||||
inbound.closeWith(Disconnect.create(TextComponent
|
||||
.of("This server only accepts connections from online-mode clients.")));
|
||||
} else {
|
||||
// Proceed with the regular login process.
|
||||
beginPreLogin();
|
||||
// Something else went wrong
|
||||
logger.error(
|
||||
"Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
|
||||
profileResponse.getCode(), login.getUsername(), playerIp);
|
||||
inbound.close();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(EncryptionResponse packet) {
|
||||
ServerLogin login = this.login;
|
||||
if (login == null) {
|
||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||
}
|
||||
|
||||
if (verify.length == 0) {
|
||||
throw new IllegalStateException("No EncryptionRequest packet sent yet.");
|
||||
}
|
||||
|
||||
try {
|
||||
KeyPair serverKeyPair = server.getServerKeyPair();
|
||||
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, packet.getVerifyToken());
|
||||
if (!Arrays.equals(verify, decryptedVerifyToken)) {
|
||||
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
|
||||
}
|
||||
|
||||
byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, packet.getSharedSecret());
|
||||
String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
|
||||
|
||||
String playerIp = ((InetSocketAddress) inbound.getRemoteAddress()).getHostString();
|
||||
server.getHttpClient()
|
||||
.get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
|
||||
.thenAcceptAsync(profileResponse -> {
|
||||
if (inbound.isClosed()) {
|
||||
// The player disconnected after we authenticated them.
|
||||
return;
|
||||
}
|
||||
|
||||
// Go ahead and enable encryption. Once the client sends EncryptionResponse, encryption is
|
||||
// enabled.
|
||||
try {
|
||||
inbound.enableEncryption(decryptedSharedSecret);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (profileResponse.getCode() == 200) {
|
||||
// All went well, initialize the session.
|
||||
initializePlayer(VelocityServer.GSON.fromJson(profileResponse.getBody(), GameProfile.class), true);
|
||||
} else if (profileResponse.getCode() == 204) {
|
||||
// Apparently an offline-mode user logged onto this online-mode proxy. The client has enabled
|
||||
// encryption, so we need to do that as well.
|
||||
logger.warn("An offline-mode client ({} from {}) tried to connect!", login.getUsername(), playerIp);
|
||||
inbound.closeWith(Disconnect.create(TextComponent.of("This server only accepts connections from online-mode clients.")));
|
||||
} else {
|
||||
// Something else went wrong
|
||||
logger.error("Got an unexpected error code {} whilst contacting Mojang to log in {} ({})",
|
||||
profileResponse.getCode(), login.getUsername(), playerIp);
|
||||
inbound.close();
|
||||
}
|
||||
}, inbound.eventLoop())
|
||||
.exceptionally(exception -> {
|
||||
logger.error("Unable to enable encryption", exception);
|
||||
inbound.close();
|
||||
return null;
|
||||
});
|
||||
} catch (GeneralSecurityException e) {
|
||||
logger.error("Unable to enable encryption", e);
|
||||
}, inbound.eventLoop())
|
||||
.exceptionally(exception -> {
|
||||
logger.error("Unable to enable encryption", exception);
|
||||
inbound.close();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return true;
|
||||
return null;
|
||||
});
|
||||
} catch (GeneralSecurityException e) {
|
||||
logger.error("Unable to enable encryption", e);
|
||||
inbound.close();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void beginPreLogin() {
|
||||
ServerLogin login = this.login;
|
||||
if (login == null) {
|
||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||
}
|
||||
PreLoginEvent event = new PreLoginEvent(apiInbound, login.getUsername());
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
if (inbound.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
PreLoginComponentResult result = event.getResult();
|
||||
Optional<Component> disconnectReason = result.getReason();
|
||||
if (disconnectReason.isPresent()) {
|
||||
// The component is guaranteed to be provided if the connection was denied.
|
||||
inbound.closeWith(Disconnect.create(disconnectReason.get()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
inbound.write(request);
|
||||
} else {
|
||||
initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false);
|
||||
}
|
||||
}, inbound.eventLoop());
|
||||
private void beginPreLogin() {
|
||||
ServerLogin login = this.login;
|
||||
if (login == null) {
|
||||
throw new IllegalStateException("No ServerLogin packet received yet.");
|
||||
}
|
||||
|
||||
private EncryptionRequest generateRequest() {
|
||||
byte[] verify = new byte[4];
|
||||
ThreadLocalRandom.current().nextBytes(verify);
|
||||
|
||||
EncryptionRequest request = new EncryptionRequest();
|
||||
request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded());
|
||||
request.setVerifyToken(verify);
|
||||
return request;
|
||||
}
|
||||
|
||||
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 -> {
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), inbound,
|
||||
apiInbound.getVirtualHost().orElse(null));
|
||||
this.connectedPlayer = player;
|
||||
|
||||
return server.getEventManager().fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))
|
||||
.thenCompose(event -> {
|
||||
// wait for permissions to load, then set the players permission function
|
||||
player.setPermissionFunction(event.createFunction(player));
|
||||
// then call & wait for the login event
|
||||
return server.getEventManager().fire(new LoginEvent(player));
|
||||
})
|
||||
// then complete the connection
|
||||
.thenAcceptAsync(event -> {
|
||||
if (inbound.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Component> reason = event.getResult().getReason();
|
||||
if (reason.isPresent()) {
|
||||
player.disconnect(reason.get());
|
||||
} else {
|
||||
handleProxyLogin(player);
|
||||
}
|
||||
}, inbound.eventLoop());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void handleProxyLogin(ConnectedPlayer player) {
|
||||
Optional<RegisteredServer> toTry = player.getNextServerToTry();
|
||||
if (!toTry.isPresent()) {
|
||||
player.close(TextComponent.of("No available servers", TextColor.RED));
|
||||
PreLoginEvent event = new PreLoginEvent(apiInbound, login.getUsername());
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
if (inbound.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server.registerConnection(player)) {
|
||||
inbound.closeWith(Disconnect.create(TextComponent.of("You are already on this proxy!", TextColor.RED)));
|
||||
}
|
||||
PreLoginComponentResult result = event.getResult();
|
||||
Optional<Component> disconnectReason = result.getReason();
|
||||
if (disconnectReason.isPresent()) {
|
||||
// The component is guaranteed to be provided if the connection was denied.
|
||||
inbound.closeWith(Disconnect.create(disconnectReason.get()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int threshold = server.getConfiguration().getCompressionThreshold();
|
||||
if (threshold >= 0) {
|
||||
inbound.write(new SetCompression(threshold));
|
||||
inbound.setCompressionThreshold(threshold);
|
||||
}
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result
|
||||
.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
inbound.write(request);
|
||||
} else {
|
||||
initializePlayer(GameProfile.forOfflinePlayer(login.getUsername()), false);
|
||||
}
|
||||
}, inbound.eventLoop());
|
||||
}
|
||||
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(player.getUsername());
|
||||
success.setUuid(player.getUniqueId());
|
||||
inbound.write(success);
|
||||
private EncryptionRequest generateRequest() {
|
||||
byte[] verify = new byte[4];
|
||||
ThreadLocalRandom.current().nextBytes(verify);
|
||||
|
||||
inbound.setAssociation(player);
|
||||
inbound.setState(StateRegistry.PLAY);
|
||||
EncryptionRequest request = new EncryptionRequest();
|
||||
request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded());
|
||||
request.setVerifyToken(verify);
|
||||
return request;
|
||||
}
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
||||
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 -> {
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(), inbound,
|
||||
apiInbound.getVirtualHost().orElse(null));
|
||||
this.connectedPlayer = player;
|
||||
|
||||
return server.getEventManager()
|
||||
.fire(new PermissionsSetupEvent(player, ConnectedPlayer.DEFAULT_PERMISSIONS))
|
||||
.thenCompose(event -> {
|
||||
// wait for permissions to load, then set the players permission function
|
||||
player.setPermissionFunction(event.createFunction(player));
|
||||
// then call & wait for the login event
|
||||
return server.getEventManager().fire(new LoginEvent(player));
|
||||
})
|
||||
// then complete the connection
|
||||
.thenAcceptAsync(event -> {
|
||||
if (inbound.isClosed()) {
|
||||
// The player was disconnected
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Component> reason = event.getResult().getReason();
|
||||
if (reason.isPresent()) {
|
||||
player.disconnect(reason.get());
|
||||
} else {
|
||||
handleProxyLogin(player);
|
||||
}
|
||||
}, inbound.eventLoop());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void handleProxyLogin(ConnectedPlayer player) {
|
||||
Optional<RegisteredServer> toTry = player.getNextServerToTry();
|
||||
if (!toTry.isPresent()) {
|
||||
player.close(TextComponent.of("No available servers", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf));
|
||||
if (!server.registerConnection(player)) {
|
||||
inbound.closeWith(
|
||||
Disconnect.create(TextComponent.of("You are already on this proxy!", TextColor.RED)));
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (connectedPlayer != null) {
|
||||
connectedPlayer.teardown();
|
||||
}
|
||||
int threshold = server.getConfiguration().getCompressionThreshold();
|
||||
if (threshold >= 0) {
|
||||
inbound.write(new SetCompression(threshold));
|
||||
inbound.setCompressionThreshold(threshold);
|
||||
}
|
||||
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(player.getUsername());
|
||||
success.setUuid(player.getUniqueId());
|
||||
inbound.write(success);
|
||||
|
||||
inbound.setAssociation(player);
|
||||
inbound.setState(StateRegistry.PLAY);
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
server.getEventManager().fire(new PostLoginEvent(player))
|
||||
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (connectedPlayer != null) {
|
||||
connectedPlayer.teardown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,46 +16,52 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
private final VelocityServer server;
|
||||
private final MinecraftConnection connection;
|
||||
private final InboundConnection inboundWrapper;
|
||||
|
||||
StatusSessionHandler(VelocityServer server, MinecraftConnection connection, InboundConnection inboundWrapper) {
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
this.inboundWrapper = inboundWrapper;
|
||||
}
|
||||
private final VelocityServer server;
|
||||
private final MinecraftConnection connection;
|
||||
private final InboundConnection inboundWrapper;
|
||||
|
||||
@Override
|
||||
public boolean handle(StatusPing packet) {
|
||||
connection.closeWith(packet);
|
||||
return true;
|
||||
}
|
||||
StatusSessionHandler(VelocityServer server, MinecraftConnection connection,
|
||||
InboundConnection inboundWrapper) {
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
this.inboundWrapper = inboundWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(StatusRequest packet) {
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
@Override
|
||||
public boolean handle(StatusPing packet) {
|
||||
connection.closeWith(packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection.getProtocolVersion() :
|
||||
ProtocolConstants.MAXIMUM_GENERIC_VERSION;
|
||||
ServerPing initialPing = new ServerPing(
|
||||
new ServerPing.Version(shownVersion, "Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
|
||||
configuration.getMotdComponent(),
|
||||
configuration.getFavicon().orElse(null),
|
||||
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
|
||||
);
|
||||
@Override
|
||||
public boolean handle(StatusRequest packet) {
|
||||
VelocityConfiguration configuration = server.getConfiguration();
|
||||
|
||||
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> connection.write(new StatusResponse(VelocityServer.GSON.toJson(event.getPing()))),
|
||||
connection.eventLoop());
|
||||
return true;
|
||||
}
|
||||
int shownVersion = ProtocolConstants.isSupported(connection.getProtocolVersion()) ? connection
|
||||
.getProtocolVersion() :
|
||||
ProtocolConstants.MAXIMUM_GENERIC_VERSION;
|
||||
ServerPing initialPing = new ServerPing(
|
||||
new ServerPing.Version(shownVersion,
|
||||
"Velocity " + ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING),
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(),
|
||||
ImmutableList.of()),
|
||||
configuration.getMotdComponent(),
|
||||
configuration.getFavicon().orElse(null),
|
||||
configuration.isAnnounceForge() ? ModInfo.DEFAULT : null
|
||||
);
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
// what even is going on?
|
||||
connection.close();
|
||||
}
|
||||
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(
|
||||
() -> connection.write(new StatusResponse(VelocityServer.GSON.toJson(event.getPing()))),
|
||||
connection.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
// what even is going on?
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,20 @@ package com.velocitypowered.proxy.connection.forge;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
|
||||
public class ForgeConstants {
|
||||
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";
|
||||
private static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
|
||||
|
||||
private ForgeConstants() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
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";
|
||||
private static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[]{-2, 0};
|
||||
|
||||
public static PluginMessage resetPacket() {
|
||||
PluginMessage msg = new PluginMessage();
|
||||
msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||
msg.setData(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone());
|
||||
return msg;
|
||||
}
|
||||
private ForgeConstants() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static PluginMessage resetPacket() {
|
||||
PluginMessage msg = new PluginMessage();
|
||||
msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL);
|
||||
msg.setData(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone());
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,39 +7,40 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ForgeUtil {
|
||||
private ForgeUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static List<ModInfo.Mod> readModList(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(message.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL),
|
||||
"message is not a FML HS plugin message");
|
||||
private ForgeUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData());
|
||||
try {
|
||||
byte discriminator = byteBuf.readByte();
|
||||
public static List<ModInfo.Mod> readModList(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions
|
||||
.checkArgument(message.getChannel().equals(ForgeConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL),
|
||||
"message is not a FML HS plugin message");
|
||||
|
||||
if (discriminator == 2) {
|
||||
ImmutableList.Builder<ModInfo.Mod> mods = ImmutableList.builder();
|
||||
int modCount = ProtocolUtils.readVarInt(byteBuf);
|
||||
ByteBuf byteBuf = Unpooled.wrappedBuffer(message.getData());
|
||||
try {
|
||||
byte discriminator = byteBuf.readByte();
|
||||
|
||||
for (int index = 0; index < modCount; index++) {
|
||||
String id = ProtocolUtils.readString(byteBuf);
|
||||
String version = ProtocolUtils.readString(byteBuf);
|
||||
mods.add(new ModInfo.Mod(id, version));
|
||||
}
|
||||
if (discriminator == 2) {
|
||||
ImmutableList.Builder<ModInfo.Mod> mods = ImmutableList.builder();
|
||||
int modCount = ProtocolUtils.readVarInt(byteBuf);
|
||||
|
||||
return mods.build();
|
||||
}
|
||||
|
||||
return ImmutableList.of();
|
||||
} finally {
|
||||
byteBuf.release();
|
||||
for (int index = 0; index < modCount; index++) {
|
||||
String id = ProtocolUtils.readString(byteBuf);
|
||||
String version = ProtocolUtils.readString(byteBuf);
|
||||
mods.add(new ModInfo.Mod(id, version));
|
||||
}
|
||||
|
||||
return mods.build();
|
||||
}
|
||||
|
||||
return ImmutableList.of();
|
||||
} finally {
|
||||
byteBuf.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,17 @@ import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,48 +2,50 @@ package com.velocitypowered.proxy.connection.util;
|
||||
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import java.util.Optional;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ConnectionRequestResults {
|
||||
public static final ConnectionRequestBuilder.Result SUCCESSFUL = plainResult(ConnectionRequestBuilder.Status.SUCCESS);
|
||||
|
||||
private ConnectionRequestResults() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
public static final ConnectionRequestBuilder.Result SUCCESSFUL = plainResult(
|
||||
ConnectionRequestBuilder.Status.SUCCESS);
|
||||
|
||||
public static ConnectionRequestBuilder.Result plainResult(ConnectionRequestBuilder.Status status) {
|
||||
return new ConnectionRequestBuilder.Result() {
|
||||
@Override
|
||||
public ConnectionRequestBuilder.Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
private ConnectionRequestResults() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Component> getReason() {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
public static ConnectionRequestBuilder.Result plainResult(
|
||||
ConnectionRequestBuilder.Status status) {
|
||||
return new ConnectionRequestBuilder.Result() {
|
||||
@Override
|
||||
public ConnectionRequestBuilder.Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public static ConnectionRequestBuilder.Result forDisconnect(Disconnect disconnect) {
|
||||
Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||
return forDisconnect(deserialized);
|
||||
}
|
||||
@Override
|
||||
public Optional<Component> getReason() {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ConnectionRequestBuilder.Result forDisconnect(Component component) {
|
||||
return new ConnectionRequestBuilder.Result() {
|
||||
@Override
|
||||
public ConnectionRequestBuilder.Status getStatus() {
|
||||
return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED;
|
||||
}
|
||||
public static ConnectionRequestBuilder.Result forDisconnect(Disconnect disconnect) {
|
||||
Component deserialized = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||
return forDisconnect(deserialized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Component> getReason() {
|
||||
return Optional.of(component);
|
||||
}
|
||||
};
|
||||
}
|
||||
public static ConnectionRequestBuilder.Result forDisconnect(Component component) {
|
||||
return new ConnectionRequestBuilder.Result() {
|
||||
@Override
|
||||
public ConnectionRequestBuilder.Status getStatus() {
|
||||
return ConnectionRequestBuilder.Status.SERVER_DISCONNECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Component> getReason() {
|
||||
return Optional.of(component);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 java.util.List;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
@@ -17,75 +18,77 @@ import org.jline.reader.Candidate;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class VelocityConsole extends SimpleTerminalConsole implements CommandSource {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
|
||||
|
||||
private final VelocityServer server;
|
||||
private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE;
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
|
||||
|
||||
public VelocityConsole(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
private final VelocityServer server;
|
||||
private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE;
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component component) {
|
||||
logger.info(ComponentSerializers.LEGACY.serialize(component));
|
||||
}
|
||||
public VelocityConsole(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
|
||||
return this.permissionFunction.getPermissionValue(permission);
|
||||
}
|
||||
@Override
|
||||
public void sendMessage(Component component) {
|
||||
logger.info(ComponentSerializers.LEGACY.serialize(component));
|
||||
}
|
||||
|
||||
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
|
||||
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
|
||||
return this.permissionFunction.getPermissionValue(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LineReader buildReader(LineReaderBuilder builder) {
|
||||
return super.buildReader(builder
|
||||
.appName("Velocity")
|
||||
.completer((reader, parsedLine, list) -> {
|
||||
try {
|
||||
boolean isCommand = parsedLine.line().indexOf(' ') == -1;
|
||||
List<String> offers = this.server.getCommandManager().offerSuggestions(this, parsedLine.line());
|
||||
for (String offer : offers) {
|
||||
if (isCommand) {
|
||||
list.add(new Candidate(offer.substring(1)));
|
||||
} else {
|
||||
list.add(new Candidate(offer));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while trying to perform tab completion.", e);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
public void setupPermissions() {
|
||||
PermissionsSetupEvent event = new PermissionsSetupEvent(this,
|
||||
s -> PermissionFunction.ALWAYS_TRUE);
|
||||
this.server.getEventManager().fire(event)
|
||||
.join(); // this is called on startup, we can safely #join
|
||||
this.permissionFunction = event.createFunction(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRunning() {
|
||||
return !this.server.isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runCommand(String command) {
|
||||
try {
|
||||
if (!this.server.getCommandManager().execute(this, command)) {
|
||||
sendMessage(TextComponent.of("Command not found.", TextColor.RED));
|
||||
@Override
|
||||
protected LineReader buildReader(LineReaderBuilder builder) {
|
||||
return super.buildReader(builder
|
||||
.appName("Velocity")
|
||||
.completer((reader, parsedLine, list) -> {
|
||||
try {
|
||||
boolean isCommand = parsedLine.line().indexOf(' ') == -1;
|
||||
List<String> offers = this.server.getCommandManager()
|
||||
.offerSuggestions(this, parsedLine.line());
|
||||
for (String offer : offers) {
|
||||
if (isCommand) {
|
||||
list.add(new Candidate(offer.substring(1)));
|
||||
} else {
|
||||
list.add(new Candidate(offer));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while running this command.", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while trying to perform tab completion.", e);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdown() {
|
||||
this.server.shutdown();
|
||||
@Override
|
||||
protected boolean isRunning() {
|
||||
return !this.server.isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runCommand(String command) {
|
||||
try {
|
||||
if (!this.server.getCommandManager().execute(this, command)) {
|
||||
sendMessage(TextComponent.of("Command not found.", TextColor.RED));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("An error occurred while running this command.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdown() {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,95 +10,99 @@ import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.WriteBufferWaterMark;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
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<>();
|
||||
private final TransportType transportType;
|
||||
private final EventLoopGroup bossGroup;
|
||||
private final EventLoopGroup workerGroup;
|
||||
private final VelocityServer server;
|
||||
public final ServerChannelInitializerHolder serverChannelInitializer;
|
||||
|
||||
public ConnectionManager(VelocityServer server) {
|
||||
this.server = server;
|
||||
this.transportType = TransportType.bestType();
|
||||
this.bossGroup = this.transportType.createEventLoopGroup(TransportType.Type.BOSS);
|
||||
this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER);
|
||||
this.serverChannelInitializer = new ServerChannelInitializerHolder(new ServerChannelInitializer(this.server));
|
||||
}
|
||||
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<>();
|
||||
private final TransportType transportType;
|
||||
private final EventLoopGroup bossGroup;
|
||||
private final EventLoopGroup workerGroup;
|
||||
private final VelocityServer server;
|
||||
public final ServerChannelInitializerHolder serverChannelInitializer;
|
||||
|
||||
public void logChannelInformation() {
|
||||
LOGGER.info("Connections will use {} channels, {} compression, {} ciphers", this.transportType, Natives.compressor.getLoadedVariant(), Natives.cipher.getLoadedVariant());
|
||||
}
|
||||
public ConnectionManager(VelocityServer server) {
|
||||
this.server = server;
|
||||
this.transportType = TransportType.bestType();
|
||||
this.bossGroup = this.transportType.createEventLoopGroup(TransportType.Type.BOSS);
|
||||
this.workerGroup = this.transportType.createEventLoopGroup(TransportType.Type.WORKER);
|
||||
this.serverChannelInitializer = new ServerChannelInitializerHolder(
|
||||
new ServerChannelInitializer(this.server));
|
||||
}
|
||||
|
||||
public void bind(final InetSocketAddress address) {
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(this.transportType.serverSocketChannelClass)
|
||||
.group(this.bossGroup, this.workerGroup)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.childHandler(this.serverChannelInitializer.get())
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.IP_TOS, 0x18)
|
||||
.localAddress(address);
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.add(channel);
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", address, future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
public void logChannelInformation() {
|
||||
LOGGER.info("Connections will use {} channels, {} compression, {} ciphers", this.transportType,
|
||||
Natives.compressor.getLoadedVariant(), Natives.cipher.getLoadedVariant());
|
||||
}
|
||||
|
||||
public void queryBind(final String hostname, final int port) {
|
||||
final Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(this.transportType.datagramChannelClass)
|
||||
.group(this.workerGroup)
|
||||
.handler(new GS4QueryHandler(this.server))
|
||||
.localAddress(hostname, port);
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.add(channel);
|
||||
LOGGER.info("Listening for GS4 query on {}", channel.localAddress());
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
public void bind(final InetSocketAddress address) {
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(this.transportType.serverSocketChannelClass)
|
||||
.group(this.bossGroup, this.workerGroup)
|
||||
.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, SERVER_WRITE_MARK)
|
||||
.childHandler(this.serverChannelInitializer.get())
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.IP_TOS, 0x18)
|
||||
.localAddress(address);
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.add(channel);
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", address, future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Bootstrap createWorker() {
|
||||
return new Bootstrap()
|
||||
.channel(this.transportType.socketChannelClass)
|
||||
.group(this.workerGroup)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.server.getConfiguration().getConnectTimeout());
|
||||
}
|
||||
public void queryBind(final String hostname, final int port) {
|
||||
final Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(this.transportType.datagramChannelClass)
|
||||
.group(this.workerGroup)
|
||||
.handler(new GS4QueryHandler(this.server))
|
||||
.localAddress(hostname, port);
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.add(channel);
|
||||
LOGGER.info("Listening for GS4 query on {}", channel.localAddress());
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
for (final Channel endpoint : this.endpoints) {
|
||||
try {
|
||||
LOGGER.info("Closing endpoint {}", endpoint.localAddress());
|
||||
endpoint.close().sync();
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
public Bootstrap createWorker() {
|
||||
return new Bootstrap()
|
||||
.channel(this.transportType.socketChannelClass)
|
||||
.group(this.workerGroup)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||
this.server.getConfiguration().getConnectTimeout());
|
||||
}
|
||||
|
||||
public ServerChannelInitializerHolder getServerChannelInitializer() {
|
||||
return this.serverChannelInitializer;
|
||||
public void shutdown() {
|
||||
for (final Channel endpoint : this.endpoints) {
|
||||
try {
|
||||
LOGGER.info("Closing endpoint {}", endpoint.localAddress());
|
||||
endpoint.close().sync();
|
||||
} catch (final InterruptedException e) {
|
||||
LOGGER.info("Interrupted whilst closing endpoint", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ServerChannelInitializerHolder getServerChannelInitializer() {
|
||||
return this.serverChannelInitializer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package com.velocitypowered.proxy.network;
|
||||
|
||||
public class Connections {
|
||||
public static final String CIPHER_DECODER = "cipher-decoder";
|
||||
public static final String CIPHER_ENCODER = "cipher-encoder";
|
||||
public static final String COMPRESSION_DECODER = "compression-decoder";
|
||||
public static final String COMPRESSION_ENCODER = "compression-encoder";
|
||||
public static final String FRAME_DECODER = "frame-decoder";
|
||||
public static final String FRAME_ENCODER = "frame-encoder";
|
||||
public static final String HANDLER = "handler";
|
||||
public static final String LEGACY_PING_DECODER = "legacy-ping-decoder";
|
||||
public static final String LEGACY_PING_ENCODER = "legacy-ping-encoder";
|
||||
public static final String MINECRAFT_DECODER = "minecraft-decoder";
|
||||
public static final String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||
public static final String READ_TIMEOUT = "read-timeout";
|
||||
|
||||
private Connections() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
public static final String CIPHER_DECODER = "cipher-decoder";
|
||||
public static final String CIPHER_ENCODER = "cipher-encoder";
|
||||
public static final String COMPRESSION_DECODER = "compression-decoder";
|
||||
public static final String COMPRESSION_ENCODER = "compression-encoder";
|
||||
public static final String FRAME_DECODER = "frame-decoder";
|
||||
public static final String FRAME_ENCODER = "frame-encoder";
|
||||
public static final String HANDLER = "handler";
|
||||
public static final String LEGACY_PING_DECODER = "legacy-ping-decoder";
|
||||
public static final String LEGACY_PING_ENCODER = "legacy-ping-encoder";
|
||||
public static final String MINECRAFT_DECODER = "minecraft-decoder";
|
||||
public static final String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||
public static final String READ_TIMEOUT = "read-timeout";
|
||||
|
||||
private Connections() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
package com.velocitypowered.proxy.network;
|
||||
|
||||
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.LEGACY_PING_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.LEGACY_PING_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||
@@ -15,43 +23,37 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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.LEGACY_PING_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.LEGACY_PING_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class ServerChannelInitializer extends ChannelInitializer<Channel> {
|
||||
private final VelocityServer server;
|
||||
|
||||
public ServerChannelInitializer(final VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.SERVERBOUND))
|
||||
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.CLIENTBOUND));
|
||||
|
||||
final MinecraftConnection connection = new MinecraftConnection(ch, this.server);
|
||||
connection.setState(StateRegistry.HANDSHAKE);
|
||||
connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server));
|
||||
ch.pipeline().addLast(Connections.HANDLER, connection);
|
||||
|
||||
if (ServerChannelInitializer.this.server.getConfiguration().isProxyProtocol()) {
|
||||
ch.pipeline().addFirst(new HAProxyMessageDecoder());
|
||||
}
|
||||
private final VelocityServer server;
|
||||
|
||||
public ServerChannelInitializer(final VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT,
|
||||
new ReadTimeoutHandler(this.server.getConfiguration().getReadTimeout(),
|
||||
TimeUnit.SECONDS))
|
||||
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.SERVERBOUND))
|
||||
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.CLIENTBOUND));
|
||||
|
||||
final MinecraftConnection connection = new MinecraftConnection(ch, this.server);
|
||||
connection.setState(StateRegistry.HANDSHAKE);
|
||||
connection.setSessionHandler(new HandshakeSessionHandler(connection, this.server));
|
||||
ch.pipeline().addLast(Connections.HANDLER, connection);
|
||||
|
||||
if (ServerChannelInitializer.this.server.getConfiguration().isProxyProtocol()) {
|
||||
ch.pipeline().addFirst(new HAProxyMessageDecoder());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,28 @@ package com.velocitypowered.proxy.network;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import java.util.function.Supplier;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ServerChannelInitializerHolder implements Supplier<ChannelInitializer<Channel>> {
|
||||
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
|
||||
private ChannelInitializer<Channel> initializer;
|
||||
|
||||
public ServerChannelInitializerHolder(final ChannelInitializer<Channel> initializer) {
|
||||
this.initializer = initializer;
|
||||
}
|
||||
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
|
||||
private ChannelInitializer<Channel> initializer;
|
||||
|
||||
@Override
|
||||
public ChannelInitializer<Channel> get() {
|
||||
return this.initializer;
|
||||
}
|
||||
public ServerChannelInitializerHolder(final ChannelInitializer<Channel> initializer) {
|
||||
this.initializer = initializer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void set(final ChannelInitializer<Channel> initializer) {
|
||||
LOGGER.warn("The server channel initializer has been replaced by {}", Thread.currentThread().getStackTrace()[2]);
|
||||
this.initializer = initializer;
|
||||
}
|
||||
@Override
|
||||
public ChannelInitializer<Channel> get() {
|
||||
return this.initializer;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void set(final ChannelInitializer<Channel> initializer) {
|
||||
LOGGER.warn("The server channel initializer has been replaced by {}",
|
||||
Thread.currentThread().getStackTrace()[2]);
|
||||
this.initializer = initializer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,68 +19,76 @@ import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
enum TransportType {
|
||||
NIO("NIO", NioServerSocketChannel.class, NioSocketChannel.class, NioDatagramChannel.class, (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class, EpollDatagramChannel.class, (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class, KQueueDatagramChannel.class, (name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
|
||||
NIO("NIO", NioServerSocketChannel.class, NioSocketChannel.class, NioDatagramChannel.class,
|
||||
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
|
||||
EpollDatagramChannel.class,
|
||||
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
|
||||
KQueueDatagramChannel.class,
|
||||
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
|
||||
|
||||
final String name;
|
||||
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||
final Class<? extends SocketChannel> socketChannelClass;
|
||||
final Class<? extends DatagramChannel> datagramChannelClass;
|
||||
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory;
|
||||
final String name;
|
||||
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||
final Class<? extends SocketChannel> socketChannelClass;
|
||||
final Class<? extends DatagramChannel> datagramChannelClass;
|
||||
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory;
|
||||
|
||||
TransportType(final String name, final Class<? extends ServerSocketChannel> serverSocketChannelClass, final Class<? extends SocketChannel> socketChannelClass, final Class<? extends DatagramChannel> datagramChannelClass, final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory) {
|
||||
this.name = name;
|
||||
this.serverSocketChannelClass = serverSocketChannelClass;
|
||||
this.socketChannelClass = socketChannelClass;
|
||||
this.datagramChannelClass = datagramChannelClass;
|
||||
this.eventLoopGroupFactory = eventLoopGroupFactory;
|
||||
TransportType(final String name,
|
||||
final Class<? extends ServerSocketChannel> serverSocketChannelClass,
|
||||
final Class<? extends SocketChannel> socketChannelClass,
|
||||
final Class<? extends DatagramChannel> datagramChannelClass,
|
||||
final BiFunction<String, Type, EventLoopGroup> eventLoopGroupFactory) {
|
||||
this.name = name;
|
||||
this.serverSocketChannelClass = serverSocketChannelClass;
|
||||
this.socketChannelClass = socketChannelClass;
|
||||
this.datagramChannelClass = datagramChannelClass;
|
||||
this.eventLoopGroupFactory = eventLoopGroupFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public EventLoopGroup createEventLoopGroup(final Type type) {
|
||||
return this.eventLoopGroupFactory.apply(this.name, type);
|
||||
}
|
||||
|
||||
private static ThreadFactory createThreadFactory(final String name, final Type type) {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat("Netty " + name + ' ' + type.toString() + " #%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static TransportType bestType() {
|
||||
if (Epoll.isAvailable()) {
|
||||
return EPOLL;
|
||||
} else if (KQueue.isAvailable()) {
|
||||
return KQUEUE;
|
||||
} else {
|
||||
return NIO;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
BOSS("Boss"),
|
||||
WORKER("Worker");
|
||||
|
||||
private final String name;
|
||||
|
||||
Type(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public EventLoopGroup createEventLoopGroup(final Type type) {
|
||||
return this.eventLoopGroupFactory.apply(this.name, type);
|
||||
}
|
||||
|
||||
private static ThreadFactory createThreadFactory(final String name, final Type type) {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat("Netty " + name + ' ' + type.toString() + " #%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static TransportType bestType() {
|
||||
if (Epoll.isAvailable()) {
|
||||
return EPOLL;
|
||||
} else if (KQueue.isAvailable()) {
|
||||
return KQUEUE;
|
||||
} else {
|
||||
return NIO;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
BOSS("Boss"),
|
||||
WORKER("Worker");
|
||||
|
||||
private final String name;
|
||||
|
||||
Type(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,82 +4,91 @@ import com.google.common.base.VerifyException;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.pool.*;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.channel.pool.AbstractChannelPoolMap;
|
||||
import io.netty.channel.pool.ChannelPoolHandler;
|
||||
import io.netty.channel.pool.ChannelPoolMap;
|
||||
import io.netty.channel.pool.FixedChannelPool;
|
||||
import io.netty.channel.pool.SimpleChannelPool;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
public class NettyHttpClient {
|
||||
private final ChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap;
|
||||
|
||||
public NettyHttpClient(VelocityServer server) {
|
||||
Bootstrap bootstrap = server.initializeGenericBootstrap();
|
||||
this.poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
|
||||
@Override
|
||||
protected SimpleChannelPool newPool(InetSocketAddress key) {
|
||||
return new FixedChannelPool(bootstrap.remoteAddress(key), new ChannelPoolHandler() {
|
||||
@Override
|
||||
public void channelReleased(Channel channel) throws Exception {
|
||||
channel.pipeline().remove("collector");
|
||||
}
|
||||
private final ChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap;
|
||||
|
||||
@Override
|
||||
public void channelAcquired(Channel channel) throws Exception {
|
||||
// We don't do anything special when acquiring channels. The channel handler cleans up after
|
||||
// each connection is used.
|
||||
}
|
||||
public NettyHttpClient(VelocityServer server) {
|
||||
Bootstrap bootstrap = server.initializeGenericBootstrap();
|
||||
this.poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
|
||||
@Override
|
||||
protected SimpleChannelPool newPool(InetSocketAddress key) {
|
||||
return new FixedChannelPool(bootstrap.remoteAddress(key), new ChannelPoolHandler() {
|
||||
@Override
|
||||
public void channelReleased(Channel channel) throws Exception {
|
||||
channel.pipeline().remove("collector");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelCreated(Channel channel) throws Exception {
|
||||
if (key.getPort() == 443) {
|
||||
SslContext context = SslContextBuilder.forClient().build();
|
||||
SSLEngine engine = context.newEngine(channel.alloc());
|
||||
channel.pipeline().addLast("ssl", new SslHandler(engine));
|
||||
}
|
||||
channel.pipeline().addLast("http", new HttpClientCodec());
|
||||
}
|
||||
}, 8);
|
||||
@Override
|
||||
public void channelAcquired(Channel channel) throws Exception {
|
||||
// We don't do anything special when acquiring channels. The channel handler cleans up after
|
||||
// each connection is used.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelCreated(Channel channel) throws Exception {
|
||||
if (key.getPort() == 443) {
|
||||
SslContext context = SslContextBuilder.forClient().build();
|
||||
SSLEngine engine = context.newEngine(channel.alloc());
|
||||
channel.pipeline().addLast("ssl", new SslHandler(engine));
|
||||
}
|
||||
};
|
||||
channel.pipeline().addLast("http", new HttpClientCodec());
|
||||
}
|
||||
}, 8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public CompletableFuture<SimpleHttpResponse> get(URL url) {
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
boolean ssl = url.getProtocol().equals("https");
|
||||
if (port == -1) {
|
||||
port = ssl ? 443 : 80;
|
||||
}
|
||||
|
||||
public CompletableFuture<SimpleHttpResponse> get(URL url) {
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
boolean ssl = url.getProtocol().equals("https");
|
||||
if (port == -1) {
|
||||
port = ssl ? 443 : 80;
|
||||
}
|
||||
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
|
||||
InetSocketAddress address = new InetSocketAddress(host, port);
|
||||
poolMap.get(address)
|
||||
.acquire()
|
||||
.addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
Channel channel = (Channel) future.getNow();
|
||||
if (channel == null) {
|
||||
throw new VerifyException("Null channel retrieved from pool!");
|
||||
}
|
||||
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
|
||||
|
||||
CompletableFuture<SimpleHttpResponse> reply = new CompletableFuture<>();
|
||||
InetSocketAddress address = new InetSocketAddress(host, port);
|
||||
poolMap.get(address)
|
||||
.acquire()
|
||||
.addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
Channel channel = (Channel) future.getNow();
|
||||
if (channel == null) {
|
||||
throw new VerifyException("Null channel retrieved from pool!");
|
||||
}
|
||||
channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
|
||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
|
||||
HttpMethod.GET, url.getPath() + "?" + url.getQuery());
|
||||
request.headers().add(HttpHeaderNames.HOST, url.getHost());
|
||||
request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity");
|
||||
channel.writeAndFlush(request);
|
||||
|
||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath() + "?" + url.getQuery());
|
||||
request.headers().add(HttpHeaderNames.HOST, url.getHost());
|
||||
request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity");
|
||||
channel.writeAndFlush(request);
|
||||
|
||||
// Make sure to release this connection
|
||||
reply.whenComplete((resp, err) -> poolMap.get(address).release(channel));
|
||||
} else {
|
||||
reply.completeExceptionally(future.cause());
|
||||
}
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
// Make sure to release this connection
|
||||
reply.whenComplete((resp, err) -> poolMap.get(address).release(channel));
|
||||
} else {
|
||||
reply.completeExceptionally(future.cause());
|
||||
}
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
package com.velocitypowered.proxy.network.http;
|
||||
|
||||
public class SimpleHttpResponse {
|
||||
private final int code;
|
||||
private final String body;
|
||||
|
||||
SimpleHttpResponse(int code, String body) {
|
||||
this.code = code;
|
||||
this.body = body;
|
||||
}
|
||||
private final int code;
|
||||
private final String body;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
SimpleHttpResponse(int code, String body) {
|
||||
this.code = code;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleHttpResponse{" +
|
||||
"code=" + code +
|
||||
", body='" + body + '\'' +
|
||||
'}';
|
||||
}
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleHttpResponse{" +
|
||||
"code=" + code +
|
||||
", body='" + body + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,50 +2,54 @@ package com.velocitypowered.proxy.network.http;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.handler.codec.http.HttpContent;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
|
||||
private final StringBuilder buffer = new StringBuilder(1024);
|
||||
private final CompletableFuture<SimpleHttpResponse> reply;
|
||||
private int httpCode;
|
||||
private boolean canKeepAlive;
|
||||
|
||||
SimpleHttpResponseCollector(CompletableFuture<SimpleHttpResponse> reply) {
|
||||
this.reply = reply;
|
||||
}
|
||||
private final StringBuilder buffer = new StringBuilder(1024);
|
||||
private final CompletableFuture<SimpleHttpResponse> reply;
|
||||
private int httpCode;
|
||||
private boolean canKeepAlive;
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
try {
|
||||
if (msg instanceof HttpResponse) {
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
HttpResponseStatus status = response.status();
|
||||
this.httpCode = status.code();
|
||||
this.canKeepAlive = HttpUtil.isKeepAlive(response);
|
||||
}
|
||||
SimpleHttpResponseCollector(CompletableFuture<SimpleHttpResponse> reply) {
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
if (msg instanceof HttpContent) {
|
||||
buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8));
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
try {
|
||||
if (msg instanceof HttpResponse) {
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
HttpResponseStatus status = response.status();
|
||||
this.httpCode = status.code();
|
||||
this.canKeepAlive = HttpUtil.isKeepAlive(response);
|
||||
}
|
||||
|
||||
if (msg instanceof LastHttpContent) {
|
||||
if (!canKeepAlive) {
|
||||
ctx.close();
|
||||
}
|
||||
reply.complete(new SimpleHttpResponse(httpCode, buffer.toString()));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(msg);
|
||||
if (msg instanceof HttpContent) {
|
||||
buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8));
|
||||
|
||||
if (msg instanceof LastHttpContent) {
|
||||
if (!canKeepAlive) {
|
||||
ctx.close();
|
||||
}
|
||||
reply.complete(new SimpleHttpResponse(httpCode, buffer.toString()));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ctx.close();
|
||||
reply.completeExceptionally(cause);
|
||||
}
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ctx.close();
|
||||
reply.completeExceptionally(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,52 +8,54 @@ import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class PluginClassLoader extends URLClassLoader {
|
||||
private static final Set<PluginClassLoader> loaders = new CopyOnWriteArraySet<>();
|
||||
|
||||
static {
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
private static final Set<PluginClassLoader> loaders = new CopyOnWriteArraySet<>();
|
||||
|
||||
static {
|
||||
ClassLoader.registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public PluginClassLoader(URL[] urls) {
|
||||
super(urls);
|
||||
}
|
||||
|
||||
public void addToClassloaders() {
|
||||
loaders.add(this);
|
||||
}
|
||||
|
||||
void addPath(Path path) {
|
||||
try {
|
||||
addURL(path.toUri().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
return loadClass0(name, resolve, true);
|
||||
}
|
||||
|
||||
private Class<?> loadClass0(String name, boolean resolve, boolean checkOther)
|
||||
throws ClassNotFoundException {
|
||||
try {
|
||||
return super.loadClass(name, resolve);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Ignored: we'll try others
|
||||
}
|
||||
|
||||
public PluginClassLoader(URL[] urls) {
|
||||
super(urls);
|
||||
}
|
||||
|
||||
public void addToClassloaders() {
|
||||
loaders.add(this);
|
||||
}
|
||||
|
||||
void addPath(Path path) {
|
||||
try {
|
||||
addURL(path.toUri().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new AssertionError(e);
|
||||
if (checkOther) {
|
||||
for (PluginClassLoader loader : loaders) {
|
||||
if (loader != this) {
|
||||
try {
|
||||
return loader.loadClass0(name, resolve, false);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// We're trying others, safe to ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
return loadClass0(name, resolve, true);
|
||||
}
|
||||
|
||||
private Class<?> loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException {
|
||||
try {
|
||||
return super.loadClass(name, resolve);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// Ignored: we'll try others
|
||||
}
|
||||
|
||||
if (checkOther) {
|
||||
for (PluginClassLoader loader : loaders) {
|
||||
if (loader != this) {
|
||||
try {
|
||||
return loader.loadClass0(name, resolve, false);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// We're trying others, safe to ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,16 @@ import com.velocitypowered.api.event.EventManager;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.kyori.event.EventSubscriber;
|
||||
import net.kyori.event.PostResult;
|
||||
import net.kyori.event.SimpleEventBus;
|
||||
@@ -21,180 +31,183 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VelocityEventManager implements EventManager {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
||||
|
||||
private final ListMultimap<Object, Object> registeredListenersByPlugin = Multimaps
|
||||
.synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new));
|
||||
private final ListMultimap<Object, EventHandler<?>> registeredHandlersByPlugin = Multimaps
|
||||
.synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new));
|
||||
private final SimpleEventBus<Object> bus;
|
||||
private final MethodSubscriptionAdapter<Object> methodAdapter;
|
||||
private final ExecutorService service;
|
||||
private final PluginManager pluginManager;
|
||||
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
||||
|
||||
public VelocityEventManager(PluginManager pluginManager) {
|
||||
PluginClassLoader cl = new PluginClassLoader(new URL[0]);
|
||||
cl.addToClassloaders();
|
||||
this.bus = new SimpleEventBus<>(Object.class);
|
||||
this.methodAdapter = new SimpleMethodSubscriptionAdapter<>(bus, new ASMEventExecutorFactory<>(cl),
|
||||
new VelocityMethodScanner());
|
||||
this.pluginManager = pluginManager;
|
||||
this.service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder()
|
||||
.setNameFormat("Velocity Event Executor - #%d").setDaemon(true).build());
|
||||
private final ListMultimap<Object, Object> registeredListenersByPlugin = Multimaps
|
||||
.synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new));
|
||||
private final ListMultimap<Object, EventHandler<?>> registeredHandlersByPlugin = Multimaps
|
||||
.synchronizedListMultimap(Multimaps.newListMultimap(new IdentityHashMap<>(), ArrayList::new));
|
||||
private final SimpleEventBus<Object> bus;
|
||||
private final MethodSubscriptionAdapter<Object> methodAdapter;
|
||||
private final ExecutorService service;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
public VelocityEventManager(PluginManager pluginManager) {
|
||||
PluginClassLoader cl = new PluginClassLoader(new URL[0]);
|
||||
cl.addToClassloaders();
|
||||
this.bus = new SimpleEventBus<>(Object.class);
|
||||
this.methodAdapter = new SimpleMethodSubscriptionAdapter<>(bus,
|
||||
new ASMEventExecutorFactory<>(cl),
|
||||
new VelocityMethodScanner());
|
||||
this.pluginManager = pluginManager;
|
||||
this.service = Executors
|
||||
.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder()
|
||||
.setNameFormat("Velocity Event Executor - #%d").setDaemon(true).build());
|
||||
}
|
||||
|
||||
private void ensurePlugin(Object plugin) {
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(),
|
||||
"Specified plugin is not loaded");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Object plugin, Object listener) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(listener, "listener");
|
||||
if (plugin == listener && registeredListenersByPlugin.containsEntry(plugin, plugin)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Trying to register the plugin main instance. Velocity already takes care of this for you.");
|
||||
}
|
||||
registeredListenersByPlugin.put(plugin, listener);
|
||||
methodAdapter.register(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("type.argument.type.incompatible")
|
||||
public <E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder,
|
||||
EventHandler<E> handler) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(eventClass, "eventClass");
|
||||
Preconditions.checkNotNull(postOrder, "postOrder");
|
||||
Preconditions.checkNotNull(handler, "listener");
|
||||
bus.register(eventClass, new KyoriToVelocityHandler<>(handler, postOrder));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E> CompletableFuture<E> fire(E event) {
|
||||
if (event == null) {
|
||||
throw new NullPointerException("event");
|
||||
}
|
||||
if (!bus.hasSubscribers(event.getClass())) {
|
||||
// Optimization: nobody's listening.
|
||||
return CompletableFuture.completedFuture(event);
|
||||
}
|
||||
|
||||
private void ensurePlugin(Object plugin) {
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "Specified plugin is not loaded");
|
||||
Runnable runEvent = () -> {
|
||||
PostResult result = bus.post(event);
|
||||
if (!result.exceptions().isEmpty()) {
|
||||
logger.error("Some errors occurred whilst posting event {}.", event);
|
||||
int i = 0;
|
||||
for (Throwable exception : result.exceptions().values()) {
|
||||
logger.error("#{}: \n", ++i, exception);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CompletableFuture<E> eventFuture = new CompletableFuture<>();
|
||||
service.execute(() -> {
|
||||
runEvent.run();
|
||||
eventFuture.complete(event);
|
||||
});
|
||||
return eventFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners(Object plugin) {
|
||||
ensurePlugin(plugin);
|
||||
Collection<Object> listeners = registeredListenersByPlugin.removeAll(plugin);
|
||||
listeners.forEach(methodAdapter::unregister);
|
||||
Collection<EventHandler<?>> handlers = registeredHandlersByPlugin.removeAll(plugin);
|
||||
handlers
|
||||
.forEach(handler -> bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener(Object plugin, Object listener) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(listener, "listener");
|
||||
registeredListenersByPlugin.remove(plugin, listener);
|
||||
methodAdapter.unregister(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E> void unregister(Object plugin, EventHandler<E> handler) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(handler, "listener");
|
||||
registeredHandlersByPlugin.remove(plugin, handler);
|
||||
bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST));
|
||||
}
|
||||
|
||||
public boolean shutdown() throws InterruptedException {
|
||||
service.shutdown();
|
||||
return service.awaitTermination(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static class VelocityMethodScanner implements MethodScanner<Object> {
|
||||
|
||||
@Override
|
||||
public boolean shouldRegister(@NonNull Object listener, @NonNull Method method) {
|
||||
return method.isAnnotationPresent(Subscribe.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(Object plugin, Object listener) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(listener, "listener");
|
||||
if (plugin == listener && registeredListenersByPlugin.containsEntry(plugin, plugin)) {
|
||||
throw new IllegalArgumentException("Trying to register the plugin main instance. Velocity already takes care of this for you.");
|
||||
}
|
||||
registeredListenersByPlugin.put(plugin, listener);
|
||||
methodAdapter.register(listener);
|
||||
public int postOrder(@NonNull Object listener, @NonNull Method method) {
|
||||
Subscribe annotation = method.getAnnotation(Subscribe.class);
|
||||
if (annotation == null) {
|
||||
throw new IllegalStateException(
|
||||
"Trying to determine post order for listener without @Subscribe annotation");
|
||||
}
|
||||
return annotation.order().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("type.argument.type.incompatible")
|
||||
public <E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder, EventHandler<E> handler) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(eventClass, "eventClass");
|
||||
Preconditions.checkNotNull(postOrder, "postOrder");
|
||||
Preconditions.checkNotNull(handler, "listener");
|
||||
bus.register(eventClass, new KyoriToVelocityHandler<>(handler, postOrder));
|
||||
public boolean consumeCancelledEvents(@NonNull Object listener, @NonNull Method method) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class KyoriToVelocityHandler<E> implements EventSubscriber<E> {
|
||||
|
||||
private final EventHandler<E> handler;
|
||||
private final int postOrder;
|
||||
|
||||
private KyoriToVelocityHandler(EventHandler<E> handler, PostOrder postOrder) {
|
||||
this.handler = handler;
|
||||
this.postOrder = postOrder.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E> CompletableFuture<E> fire(E event) {
|
||||
if (event == null) {
|
||||
throw new NullPointerException("event");
|
||||
}
|
||||
if (!bus.hasSubscribers(event.getClass())) {
|
||||
// Optimization: nobody's listening.
|
||||
return CompletableFuture.completedFuture(event);
|
||||
}
|
||||
|
||||
Runnable runEvent = () -> {
|
||||
PostResult result = bus.post(event);
|
||||
if (!result.exceptions().isEmpty()) {
|
||||
logger.error("Some errors occurred whilst posting event {}.", event);
|
||||
int i = 0;
|
||||
for (Throwable exception : result.exceptions().values()) {
|
||||
logger.error("#{}: \n", ++i, exception);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CompletableFuture<E> eventFuture = new CompletableFuture<>();
|
||||
service.execute(() -> {
|
||||
runEvent.run();
|
||||
eventFuture.complete(event);
|
||||
});
|
||||
return eventFuture;
|
||||
public void invoke(@NonNull E event) {
|
||||
handler.execute(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListeners(Object plugin) {
|
||||
ensurePlugin(plugin);
|
||||
Collection<Object> listeners = registeredListenersByPlugin.removeAll(plugin);
|
||||
listeners.forEach(methodAdapter::unregister);
|
||||
Collection<EventHandler<?>> handlers = registeredHandlersByPlugin.removeAll(plugin);
|
||||
handlers.forEach(handler -> bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST)));
|
||||
public int postOrder() {
|
||||
return postOrder;
|
||||
}
|
||||
|
||||
public EventHandler<E> getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener(Object plugin, Object listener) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(listener, "listener");
|
||||
registeredListenersByPlugin.remove(plugin, listener);
|
||||
methodAdapter.unregister(listener);
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
KyoriToVelocityHandler<?> that = (KyoriToVelocityHandler<?>) o;
|
||||
return Objects.equals(handler, that.handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E> void unregister(Object plugin, EventHandler<E> handler) {
|
||||
ensurePlugin(plugin);
|
||||
Preconditions.checkNotNull(handler, "listener");
|
||||
registeredHandlersByPlugin.remove(plugin, handler);
|
||||
bus.unregister(new KyoriToVelocityHandler<>(handler, PostOrder.LAST));
|
||||
}
|
||||
|
||||
public boolean shutdown() throws InterruptedException {
|
||||
service.shutdown();
|
||||
return service.awaitTermination(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static class VelocityMethodScanner implements MethodScanner<Object> {
|
||||
@Override
|
||||
public boolean shouldRegister(@NonNull Object listener, @NonNull Method method) {
|
||||
return method.isAnnotationPresent(Subscribe.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int postOrder(@NonNull Object listener, @NonNull Method method) {
|
||||
Subscribe annotation = method.getAnnotation(Subscribe.class);
|
||||
if (annotation == null) {
|
||||
throw new IllegalStateException("Trying to determine post order for listener without @Subscribe annotation");
|
||||
}
|
||||
return annotation.order().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean consumeCancelledEvents(@NonNull Object listener, @NonNull Method method) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class KyoriToVelocityHandler<E> implements EventSubscriber<E> {
|
||||
private final EventHandler<E> handler;
|
||||
private final int postOrder;
|
||||
|
||||
private KyoriToVelocityHandler(EventHandler<E> handler, PostOrder postOrder) {
|
||||
this.handler = handler;
|
||||
this.postOrder = postOrder.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NonNull E event) {
|
||||
handler.execute(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int postOrder() {
|
||||
return postOrder;
|
||||
}
|
||||
|
||||
public EventHandler<E> getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
KyoriToVelocityHandler<?> that = (KyoriToVelocityHandler<?>) o;
|
||||
return Objects.equals(handler, that.handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(handler);
|
||||
}
|
||||
public int hashCode() {
|
||||
return Objects.hash(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.velocitypowered.proxy.plugin;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
@@ -7,124 +10,131 @@ import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.plugin.loader.JavaPluginLoader;
|
||||
import com.velocitypowered.proxy.plugin.util.PluginDependencyUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class VelocityPluginManager implements PluginManager {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class);
|
||||
|
||||
private final Map<String, PluginContainer> plugins = new HashMap<>();
|
||||
private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>();
|
||||
private final VelocityServer server;
|
||||
private static final Logger logger = LogManager.getLogger(VelocityPluginManager.class);
|
||||
|
||||
public VelocityPluginManager(VelocityServer server) {
|
||||
this.server = checkNotNull(server, "server");
|
||||
private final Map<String, PluginContainer> plugins = new HashMap<>();
|
||||
private final Map<Object, PluginContainer> pluginInstances = new IdentityHashMap<>();
|
||||
private final VelocityServer server;
|
||||
|
||||
public VelocityPluginManager(VelocityServer server) {
|
||||
this.server = checkNotNull(server, "server");
|
||||
}
|
||||
|
||||
private void registerPlugin(PluginContainer plugin) {
|
||||
plugins.put(plugin.getDescription().getId(), plugin);
|
||||
Optional<?> instance = plugin.getInstance();
|
||||
if (instance.isPresent()) {
|
||||
pluginInstances.put(instance.get(), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPlugin(PluginContainer plugin) {
|
||||
plugins.put(plugin.getDescription().getId(), plugin);
|
||||
Optional<?> instance = plugin.getInstance();
|
||||
if (instance.isPresent()) {
|
||||
pluginInstances.put(instance.get(), plugin);
|
||||
public void loadPlugins(Path directory) throws IOException {
|
||||
checkNotNull(directory, "directory");
|
||||
checkArgument(directory.toFile().isDirectory(), "provided path isn't a directory");
|
||||
|
||||
List<PluginDescription> found = new ArrayList<>();
|
||||
JavaPluginLoader loader = new JavaPluginLoader(server, directory);
|
||||
|
||||
try (DirectoryStream<Path> stream = Files
|
||||
.newDirectoryStream(directory, p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
|
||||
for (Path path : stream) {
|
||||
try {
|
||||
found.add(loader.loadPlugin(path));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to load plugin {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadPlugins(Path directory) throws IOException {
|
||||
checkNotNull(directory, "directory");
|
||||
checkArgument(directory.toFile().isDirectory(), "provided path isn't a directory");
|
||||
if (found.isEmpty()) {
|
||||
// No plugins found
|
||||
return;
|
||||
}
|
||||
|
||||
List<PluginDescription> found = new ArrayList<>();
|
||||
JavaPluginLoader loader = new JavaPluginLoader(server, directory);
|
||||
List<PluginDescription> sortedPlugins = PluginDependencyUtils.sortCandidates(found);
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
|
||||
for (Path path : stream) {
|
||||
try {
|
||||
found.add(loader.loadPlugin(path));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to load plugin {}", path, e);
|
||||
}
|
||||
}
|
||||
// Now load the plugins
|
||||
pluginLoad:
|
||||
for (PluginDescription plugin : sortedPlugins) {
|
||||
// Verify dependencies
|
||||
for (PluginDependency dependency : plugin.getDependencies()) {
|
||||
if (!dependency.isOptional() && !isLoaded(dependency.getId())) {
|
||||
logger.error("Can't load plugin {} due to missing dependency {}", plugin.getId(),
|
||||
dependency.getId());
|
||||
continue pluginLoad;
|
||||
}
|
||||
}
|
||||
|
||||
if (found.isEmpty()) {
|
||||
// No plugins found
|
||||
return;
|
||||
}
|
||||
// Actually create the plugin
|
||||
PluginContainer pluginObject;
|
||||
|
||||
List<PluginDescription> sortedPlugins = PluginDependencyUtils.sortCandidates(found);
|
||||
try {
|
||||
pluginObject = loader.createPlugin(plugin);
|
||||
} catch (Exception e) {
|
||||
logger.error("Can't create plugin {}", plugin.getId(), e);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now load the plugins
|
||||
pluginLoad:
|
||||
for (PluginDescription plugin : sortedPlugins) {
|
||||
// Verify dependencies
|
||||
for (PluginDependency dependency : plugin.getDependencies()) {
|
||||
if (!dependency.isOptional() && !isLoaded(dependency.getId())) {
|
||||
logger.error("Can't load plugin {} due to missing dependency {}", plugin.getId(), dependency.getId());
|
||||
continue pluginLoad;
|
||||
}
|
||||
}
|
||||
registerPlugin(pluginObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Actually create the plugin
|
||||
PluginContainer pluginObject;
|
||||
@Override
|
||||
public Optional<PluginContainer> fromInstance(Object instance) {
|
||||
checkNotNull(instance, "instance");
|
||||
|
||||
try {
|
||||
pluginObject = loader.createPlugin(plugin);
|
||||
} catch (Exception e) {
|
||||
logger.error("Can't create plugin {}", plugin.getId(), e);
|
||||
continue;
|
||||
}
|
||||
|
||||
registerPlugin(pluginObject);
|
||||
}
|
||||
if (instance instanceof PluginContainer) {
|
||||
return Optional.of((PluginContainer) instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PluginContainer> fromInstance(Object instance) {
|
||||
checkNotNull(instance, "instance");
|
||||
return Optional.ofNullable(pluginInstances.get(instance));
|
||||
}
|
||||
|
||||
if (instance instanceof PluginContainer) {
|
||||
return Optional.of((PluginContainer) instance);
|
||||
}
|
||||
@Override
|
||||
public Optional<PluginContainer> getPlugin(String id) {
|
||||
checkNotNull(id, "id");
|
||||
return Optional.ofNullable(plugins.get(id));
|
||||
}
|
||||
|
||||
return Optional.ofNullable(pluginInstances.get(instance));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PluginContainer> getPlugin(String id) {
|
||||
checkNotNull(id, "id");
|
||||
return Optional.ofNullable(plugins.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PluginContainer> getPlugins() {
|
||||
return Collections.unmodifiableCollection(plugins.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded(String id) {
|
||||
return plugins.containsKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToClasspath(Object plugin, Path path) {
|
||||
checkNotNull(plugin, "instance");
|
||||
checkNotNull(path, "path");
|
||||
checkArgument(pluginInstances.containsKey(plugin), "plugin is not loaded");
|
||||
|
||||
ClassLoader pluginClassloader = plugin.getClass().getClassLoader();
|
||||
if (pluginClassloader instanceof PluginClassLoader) {
|
||||
((PluginClassLoader) pluginClassloader).addPath(path);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Operation is not supported on non-Java Velocity plugins.");
|
||||
}
|
||||
@Override
|
||||
public Collection<PluginContainer> getPlugins() {
|
||||
return Collections.unmodifiableCollection(plugins.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoaded(String id) {
|
||||
return plugins.containsKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToClasspath(Object plugin, Path path) {
|
||||
checkNotNull(plugin, "instance");
|
||||
checkNotNull(path, "path");
|
||||
checkArgument(pluginInstances.containsKey(plugin), "plugin is not loaded");
|
||||
|
||||
ClassLoader pluginClassloader = plugin.getClass().getClassLoader();
|
||||
if (pluginClassloader instanceof PluginClassLoader) {
|
||||
((PluginClassLoader) pluginClassloader).addPath(path);
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Operation is not supported on non-Java Velocity plugins.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.plugin.PluginClassLoader;
|
||||
import com.velocitypowered.proxy.plugin.loader.java.JavaVelocityPluginDescription;
|
||||
import com.velocitypowered.proxy.plugin.loader.java.VelocityPluginModule;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
@@ -26,100 +25,108 @@ import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
public class JavaPluginLoader implements PluginLoader {
|
||||
private final ProxyServer server;
|
||||
private final Path baseDirectory;
|
||||
|
||||
public JavaPluginLoader(ProxyServer server, Path baseDirectory) {
|
||||
this.server = server;
|
||||
this.baseDirectory = baseDirectory;
|
||||
private final ProxyServer server;
|
||||
private final Path baseDirectory;
|
||||
|
||||
public JavaPluginLoader(ProxyServer server, Path baseDirectory) {
|
||||
this.server = server;
|
||||
this.baseDirectory = baseDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginDescription loadPlugin(Path source) throws Exception {
|
||||
Optional<SerializedPluginDescription> serialized = getSerializedPluginInfo(source);
|
||||
|
||||
if (!serialized.isPresent()) {
|
||||
throw new InvalidPluginException("Did not find a valid velocity-info.json.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginDescription loadPlugin(Path source) throws Exception {
|
||||
Optional<SerializedPluginDescription> serialized = getSerializedPluginInfo(source);
|
||||
|
||||
if (!serialized.isPresent()) {
|
||||
throw new InvalidPluginException("Did not find a valid velocity-info.json.");
|
||||
}
|
||||
|
||||
SerializedPluginDescription pd = serialized.get();
|
||||
if (!PluginDescription.ID_PATTERN.matcher(pd.getId()).matches()) {
|
||||
throw new InvalidPluginException("Plugin ID '" + pd.getId() + "' must match pattern " +
|
||||
PluginDescription.ID_PATTERN.pattern());
|
||||
}
|
||||
|
||||
PluginClassLoader loader = new PluginClassLoader(
|
||||
new URL[] {source.toUri().toURL() }
|
||||
);
|
||||
loader.addToClassloaders();
|
||||
|
||||
Class mainClass = loader.loadClass(pd.getMain());
|
||||
return createDescription(pd, source, mainClass);
|
||||
SerializedPluginDescription pd = serialized.get();
|
||||
if (!PluginDescription.ID_PATTERN.matcher(pd.getId()).matches()) {
|
||||
throw new InvalidPluginException("Plugin ID '" + pd.getId() + "' must match pattern " +
|
||||
PluginDescription.ID_PATTERN.pattern());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginContainer createPlugin(PluginDescription description) throws Exception {
|
||||
if (!(description instanceof JavaVelocityPluginDescription)) {
|
||||
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
|
||||
}
|
||||
PluginClassLoader loader = new PluginClassLoader(
|
||||
new URL[]{source.toUri().toURL()}
|
||||
);
|
||||
loader.addToClassloaders();
|
||||
|
||||
JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description;
|
||||
Optional<Path> source = javaDescription.getSource();
|
||||
Class mainClass = loader.loadClass(pd.getMain());
|
||||
return createDescription(pd, source, mainClass);
|
||||
}
|
||||
|
||||
if (!source.isPresent()) {
|
||||
throw new IllegalArgumentException("No path in plugin description");
|
||||
}
|
||||
|
||||
Injector injector = Guice.createInjector(new VelocityPluginModule(server, javaDescription, baseDirectory));
|
||||
Object instance = injector.getInstance(javaDescription.getMainClass());
|
||||
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("Got nothing from injector for plugin " + javaDescription.getId());
|
||||
}
|
||||
|
||||
return new VelocityPluginContainer(description, instance);
|
||||
@Override
|
||||
public PluginContainer createPlugin(PluginDescription description) throws Exception {
|
||||
if (!(description instanceof JavaVelocityPluginDescription)) {
|
||||
throw new IllegalArgumentException("Description provided isn't of the Java plugin loader");
|
||||
}
|
||||
|
||||
private Optional<SerializedPluginDescription> getSerializedPluginInfo(Path source) throws Exception {
|
||||
try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(source)))) {
|
||||
JarEntry entry;
|
||||
while ((entry = in.getNextJarEntry()) != null) {
|
||||
if (entry.getName().equals("velocity-plugin.json")) {
|
||||
try (Reader pluginInfoReader = new InputStreamReader(in)) {
|
||||
return Optional.of(VelocityServer.GSON.fromJson(pluginInfoReader, SerializedPluginDescription.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
JavaVelocityPluginDescription javaDescription = (JavaVelocityPluginDescription) description;
|
||||
Optional<Path> source = javaDescription.getSource();
|
||||
|
||||
return Optional.empty();
|
||||
if (!source.isPresent()) {
|
||||
throw new IllegalArgumentException("No path in plugin description");
|
||||
}
|
||||
|
||||
Injector injector = Guice
|
||||
.createInjector(new VelocityPluginModule(server, javaDescription, baseDirectory));
|
||||
Object instance = injector.getInstance(javaDescription.getMainClass());
|
||||
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException(
|
||||
"Got nothing from injector for plugin " + javaDescription.getId());
|
||||
}
|
||||
|
||||
return new VelocityPluginContainer(description, instance);
|
||||
}
|
||||
|
||||
private Optional<SerializedPluginDescription> getSerializedPluginInfo(Path source)
|
||||
throws Exception {
|
||||
try (JarInputStream in = new JarInputStream(
|
||||
new BufferedInputStream(Files.newInputStream(source)))) {
|
||||
JarEntry entry;
|
||||
while ((entry = in.getNextJarEntry()) != null) {
|
||||
if (entry.getName().equals("velocity-plugin.json")) {
|
||||
try (Reader pluginInfoReader = new InputStreamReader(in)) {
|
||||
return Optional.of(VelocityServer.GSON
|
||||
.fromJson(pluginInfoReader, SerializedPluginDescription.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private VelocityPluginDescription createDescription(SerializedPluginDescription description,
|
||||
Path source, Class mainClass) {
|
||||
Set<PluginDependency> dependencies = new HashSet<>();
|
||||
|
||||
for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) {
|
||||
dependencies.add(toDependencyMeta(dependency));
|
||||
}
|
||||
|
||||
private VelocityPluginDescription createDescription(SerializedPluginDescription description, Path source, Class mainClass) {
|
||||
Set<PluginDependency> dependencies = new HashSet<>();
|
||||
return new JavaVelocityPluginDescription(
|
||||
description.getId(),
|
||||
description.getName(),
|
||||
description.getVersion(),
|
||||
description.getDescription(),
|
||||
description.getUrl(),
|
||||
description.getAuthors(),
|
||||
dependencies,
|
||||
source,
|
||||
mainClass
|
||||
);
|
||||
}
|
||||
|
||||
for (SerializedPluginDescription.Dependency dependency : description.getDependencies()) {
|
||||
dependencies.add(toDependencyMeta(dependency));
|
||||
}
|
||||
|
||||
return new JavaVelocityPluginDescription(
|
||||
description.getId(),
|
||||
description.getName(),
|
||||
description.getVersion(),
|
||||
description.getDescription(),
|
||||
description.getUrl(),
|
||||
description.getAuthors(),
|
||||
dependencies,
|
||||
source,
|
||||
mainClass
|
||||
);
|
||||
}
|
||||
|
||||
private static PluginDependency toDependencyMeta(SerializedPluginDescription.Dependency dependency) {
|
||||
return new PluginDependency(
|
||||
dependency.getId(),
|
||||
null, // TODO Implement version matching in dependency annotation
|
||||
dependency.isOptional()
|
||||
);
|
||||
}
|
||||
private static PluginDependency toDependencyMeta(
|
||||
SerializedPluginDescription.Dependency dependency) {
|
||||
return new PluginDependency(
|
||||
dependency.getId(),
|
||||
null, // TODO Implement version matching in dependency annotation
|
||||
dependency.isOptional()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ package com.velocitypowered.proxy.plugin.loader;
|
||||
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* This interface is used for loading plugins.
|
||||
*/
|
||||
public interface PluginLoader {
|
||||
PluginDescription loadPlugin(Path source) throws Exception;
|
||||
|
||||
PluginContainer createPlugin(PluginDescription plugin) throws Exception;
|
||||
PluginDescription loadPlugin(Path source) throws Exception;
|
||||
|
||||
PluginContainer createPlugin(PluginDescription plugin) throws Exception;
|
||||
}
|
||||
|
||||
@@ -2,25 +2,25 @@ package com.velocitypowered.proxy.plugin.loader;
|
||||
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class VelocityPluginContainer implements PluginContainer {
|
||||
private final PluginDescription description;
|
||||
private final Object instance;
|
||||
|
||||
public VelocityPluginContainer(PluginDescription description, Object instance) {
|
||||
this.description = description;
|
||||
this.instance = instance;
|
||||
}
|
||||
private final PluginDescription description;
|
||||
private final Object instance;
|
||||
|
||||
@Override
|
||||
public PluginDescription getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
public VelocityPluginContainer(PluginDescription description, Object instance) {
|
||||
this.description = description;
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<?> getInstance() {
|
||||
return Optional.ofNullable(instance);
|
||||
}
|
||||
@Override
|
||||
public PluginDescription getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<?> getInstance() {
|
||||
return Optional.ofNullable(instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +1,99 @@
|
||||
package com.velocitypowered.proxy.plugin.loader;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class VelocityPluginDescription implements PluginDescription {
|
||||
private final String id;
|
||||
private final @Nullable String name;
|
||||
private final @Nullable String version;
|
||||
private final @Nullable String description;
|
||||
private final @Nullable String url;
|
||||
private final List<String> authors;
|
||||
private final Map<String, PluginDependency> dependencies;
|
||||
private final Path source;
|
||||
|
||||
public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String description, @Nullable String url,
|
||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source) {
|
||||
this.id = checkNotNull(id, "id");
|
||||
this.name = Strings.emptyToNull(name);
|
||||
this.version = Strings.emptyToNull(version);
|
||||
this.description = Strings.emptyToNull(description);
|
||||
this.url = Strings.emptyToNull(url);
|
||||
this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors);
|
||||
this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId());
|
||||
this.source = source;
|
||||
}
|
||||
private final String id;
|
||||
private final @Nullable String name;
|
||||
private final @Nullable String version;
|
||||
private final @Nullable String description;
|
||||
private final @Nullable String url;
|
||||
private final List<String> authors;
|
||||
private final Map<String, PluginDependency> dependencies;
|
||||
private final Path source;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
public VelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
||||
@Nullable String description, @Nullable String url,
|
||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source) {
|
||||
this.id = checkNotNull(id, "id");
|
||||
this.name = Strings.emptyToNull(name);
|
||||
this.version = Strings.emptyToNull(version);
|
||||
this.description = Strings.emptyToNull(description);
|
||||
this.url = Strings.emptyToNull(url);
|
||||
this.authors = authors == null ? ImmutableList.of() : ImmutableList.copyOf(authors);
|
||||
this.dependencies = Maps.uniqueIndex(dependencies, d -> d == null ? null : d.getId());
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getName() {
|
||||
return Optional.ofNullable(name);
|
||||
}
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getVersion() {
|
||||
return Optional.ofNullable(version);
|
||||
}
|
||||
@Override
|
||||
public Optional<String> getName() {
|
||||
return Optional.ofNullable(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getDescription() {
|
||||
return Optional.ofNullable(description);
|
||||
}
|
||||
@Override
|
||||
public Optional<String> getVersion() {
|
||||
return Optional.ofNullable(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getUrl() {
|
||||
return Optional.ofNullable(url);
|
||||
}
|
||||
@Override
|
||||
public Optional<String> getDescription() {
|
||||
return Optional.ofNullable(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAuthors() {
|
||||
return authors;
|
||||
}
|
||||
@Override
|
||||
public Optional<String> getUrl() {
|
||||
return Optional.ofNullable(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PluginDependency> getDependencies() {
|
||||
return dependencies.values();
|
||||
}
|
||||
@Override
|
||||
public List<String> getAuthors() {
|
||||
return authors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PluginDependency> getDependency(String id) {
|
||||
return Optional.ofNullable(dependencies.get(id));
|
||||
}
|
||||
@Override
|
||||
public Collection<PluginDependency> getDependencies() {
|
||||
return dependencies.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getSource() {
|
||||
return Optional.ofNullable(source);
|
||||
}
|
||||
@Override
|
||||
public Optional<PluginDependency> getDependency(String id) {
|
||||
return Optional.ofNullable(dependencies.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityPluginDescription{" +
|
||||
"id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", version='" + version + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
", authors=" + authors +
|
||||
", dependencies=" + dependencies +
|
||||
", source=" + source +
|
||||
'}';
|
||||
}
|
||||
@Override
|
||||
public Optional<Path> getSource() {
|
||||
return Optional.ofNullable(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityPluginDescription{" +
|
||||
"id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", version='" + version + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
", authors=" + authors +
|
||||
", dependencies=" + dependencies +
|
||||
", source=" + source +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
package com.velocitypowered.proxy.plugin.loader.java;
|
||||
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class JavaVelocityPluginDescription extends VelocityPluginDescription {
|
||||
private final Class<?> mainClass;
|
||||
|
||||
public JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version, @Nullable String description, @Nullable String url,
|
||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source, Class<?> mainClass) {
|
||||
super(id, name, version, description, url, authors, dependencies, source);
|
||||
this.mainClass = checkNotNull(mainClass);
|
||||
}
|
||||
private final Class<?> mainClass;
|
||||
|
||||
public Class<?> getMainClass() {
|
||||
return mainClass;
|
||||
}
|
||||
public JavaVelocityPluginDescription(String id, @Nullable String name, @Nullable String version,
|
||||
@Nullable String description, @Nullable String url,
|
||||
@Nullable List<String> authors, Collection<PluginDependency> dependencies, Path source,
|
||||
Class<?> mainClass) {
|
||||
super(id, name, version, description, url, authors, dependencies, source);
|
||||
this.mainClass = checkNotNull(mainClass);
|
||||
}
|
||||
|
||||
public Class<?> getMainClass() {
|
||||
return mainClass;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,30 +8,32 @@ import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import java.nio.file.Path;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class VelocityPluginModule implements Module {
|
||||
private final ProxyServer server;
|
||||
private final JavaVelocityPluginDescription description;
|
||||
private final Path basePluginPath;
|
||||
|
||||
public VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description, Path basePluginPath) {
|
||||
this.server = server;
|
||||
this.description = description;
|
||||
this.basePluginPath = basePluginPath;
|
||||
}
|
||||
private final ProxyServer server;
|
||||
private final JavaVelocityPluginDescription description;
|
||||
private final Path basePluginPath;
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId()));
|
||||
binder.bind(ProxyServer.class).toInstance(server);
|
||||
binder.bind(Path.class).annotatedWith(DataDirectory.class).toInstance(basePluginPath.resolve(description.getId()));
|
||||
binder.bind(PluginDescription.class).toInstance(description);
|
||||
binder.bind(PluginManager.class).toInstance(server.getPluginManager());
|
||||
binder.bind(EventManager.class).toInstance(server.getEventManager());
|
||||
binder.bind(CommandManager.class).toInstance(server.getCommandManager());
|
||||
}
|
||||
public VelocityPluginModule(ProxyServer server, JavaVelocityPluginDescription description,
|
||||
Path basePluginPath) {
|
||||
this.server = server;
|
||||
this.description = description;
|
||||
this.basePluginPath = basePluginPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
binder.bind(Logger.class).toInstance(LoggerFactory.getLogger(description.getId()));
|
||||
binder.bind(ProxyServer.class).toInstance(server);
|
||||
binder.bind(Path.class).annotatedWith(DataDirectory.class)
|
||||
.toInstance(basePluginPath.resolve(description.getId()));
|
||||
binder.bind(PluginDescription.class).toInstance(description);
|
||||
binder.bind(PluginManager.class).toInstance(server.getPluginManager());
|
||||
binder.bind(EventManager.class).toInstance(server.getEventManager());
|
||||
binder.bind(CommandManager.class).toInstance(server.getCommandManager());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,96 +8,107 @@ import com.google.common.graph.GraphBuilder;
|
||||
import com.google.common.graph.MutableGraph;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PluginDependencyUtils {
|
||||
private PluginDependencyUtils() {
|
||||
throw new AssertionError();
|
||||
|
||||
private PluginDependencyUtils() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static List<PluginDescription> sortCandidates(
|
||||
List<@NonNull PluginDescription> candidates) {
|
||||
// Create our graph, we're going to be using this for Kahn's algorithm.
|
||||
MutableGraph<PluginDescription> graph = GraphBuilder.directed().allowsSelfLoops(false).build();
|
||||
Map<String, PluginDescription> candidateMap = Maps
|
||||
.uniqueIndex(candidates, d -> d == null ? null : d.getId());
|
||||
|
||||
// Add edges
|
||||
for (PluginDescription description : candidates) {
|
||||
graph.addNode(description);
|
||||
|
||||
for (PluginDependency dependency : description.getDependencies()) {
|
||||
PluginDescription in = candidateMap.get(dependency.getId());
|
||||
|
||||
if (in != null) {
|
||||
graph.putEdge(description, in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<PluginDescription> sortCandidates(List<@NonNull PluginDescription> candidates) {
|
||||
// Create our graph, we're going to be using this for Kahn's algorithm.
|
||||
MutableGraph<PluginDescription> graph = GraphBuilder.directed().allowsSelfLoops(false).build();
|
||||
Map<String, PluginDescription> candidateMap = Maps.uniqueIndex(candidates, d -> d == null ? null : d.getId());
|
||||
// Find nodes that have no edges
|
||||
Queue<PluginDescription> noEdges = getNoDependencyCandidates(graph);
|
||||
|
||||
// Add edges
|
||||
for (PluginDescription description : candidates) {
|
||||
graph.addNode(description);
|
||||
// Actually run Kahn's algorithm
|
||||
List<PluginDescription> sorted = new ArrayList<>();
|
||||
while (!noEdges.isEmpty()) {
|
||||
PluginDescription candidate = noEdges.remove();
|
||||
sorted.add(candidate);
|
||||
|
||||
for (PluginDependency dependency : description.getDependencies()) {
|
||||
PluginDescription in = candidateMap.get(dependency.getId());
|
||||
for (PluginDescription node : ImmutableSet.copyOf(graph.adjacentNodes(candidate))) {
|
||||
graph.removeEdge(node, candidate);
|
||||
|
||||
if (in != null) {
|
||||
graph.putEdge(description, in);
|
||||
}
|
||||
}
|
||||
if (graph.adjacentNodes(node).isEmpty()) {
|
||||
noEdges.add(node);
|
||||
}
|
||||
|
||||
// Find nodes that have no edges
|
||||
Queue<PluginDescription> noEdges = getNoDependencyCandidates(graph);
|
||||
|
||||
// Actually run Kahn's algorithm
|
||||
List<PluginDescription> sorted = new ArrayList<>();
|
||||
while (!noEdges.isEmpty()) {
|
||||
PluginDescription candidate = noEdges.remove();
|
||||
sorted.add(candidate);
|
||||
|
||||
for (PluginDescription node : ImmutableSet.copyOf(graph.adjacentNodes(candidate))) {
|
||||
graph.removeEdge(node, candidate);
|
||||
|
||||
if (graph.adjacentNodes(node).isEmpty()) {
|
||||
noEdges.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!graph.edges().isEmpty()) {
|
||||
throw new IllegalStateException("Plugin circular dependency found: " + createLoopInformation(graph));
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
||||
public static Queue<PluginDescription> getNoDependencyCandidates(Graph<PluginDescription> graph) {
|
||||
Queue<PluginDescription> found = new ArrayDeque<>();
|
||||
|
||||
for (PluginDescription node : graph.nodes()) {
|
||||
if (graph.outDegree(node) == 0) {
|
||||
found.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
if (!graph.edges().isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"Plugin circular dependency found: " + createLoopInformation(graph));
|
||||
}
|
||||
|
||||
public static String createLoopInformation(Graph<PluginDescription> graph) {
|
||||
StringBuilder repr = new StringBuilder("{");
|
||||
for (EndpointPair<PluginDescription> edge : graph.edges()) {
|
||||
repr.append(edge.target().getId()).append(": [");
|
||||
repr.append(dependencyLoopInfo(graph, edge.target(), new HashSet<>())).append("], ");
|
||||
}
|
||||
repr.setLength(repr.length() - 2);
|
||||
repr.append("}");
|
||||
return repr.toString();
|
||||
return sorted;
|
||||
}
|
||||
|
||||
public static Queue<PluginDescription> getNoDependencyCandidates(Graph<PluginDescription> graph) {
|
||||
Queue<PluginDescription> found = new ArrayDeque<>();
|
||||
|
||||
for (PluginDescription node : graph.nodes()) {
|
||||
if (graph.outDegree(node) == 0) {
|
||||
found.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static String dependencyLoopInfo(Graph<PluginDescription> graph, PluginDescription dependency, Set<PluginDescription> seen) {
|
||||
StringBuilder repr = new StringBuilder();
|
||||
for (PluginDescription pd : graph.adjacentNodes(dependency)) {
|
||||
if (seen.add(pd)) {
|
||||
repr.append(pd.getId()).append(": [").append(dependencyLoopInfo(graph, dependency, seen)).append("], ");
|
||||
} else {
|
||||
repr.append(pd.getId()).append(", ");
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
if (repr.length() != 0) {
|
||||
repr.setLength(repr.length() - 2);
|
||||
return repr.toString();
|
||||
} else {
|
||||
return "<no depends>";
|
||||
}
|
||||
public static String createLoopInformation(Graph<PluginDescription> graph) {
|
||||
StringBuilder repr = new StringBuilder("{");
|
||||
for (EndpointPair<PluginDescription> edge : graph.edges()) {
|
||||
repr.append(edge.target().getId()).append(": [");
|
||||
repr.append(dependencyLoopInfo(graph, edge.target(), new HashSet<>())).append("], ");
|
||||
}
|
||||
repr.setLength(repr.length() - 2);
|
||||
repr.append("}");
|
||||
return repr.toString();
|
||||
}
|
||||
|
||||
private static String dependencyLoopInfo(Graph<PluginDescription> graph,
|
||||
PluginDescription dependency, Set<PluginDescription> seen) {
|
||||
StringBuilder repr = new StringBuilder();
|
||||
for (PluginDescription pd : graph.adjacentNodes(dependency)) {
|
||||
if (seen.add(pd)) {
|
||||
repr.append(pd.getId()).append(": [").append(dependencyLoopInfo(graph, dependency, seen))
|
||||
.append("], ");
|
||||
} else {
|
||||
repr.append(pd.getId()).append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
if (repr.length() != 0) {
|
||||
repr.setLength(repr.length() - 2);
|
||||
return repr.toString();
|
||||
} else {
|
||||
return "<no depends>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface MinecraftPacket {
|
||||
void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
|
||||
|
||||
void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
|
||||
void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
|
||||
|
||||
boolean handle(MinecraftSessionHandler handler);
|
||||
void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
|
||||
|
||||
boolean handle(MinecraftSessionHandler handler);
|
||||
}
|
||||
|
||||
@@ -2,56 +2,59 @@ package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import com.google.common.primitives.ImmutableIntArray;
|
||||
|
||||
public enum ProtocolConstants { ;
|
||||
public static final int LEGACY = -1;
|
||||
public enum ProtocolConstants {
|
||||
;
|
||||
public static final int LEGACY = -1;
|
||||
|
||||
public static final int MINECRAFT_1_8 = 47;
|
||||
public static final int MINECRAFT_1_9 = 107;
|
||||
public static final int MINECRAFT_1_9_1 = 108;
|
||||
public static final int MINECRAFT_1_9_2 = 109;
|
||||
public static final int MINECRAFT_1_9_4 = 110;
|
||||
public static final int MINECRAFT_1_10 = 210;
|
||||
public static final int MINECRAFT_1_11 = 315;
|
||||
public static final int MINECRAFT_1_11_1 = 316;
|
||||
public static final int MINECRAFT_1_12 = 335;
|
||||
public static final int MINECRAFT_1_12_1 = 338;
|
||||
public static final int MINECRAFT_1_12_2 = 340;
|
||||
public static final int MINECRAFT_1_13 = 393;
|
||||
public static final int MINECRAFT_1_13_1 = 401;
|
||||
public static final int MINECRAFT_1_13_2 = 404;
|
||||
public static final int MINECRAFT_1_8 = 47;
|
||||
public static final int MINECRAFT_1_9 = 107;
|
||||
public static final int MINECRAFT_1_9_1 = 108;
|
||||
public static final int MINECRAFT_1_9_2 = 109;
|
||||
public static final int MINECRAFT_1_9_4 = 110;
|
||||
public static final int MINECRAFT_1_10 = 210;
|
||||
public static final int MINECRAFT_1_11 = 315;
|
||||
public static final int MINECRAFT_1_11_1 = 316;
|
||||
public static final int MINECRAFT_1_12 = 335;
|
||||
public static final int MINECRAFT_1_12_1 = 338;
|
||||
public static final int MINECRAFT_1_12_2 = 340;
|
||||
public static final int MINECRAFT_1_13 = 393;
|
||||
public static final int MINECRAFT_1_13_1 = 401;
|
||||
public static final int MINECRAFT_1_13_2 = 404;
|
||||
|
||||
public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8;
|
||||
public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13_2;
|
||||
public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_8;
|
||||
public static final int MAXIMUM_GENERIC_VERSION = MINECRAFT_1_13_2;
|
||||
|
||||
public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13.2";
|
||||
public static final String SUPPORTED_GENERIC_VERSION_STRING = "1.8-1.13.2";
|
||||
|
||||
public static final ImmutableIntArray SUPPORTED_VERSIONS = ImmutableIntArray.of(
|
||||
MINECRAFT_1_8,
|
||||
MINECRAFT_1_9,
|
||||
MINECRAFT_1_9_1,
|
||||
MINECRAFT_1_9_2,
|
||||
MINECRAFT_1_9_4,
|
||||
MINECRAFT_1_10,
|
||||
MINECRAFT_1_11,
|
||||
MINECRAFT_1_11_1,
|
||||
MINECRAFT_1_12,
|
||||
MINECRAFT_1_12_1,
|
||||
MINECRAFT_1_12_2,
|
||||
MINECRAFT_1_13,
|
||||
MINECRAFT_1_13_1,
|
||||
MINECRAFT_1_13_2
|
||||
);
|
||||
public static final ImmutableIntArray SUPPORTED_VERSIONS = ImmutableIntArray.of(
|
||||
MINECRAFT_1_8,
|
||||
MINECRAFT_1_9,
|
||||
MINECRAFT_1_9_1,
|
||||
MINECRAFT_1_9_2,
|
||||
MINECRAFT_1_9_4,
|
||||
MINECRAFT_1_10,
|
||||
MINECRAFT_1_11,
|
||||
MINECRAFT_1_11_1,
|
||||
MINECRAFT_1_12,
|
||||
MINECRAFT_1_12_1,
|
||||
MINECRAFT_1_12_2,
|
||||
MINECRAFT_1_13,
|
||||
MINECRAFT_1_13_1,
|
||||
MINECRAFT_1_13_2
|
||||
);
|
||||
|
||||
public static boolean isSupported(int version) {
|
||||
return SUPPORTED_VERSIONS.contains(version);
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
SERVERBOUND,
|
||||
CLIENTBOUND;
|
||||
|
||||
public StateRegistry.PacketRegistry.ProtocolVersion getProtocol(StateRegistry state, int protocolVersion) {
|
||||
return (this == SERVERBOUND ? state.SERVERBOUND : state.CLIENTBOUND).getVersion(protocolVersion);
|
||||
}
|
||||
public static boolean isSupported(int version) {
|
||||
return SUPPORTED_VERSIONS.contains(version);
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
SERVERBOUND,
|
||||
CLIENTBOUND;
|
||||
|
||||
public StateRegistry.PacketRegistry.ProtocolVersion getProtocol(StateRegistry state,
|
||||
int protocolVersion) {
|
||||
return (this == SERVERBOUND ? state.SERVERBOUND : state.CLIENTBOUND)
|
||||
.getVersion(protocolVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,113 +4,119 @@ import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public enum ProtocolUtils { ;
|
||||
private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
|
||||
public enum ProtocolUtils {
|
||||
;
|
||||
private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
|
||||
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (true) {
|
||||
int k = buf.readByte();
|
||||
i |= (k & 0x7F) << j++ * 7;
|
||||
if (j > 5) throw new RuntimeException("VarInt too big");
|
||||
if ((k & 0x80) != 128) break;
|
||||
}
|
||||
return i;
|
||||
public static int readVarInt(ByteBuf buf) {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (true) {
|
||||
int k = buf.readByte();
|
||||
i |= (k & 0x7F) << j++ * 7;
|
||||
if (j > 5) {
|
||||
throw new RuntimeException("VarInt too big");
|
||||
}
|
||||
if ((k & 0x80) != 128) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void writeVarInt(ByteBuf buf, int value) {
|
||||
while (true) {
|
||||
if ((value & 0xFFFFFF80) == 0) {
|
||||
buf.writeByte(value);
|
||||
return;
|
||||
}
|
||||
public static void writeVarInt(ByteBuf buf, int value) {
|
||||
while (true) {
|
||||
if ((value & 0xFFFFFF80) == 0) {
|
||||
buf.writeByte(value);
|
||||
return;
|
||||
}
|
||||
|
||||
buf.writeByte(value & 0x7F | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
buf.writeByte(value & 0x7F | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
}
|
||||
|
||||
public static String readString(ByteBuf buf) {
|
||||
return readString(buf, DEFAULT_MAX_STRING_SIZE);
|
||||
}
|
||||
public static String readString(ByteBuf buf) {
|
||||
return readString(buf, DEFAULT_MAX_STRING_SIZE);
|
||||
}
|
||||
|
||||
public static String readString(ByteBuf buf, int cap) {
|
||||
int length = readVarInt(buf);
|
||||
Preconditions.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap);
|
||||
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buf.skipBytes(length);
|
||||
return str;
|
||||
}
|
||||
public static String readString(ByteBuf buf, int cap) {
|
||||
int length = readVarInt(buf);
|
||||
Preconditions
|
||||
.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap);
|
||||
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
buf.skipBytes(length);
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void writeString(ByteBuf buf, String str) {
|
||||
int size = ByteBufUtil.utf8Bytes(str);
|
||||
writeVarInt(buf, size);
|
||||
ByteBufUtil.writeUtf8(buf, str);
|
||||
}
|
||||
public static void writeString(ByteBuf buf, String str) {
|
||||
int size = ByteBufUtil.utf8Bytes(str);
|
||||
writeVarInt(buf, size);
|
||||
ByteBufUtil.writeUtf8(buf, str);
|
||||
}
|
||||
|
||||
public static byte[] readByteArray(ByteBuf buf) {
|
||||
return readByteArray(buf, DEFAULT_MAX_STRING_SIZE);
|
||||
}
|
||||
public static byte[] readByteArray(ByteBuf buf) {
|
||||
return readByteArray(buf, DEFAULT_MAX_STRING_SIZE);
|
||||
}
|
||||
|
||||
public static byte[] readByteArray(ByteBuf buf, int cap) {
|
||||
int length = readVarInt(buf);
|
||||
Preconditions.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap);
|
||||
byte[] array = new byte[length];
|
||||
buf.readBytes(array);
|
||||
return array;
|
||||
}
|
||||
public static byte[] readByteArray(ByteBuf buf, int cap) {
|
||||
int length = readVarInt(buf);
|
||||
Preconditions
|
||||
.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap);
|
||||
byte[] array = new byte[length];
|
||||
buf.readBytes(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static void writeByteArray(ByteBuf buf, byte[] array) {
|
||||
writeVarInt(buf, array.length);
|
||||
buf.writeBytes(array);
|
||||
}
|
||||
public static void writeByteArray(ByteBuf buf, byte[] array) {
|
||||
writeVarInt(buf, array.length);
|
||||
buf.writeBytes(array);
|
||||
}
|
||||
|
||||
public static UUID readUuid(ByteBuf buf) {
|
||||
long msb = buf.readLong();
|
||||
long lsb = buf.readLong();
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
public static UUID readUuid(ByteBuf buf) {
|
||||
long msb = buf.readLong();
|
||||
long lsb = buf.readLong();
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
public static void writeUuid(ByteBuf buf, UUID uuid) {
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
buf.writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
public static void writeUuid(ByteBuf buf, UUID uuid) {
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
buf.writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static void writeProperties(ByteBuf buf, List<GameProfile.Property> properties) {
|
||||
writeVarInt(buf, properties.size());
|
||||
for (GameProfile.Property property : properties) {
|
||||
writeString(buf, property.getName());
|
||||
writeString(buf, property.getValue());
|
||||
String signature = property.getSignature();
|
||||
if (signature != null) {
|
||||
buf.writeBoolean(true);
|
||||
writeString(buf, signature);
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
public static void writeProperties(ByteBuf buf, List<GameProfile.Property> properties) {
|
||||
writeVarInt(buf, properties.size());
|
||||
for (GameProfile.Property property : properties) {
|
||||
writeString(buf, property.getName());
|
||||
writeString(buf, property.getValue());
|
||||
String signature = property.getSignature();
|
||||
if (signature != null) {
|
||||
buf.writeBoolean(true);
|
||||
writeString(buf, signature);
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<GameProfile.Property> readProperties(ByteBuf buf) {
|
||||
List<GameProfile.Property> properties = new ArrayList<>();
|
||||
int size = readVarInt(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
String name = readString(buf);
|
||||
String value = readString(buf);
|
||||
String signature = "";
|
||||
boolean hasSignature = buf.readBoolean();
|
||||
if (hasSignature) {
|
||||
signature = readString(buf);
|
||||
}
|
||||
properties.add(new GameProfile.Property(name, value, signature));
|
||||
}
|
||||
return properties;
|
||||
public static List<GameProfile.Property> readProperties(ByteBuf buf) {
|
||||
List<GameProfile.Property> properties = new ArrayList<>();
|
||||
int size = readVarInt(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
String name = readString(buf);
|
||||
String value = readString(buf);
|
||||
String signature = "";
|
||||
boolean hasSignature = buf.readBoolean();
|
||||
if (hasSignature) {
|
||||
signature = readString(buf);
|
||||
}
|
||||
properties.add(new GameProfile.Property(name, value, signature));
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,305 +1,362 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_10;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_11;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_11_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13_2;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_8;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9_2;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9_4;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINIMUM_GENERIC_VERSION;
|
||||
|
||||
import com.google.common.primitives.ImmutableIntArray;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.BossBar;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||
import com.velocitypowered.proxy.protocol.packet.JoinGame;
|
||||
import com.velocitypowered.proxy.protocol.packet.KeepAlive;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.Respawn;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.TitlePacket;
|
||||
import io.netty.util.collection.IntObjectHashMap;
|
||||
import io.netty.util.collection.IntObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public enum StateRegistry {
|
||||
|
||||
HANDSHAKE {
|
||||
{
|
||||
SERVERBOUND.register(Handshake.class, Handshake::new,
|
||||
genericMappings(0x00));
|
||||
}
|
||||
},
|
||||
STATUS {
|
||||
{
|
||||
SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
|
||||
genericMappings(0x00));
|
||||
SERVERBOUND.register(StatusPing.class, StatusPing::new,
|
||||
genericMappings(0x01));
|
||||
|
||||
CLIENTBOUND.register(StatusResponse.class, StatusResponse::new,
|
||||
genericMappings(0x00));
|
||||
CLIENTBOUND.register(StatusPing.class, StatusPing::new,
|
||||
genericMappings(0x01));
|
||||
}
|
||||
},
|
||||
PLAY {
|
||||
{
|
||||
SERVERBOUND.fallback = false;
|
||||
CLIENTBOUND.fallback = false;
|
||||
HANDSHAKE {
|
||||
{
|
||||
SERVERBOUND.register(Handshake.class, Handshake::new,
|
||||
genericMappings(0x00));
|
||||
}
|
||||
},
|
||||
STATUS {
|
||||
{
|
||||
SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
|
||||
genericMappings(0x00));
|
||||
SERVERBOUND.register(StatusPing.class, StatusPing::new,
|
||||
genericMappings(0x01));
|
||||
|
||||
SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new,
|
||||
map(0x14, MINECRAFT_1_8, false),
|
||||
map(0x01, MINECRAFT_1_9, false),
|
||||
map(0x02, MINECRAFT_1_12, false),
|
||||
map(0x01, MINECRAFT_1_12_1, false));
|
||||
SERVERBOUND.register(Chat.class, Chat::new,
|
||||
map(0x01, MINECRAFT_1_8, false),
|
||||
map(0x02, MINECRAFT_1_9, false),
|
||||
map(0x03, MINECRAFT_1_12, false),
|
||||
map(0x02, MINECRAFT_1_12_1, false),
|
||||
map(0x02, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x15, MINECRAFT_1_8, false),
|
||||
map(0x04, MINECRAFT_1_9, false),
|
||||
map(0x05, MINECRAFT_1_12, false),
|
||||
map(0x04, MINECRAFT_1_12_1, false),
|
||||
map(0x04, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x17, MINECRAFT_1_8, false),
|
||||
map(0x09, MINECRAFT_1_9, false),
|
||||
map(0x0A, MINECRAFT_1_12, false),
|
||||
map(0x09, MINECRAFT_1_12_1, false),
|
||||
map(0x0A, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_8, false),
|
||||
map(0x0B, MINECRAFT_1_9, false),
|
||||
map(0x0C, MINECRAFT_1_12, false),
|
||||
map(0x0B, MINECRAFT_1_12_1, false),
|
||||
map(0x0E, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(StatusResponse.class, StatusResponse::new,
|
||||
genericMappings(0x00));
|
||||
CLIENTBOUND.register(StatusPing.class, StatusPing::new,
|
||||
genericMappings(0x01));
|
||||
}
|
||||
},
|
||||
PLAY {
|
||||
{
|
||||
SERVERBOUND.fallback = false;
|
||||
CLIENTBOUND.fallback = false;
|
||||
|
||||
CLIENTBOUND.register(BossBar.class, BossBar::new,
|
||||
map(0x0C, MINECRAFT_1_9, false),
|
||||
map(0x0C, MINECRAFT_1_12, false),
|
||||
map(0x0C, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(Chat.class, Chat::new,
|
||||
map(0x02, MINECRAFT_1_8, true),
|
||||
map(0x0F, MINECRAFT_1_9, true),
|
||||
map(0x0F, MINECRAFT_1_12, true),
|
||||
map(0x0E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new,
|
||||
map(0x3A, MINECRAFT_1_8, false),
|
||||
map(0x0E, MINECRAFT_1_9, false),
|
||||
map(0x0E, MINECRAFT_1_12, false));
|
||||
CLIENTBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x3F, MINECRAFT_1_8, false),
|
||||
map(0x18, MINECRAFT_1_9, false),
|
||||
map(0x18, MINECRAFT_1_12, false),
|
||||
map(0x19, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(Disconnect.class, Disconnect::new,
|
||||
map(0x40, MINECRAFT_1_8, false),
|
||||
map(0x1A, MINECRAFT_1_9, false),
|
||||
map(0x1A, MINECRAFT_1_12, false),
|
||||
map(0x1B, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_8, false),
|
||||
map(0x1F, MINECRAFT_1_9, false),
|
||||
map(0x1F, MINECRAFT_1_12, false),
|
||||
map(0x21, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(JoinGame.class, JoinGame::new,
|
||||
map(0x01, MINECRAFT_1_8, false),
|
||||
map(0x23, MINECRAFT_1_9, false),
|
||||
map(0x23, MINECRAFT_1_12, false),
|
||||
map(0x25, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(Respawn.class, Respawn::new,
|
||||
map(0x07, MINECRAFT_1_8, true),
|
||||
map(0x33, MINECRAFT_1_9, true),
|
||||
map(0x34, MINECRAFT_1_12, true),
|
||||
map(0x35, MINECRAFT_1_12_2, true),
|
||||
map(0x38, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
||||
map(0x47, MINECRAFT_1_8, true),
|
||||
map(0x48, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_9_4, true),
|
||||
map(0x49, MINECRAFT_1_12, true),
|
||||
map(0x4A, MINECRAFT_1_12_1, true),
|
||||
map(0x4E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TitlePacket.class, TitlePacket::new,
|
||||
map(0x45, MINECRAFT_1_8, true),
|
||||
map(0x45, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, true),
|
||||
map(0x4B, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(PlayerListItem.class, PlayerListItem::new,
|
||||
map(0x38, MINECRAFT_1_8, false),
|
||||
map(0x2D, MINECRAFT_1_9, false),
|
||||
map(0x2D, MINECRAFT_1_12, false),
|
||||
map(0x2E, MINECRAFT_1_12_1, false),
|
||||
map(0x30, MINECRAFT_1_13, false));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
{
|
||||
SERVERBOUND.register(ServerLogin.class, ServerLogin::new,
|
||||
genericMappings(0x00));
|
||||
SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new,
|
||||
genericMappings(0x01));
|
||||
SERVERBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new,
|
||||
map(0x02, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(TabCompleteRequest.class, TabCompleteRequest::new,
|
||||
map(0x14, MINECRAFT_1_8, false),
|
||||
map(0x01, MINECRAFT_1_9, false),
|
||||
map(0x02, MINECRAFT_1_12, false),
|
||||
map(0x01, MINECRAFT_1_12_1, false));
|
||||
SERVERBOUND.register(Chat.class, Chat::new,
|
||||
map(0x01, MINECRAFT_1_8, false),
|
||||
map(0x02, MINECRAFT_1_9, false),
|
||||
map(0x03, MINECRAFT_1_12, false),
|
||||
map(0x02, MINECRAFT_1_12_1, false),
|
||||
map(0x02, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x15, MINECRAFT_1_8, false),
|
||||
map(0x04, MINECRAFT_1_9, false),
|
||||
map(0x05, MINECRAFT_1_12, false),
|
||||
map(0x04, MINECRAFT_1_12_1, false),
|
||||
map(0x04, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x17, MINECRAFT_1_8, false),
|
||||
map(0x09, MINECRAFT_1_9, false),
|
||||
map(0x0A, MINECRAFT_1_12, false),
|
||||
map(0x09, MINECRAFT_1_12_1, false),
|
||||
map(0x0A, MINECRAFT_1_13, false));
|
||||
SERVERBOUND.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_8, false),
|
||||
map(0x0B, MINECRAFT_1_9, false),
|
||||
map(0x0C, MINECRAFT_1_12, false),
|
||||
map(0x0B, MINECRAFT_1_12_1, false),
|
||||
map(0x0E, MINECRAFT_1_13, false));
|
||||
|
||||
CLIENTBOUND.register(Disconnect.class, Disconnect::new,
|
||||
genericMappings(0x00));
|
||||
CLIENTBOUND.register(EncryptionRequest.class, EncryptionRequest::new,
|
||||
genericMappings(0x01));
|
||||
CLIENTBOUND.register(ServerLoginSuccess.class, ServerLoginSuccess::new,
|
||||
genericMappings(0x02));
|
||||
CLIENTBOUND.register(SetCompression.class, SetCompression::new,
|
||||
genericMappings(0x03));
|
||||
CLIENTBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new,
|
||||
map(0x04, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(BossBar.class, BossBar::new,
|
||||
map(0x0C, MINECRAFT_1_9, false),
|
||||
map(0x0C, MINECRAFT_1_12, false),
|
||||
map(0x0C, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(Chat.class, Chat::new,
|
||||
map(0x02, MINECRAFT_1_8, true),
|
||||
map(0x0F, MINECRAFT_1_9, true),
|
||||
map(0x0F, MINECRAFT_1_12, true),
|
||||
map(0x0E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new,
|
||||
map(0x3A, MINECRAFT_1_8, false),
|
||||
map(0x0E, MINECRAFT_1_9, false),
|
||||
map(0x0E, MINECRAFT_1_12, false));
|
||||
CLIENTBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x3F, MINECRAFT_1_8, false),
|
||||
map(0x18, MINECRAFT_1_9, false),
|
||||
map(0x18, MINECRAFT_1_12, false),
|
||||
map(0x19, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(Disconnect.class, Disconnect::new,
|
||||
map(0x40, MINECRAFT_1_8, false),
|
||||
map(0x1A, MINECRAFT_1_9, false),
|
||||
map(0x1A, MINECRAFT_1_12, false),
|
||||
map(0x1B, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_8, false),
|
||||
map(0x1F, MINECRAFT_1_9, false),
|
||||
map(0x1F, MINECRAFT_1_12, false),
|
||||
map(0x21, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(JoinGame.class, JoinGame::new,
|
||||
map(0x01, MINECRAFT_1_8, false),
|
||||
map(0x23, MINECRAFT_1_9, false),
|
||||
map(0x23, MINECRAFT_1_12, false),
|
||||
map(0x25, MINECRAFT_1_13, false));
|
||||
CLIENTBOUND.register(Respawn.class, Respawn::new,
|
||||
map(0x07, MINECRAFT_1_8, true),
|
||||
map(0x33, MINECRAFT_1_9, true),
|
||||
map(0x34, MINECRAFT_1_12, true),
|
||||
map(0x35, MINECRAFT_1_12_2, true),
|
||||
map(0x38, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
||||
map(0x47, MINECRAFT_1_8, true),
|
||||
map(0x48, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_9_4, true),
|
||||
map(0x49, MINECRAFT_1_12, true),
|
||||
map(0x4A, MINECRAFT_1_12_1, true),
|
||||
map(0x4E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TitlePacket.class, TitlePacket::new,
|
||||
map(0x45, MINECRAFT_1_8, true),
|
||||
map(0x45, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, true),
|
||||
map(0x4B, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(PlayerListItem.class, PlayerListItem::new,
|
||||
map(0x38, MINECRAFT_1_8, false),
|
||||
map(0x2D, MINECRAFT_1_9, false),
|
||||
map(0x2D, MINECRAFT_1_12, false),
|
||||
map(0x2E, MINECRAFT_1_12_1, false),
|
||||
map(0x30, MINECRAFT_1_13, false));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
{
|
||||
SERVERBOUND.register(ServerLogin.class, ServerLogin::new,
|
||||
genericMappings(0x00));
|
||||
SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new,
|
||||
genericMappings(0x01));
|
||||
SERVERBOUND.register(LoginPluginResponse.class, LoginPluginResponse::new,
|
||||
map(0x02, MINECRAFT_1_13, false));
|
||||
|
||||
CLIENTBOUND.register(Disconnect.class, Disconnect::new,
|
||||
genericMappings(0x00));
|
||||
CLIENTBOUND.register(EncryptionRequest.class, EncryptionRequest::new,
|
||||
genericMappings(0x01));
|
||||
CLIENTBOUND.register(ServerLoginSuccess.class, ServerLoginSuccess::new,
|
||||
genericMappings(0x02));
|
||||
CLIENTBOUND.register(SetCompression.class, SetCompression::new,
|
||||
genericMappings(0x03));
|
||||
CLIENTBOUND.register(LoginPluginMessage.class, LoginPluginMessage::new,
|
||||
map(0x04, MINECRAFT_1_13, false));
|
||||
}
|
||||
};
|
||||
|
||||
public static final int STATUS_ID = 1;
|
||||
public static final int LOGIN_ID = 2;
|
||||
public final PacketRegistry CLIENTBOUND = new PacketRegistry(
|
||||
ProtocolConstants.Direction.CLIENTBOUND);
|
||||
public final PacketRegistry SERVERBOUND = new PacketRegistry(
|
||||
ProtocolConstants.Direction.SERVERBOUND);
|
||||
|
||||
public static class PacketRegistry {
|
||||
|
||||
private static final IntObjectMap<ImmutableIntArray> LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>();
|
||||
|
||||
static {
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9,
|
||||
ImmutableIntArray.of(MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4,
|
||||
ImmutableIntArray.of(MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, ImmutableIntArray.of(MINECRAFT_1_12_1));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, ImmutableIntArray.of(MINECRAFT_1_12_2));
|
||||
LINKED_PROTOCOL_VERSIONS
|
||||
.put(MINECRAFT_1_13, ImmutableIntArray.of(MINECRAFT_1_13_1, MINECRAFT_1_13_2));
|
||||
}
|
||||
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private final IntObjectMap<ProtocolVersion> versions = new IntObjectHashMap<>(16);
|
||||
private boolean fallback = true;
|
||||
|
||||
public PacketRegistry(Direction direction) {
|
||||
this.direction = direction;
|
||||
ProtocolConstants.SUPPORTED_VERSIONS
|
||||
.forEach(version -> versions.put(version, new ProtocolVersion(version)));
|
||||
}
|
||||
|
||||
public ProtocolVersion getVersion(final int version) {
|
||||
ProtocolVersion result = versions.get(version);
|
||||
if (result == null) {
|
||||
if (fallback) {
|
||||
return getVersion(MINIMUM_GENERIC_VERSION);
|
||||
}
|
||||
throw new IllegalArgumentException("Could not find data for protocol version " + version);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public <P extends MinecraftPacket> void register(Class<P> clazz, Supplier<P> packetSupplier,
|
||||
PacketMapping... mappings) {
|
||||
if (mappings.length == 0) {
|
||||
throw new IllegalArgumentException("At least one mapping must be provided.");
|
||||
}
|
||||
|
||||
for (final PacketMapping mapping : mappings) {
|
||||
ProtocolVersion version = this.versions.get(mapping.protocolVersion);
|
||||
if (version == null) {
|
||||
throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion);
|
||||
}
|
||||
if (!mapping.encodeOnly) {
|
||||
version.packetIdToSupplier.put(mapping.id, packetSupplier);
|
||||
}
|
||||
version.packetClassToId.put(clazz, mapping.id);
|
||||
|
||||
ImmutableIntArray linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion);
|
||||
if (linked != null) {
|
||||
links:
|
||||
for (int i = 0; i < linked.length(); i++) {
|
||||
int linkedVersion = linked.get(i);
|
||||
// Make sure that later mappings override this one.
|
||||
for (PacketMapping m : mappings) {
|
||||
if (linkedVersion == m.protocolVersion) {
|
||||
continue links;
|
||||
}
|
||||
}
|
||||
register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.encodeOnly));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProtocolVersion {
|
||||
|
||||
public final int version;
|
||||
final IntObjectMap<Supplier<? extends MinecraftPacket>> packetIdToSupplier = new IntObjectHashMap<>(
|
||||
16, 0.5f);
|
||||
final Object2IntMap<Class<? extends MinecraftPacket>> packetClassToId = new Object2IntOpenHashMap<>(
|
||||
16, 0.5f);
|
||||
|
||||
ProtocolVersion(final int version) {
|
||||
this.version = version;
|
||||
this.packetClassToId.defaultReturnValue(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
public @Nullable MinecraftPacket createPacket(final int id) {
|
||||
final Supplier<? extends MinecraftPacket> supplier = this.packetIdToSupplier.get(id);
|
||||
if (supplier == null) {
|
||||
return null;
|
||||
}
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
public int getPacketId(final MinecraftPacket packet) {
|
||||
final int id = this.packetClassToId.getInt(packet.getClass());
|
||||
if (id == Integer.MIN_VALUE) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unable to find id for packet of type %s in %s protocol %s",
|
||||
packet.getClass().getName(), PacketRegistry.this.direction, this.version
|
||||
));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PacketMapping {
|
||||
|
||||
private final int id;
|
||||
private final int protocolVersion;
|
||||
private final boolean encodeOnly;
|
||||
|
||||
public PacketMapping(int id, int protocolVersion, boolean packetDecoding) {
|
||||
this.id = id;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.encodeOnly = packetDecoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketMapping{" +
|
||||
"id=" + id +
|
||||
", protocolVersion=" + protocolVersion +
|
||||
", encodeOnly=" + encodeOnly +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PacketMapping that = (PacketMapping) o;
|
||||
return id == that.id &&
|
||||
protocolVersion == that.protocolVersion &&
|
||||
encodeOnly == that.encodeOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, protocolVersion, encodeOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PacketMapping using the provided arguments
|
||||
*
|
||||
* @param id Packet Id
|
||||
* @param version Protocol version
|
||||
* @param encodeOnly When true packet decoding will be disabled
|
||||
* @return PacketMapping with the provided arguments
|
||||
*/
|
||||
private static PacketMapping map(int id, int version, boolean encodeOnly) {
|
||||
return new PacketMapping(id, version, encodeOnly);
|
||||
}
|
||||
|
||||
private static PacketMapping[] genericMappings(int id) {
|
||||
return new PacketMapping[]{
|
||||
map(id, MINECRAFT_1_8, false),
|
||||
map(id, MINECRAFT_1_9, false),
|
||||
map(id, MINECRAFT_1_12, false),
|
||||
map(id, MINECRAFT_1_13, false)
|
||||
};
|
||||
|
||||
public static final int STATUS_ID = 1;
|
||||
public static final int LOGIN_ID = 2;
|
||||
public final PacketRegistry CLIENTBOUND = new PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND);
|
||||
public final PacketRegistry SERVERBOUND = new PacketRegistry(ProtocolConstants.Direction.SERVERBOUND);
|
||||
|
||||
public static class PacketRegistry {
|
||||
private static final IntObjectMap<ImmutableIntArray> LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>();
|
||||
|
||||
static {
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9, ImmutableIntArray.of(MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, ImmutableIntArray.of(MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, ImmutableIntArray.of(MINECRAFT_1_12_1));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, ImmutableIntArray.of(MINECRAFT_1_12_2));
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_13, ImmutableIntArray.of(MINECRAFT_1_13_1, MINECRAFT_1_13_2));
|
||||
}
|
||||
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private final IntObjectMap<ProtocolVersion> versions = new IntObjectHashMap<>(16);
|
||||
private boolean fallback = true;
|
||||
|
||||
public PacketRegistry(Direction direction) {
|
||||
this.direction = direction;
|
||||
ProtocolConstants.SUPPORTED_VERSIONS.forEach(version -> versions.put(version, new ProtocolVersion(version)));
|
||||
}
|
||||
|
||||
public ProtocolVersion getVersion(final int version) {
|
||||
ProtocolVersion result = versions.get(version);
|
||||
if (result == null) {
|
||||
if (fallback) {
|
||||
return getVersion(MINIMUM_GENERIC_VERSION);
|
||||
}
|
||||
throw new IllegalArgumentException("Could not find data for protocol version " + version);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public <P extends MinecraftPacket> void register(Class<P> clazz, Supplier<P> packetSupplier, PacketMapping... mappings) {
|
||||
if (mappings.length == 0) {
|
||||
throw new IllegalArgumentException("At least one mapping must be provided.");
|
||||
}
|
||||
|
||||
for (final PacketMapping mapping : mappings) {
|
||||
ProtocolVersion version = this.versions.get(mapping.protocolVersion);
|
||||
if (version == null) {
|
||||
throw new IllegalArgumentException("Unknown protocol version " + mapping.protocolVersion);
|
||||
}
|
||||
if (!mapping.encodeOnly) {
|
||||
version.packetIdToSupplier.put(mapping.id, packetSupplier);
|
||||
}
|
||||
version.packetClassToId.put(clazz, mapping.id);
|
||||
|
||||
ImmutableIntArray linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion);
|
||||
if (linked != null) {
|
||||
links: for (int i = 0; i < linked.length(); i++) {
|
||||
int linkedVersion = linked.get(i);
|
||||
// Make sure that later mappings override this one.
|
||||
for (PacketMapping m : mappings) {
|
||||
if (linkedVersion == m.protocolVersion) continue links;
|
||||
}
|
||||
register(clazz, packetSupplier, map(mapping.id, linkedVersion, mapping.encodeOnly));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProtocolVersion {
|
||||
public final int version;
|
||||
final IntObjectMap<Supplier<? extends MinecraftPacket>> packetIdToSupplier = new IntObjectHashMap<>(16, 0.5f);
|
||||
final Object2IntMap<Class<? extends MinecraftPacket>> packetClassToId = new Object2IntOpenHashMap<>(16, 0.5f);
|
||||
|
||||
ProtocolVersion(final int version) {
|
||||
this.version = version;
|
||||
this.packetClassToId.defaultReturnValue(Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
public @Nullable MinecraftPacket createPacket(final int id) {
|
||||
final Supplier<? extends MinecraftPacket> supplier = this.packetIdToSupplier.get(id);
|
||||
if (supplier == null) {
|
||||
return null;
|
||||
}
|
||||
return supplier.get();
|
||||
}
|
||||
|
||||
public int getPacketId(final MinecraftPacket packet) {
|
||||
final int id = this.packetClassToId.getInt(packet.getClass());
|
||||
if (id == Integer.MIN_VALUE) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Unable to find id for packet of type %s in %s protocol %s",
|
||||
packet.getClass().getName(), PacketRegistry.this.direction, this.version
|
||||
));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PacketMapping {
|
||||
private final int id;
|
||||
private final int protocolVersion;
|
||||
private final boolean encodeOnly;
|
||||
|
||||
public PacketMapping(int id, int protocolVersion, boolean packetDecoding) {
|
||||
this.id = id;
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.encodeOnly = packetDecoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketMapping{" +
|
||||
"id=" + id +
|
||||
", protocolVersion=" + protocolVersion +
|
||||
", encodeOnly=" + encodeOnly +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PacketMapping that = (PacketMapping) o;
|
||||
return id == that.id &&
|
||||
protocolVersion == that.protocolVersion &&
|
||||
encodeOnly == that.encodeOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, protocolVersion, encodeOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PacketMapping using the provided arguments
|
||||
* @param id Packet Id
|
||||
* @param version Protocol version
|
||||
* @param encodeOnly When true packet decoding will be disabled
|
||||
* @return PacketMapping with the provided arguments
|
||||
*/
|
||||
private static PacketMapping map(int id, int version, boolean encodeOnly) {
|
||||
return new PacketMapping(id, version, encodeOnly);
|
||||
}
|
||||
|
||||
private static PacketMapping[] genericMappings(int id) {
|
||||
return new PacketMapping[]{
|
||||
map(id, MINECRAFT_1_8, false),
|
||||
map(id, MINECRAFT_1_9, false),
|
||||
map(id, MINECRAFT_1_12, false),
|
||||
map(id, MINECRAFT_1_13, false)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.BASIC;
|
||||
import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.FULL;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -13,11 +16,6 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
@@ -28,239 +26,254 @@ import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.BASIC;
|
||||
import static com.velocitypowered.api.event.query.ProxyQueryEvent.QueryType.FULL;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||
private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class);
|
||||
|
||||
private static final short QUERY_MAGIC_FIRST = 0xFE;
|
||||
private static final short QUERY_MAGIC_SECOND = 0xFD;
|
||||
private static final byte QUERY_TYPE_HANDSHAKE = 0x09;
|
||||
private static final byte QUERY_TYPE_STAT = 0x00;
|
||||
private static final byte[] QUERY_RESPONSE_FULL_PADDING = new byte[] { 0x73, 0x70, 0x6C, 0x69, 0x74, 0x6E, 0x75, 0x6D, 0x00, (byte) 0x80, 0x00 };
|
||||
private static final byte[] QUERY_RESPONSE_FULL_PADDING2 = new byte[] { 0x01, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x5F, 0x00, 0x00 };
|
||||
private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class);
|
||||
|
||||
// Contents to add into basic stat response. See ResponseWriter class below
|
||||
private static final Set<String> QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of(
|
||||
"hostname",
|
||||
"gametype",
|
||||
"map",
|
||||
"numplayers",
|
||||
"maxplayers",
|
||||
"hostport",
|
||||
"hostip"
|
||||
);
|
||||
private static final short QUERY_MAGIC_FIRST = 0xFE;
|
||||
private static final short QUERY_MAGIC_SECOND = 0xFD;
|
||||
private static final byte QUERY_TYPE_HANDSHAKE = 0x09;
|
||||
private static final byte QUERY_TYPE_STAT = 0x00;
|
||||
private static final byte[] QUERY_RESPONSE_FULL_PADDING = new byte[]{0x73, 0x70, 0x6C, 0x69, 0x74,
|
||||
0x6E, 0x75, 0x6D, 0x00, (byte) 0x80, 0x00};
|
||||
private static final byte[] QUERY_RESPONSE_FULL_PADDING2 = new byte[]{0x01, 0x70, 0x6C, 0x61,
|
||||
0x79, 0x65, 0x72, 0x5F, 0x00, 0x00};
|
||||
|
||||
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
// Contents to add into basic stat response. See ResponseWriter class below
|
||||
private static final Set<String> QUERY_BASIC_RESPONSE_CONTENTS = ImmutableSet.of(
|
||||
"hostname",
|
||||
"gametype",
|
||||
"map",
|
||||
"numplayers",
|
||||
"maxplayers",
|
||||
"hostport",
|
||||
"hostip"
|
||||
);
|
||||
|
||||
@MonotonicNonNull
|
||||
private volatile List<QueryResponse.PluginInformation> pluginInformationList = null;
|
||||
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private final VelocityServer server;
|
||||
@MonotonicNonNull
|
||||
private volatile List<QueryResponse.PluginInformation> pluginInformationList = null;
|
||||
|
||||
public GS4QueryHandler(VelocityServer server) {
|
||||
this.server = server;
|
||||
private final VelocityServer server;
|
||||
|
||||
public GS4QueryHandler(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
|
||||
ByteBuf queryMessage = msg.content();
|
||||
InetAddress senderAddress = msg.sender().getAddress();
|
||||
|
||||
// Allocate buffer for response
|
||||
ByteBuf queryResponse = ctx.alloc().buffer();
|
||||
DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender());
|
||||
|
||||
try {
|
||||
// Verify query packet magic
|
||||
if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST
|
||||
|| queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) {
|
||||
throw new IllegalStateException("Invalid query packet magic");
|
||||
}
|
||||
|
||||
// Read packet header
|
||||
short type = queryMessage.readUnsignedByte();
|
||||
int sessionId = queryMessage.readInt();
|
||||
|
||||
switch (type) {
|
||||
case QUERY_TYPE_HANDSHAKE: {
|
||||
// Generate new challenge token and put it into the sessions cache
|
||||
int challengeToken = ThreadLocalRandom.current().nextInt();
|
||||
sessions.put(senderAddress, challengeToken);
|
||||
|
||||
// Respond with challenge token
|
||||
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
|
||||
queryResponse.writeInt(sessionId);
|
||||
writeString(queryResponse, Integer.toString(challengeToken));
|
||||
ctx.writeAndFlush(responsePacket);
|
||||
break;
|
||||
}
|
||||
|
||||
case QUERY_TYPE_STAT: {
|
||||
// Check if query was done with session previously generated using a handshake packet
|
||||
int challengeToken = queryMessage.readInt();
|
||||
Integer session = sessions.getIfPresent(senderAddress);
|
||||
if (session == null || session != challengeToken) {
|
||||
throw new IllegalStateException("Invalid challenge token");
|
||||
}
|
||||
|
||||
// Check which query response client expects
|
||||
if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) {
|
||||
throw new IllegalStateException("Invalid query packet");
|
||||
}
|
||||
|
||||
// Build query response
|
||||
QueryResponse response = QueryResponse.builder()
|
||||
.hostname(ComponentSerializers.PLAIN
|
||||
.serialize(server.getConfiguration().getMotdComponent()))
|
||||
.gameVersion(ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING)
|
||||
.map(server.getConfiguration().getQueryMap())
|
||||
.currentPlayers(server.getPlayerCount())
|
||||
.maxPlayers(server.getConfiguration().getShowMaxPlayers())
|
||||
.proxyPort(server.getConfiguration().getBind().getPort())
|
||||
.proxyHost(server.getConfiguration().getBind().getHostString())
|
||||
.players(server.getAllPlayers().stream().map(Player::getUsername)
|
||||
.collect(Collectors.toList()))
|
||||
.proxyVersion("Velocity")
|
||||
.plugins(
|
||||
server.getConfiguration().shouldQueryShowPlugins() ? getRealPluginInformation()
|
||||
: Collections.emptyList())
|
||||
.build();
|
||||
|
||||
boolean isBasic = queryMessage.readableBytes() == 0;
|
||||
|
||||
// Call event and write response
|
||||
server.getEventManager()
|
||||
.fire(new ProxyQueryEvent(isBasic ? BASIC : FULL, senderAddress, response))
|
||||
.whenCompleteAsync((event, exc) -> {
|
||||
// Packet header
|
||||
queryResponse.writeByte(QUERY_TYPE_STAT);
|
||||
queryResponse.writeInt(sessionId);
|
||||
|
||||
// Start writing the response
|
||||
ResponseWriter responseWriter = new ResponseWriter(queryResponse, isBasic);
|
||||
responseWriter.write("hostname", event.getResponse().getHostname());
|
||||
responseWriter.write("gametype", "SMP");
|
||||
|
||||
responseWriter.write("game_id", "MINECRAFT");
|
||||
responseWriter.write("version", event.getResponse().getGameVersion());
|
||||
responseWriter.writePlugins(event.getResponse().getProxyVersion(),
|
||||
event.getResponse().getPlugins());
|
||||
|
||||
responseWriter.write("map", event.getResponse().getMap());
|
||||
responseWriter.write("numplayers", event.getResponse().getCurrentPlayers());
|
||||
responseWriter.write("maxplayers", event.getResponse().getMaxPlayers());
|
||||
responseWriter.write("hostport", event.getResponse().getProxyPort());
|
||||
responseWriter.write("hostip", event.getResponse().getProxyHost());
|
||||
|
||||
if (!responseWriter.isBasic) {
|
||||
responseWriter.writePlayers(event.getResponse().getPlayers());
|
||||
}
|
||||
|
||||
// Send the response
|
||||
ctx.writeAndFlush(responsePacket);
|
||||
}, ctx.channel().eventLoop());
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Invalid query type: " + type);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
|
||||
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
|
||||
// count.
|
||||
responsePacket.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(ByteBuf buf, String string) {
|
||||
buf.writeCharSequence(string, StandardCharsets.ISO_8859_1);
|
||||
buf.writeByte(0x00);
|
||||
}
|
||||
|
||||
private List<QueryResponse.PluginInformation> getRealPluginInformation() {
|
||||
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
|
||||
List<QueryResponse.PluginInformation> res = pluginInformationList;
|
||||
if (res == null) {
|
||||
synchronized (this) {
|
||||
if (pluginInformationList == null) {
|
||||
pluginInformationList = res = server.getPluginManager().getPlugins().stream()
|
||||
.map(PluginContainer::getDescription)
|
||||
.map(desc -> QueryResponse.PluginInformation
|
||||
.of(desc.getName().orElse(desc.getId()), desc.getVersion().orElse(null)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static class ResponseWriter {
|
||||
|
||||
private final ByteBuf buf;
|
||||
private final boolean isBasic;
|
||||
|
||||
ResponseWriter(ByteBuf buf, boolean isBasic) {
|
||||
this.buf = buf;
|
||||
this.isBasic = isBasic;
|
||||
|
||||
if (!isBasic) {
|
||||
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
|
||||
ByteBuf queryMessage = msg.content();
|
||||
InetAddress senderAddress = msg.sender().getAddress();
|
||||
|
||||
// Allocate buffer for response
|
||||
ByteBuf queryResponse = ctx.alloc().buffer();
|
||||
DatagramPacket responsePacket = new DatagramPacket(queryResponse, msg.sender());
|
||||
|
||||
try {
|
||||
// Verify query packet magic
|
||||
if (queryMessage.readUnsignedByte() != QUERY_MAGIC_FIRST || queryMessage.readUnsignedByte() != QUERY_MAGIC_SECOND) {
|
||||
throw new IllegalStateException("Invalid query packet magic");
|
||||
}
|
||||
|
||||
// Read packet header
|
||||
short type = queryMessage.readUnsignedByte();
|
||||
int sessionId = queryMessage.readInt();
|
||||
|
||||
switch (type) {
|
||||
case QUERY_TYPE_HANDSHAKE: {
|
||||
// Generate new challenge token and put it into the sessions cache
|
||||
int challengeToken = ThreadLocalRandom.current().nextInt();
|
||||
sessions.put(senderAddress, challengeToken);
|
||||
|
||||
// Respond with challenge token
|
||||
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
|
||||
queryResponse.writeInt(sessionId);
|
||||
writeString(queryResponse, Integer.toString(challengeToken));
|
||||
ctx.writeAndFlush(responsePacket);
|
||||
break;
|
||||
}
|
||||
|
||||
case QUERY_TYPE_STAT: {
|
||||
// Check if query was done with session previously generated using a handshake packet
|
||||
int challengeToken = queryMessage.readInt();
|
||||
Integer session = sessions.getIfPresent(senderAddress);
|
||||
if (session == null || session != challengeToken) {
|
||||
throw new IllegalStateException("Invalid challenge token");
|
||||
}
|
||||
|
||||
// Check which query response client expects
|
||||
if (queryMessage.readableBytes() != 0 && queryMessage.readableBytes() != 4) {
|
||||
throw new IllegalStateException("Invalid query packet");
|
||||
}
|
||||
|
||||
// Build query response
|
||||
QueryResponse response = QueryResponse.builder()
|
||||
.hostname(ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()))
|
||||
.gameVersion(ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING)
|
||||
.map(server.getConfiguration().getQueryMap())
|
||||
.currentPlayers(server.getPlayerCount())
|
||||
.maxPlayers(server.getConfiguration().getShowMaxPlayers())
|
||||
.proxyPort(server.getConfiguration().getBind().getPort())
|
||||
.proxyHost(server.getConfiguration().getBind().getHostString())
|
||||
.players(server.getAllPlayers().stream().map(Player::getUsername).collect(Collectors.toList()))
|
||||
.proxyVersion("Velocity")
|
||||
.plugins(server.getConfiguration().shouldQueryShowPlugins() ? getRealPluginInformation() : Collections.emptyList())
|
||||
.build();
|
||||
|
||||
boolean isBasic = queryMessage.readableBytes() == 0;
|
||||
|
||||
// Call event and write response
|
||||
server.getEventManager().fire(new ProxyQueryEvent(isBasic ? BASIC : FULL, senderAddress, response)).whenCompleteAsync((event, exc) -> {
|
||||
// Packet header
|
||||
queryResponse.writeByte(QUERY_TYPE_STAT);
|
||||
queryResponse.writeInt(sessionId);
|
||||
|
||||
// Start writing the response
|
||||
ResponseWriter responseWriter = new ResponseWriter(queryResponse, isBasic);
|
||||
responseWriter.write("hostname", event.getResponse().getHostname());
|
||||
responseWriter.write("gametype", "SMP");
|
||||
|
||||
responseWriter.write("game_id", "MINECRAFT");
|
||||
responseWriter.write("version", event.getResponse().getGameVersion());
|
||||
responseWriter.writePlugins(event.getResponse().getProxyVersion(), event.getResponse().getPlugins());
|
||||
|
||||
responseWriter.write("map", event.getResponse().getMap());
|
||||
responseWriter.write("numplayers", event.getResponse().getCurrentPlayers());
|
||||
responseWriter.write("maxplayers", event.getResponse().getMaxPlayers());
|
||||
responseWriter.write("hostport", event.getResponse().getProxyPort());
|
||||
responseWriter.write("hostip", event.getResponse().getProxyHost());
|
||||
|
||||
if (!responseWriter.isBasic) {
|
||||
responseWriter.writePlayers(event.getResponse().getPlayers());
|
||||
}
|
||||
|
||||
// Send the response
|
||||
ctx.writeAndFlush(responsePacket);
|
||||
}, ctx.channel().eventLoop());
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Invalid query type: " + type);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
|
||||
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
|
||||
// count.
|
||||
responsePacket.release();
|
||||
// Writes k/v to stat packet body if this writer is initialized
|
||||
// for full stat response. Otherwise this follows
|
||||
// GS4QueryHandler#QUERY_BASIC_RESPONSE_CONTENTS to decide what
|
||||
// to write into packet body
|
||||
void write(String key, Object value) {
|
||||
if (isBasic) {
|
||||
// Basic contains only specific set of data
|
||||
if (!QUERY_BASIC_RESPONSE_CONTENTS.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case hostport
|
||||
if (key.equals("hostport")) {
|
||||
buf.writeShortLE((Integer) value);
|
||||
} else {
|
||||
writeString(buf, value.toString());
|
||||
}
|
||||
} else {
|
||||
writeString(buf, key);
|
||||
writeString(buf, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(ByteBuf buf, String string) {
|
||||
buf.writeCharSequence(string, StandardCharsets.ISO_8859_1);
|
||||
buf.writeByte(0x00);
|
||||
// Ends packet k/v body writing and writes stat player list to
|
||||
// the packet if this writer is initialized for full stat response
|
||||
void writePlayers(Collection<String> players) {
|
||||
if (isBasic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ends the full stat key-value body with \0
|
||||
buf.writeByte(0x00);
|
||||
|
||||
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2);
|
||||
players.forEach(player -> writeString(buf, player));
|
||||
buf.writeByte(0x00);
|
||||
}
|
||||
|
||||
private List<QueryResponse.PluginInformation> getRealPluginInformation() {
|
||||
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
|
||||
List<QueryResponse.PluginInformation> res = pluginInformationList;
|
||||
if (res == null) {
|
||||
synchronized (this) {
|
||||
if (pluginInformationList == null) {
|
||||
pluginInformationList = res = server.getPluginManager().getPlugins().stream()
|
||||
.map(PluginContainer::getDescription)
|
||||
.map(desc -> QueryResponse.PluginInformation.of(desc.getName().orElse(desc.getId()), desc.getVersion().orElse(null)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static class ResponseWriter {
|
||||
private final ByteBuf buf;
|
||||
private final boolean isBasic;
|
||||
|
||||
ResponseWriter(ByteBuf buf, boolean isBasic) {
|
||||
this.buf = buf;
|
||||
this.isBasic = isBasic;
|
||||
|
||||
if (!isBasic) {
|
||||
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes k/v to stat packet body if this writer is initialized
|
||||
// for full stat response. Otherwise this follows
|
||||
// GS4QueryHandler#QUERY_BASIC_RESPONSE_CONTENTS to decide what
|
||||
// to write into packet body
|
||||
void write(String key, Object value) {
|
||||
if (isBasic) {
|
||||
// Basic contains only specific set of data
|
||||
if (!QUERY_BASIC_RESPONSE_CONTENTS.contains(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case hostport
|
||||
if (key.equals("hostport")) {
|
||||
buf.writeShortLE((Integer) value);
|
||||
} else {
|
||||
writeString(buf, value.toString());
|
||||
}
|
||||
} else {
|
||||
writeString(buf, key);
|
||||
writeString(buf, value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Ends packet k/v body writing and writes stat player list to
|
||||
// the packet if this writer is initialized for full stat response
|
||||
void writePlayers(Collection<String> players) {
|
||||
if (isBasic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ends the full stat key-value body with \0
|
||||
buf.writeByte(0x00);
|
||||
|
||||
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2);
|
||||
players.forEach(player -> writeString(buf, player));
|
||||
buf.writeByte(0x00);
|
||||
}
|
||||
|
||||
void writePlugins(String serverVersion, Collection<QueryResponse.PluginInformation> plugins) {
|
||||
if (isBasic)
|
||||
return;
|
||||
|
||||
StringBuilder pluginsString = new StringBuilder();
|
||||
pluginsString.append(serverVersion).append(':').append(' ');
|
||||
Iterator<QueryResponse.PluginInformation> iterator = plugins.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
QueryResponse.PluginInformation info = iterator.next();
|
||||
pluginsString.append(info.getName());
|
||||
if (info.getVersion() != null) {
|
||||
pluginsString.append(' ').append(info.getVersion());
|
||||
}
|
||||
if (iterator.hasNext()) {
|
||||
pluginsString.append(';').append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
writeString(buf, pluginsString.toString());
|
||||
void writePlugins(String serverVersion, Collection<QueryResponse.PluginInformation> plugins) {
|
||||
if (isBasic) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder pluginsString = new StringBuilder();
|
||||
pluginsString.append(serverVersion).append(':').append(' ');
|
||||
Iterator<QueryResponse.PluginInformation> iterator = plugins.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
QueryResponse.PluginInformation info = iterator.next();
|
||||
pluginsString.append(info.getName());
|
||||
if (info.getVersion() != null) {
|
||||
pluginsString.append(' ').append(info.getVersion());
|
||||
}
|
||||
if (iterator.hasNext()) {
|
||||
pluginsString.append(';').append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
writeString(buf, pluginsString.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,26 +5,26 @@ import com.velocitypowered.proxy.protocol.packet.LegacyPing;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class LegacyPingDecoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (in.readableBytes() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
short first = in.getUnsignedByte(in.readerIndex());
|
||||
short second = in.getUnsignedByte(in.readerIndex() + 1);
|
||||
if (first == 0xfe && second == 0x01) {
|
||||
in.skipBytes(in.readableBytes());
|
||||
out.add(new LegacyPing());
|
||||
} else if (first == 0x02) {
|
||||
in.skipBytes(in.readableBytes());
|
||||
out.add(new LegacyHandshake());
|
||||
} else {
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (in.readableBytes() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
short first = in.getUnsignedByte(in.readerIndex());
|
||||
short second = in.getUnsignedByte(in.readerIndex() + 1);
|
||||
if (first == 0xfe && second == 0x01) {
|
||||
in.skipBytes(in.readableBytes());
|
||||
out.add(new LegacyPing());
|
||||
} else if (first == 0x02) {
|
||||
in.skipBytes(in.readableBytes());
|
||||
out.add(new LegacyHandshake());
|
||||
} else {
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,25 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
|
||||
public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder();
|
||||
|
||||
private LegacyPingEncoder() {}
|
||||
public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder();
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out) throws Exception {
|
||||
out.writeByte(0xff);
|
||||
writeLegacyString(out, msg.getReason());
|
||||
}
|
||||
private LegacyPingEncoder() {
|
||||
}
|
||||
|
||||
private static void writeLegacyString(ByteBuf out, String string) {
|
||||
out.writeShort(string.length());
|
||||
out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE));
|
||||
}
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out)
|
||||
throws Exception {
|
||||
out.writeByte(0xff);
|
||||
writeLegacyString(out, msg.getReason());
|
||||
}
|
||||
|
||||
private static void writeLegacyString(ByteBuf out, String string) {
|
||||
out.writeShort(string.length());
|
||||
out.writeBytes(string.getBytes(StandardCharsets.UTF_16BE));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,30 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
||||
private final VelocityCipher cipher;
|
||||
|
||||
public MinecraftCipherDecoder(VelocityCipher cipher) {
|
||||
this.cipher = Preconditions.checkNotNull(cipher, "cipher");
|
||||
}
|
||||
private final VelocityCipher cipher;
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
ByteBuf decrypted = ctx.alloc().buffer(in.readableBytes());
|
||||
try {
|
||||
cipher.process(in, decrypted);
|
||||
out.add(decrypted);
|
||||
} catch (Exception e) {
|
||||
decrypted.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
public MinecraftCipherDecoder(VelocityCipher cipher) {
|
||||
this.cipher = Preconditions.checkNotNull(cipher, "cipher");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
||||
cipher.dispose();
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
ByteBuf decrypted = ctx.alloc().buffer(in.readableBytes());
|
||||
try {
|
||||
cipher.process(in, decrypted);
|
||||
out.add(decrypted);
|
||||
} catch (Exception e) {
|
||||
decrypted.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
||||
cipher.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,24 +7,26 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class MinecraftCipherEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||
private final VelocityCipher cipher;
|
||||
|
||||
public MinecraftCipherEncoder(VelocityCipher cipher) {
|
||||
this.cipher = Preconditions.checkNotNull(cipher, "cipher");
|
||||
}
|
||||
private final VelocityCipher cipher;
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
cipher.process(msg, out);
|
||||
}
|
||||
public MinecraftCipherEncoder(VelocityCipher cipher) {
|
||||
this.cipher = Preconditions.checkNotNull(cipher, "cipher");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
|
||||
return ctx.alloc().directBuffer(msg.readableBytes());
|
||||
}
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
cipher.process(msg, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
cipher.dispose();
|
||||
}
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
|
||||
throws Exception {
|
||||
return ctx.alloc().directBuffer(msg.readableBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
cipher.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,46 +6,49 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
private static final int MAXIMUM_INITIAL_BUFFER_SIZE = 65536; // 64KiB
|
||||
|
||||
private final int threshold;
|
||||
private final VelocityCompressor compressor;
|
||||
private static final int MAXIMUM_INITIAL_BUFFER_SIZE = 65536; // 64KiB
|
||||
|
||||
public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) {
|
||||
this.threshold = threshold;
|
||||
this.compressor = compressor;
|
||||
private final int threshold;
|
||||
private final VelocityCompressor compressor;
|
||||
|
||||
public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) {
|
||||
this.threshold = threshold;
|
||||
this.compressor = compressor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
int uncompressedSize = ProtocolUtils.readVarInt(msg);
|
||||
if (uncompressedSize == 0) {
|
||||
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
||||
out.add(msg.retainedSlice());
|
||||
msg.skipBytes(msg.readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
int uncompressedSize = ProtocolUtils.readVarInt(msg);
|
||||
if (uncompressedSize == 0) {
|
||||
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
||||
out.add(msg.retainedSlice());
|
||||
msg.skipBytes(msg.readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
Preconditions.checkState(uncompressedSize >= threshold, "Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold);
|
||||
// Try to use the uncompressed size, but place a cap if it might be too big (possibly malicious).
|
||||
ByteBuf uncompressed = ctx.alloc().buffer(Math.min(uncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE));
|
||||
try {
|
||||
compressor.inflate(msg, uncompressed);
|
||||
Preconditions.checkState(uncompressedSize == uncompressed.readableBytes(), "Mismatched compression sizes");
|
||||
out.add(uncompressed);
|
||||
} catch (Exception e) {
|
||||
// If something went wrong, rethrow the exception, but ensure we free our temporary buffer first.
|
||||
uncompressed.release();
|
||||
throw e;
|
||||
}
|
||||
Preconditions.checkState(uncompressedSize >= threshold,
|
||||
"Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold);
|
||||
// Try to use the uncompressed size, but place a cap if it might be too big (possibly malicious).
|
||||
ByteBuf uncompressed = ctx.alloc()
|
||||
.buffer(Math.min(uncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE));
|
||||
try {
|
||||
compressor.inflate(msg, uncompressed);
|
||||
Preconditions.checkState(uncompressedSize == uncompressed.readableBytes(),
|
||||
"Mismatched compression sizes");
|
||||
out.add(uncompressed);
|
||||
} catch (Exception e) {
|
||||
// If something went wrong, rethrow the exception, but ensure we free our temporary buffer first.
|
||||
uncompressed.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
compressor.dispose();
|
||||
}
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
compressor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,38 +7,40 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||
private final int threshold;
|
||||
private final VelocityCompressor compressor;
|
||||
|
||||
public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) {
|
||||
this.threshold = threshold;
|
||||
this.compressor = compressor;
|
||||
}
|
||||
private final int threshold;
|
||||
private final VelocityCompressor compressor;
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
int uncompressed = msg.readableBytes();
|
||||
if (uncompressed <= threshold) {
|
||||
// Under the threshold, there is nothing to do.
|
||||
ProtocolUtils.writeVarInt(out, 0);
|
||||
out.writeBytes(msg);
|
||||
} else {
|
||||
ProtocolUtils.writeVarInt(out, uncompressed);
|
||||
compressor.deflate(msg, out);
|
||||
}
|
||||
}
|
||||
public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) {
|
||||
this.threshold = threshold;
|
||||
this.compressor = compressor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
|
||||
if (msg.readableBytes() <= threshold) {
|
||||
return ctx.alloc().directBuffer(msg.readableBytes() + 1);
|
||||
}
|
||||
// A reasonable assumption about compression savings
|
||||
return ctx.alloc().directBuffer(msg.readableBytes() / 3);
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
int uncompressed = msg.readableBytes();
|
||||
if (uncompressed <= threshold) {
|
||||
// Under the threshold, there is nothing to do.
|
||||
ProtocolUtils.writeVarInt(out, 0);
|
||||
out.writeBytes(msg);
|
||||
} else {
|
||||
ProtocolUtils.writeVarInt(out, uncompressed);
|
||||
compressor.deflate(msg, out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
compressor.dispose();
|
||||
@Override
|
||||
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
|
||||
throws Exception {
|
||||
if (msg.readableBytes() <= threshold) {
|
||||
return ctx.alloc().directBuffer(msg.readableBytes() + 1);
|
||||
}
|
||||
// A reasonable assumption about compression savings
|
||||
return ctx.alloc().directBuffer(msg.readableBytes() / 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
compressor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,54 +9,59 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private StateRegistry state;
|
||||
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
|
||||
|
||||
public MinecraftDecoder(ProtocolConstants.Direction direction) {
|
||||
this.direction = Preconditions.checkNotNull(direction, "direction");
|
||||
this.protocolVersion = direction.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private StateRegistry state;
|
||||
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
|
||||
|
||||
public MinecraftDecoder(ProtocolConstants.Direction direction) {
|
||||
this.direction = Preconditions.checkNotNull(direction, "direction");
|
||||
this.protocolVersion = direction
|
||||
.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
if (!msg.isReadable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
if (!msg.isReadable()) {
|
||||
return;
|
||||
}
|
||||
ByteBuf slice = msg.slice();
|
||||
|
||||
ByteBuf slice = msg.slice();
|
||||
|
||||
int packetId = ProtocolUtils.readVarInt(msg);
|
||||
MinecraftPacket packet = this.protocolVersion.createPacket(packetId);
|
||||
if (packet == null) {
|
||||
msg.skipBytes(msg.readableBytes());
|
||||
out.add(slice.retain());
|
||||
} else {
|
||||
try {
|
||||
packet.decode(msg, direction, protocolVersion.version);
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + protocolVersion.version + " 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.version + " State " + state + " ID " + Integer.toHexString(packetId));
|
||||
}
|
||||
out.add(packet);
|
||||
}
|
||||
int packetId = ProtocolUtils.readVarInt(msg);
|
||||
MinecraftPacket packet = this.protocolVersion.createPacket(packetId);
|
||||
if (packet == null) {
|
||||
msg.skipBytes(msg.readableBytes());
|
||||
out.add(slice.retain());
|
||||
} else {
|
||||
try {
|
||||
packet.decode(msg, direction, protocolVersion.version);
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException(
|
||||
"Error decoding " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + protocolVersion.version + " 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.version + " State " + state + " ID " + Integer
|
||||
.toHexString(packetId));
|
||||
}
|
||||
out.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = direction.getProtocol(state, protocolVersion);
|
||||
}
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = direction.getProtocol(state, protocolVersion);
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.setProtocolVersion(protocolVersion.version);
|
||||
}
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.setProtocolVersion(protocolVersion.version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,29 +10,31 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private StateRegistry state;
|
||||
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
|
||||
|
||||
public MinecraftEncoder(ProtocolConstants.Direction direction) {
|
||||
this.direction = Preconditions.checkNotNull(direction, "direction");
|
||||
this.protocolVersion = direction.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
}
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private StateRegistry state;
|
||||
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) {
|
||||
int packetId = this.protocolVersion.getPacketId(msg);
|
||||
ProtocolUtils.writeVarInt(out, packetId);
|
||||
msg.encode(out, direction, protocolVersion.version);
|
||||
}
|
||||
public MinecraftEncoder(ProtocolConstants.Direction direction) {
|
||||
this.direction = Preconditions.checkNotNull(direction, "direction");
|
||||
this.protocolVersion = direction
|
||||
.getProtocol(StateRegistry.HANDSHAKE, ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(final int protocolVersion) {
|
||||
this.protocolVersion = direction.getProtocol(state, protocolVersion);
|
||||
}
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) {
|
||||
int packetId = this.protocolVersion.getPacketId(msg);
|
||||
ProtocolUtils.writeVarInt(out, packetId);
|
||||
msg.encode(out, direction, protocolVersion.version);
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.setProtocolVersion(protocolVersion.version);
|
||||
}
|
||||
public void setProtocolVersion(final int protocolVersion) {
|
||||
this.protocolVersion = direction.getProtocol(state, protocolVersion);
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.setProtocolVersion(protocolVersion.version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,42 +6,42 @@ 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;
|
||||
|
||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (!in.isReadable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
in.markReaderIndex();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CorruptedFrameException("VarInt too big");
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (!in.isReadable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
in.markReaderIndex();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CorruptedFrameException("VarInt too big");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,20 +5,22 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||
|
||||
private MinecraftVarintLengthEncoder() { }
|
||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint
|
||||
ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes());
|
||||
list.add(lengthBuf);
|
||||
list.add(buf.retain());
|
||||
}
|
||||
private MinecraftVarintLengthEncoder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list)
|
||||
throws Exception {
|
||||
ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint
|
||||
ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes());
|
||||
list.add(lengthBuf);
|
||||
list.add(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,172 +5,172 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BossBar implements MinecraftPacket {
|
||||
public static final int ADD = 0;
|
||||
public static final int REMOVE = 1;
|
||||
public static final int UPDATE_PERCENT = 2;
|
||||
public static final int UPDATE_NAME = 3;
|
||||
public static final int UPDATE_STYLE = 4;
|
||||
public static final int UPDATE_PROPERTIES = 5;
|
||||
private @Nullable UUID uuid;
|
||||
private int action;
|
||||
private @Nullable String name;
|
||||
private float percent;
|
||||
private int color;
|
||||
private int overlay;
|
||||
private short flags;
|
||||
|
||||
public UUID getUuid() {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No boss bar UUID specified");
|
||||
public static final int ADD = 0;
|
||||
public static final int REMOVE = 1;
|
||||
public static final int UPDATE_PERCENT = 2;
|
||||
public static final int UPDATE_NAME = 3;
|
||||
public static final int UPDATE_STYLE = 4;
|
||||
public static final int UPDATE_PROPERTIES = 5;
|
||||
private @Nullable UUID uuid;
|
||||
private int action;
|
||||
private @Nullable String name;
|
||||
private float percent;
|
||||
private int color;
|
||||
private int overlay;
|
||||
private short flags;
|
||||
|
||||
public UUID getUuid() {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No boss bar UUID specified");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public @Nullable String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public float getPercent() {
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(float percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getOverlay() {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
public void setOverlay(int overlay) {
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
public short getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(short flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BossBar{" +
|
||||
"uuid=" + uuid +
|
||||
", action=" + action +
|
||||
", name='" + name + '\'' +
|
||||
", percent=" + percent +
|
||||
", color=" + color +
|
||||
", overlay=" + overlay +
|
||||
", flags=" + flags +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.uuid = ProtocolUtils.readUuid(buf);
|
||||
this.action = ProtocolUtils.readVarInt(buf);
|
||||
switch (action) {
|
||||
case ADD:
|
||||
this.name = ProtocolUtils.readString(buf);
|
||||
this.percent = buf.readFloat();
|
||||
this.color = ProtocolUtils.readVarInt(buf);
|
||||
this.overlay = ProtocolUtils.readVarInt(buf);
|
||||
this.flags = buf.readUnsignedByte();
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case UPDATE_PERCENT:
|
||||
this.percent = buf.readFloat();
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
this.name = ProtocolUtils.readString(buf);
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
this.color = ProtocolUtils.readVarInt(buf);
|
||||
this.overlay = ProtocolUtils.readVarInt(buf);
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
this.flags = buf.readUnsignedByte();
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No boss bar UUID specified");
|
||||
}
|
||||
ProtocolUtils.writeUuid(buf, uuid);
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
switch (action) {
|
||||
case ADD:
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public @Nullable String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public float getPercent() {
|
||||
return percent;
|
||||
}
|
||||
|
||||
public void setPercent(float percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getOverlay() {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
public void setOverlay(int overlay) {
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
public short getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(short flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BossBar{" +
|
||||
"uuid=" + uuid +
|
||||
", action=" + action +
|
||||
", name='" + name + '\'' +
|
||||
", percent=" + percent +
|
||||
", color=" + color +
|
||||
", overlay=" + overlay +
|
||||
", flags=" + flags +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.uuid = ProtocolUtils.readUuid(buf);
|
||||
this.action = ProtocolUtils.readVarInt(buf);
|
||||
switch (action) {
|
||||
case ADD:
|
||||
this.name = ProtocolUtils.readString(buf);
|
||||
this.percent = buf.readFloat();
|
||||
this.color = ProtocolUtils.readVarInt(buf);
|
||||
this.overlay = ProtocolUtils.readVarInt(buf);
|
||||
this.flags = buf.readUnsignedByte();
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case UPDATE_PERCENT:
|
||||
this.percent = buf.readFloat();
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
this.name = ProtocolUtils.readString(buf);
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
this.color = ProtocolUtils.readVarInt(buf);
|
||||
this.overlay = ProtocolUtils.readVarInt(buf);
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
this.flags = buf.readUnsignedByte();
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
ProtocolUtils.writeString(buf, name);
|
||||
buf.writeFloat(percent);
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
buf.writeByte(flags);
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case UPDATE_PERCENT:
|
||||
buf.writeFloat(percent);
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, name);
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
buf.writeByte(flags);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No boss bar UUID specified");
|
||||
}
|
||||
ProtocolUtils.writeUuid(buf, uuid);
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
switch (action) {
|
||||
case ADD:
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, name);
|
||||
buf.writeFloat(percent);
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
buf.writeByte(flags);
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case UPDATE_PERCENT:
|
||||
buf.writeFloat(percent);
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
if (name == null) {
|
||||
throw new IllegalStateException("No name specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, name);
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
buf.writeByte(flags);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,81 +11,82 @@ import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class Chat implements MinecraftPacket {
|
||||
public static final byte CHAT_TYPE = (byte) 0;
|
||||
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
|
||||
|
||||
private @Nullable String message;
|
||||
private byte type;
|
||||
public static final byte CHAT_TYPE = (byte) 0;
|
||||
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
|
||||
|
||||
public Chat() {
|
||||
private @Nullable String message;
|
||||
private byte type;
|
||||
|
||||
public Chat() {
|
||||
}
|
||||
|
||||
public Chat(String message, byte type) {
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
if (message == null) {
|
||||
throw new IllegalStateException("Message is not specified");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public Chat(String message, byte type) {
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
}
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
if (message == null) {
|
||||
throw new IllegalStateException("Message is not specified");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
public byte getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
public void setType(byte type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public byte getType() {
|
||||
return type;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Chat{" +
|
||||
"message='" + message + '\'' +
|
||||
", type=" + type +
|
||||
'}';
|
||||
}
|
||||
|
||||
public void setType(byte type) {
|
||||
this.type = type;
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
message = ProtocolUtils.readString(buf);
|
||||
if (direction == ProtocolConstants.Direction.CLIENTBOUND) {
|
||||
type = buf.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Chat{" +
|
||||
"message='" + message + '\'' +
|
||||
", type=" + type +
|
||||
'}';
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (message == null) {
|
||||
throw new IllegalStateException("Message is not specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, message);
|
||||
if (direction == ProtocolConstants.Direction.CLIENTBOUND) {
|
||||
buf.writeByte(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
message = ProtocolUtils.readString(buf);
|
||||
if (direction == ProtocolConstants.Direction.CLIENTBOUND) {
|
||||
type = buf.readByte();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (message == null) {
|
||||
throw new IllegalStateException("Message is not specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, message);
|
||||
if (direction == ProtocolConstants.Direction.CLIENTBOUND) {
|
||||
buf.writeByte(type);
|
||||
}
|
||||
}
|
||||
public static Chat createClientbound(Component component) {
|
||||
return createClientbound(component, CHAT_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
public static Chat createClientbound(Component component, byte type) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Chat(ComponentSerializers.JSON.serialize(component), type);
|
||||
}
|
||||
|
||||
public static Chat createClientbound(Component component) {
|
||||
return createClientbound(component, CHAT_TYPE);
|
||||
}
|
||||
|
||||
public static Chat createClientbound(Component component, byte type) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Chat(ComponentSerializers.JSON.serialize(component), type);
|
||||
}
|
||||
|
||||
public static Chat createServerbound(String message) {
|
||||
return new Chat(message, CHAT_TYPE);
|
||||
}
|
||||
public static Chat createServerbound(String message) {
|
||||
return new Chat(message, CHAT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,119 +9,120 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ClientSettings implements MinecraftPacket {
|
||||
|
||||
private @Nullable String locale;
|
||||
private byte viewDistance;
|
||||
private int chatVisibility;
|
||||
private boolean chatColors;
|
||||
private short skinParts;
|
||||
private int mainHand;
|
||||
private @Nullable String locale;
|
||||
private byte viewDistance;
|
||||
private int chatVisibility;
|
||||
private boolean chatColors;
|
||||
private short skinParts;
|
||||
private int mainHand;
|
||||
|
||||
public ClientSettings() {
|
||||
public ClientSettings() {
|
||||
}
|
||||
|
||||
public ClientSettings(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
|
||||
short skinParts, int mainHand) {
|
||||
this.locale = locale;
|
||||
this.viewDistance = viewDistance;
|
||||
this.chatVisibility = chatVisibility;
|
||||
this.chatColors = chatColors;
|
||||
this.skinParts = skinParts;
|
||||
this.mainHand = mainHand;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
if (locale == null) {
|
||||
throw new IllegalStateException("No locale specified");
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
public ClientSettings(String locale, byte viewDistance, int chatVisibility, boolean chatColors, short skinParts, int mainHand) {
|
||||
this.locale = locale;
|
||||
this.viewDistance = viewDistance;
|
||||
this.chatVisibility = chatVisibility;
|
||||
this.chatColors = chatColors;
|
||||
this.skinParts = skinParts;
|
||||
this.mainHand = mainHand;
|
||||
public void setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public byte getViewDistance() {
|
||||
return viewDistance;
|
||||
}
|
||||
|
||||
public void setViewDistance(byte viewDistance) {
|
||||
this.viewDistance = viewDistance;
|
||||
}
|
||||
|
||||
public int getChatVisibility() {
|
||||
return chatVisibility;
|
||||
}
|
||||
|
||||
public void setChatVisibility(int chatVisibility) {
|
||||
this.chatVisibility = chatVisibility;
|
||||
}
|
||||
|
||||
public boolean isChatColors() {
|
||||
return chatColors;
|
||||
}
|
||||
|
||||
public void setChatColors(boolean chatColors) {
|
||||
this.chatColors = chatColors;
|
||||
}
|
||||
|
||||
public short getSkinParts() {
|
||||
return skinParts;
|
||||
}
|
||||
|
||||
public void setSkinParts(short skinParts) {
|
||||
this.skinParts = skinParts;
|
||||
}
|
||||
|
||||
public int getMainHand() {
|
||||
return mainHand;
|
||||
}
|
||||
|
||||
public void setMainHand(int mainHand) {
|
||||
this.mainHand = mainHand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientSettings{"
|
||||
+ "locale='" + locale + '\''
|
||||
+ ", viewDistance=" + viewDistance
|
||||
+ ", chatVisibility=" + chatVisibility
|
||||
+ ", chatColors=" + chatColors
|
||||
+ ", skinParts=" + skinParts
|
||||
+ ", mainHand=" + mainHand
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.locale = ProtocolUtils.readString(buf, 16);
|
||||
this.viewDistance = buf.readByte();
|
||||
this.chatVisibility = ProtocolUtils.readVarInt(buf);
|
||||
this.chatColors = buf.readBoolean();
|
||||
this.skinParts = buf.readUnsignedByte();
|
||||
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
|
||||
this.mainHand = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
if (locale == null) {
|
||||
throw new IllegalStateException("No locale specified");
|
||||
}
|
||||
return locale;
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (locale == null) {
|
||||
throw new IllegalStateException("No locale specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, locale);
|
||||
buf.writeByte(viewDistance);
|
||||
ProtocolUtils.writeVarInt(buf, chatVisibility);
|
||||
buf.writeBoolean(chatColors);
|
||||
buf.writeByte(skinParts);
|
||||
|
||||
public void setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
|
||||
ProtocolUtils.writeVarInt(buf, mainHand);
|
||||
}
|
||||
}
|
||||
|
||||
public byte getViewDistance() {
|
||||
return viewDistance;
|
||||
}
|
||||
|
||||
public void setViewDistance(byte viewDistance) {
|
||||
this.viewDistance = viewDistance;
|
||||
}
|
||||
|
||||
public int getChatVisibility() {
|
||||
return chatVisibility;
|
||||
}
|
||||
|
||||
public void setChatVisibility(int chatVisibility) {
|
||||
this.chatVisibility = chatVisibility;
|
||||
}
|
||||
|
||||
public boolean isChatColors() {
|
||||
return chatColors;
|
||||
}
|
||||
|
||||
public void setChatColors(boolean chatColors) {
|
||||
this.chatColors = chatColors;
|
||||
}
|
||||
|
||||
public short getSkinParts() {
|
||||
return skinParts;
|
||||
}
|
||||
|
||||
public void setSkinParts(short skinParts) {
|
||||
this.skinParts = skinParts;
|
||||
}
|
||||
|
||||
public int getMainHand() {
|
||||
return mainHand;
|
||||
}
|
||||
|
||||
public void setMainHand(int mainHand) {
|
||||
this.mainHand = mainHand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClientSettings{"
|
||||
+ "locale='" + locale + '\''
|
||||
+ ", viewDistance=" + viewDistance
|
||||
+ ", chatVisibility=" + chatVisibility
|
||||
+ ", chatColors=" + chatColors
|
||||
+ ", skinParts=" + skinParts
|
||||
+ ", mainHand=" + mainHand
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.locale = ProtocolUtils.readString(buf, 16);
|
||||
this.viewDistance = buf.readByte();
|
||||
this.chatVisibility = ProtocolUtils.readVarInt(buf);
|
||||
this.chatColors = buf.readBoolean();
|
||||
this.skinParts = buf.readUnsignedByte();
|
||||
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
|
||||
this.mainHand = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (locale == null) {
|
||||
throw new IllegalStateException("No locale specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, locale);
|
||||
buf.writeByte(viewDistance);
|
||||
ProtocolUtils.writeVarInt(buf, chatVisibility);
|
||||
buf.writeBoolean(chatColors);
|
||||
buf.writeByte(skinParts);
|
||||
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
|
||||
ProtocolUtils.writeVarInt(buf, mainHand);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,53 +11,54 @@ import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class Disconnect implements MinecraftPacket {
|
||||
private @Nullable String reason;
|
||||
|
||||
public Disconnect() {
|
||||
}
|
||||
private @Nullable String reason;
|
||||
|
||||
public Disconnect(String reason) {
|
||||
this.reason = Preconditions.checkNotNull(reason, "reason");
|
||||
}
|
||||
public Disconnect() {
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
if (reason == null) {
|
||||
throw new IllegalStateException("No reason specified");
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
public Disconnect(String reason) {
|
||||
this.reason = Preconditions.checkNotNull(reason, "reason");
|
||||
}
|
||||
|
||||
public void setReason(@Nullable String reason) {
|
||||
this.reason = reason;
|
||||
public String getReason() {
|
||||
if (reason == null) {
|
||||
throw new IllegalStateException("No reason specified");
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Disconnect{" +
|
||||
"reason='" + reason + '\'' +
|
||||
'}';
|
||||
}
|
||||
public void setReason(@Nullable String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
reason = ProtocolUtils.readString(buf);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Disconnect{" +
|
||||
"reason='" + reason + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (reason == null) {
|
||||
throw new IllegalStateException("No reason specified.");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, reason);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
reason = ProtocolUtils.readString(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (reason == null) {
|
||||
throw new IllegalStateException("No reason specified.");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, reason);
|
||||
}
|
||||
|
||||
public static Disconnect create(Component component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Disconnect(ComponentSerializers.JSON.serialize(component));
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public static Disconnect create(Component component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Disconnect(ComponentSerializers.JSON.serialize(component));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.*;
|
||||
|
||||
public class EncryptionRequest implements MinecraftPacket {
|
||||
private String serverId = "";
|
||||
private byte[] publicKey = EMPTY_BYTE_ARRAY;
|
||||
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
private String serverId = "";
|
||||
private byte[] publicKey = EMPTY_BYTE_ARRAY;
|
||||
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
|
||||
|
||||
public void setPublicKey(byte[] publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
public byte[] getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
public void setPublicKey(byte[] publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public void setVerifyToken(byte[] verifyToken) {
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EncryptionRequest{" +
|
||||
"publicKey=" + Arrays.toString(publicKey) +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
public void setVerifyToken(byte[] verifyToken) {
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.serverId = ProtocolUtils.readString(buf, 20);
|
||||
publicKey = ProtocolUtils.readByteArray(buf, 256);
|
||||
verifyToken = ProtocolUtils.readByteArray(buf, 16);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EncryptionRequest{" +
|
||||
"publicKey=" + Arrays.toString(publicKey) +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, this.serverId);
|
||||
ProtocolUtils.writeByteArray(buf, publicKey);
|
||||
ProtocolUtils.writeByteArray(buf, verifyToken);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.serverId = ProtocolUtils.readString(buf, 20);
|
||||
publicKey = ProtocolUtils.readByteArray(buf, 256);
|
||||
verifyToken = ProtocolUtils.readByteArray(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, this.serverId);
|
||||
ProtocolUtils.writeByteArray(buf, publicKey);
|
||||
ProtocolUtils.writeByteArray(buf, verifyToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.*;
|
||||
|
||||
public class EncryptionResponse implements MinecraftPacket {
|
||||
private byte[] sharedSecret = EMPTY_BYTE_ARRAY;
|
||||
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
|
||||
|
||||
public byte[] getSharedSecret() {
|
||||
return sharedSecret;
|
||||
}
|
||||
private byte[] sharedSecret = EMPTY_BYTE_ARRAY;
|
||||
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
|
||||
|
||||
public void setSharedSecret(byte[] sharedSecret) {
|
||||
this.sharedSecret = sharedSecret;
|
||||
}
|
||||
public byte[] getSharedSecret() {
|
||||
return sharedSecret;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
public void setSharedSecret(byte[] sharedSecret) {
|
||||
this.sharedSecret = sharedSecret;
|
||||
}
|
||||
|
||||
public void setVerifyToken(byte[] verifyToken) {
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EncryptionResponse{" +
|
||||
"sharedSecret=" + Arrays.toString(sharedSecret) +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
public void setVerifyToken(byte[] verifyToken) {
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.sharedSecret = ProtocolUtils.readByteArray(buf, 256);
|
||||
this.verifyToken = ProtocolUtils.readByteArray(buf, 128);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EncryptionResponse{" +
|
||||
"sharedSecret=" + Arrays.toString(sharedSecret) +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeByteArray(buf, sharedSecret);
|
||||
ProtocolUtils.writeByteArray(buf, verifyToken);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.sharedSecret = ProtocolUtils.readByteArray(buf, 256);
|
||||
this.verifyToken = ProtocolUtils.readByteArray(buf, 128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeByteArray(buf, sharedSecret);
|
||||
ProtocolUtils.writeByteArray(buf, verifyToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,71 +7,72 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class Handshake implements MinecraftPacket {
|
||||
private int protocolVersion;
|
||||
private String serverAddress = "";
|
||||
private int port;
|
||||
private int nextStatus;
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
private int protocolVersion;
|
||||
private String serverAddress = "";
|
||||
private int port;
|
||||
private int nextStatus;
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public String getServerAddress() {
|
||||
return serverAddress;
|
||||
}
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
public void setServerAddress(String serverAddress) {
|
||||
this.serverAddress = serverAddress;
|
||||
}
|
||||
public String getServerAddress() {
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
public void setServerAddress(String serverAddress) {
|
||||
this.serverAddress = serverAddress;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public int getNextStatus() {
|
||||
return nextStatus;
|
||||
}
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void setNextStatus(int nextStatus) {
|
||||
this.nextStatus = nextStatus;
|
||||
}
|
||||
public int getNextStatus() {
|
||||
return nextStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Handshake{" +
|
||||
"protocolVersion=" + protocolVersion +
|
||||
", serverAddress='" + serverAddress + '\'' +
|
||||
", port=" + port +
|
||||
", nextStatus=" + nextStatus +
|
||||
'}';
|
||||
}
|
||||
public void setNextStatus(int nextStatus) {
|
||||
this.nextStatus = nextStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.protocolVersion = ProtocolUtils.readVarInt(buf);
|
||||
this.serverAddress = ProtocolUtils.readString(buf);
|
||||
this.port = buf.readUnsignedShort();
|
||||
this.nextStatus = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Handshake{" +
|
||||
"protocolVersion=" + protocolVersion +
|
||||
", serverAddress='" + serverAddress + '\'' +
|
||||
", port=" + port +
|
||||
", nextStatus=" + nextStatus +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, this.protocolVersion);
|
||||
ProtocolUtils.writeString(buf, this.serverAddress);
|
||||
buf.writeShort(this.port);
|
||||
ProtocolUtils.writeVarInt(buf, this.nextStatus);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.protocolVersion = ProtocolUtils.readVarInt(buf);
|
||||
this.serverAddress = ProtocolUtils.readString(buf);
|
||||
this.port = buf.readUnsignedShort();
|
||||
this.nextStatus = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, this.protocolVersion);
|
||||
ProtocolUtils.writeString(buf, this.serverAddress);
|
||||
buf.writeShort(this.port);
|
||||
ProtocolUtils.writeVarInt(buf, this.nextStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
@@ -9,54 +11,53 @@ import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializer;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
|
||||
|
||||
public class HeaderAndFooter implements MinecraftPacket {
|
||||
private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}";
|
||||
private static final HeaderAndFooter RESET = new HeaderAndFooter();
|
||||
|
||||
private String header;
|
||||
private String footer;
|
||||
|
||||
public HeaderAndFooter() {
|
||||
this(EMPTY_COMPONENT, EMPTY_COMPONENT);
|
||||
}
|
||||
private static final String EMPTY_COMPONENT = "{\"translate\":\"\"}";
|
||||
private static final HeaderAndFooter RESET = new HeaderAndFooter();
|
||||
|
||||
public HeaderAndFooter(String header, String footer) {
|
||||
this.header = Preconditions.checkNotNull(header, "header");
|
||||
this.footer = Preconditions.checkNotNull(footer, "footer");
|
||||
}
|
||||
private String header;
|
||||
private String footer;
|
||||
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
public HeaderAndFooter() {
|
||||
this(EMPTY_COMPONENT, EMPTY_COMPONENT);
|
||||
}
|
||||
|
||||
public String getFooter() {
|
||||
return footer;
|
||||
}
|
||||
public HeaderAndFooter(String header, String footer) {
|
||||
this.header = Preconditions.checkNotNull(header, "header");
|
||||
this.footer = Preconditions.checkNotNull(footer, "footer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException("Decode is not implemented");
|
||||
}
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, Direction direction, int protocolVersion) {
|
||||
writeString(buf, header);
|
||||
writeString(buf, footer);
|
||||
}
|
||||
public String getFooter() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException("Decode is not implemented");
|
||||
}
|
||||
|
||||
public static HeaderAndFooter create(Component header, Component footer) {
|
||||
ComponentSerializer<Component, Component, String> json = ComponentSerializers.JSON;
|
||||
return new HeaderAndFooter(json.serialize(header), json.serialize(footer));
|
||||
}
|
||||
|
||||
public static HeaderAndFooter reset() {
|
||||
return RESET;
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, Direction direction, int protocolVersion) {
|
||||
writeString(buf, header);
|
||||
writeString(buf, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
public static HeaderAndFooter create(Component header, Component footer) {
|
||||
ComponentSerializer<Component, Component, String> json = ComponentSerializers.JSON;
|
||||
return new HeaderAndFooter(json.serialize(header), json.serialize(footer));
|
||||
}
|
||||
|
||||
public static HeaderAndFooter reset() {
|
||||
return RESET;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,121 +8,122 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class JoinGame implements MinecraftPacket {
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private short difficulty;
|
||||
private short maxPlayers;
|
||||
private @Nullable String levelType;
|
||||
private boolean reducedDebugInfo;
|
||||
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private short difficulty;
|
||||
private short maxPlayers;
|
||||
private @Nullable String levelType;
|
||||
private boolean reducedDebugInfo;
|
||||
|
||||
public void setEntityId(int entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
public short getGamemode() {
|
||||
return gamemode;
|
||||
}
|
||||
public void setEntityId(int entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
public void setGamemode(short gamemode) {
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
public short getGamemode() {
|
||||
return gamemode;
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
public void setGamemode(short gamemode) {
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
|
||||
public void setDimension(int dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
public void setDimension(int dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public void setDifficulty(short difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public short getMaxPlayers() {
|
||||
return maxPlayers;
|
||||
}
|
||||
public void setDifficulty(short difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
public void setMaxPlayers(short maxPlayers) {
|
||||
this.maxPlayers = maxPlayers;
|
||||
}
|
||||
public short getMaxPlayers() {
|
||||
return maxPlayers;
|
||||
}
|
||||
|
||||
public String getLevelType() {
|
||||
if (levelType == null) {
|
||||
throw new IllegalStateException("No level type specified.");
|
||||
}
|
||||
return levelType;
|
||||
}
|
||||
public void setMaxPlayers(short maxPlayers) {
|
||||
this.maxPlayers = maxPlayers;
|
||||
}
|
||||
|
||||
public void setLevelType(String levelType) {
|
||||
this.levelType = levelType;
|
||||
public String getLevelType() {
|
||||
if (levelType == null) {
|
||||
throw new IllegalStateException("No level type specified.");
|
||||
}
|
||||
return levelType;
|
||||
}
|
||||
|
||||
public boolean isReducedDebugInfo() {
|
||||
return reducedDebugInfo;
|
||||
}
|
||||
public void setLevelType(String levelType) {
|
||||
this.levelType = levelType;
|
||||
}
|
||||
|
||||
public void setReducedDebugInfo(boolean reducedDebugInfo) {
|
||||
this.reducedDebugInfo = reducedDebugInfo;
|
||||
}
|
||||
public boolean isReducedDebugInfo() {
|
||||
return reducedDebugInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoinGame{" +
|
||||
"entityId=" + entityId +
|
||||
", gamemode=" + gamemode +
|
||||
", dimension=" + dimension +
|
||||
", difficulty=" + difficulty +
|
||||
", maxPlayers=" + maxPlayers +
|
||||
", levelType='" + levelType + '\'' +
|
||||
", reducedDebugInfo=" + reducedDebugInfo +
|
||||
'}';
|
||||
}
|
||||
public void setReducedDebugInfo(boolean reducedDebugInfo) {
|
||||
this.reducedDebugInfo = reducedDebugInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.entityId = buf.readInt();
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) {
|
||||
this.dimension = buf.readInt();
|
||||
} else {
|
||||
this.dimension = buf.readByte();
|
||||
}
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
this.maxPlayers = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
this.reducedDebugInfo = buf.readBoolean();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoinGame{" +
|
||||
"entityId=" + entityId +
|
||||
", gamemode=" + gamemode +
|
||||
", dimension=" + dimension +
|
||||
", difficulty=" + difficulty +
|
||||
", maxPlayers=" + maxPlayers +
|
||||
", levelType='" + levelType + '\'' +
|
||||
", reducedDebugInfo=" + reducedDebugInfo +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeByte(gamemode);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) {
|
||||
buf.writeInt(dimension);
|
||||
} else {
|
||||
buf.writeByte(dimension);
|
||||
}
|
||||
buf.writeByte(difficulty);
|
||||
buf.writeByte(maxPlayers);
|
||||
if (levelType == null) {
|
||||
throw new IllegalStateException("No level type specified.");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
buf.writeBoolean(reducedDebugInfo);
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.entityId = buf.readInt();
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) {
|
||||
this.dimension = buf.readInt();
|
||||
} else {
|
||||
this.dimension = buf.readByte();
|
||||
}
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
this.maxPlayers = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
this.reducedDebugInfo = buf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeByte(gamemode);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9_1) {
|
||||
buf.writeInt(dimension);
|
||||
} else {
|
||||
buf.writeByte(dimension);
|
||||
}
|
||||
buf.writeByte(difficulty);
|
||||
buf.writeByte(maxPlayers);
|
||||
if (levelType == null) {
|
||||
throw new IllegalStateException("No level type specified.");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
buf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,52 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
|
||||
|
||||
public class KeepAlive implements MinecraftPacket {
|
||||
private long randomId;
|
||||
|
||||
public long getRandomId() {
|
||||
return randomId;
|
||||
}
|
||||
private long randomId;
|
||||
|
||||
public void setRandomId(long randomId) {
|
||||
this.randomId = randomId;
|
||||
}
|
||||
public long getRandomId() {
|
||||
return randomId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeepAlive{" +
|
||||
"randomId=" + randomId +
|
||||
'}';
|
||||
}
|
||||
public void setRandomId(long randomId) {
|
||||
this.randomId = randomId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_12_2) {
|
||||
randomId = buf.readLong();
|
||||
} else {
|
||||
randomId = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeepAlive{" +
|
||||
"randomId=" + randomId +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_12_2) {
|
||||
buf.writeLong(randomId);
|
||||
} else {
|
||||
ProtocolUtils.writeVarInt(buf, (int) randomId);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_12_2) {
|
||||
randomId = buf.readLong();
|
||||
} else {
|
||||
randomId = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_12_2) {
|
||||
buf.writeLong(randomId);
|
||||
} else {
|
||||
ProtocolUtils.writeVarInt(buf, (int) randomId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,29 +4,30 @@ import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
public class LegacyDisconnect {
|
||||
private final String reason;
|
||||
|
||||
public LegacyDisconnect(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
private final String reason;
|
||||
|
||||
public static LegacyDisconnect fromPingResponse(LegacyPingResponse response) {
|
||||
String kickMessage = String.join("\0",
|
||||
"§1",
|
||||
Integer.toString(response.getProtocolVersion()),
|
||||
response.getServerVersion(),
|
||||
response.getMotd(),
|
||||
Integer.toString(response.getPlayersOnline()),
|
||||
Integer.toString(response.getPlayersMax())
|
||||
);
|
||||
return new LegacyDisconnect(kickMessage);
|
||||
}
|
||||
public LegacyDisconnect(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public static LegacyDisconnect from(TextComponent component) {
|
||||
return new LegacyDisconnect(ComponentSerializers.LEGACY.serialize(component));
|
||||
}
|
||||
public static LegacyDisconnect fromPingResponse(LegacyPingResponse response) {
|
||||
String kickMessage = String.join("\0",
|
||||
"§1",
|
||||
Integer.toString(response.getProtocolVersion()),
|
||||
response.getServerVersion(),
|
||||
response.getMotd(),
|
||||
Integer.toString(response.getPlayersOnline()),
|
||||
Integer.toString(response.getPlayersMax())
|
||||
);
|
||||
return new LegacyDisconnect(kickMessage);
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
public static LegacyDisconnect from(TextComponent component) {
|
||||
return new LegacyDisconnect(ComponentSerializers.LEGACY.serialize(component));
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,19 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class LegacyHandshake implements MinecraftPacket {
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,19 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class LegacyPing implements MinecraftPacket {
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,57 +5,60 @@ import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
public class LegacyPingResponse {
|
||||
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0, ImmutableList.of());
|
||||
private final int protocolVersion;
|
||||
private final String serverVersion;
|
||||
private final String motd;
|
||||
private final int playersOnline;
|
||||
private final int playersMax;
|
||||
|
||||
public LegacyPingResponse(int protocolVersion, String serverVersion, String motd, int playersOnline, int playersMax) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.serverVersion = serverVersion;
|
||||
this.motd = motd;
|
||||
this.playersOnline = playersOnline;
|
||||
this.playersMax = playersMax;
|
||||
}
|
||||
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0,
|
||||
ImmutableList.of());
|
||||
private final int protocolVersion;
|
||||
private final String serverVersion;
|
||||
private final String motd;
|
||||
private final int playersOnline;
|
||||
private final int playersMax;
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
public LegacyPingResponse(int protocolVersion, String serverVersion, String motd,
|
||||
int playersOnline, int playersMax) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.serverVersion = serverVersion;
|
||||
this.motd = motd;
|
||||
this.playersOnline = playersOnline;
|
||||
this.playersMax = playersMax;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
return serverVersion;
|
||||
}
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public String getMotd() {
|
||||
return motd;
|
||||
}
|
||||
public String getServerVersion() {
|
||||
return serverVersion;
|
||||
}
|
||||
|
||||
public int getPlayersOnline() {
|
||||
return playersOnline;
|
||||
}
|
||||
public String getMotd() {
|
||||
return motd;
|
||||
}
|
||||
|
||||
public int getPlayersMax() {
|
||||
return playersMax;
|
||||
}
|
||||
public int getPlayersOnline() {
|
||||
return playersOnline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LegacyPingResponse{" +
|
||||
"protocolVersion=" + protocolVersion +
|
||||
", serverVersion='" + serverVersion + '\'' +
|
||||
", motd='" + motd + '\'' +
|
||||
", playersOnline=" + playersOnline +
|
||||
", playersMax=" + playersMax +
|
||||
'}';
|
||||
}
|
||||
public int getPlayersMax() {
|
||||
return playersMax;
|
||||
}
|
||||
|
||||
public static LegacyPingResponse from(ServerPing ping) {
|
||||
return new LegacyPingResponse(ping.getVersion().getProtocol(),
|
||||
ping.getVersion().getName(),
|
||||
ComponentSerializers.LEGACY.serialize(ping.getDescription()),
|
||||
ping.getPlayers().orElse(FAKE_PLAYERS).getOnline(),
|
||||
ping.getPlayers().orElse(FAKE_PLAYERS).getMax());
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LegacyPingResponse{" +
|
||||
"protocolVersion=" + protocolVersion +
|
||||
", serverVersion='" + serverVersion + '\'' +
|
||||
", motd='" + motd + '\'' +
|
||||
", playersOnline=" + playersOnline +
|
||||
", playersMax=" + playersMax +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static LegacyPingResponse from(ServerPing ping) {
|
||||
return new LegacyPingResponse(ping.getVersion().getProtocol(),
|
||||
ping.getVersion().getName(),
|
||||
ComponentSerializers.LEGACY.serialize(ping.getDescription()),
|
||||
ping.getPlayers().orElse(FAKE_PLAYERS).getOnline(),
|
||||
ping.getPlayers().orElse(FAKE_PLAYERS).getMax());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,67 +9,68 @@ import io.netty.buffer.Unpooled;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class LoginPluginMessage implements MinecraftPacket {
|
||||
private int id;
|
||||
private @Nullable String channel;
|
||||
private ByteBuf data = Unpooled.EMPTY_BUFFER;
|
||||
|
||||
public LoginPluginMessage() {
|
||||
private int id;
|
||||
private @Nullable String channel;
|
||||
private ByteBuf data = Unpooled.EMPTY_BUFFER;
|
||||
|
||||
public LoginPluginMessage() {
|
||||
|
||||
}
|
||||
|
||||
public LoginPluginMessage(int id, @Nullable String channel, ByteBuf data) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified!");
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
public LoginPluginMessage(int id, @Nullable String channel, ByteBuf data) {
|
||||
this.id = id;
|
||||
this.channel = channel;
|
||||
this.data = data;
|
||||
}
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginPluginMessage{" +
|
||||
"id=" + id +
|
||||
", channel='" + channel + '\'' +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified!");
|
||||
}
|
||||
return channel;
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readVarInt(buf);
|
||||
this.channel = ProtocolUtils.readString(buf);
|
||||
if (buf.isReadable()) {
|
||||
this.data = buf.readSlice(buf.readableBytes());
|
||||
} else {
|
||||
this.data = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, channel);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginPluginMessage{" +
|
||||
"id=" + id +
|
||||
", channel='" + channel + '\'' +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readVarInt(buf);
|
||||
this.channel = ProtocolUtils.readString(buf);
|
||||
if (buf.isReadable()) {
|
||||
this.data = buf.readSlice(buf.readableBytes());
|
||||
} else {
|
||||
this.data = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, channel);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,63 +8,64 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class LoginPluginResponse implements MinecraftPacket {
|
||||
private int id;
|
||||
private boolean success;
|
||||
private ByteBuf data = Unpooled.EMPTY_BUFFER;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
private int id;
|
||||
private boolean success;
|
||||
private ByteBuf data = Unpooled.EMPTY_BUFFER;
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
}
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public void setData(ByteBuf data) {
|
||||
this.data = data;
|
||||
}
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginPluginResponse{" +
|
||||
"id=" + id +
|
||||
", success=" + success +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
public void setData(ByteBuf data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readVarInt(buf);
|
||||
this.success = buf.readBoolean();
|
||||
if (buf.isReadable()) {
|
||||
this.data = buf.readSlice(buf.readableBytes());
|
||||
} else {
|
||||
this.data = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginPluginResponse{" +
|
||||
"id=" + id +
|
||||
", success=" + success +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
buf.writeBoolean(success);
|
||||
buf.writeBytes(data);
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readVarInt(buf);
|
||||
this.success = buf.readBoolean();
|
||||
if (buf.isReadable()) {
|
||||
this.data = buf.readSlice(buf.readableBytes());
|
||||
} else {
|
||||
this.data = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
buf.writeBoolean(success);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,194 +8,196 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerListItem implements MinecraftPacket {
|
||||
public static final int ADD_PLAYER = 0;
|
||||
public static final int UPDATE_GAMEMODE = 1;
|
||||
public static final int UPDATE_LATENCY = 2;
|
||||
public static final int UPDATE_DISPLAY_NAME = 3;
|
||||
public static final int REMOVE_PLAYER = 4;
|
||||
private int action;
|
||||
private final List<Item> items = new ArrayList<>();
|
||||
|
||||
public PlayerListItem(int action, List<Item> items) {
|
||||
this.action = action;
|
||||
this.items.addAll(items);
|
||||
public static final int ADD_PLAYER = 0;
|
||||
public static final int UPDATE_GAMEMODE = 1;
|
||||
public static final int UPDATE_LATENCY = 2;
|
||||
public static final int UPDATE_DISPLAY_NAME = 3;
|
||||
public static final int REMOVE_PLAYER = 4;
|
||||
private int action;
|
||||
private final List<Item> items = new ArrayList<>();
|
||||
|
||||
public PlayerListItem(int action, List<Item> items) {
|
||||
this.action = action;
|
||||
this.items.addAll(items);
|
||||
}
|
||||
|
||||
public PlayerListItem() {
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public List<Item> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
action = ProtocolUtils.readVarInt(buf);
|
||||
int length = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
Item item = new Item(ProtocolUtils.readUuid(buf));
|
||||
items.add(item);
|
||||
switch (action) {
|
||||
case ADD_PLAYER: {
|
||||
item.setName(ProtocolUtils.readString(buf));
|
||||
item.setProperties(ProtocolUtils.readProperties(buf));
|
||||
item.setGameMode(ProtocolUtils.readVarInt(buf));
|
||||
item.setLatency(ProtocolUtils.readVarInt(buf));
|
||||
item.setDisplayName(readOptionalComponent(buf));
|
||||
break;
|
||||
}
|
||||
case UPDATE_GAMEMODE:
|
||||
item.setGameMode(ProtocolUtils.readVarInt(buf));
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
item.setLatency(ProtocolUtils.readVarInt(buf));
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
item.setDisplayName(readOptionalComponent(buf));
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
//Do nothing, all that is needed is the uuid
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Component readOptionalComponent(ByteBuf buf) {
|
||||
if (buf.readBoolean()) {
|
||||
return ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
ProtocolUtils.writeVarInt(buf, items.size());
|
||||
for (Item item : items) {
|
||||
ProtocolUtils.writeUuid(buf, item.getUuid());
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
ProtocolUtils.writeString(buf, item.getName());
|
||||
ProtocolUtils.writeProperties(buf, item.getProperties());
|
||||
ProtocolUtils.writeVarInt(buf, item.getGameMode());
|
||||
ProtocolUtils.writeVarInt(buf, item.getLatency());
|
||||
|
||||
writeDisplayName(buf, item.getDisplayName());
|
||||
break;
|
||||
case UPDATE_GAMEMODE:
|
||||
ProtocolUtils.writeVarInt(buf, item.getGameMode());
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
ProtocolUtils.writeVarInt(buf, item.getLatency());
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
writeDisplayName(buf, item.getDisplayName());
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
//Do nothing, all that is needed is the uuid
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
|
||||
private void writeDisplayName(ByteBuf buf, @Nullable Component displayName) {
|
||||
buf.writeBoolean(displayName != null);
|
||||
if (displayName != null) {
|
||||
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(displayName));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Item {
|
||||
|
||||
private final UUID uuid;
|
||||
private String name = "";
|
||||
private List<GameProfile.Property> properties = ImmutableList.of();
|
||||
private int gameMode;
|
||||
private int latency;
|
||||
private @Nullable Component displayName;
|
||||
|
||||
public Item(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public PlayerListItem() {}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
public static Item from(TabListEntry entry) {
|
||||
return new Item(entry.getProfile().idAsUuid())
|
||||
.setName(entry.getProfile().getName())
|
||||
.setProperties(entry.getProfile().getProperties())
|
||||
.setLatency(entry.getLatency())
|
||||
.setGameMode(entry.getGameMode())
|
||||
.setDisplayName(entry.getDisplayName().orElse(null));
|
||||
}
|
||||
|
||||
public List<Item> getItems() {
|
||||
return items;
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
action = ProtocolUtils.readVarInt(buf);
|
||||
int length = ProtocolUtils.readVarInt(buf);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
Item item = new Item(ProtocolUtils.readUuid(buf));
|
||||
items.add(item);
|
||||
switch (action) {
|
||||
case ADD_PLAYER: {
|
||||
item.setName(ProtocolUtils.readString(buf));
|
||||
item.setProperties(ProtocolUtils.readProperties(buf));
|
||||
item.setGameMode(ProtocolUtils.readVarInt(buf));
|
||||
item.setLatency(ProtocolUtils.readVarInt(buf));
|
||||
item.setDisplayName(readOptionalComponent(buf));
|
||||
break;
|
||||
}
|
||||
case UPDATE_GAMEMODE:
|
||||
item.setGameMode(ProtocolUtils.readVarInt(buf));
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
item.setLatency(ProtocolUtils.readVarInt(buf));
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
item.setDisplayName(readOptionalComponent(buf));
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
//Do nothing, all that is needed is the uuid
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Component readOptionalComponent(ByteBuf buf) {
|
||||
if (buf.readBoolean()) {
|
||||
return ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf));
|
||||
}
|
||||
return null;
|
||||
public Item setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
ProtocolUtils.writeVarInt(buf, items.size());
|
||||
for (Item item : items) {
|
||||
ProtocolUtils.writeUuid(buf, item.getUuid());
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
ProtocolUtils.writeString(buf, item.getName());
|
||||
ProtocolUtils.writeProperties(buf, item.getProperties());
|
||||
ProtocolUtils.writeVarInt(buf, item.getGameMode());
|
||||
ProtocolUtils.writeVarInt(buf, item.getLatency());
|
||||
|
||||
writeDisplayName(buf, item.getDisplayName());
|
||||
break;
|
||||
case UPDATE_GAMEMODE:
|
||||
ProtocolUtils.writeVarInt(buf, item.getGameMode());
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
ProtocolUtils.writeVarInt(buf, item.getLatency());
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
writeDisplayName(buf, item.getDisplayName());
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
//Do nothing, all that is needed is the uuid
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
public List<GameProfile.Property> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
public Item setProperties(List<GameProfile.Property> properties) {
|
||||
this.properties = properties;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void writeDisplayName(ByteBuf buf, @Nullable Component displayName) {
|
||||
buf.writeBoolean(displayName != null);
|
||||
if (displayName != null) {
|
||||
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(displayName));
|
||||
}
|
||||
public int getGameMode() {
|
||||
return gameMode;
|
||||
}
|
||||
|
||||
public static class Item {
|
||||
private final UUID uuid;
|
||||
private String name = "";
|
||||
private List<GameProfile.Property> properties = ImmutableList.of();
|
||||
private int gameMode;
|
||||
private int latency;
|
||||
private @Nullable Component displayName;
|
||||
|
||||
public Item(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public static Item from(TabListEntry entry) {
|
||||
return new Item(entry.getProfile().idAsUuid())
|
||||
.setName(entry.getProfile().getName())
|
||||
.setProperties(entry.getProfile().getProperties())
|
||||
.setLatency(entry.getLatency())
|
||||
.setGameMode(entry.getGameMode())
|
||||
.setDisplayName(entry.getDisplayName().orElse(null));
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Item setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<GameProfile.Property> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Item setProperties(List<GameProfile.Property> properties) {
|
||||
this.properties = properties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getGameMode() {
|
||||
return gameMode;
|
||||
}
|
||||
|
||||
public Item setGameMode(int gamemode) {
|
||||
this.gameMode = gamemode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getLatency() {
|
||||
return latency;
|
||||
}
|
||||
|
||||
public Item setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public Item setDisplayName(@Nullable Component displayName) {
|
||||
this.displayName = displayName;
|
||||
return this;
|
||||
}
|
||||
public Item setGameMode(int gamemode) {
|
||||
this.gameMode = gamemode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getLatency() {
|
||||
return latency;
|
||||
}
|
||||
|
||||
public Item setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @Nullable Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public Item setDisplayName(@Nullable Component displayName) {
|
||||
this.displayName = displayName;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
@@ -8,57 +10,56 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.*;
|
||||
|
||||
public class PluginMessage implements MinecraftPacket {
|
||||
private @Nullable String channel;
|
||||
private byte[] data = EMPTY_BYTE_ARRAY;
|
||||
|
||||
public String getChannel() {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified.");
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
private @Nullable String channel;
|
||||
private byte[] data = EMPTY_BYTE_ARRAY;
|
||||
|
||||
public void setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
public String getChannel() {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified.");
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
public void setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginMessage{" +
|
||||
"channel='" + channel + '\'' +
|
||||
", data=" + ByteBufUtil.hexDump(data) +
|
||||
'}';
|
||||
}
|
||||
public void setData(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.channel = ProtocolUtils.readString(buf);
|
||||
this.data = new byte[buf.readableBytes()];
|
||||
buf.readBytes(data);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginMessage{" +
|
||||
"channel='" + channel + '\'' +
|
||||
", data=" + ByteBufUtil.hexDump(data) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified.");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, channel);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.channel = ProtocolUtils.readString(buf);
|
||||
this.data = new byte[buf.readableBytes()];
|
||||
buf.readBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (channel == null) {
|
||||
throw new IllegalStateException("Channel is not specified.");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, channel);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,81 +7,82 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class Respawn implements MinecraftPacket {
|
||||
private int dimension;
|
||||
private short difficulty;
|
||||
private short gamemode;
|
||||
private String levelType = "";
|
||||
|
||||
public Respawn() {
|
||||
}
|
||||
private int dimension;
|
||||
private short difficulty;
|
||||
private short gamemode;
|
||||
private String levelType = "";
|
||||
|
||||
public Respawn(int dimension, short difficulty, short gamemode, String levelType) {
|
||||
this.dimension = dimension;
|
||||
this.difficulty = difficulty;
|
||||
this.gamemode = gamemode;
|
||||
this.levelType = levelType;
|
||||
}
|
||||
public Respawn() {
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
public Respawn(int dimension, short difficulty, short gamemode, String levelType) {
|
||||
this.dimension = dimension;
|
||||
this.difficulty = difficulty;
|
||||
this.gamemode = gamemode;
|
||||
this.levelType = levelType;
|
||||
}
|
||||
|
||||
public void setDimension(int dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
public void setDimension(int dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public void setDifficulty(short difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public short getGamemode() {
|
||||
return gamemode;
|
||||
}
|
||||
public void setDifficulty(short difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
public void setGamemode(short gamemode) {
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
public short getGamemode() {
|
||||
return gamemode;
|
||||
}
|
||||
|
||||
public String getLevelType() {
|
||||
return levelType;
|
||||
}
|
||||
public void setGamemode(short gamemode) {
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
|
||||
public void setLevelType(String levelType) {
|
||||
this.levelType = levelType;
|
||||
}
|
||||
public String getLevelType() {
|
||||
return levelType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Respawn{" +
|
||||
"dimension=" + dimension +
|
||||
", difficulty=" + difficulty +
|
||||
", gamemode=" + gamemode +
|
||||
", levelType='" + levelType + '\'' +
|
||||
'}';
|
||||
}
|
||||
public void setLevelType(String levelType) {
|
||||
this.levelType = levelType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.dimension = buf.readInt();
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Respawn{" +
|
||||
"dimension=" + dimension +
|
||||
", difficulty=" + difficulty +
|
||||
", gamemode=" + gamemode +
|
||||
", levelType='" + levelType + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeInt(dimension);
|
||||
buf.writeByte(difficulty);
|
||||
buf.writeByte(gamemode);
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.dimension = buf.readInt();
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeInt(dimension);
|
||||
buf.writeByte(difficulty);
|
||||
buf.writeByte(gamemode);
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,43 +9,45 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ServerLogin implements MinecraftPacket {
|
||||
private @Nullable String username;
|
||||
|
||||
public ServerLogin() {}
|
||||
private @Nullable String username;
|
||||
|
||||
public ServerLogin(String username) {
|
||||
this.username = Preconditions.checkNotNull(username, "username");
|
||||
public ServerLogin() {
|
||||
}
|
||||
|
||||
public ServerLogin(String username) {
|
||||
this.username = Preconditions.checkNotNull(username, "username");
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username found!");
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username found!");
|
||||
}
|
||||
return username;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerLogin{" +
|
||||
"username='" + username + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerLogin{" +
|
||||
"username='" + username + '\'' +
|
||||
'}';
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
username = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
username = ProtocolUtils.readString(buf, 16);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username found!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username found!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,64 +5,64 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ServerLoginSuccess implements MinecraftPacket {
|
||||
private @Nullable UUID uuid;
|
||||
private @Nullable String username;
|
||||
|
||||
public UUID getUuid() {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No UUID specified!");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
private @Nullable UUID uuid;
|
||||
private @Nullable String username;
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
public UUID getUuid() {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No UUID specified!");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username specified!");
|
||||
}
|
||||
return username;
|
||||
}
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
public String getUsername() {
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username specified!");
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerLoginSuccess{" +
|
||||
"uuid=" + uuid +
|
||||
", username='" + username + '\'' +
|
||||
'}';
|
||||
}
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
uuid = UUID.fromString(ProtocolUtils.readString(buf, 36));
|
||||
username = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerLoginSuccess{" +
|
||||
"uuid=" + uuid +
|
||||
", username='" + username + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No UUID specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, uuid.toString());
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, username);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
uuid = UUID.fromString(ProtocolUtils.readString(buf, 36));
|
||||
username = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (uuid == null) {
|
||||
throw new IllegalStateException("No UUID specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, uuid.toString());
|
||||
if (username == null) {
|
||||
throw new IllegalStateException("No username specified!");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,41 +7,43 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class SetCompression implements MinecraftPacket {
|
||||
private int threshold;
|
||||
|
||||
public SetCompression() {}
|
||||
private int threshold;
|
||||
|
||||
public SetCompression(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
public SetCompression() {
|
||||
}
|
||||
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
public SetCompression(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
public void setThreshold(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SetCompression{" +
|
||||
"threshold=" + threshold +
|
||||
'}';
|
||||
}
|
||||
public void setThreshold(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.threshold = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SetCompression{" +
|
||||
"threshold=" + threshold +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, threshold);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.threshold = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, threshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,21 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusPing implements MinecraftPacket {
|
||||
private long randomId;
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
randomId = buf.readLong();
|
||||
}
|
||||
private long randomId;
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeLong(randomId);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
randomId = buf.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeLong(randomId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,30 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusRequest implements MinecraftPacket {
|
||||
public static final StatusRequest INSTANCE = new StatusRequest();
|
||||
|
||||
private StatusRequest() {
|
||||
public static final StatusRequest INSTANCE = new StatusRequest();
|
||||
|
||||
}
|
||||
private StatusRequest() {
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
// There is no additional data to decode.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
// There is no data to decode.
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
// There is no additional data to decode.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StatusRequest";
|
||||
}
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
// There is no data to decode.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StatusRequest";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,43 +8,45 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class StatusResponse implements MinecraftPacket {
|
||||
private @Nullable String status;
|
||||
|
||||
public StatusResponse() {}
|
||||
private @Nullable String status;
|
||||
|
||||
public StatusResponse(String status) {
|
||||
this.status = status;
|
||||
public StatusResponse() {
|
||||
}
|
||||
|
||||
public StatusResponse(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Status is not specified");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Status is not specified");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StatusResponse{" +
|
||||
"status='" + status + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StatusResponse{" +
|
||||
"status='" + status + '\'' +
|
||||
'}';
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
status = ProtocolUtils.readString(buf, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
status = ProtocolUtils.readString(buf, Short.MAX_VALUE);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Status is not specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (status == null) {
|
||||
throw new IllegalStateException("Status is not specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
|
||||
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
@@ -7,88 +9,87 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
|
||||
|
||||
public class TabCompleteRequest implements MinecraftPacket {
|
||||
private @Nullable String command;
|
||||
private boolean assumeCommand;
|
||||
private boolean hasPosition;
|
||||
private long position;
|
||||
|
||||
public String getCommand() {
|
||||
if (command == null) {
|
||||
throw new IllegalStateException("Command is not specified");
|
||||
}
|
||||
return command;
|
||||
}
|
||||
private @Nullable String command;
|
||||
private boolean assumeCommand;
|
||||
private boolean hasPosition;
|
||||
private long position;
|
||||
|
||||
public void setCommand(String command) {
|
||||
this.command = command;
|
||||
public String getCommand() {
|
||||
if (command == null) {
|
||||
throw new IllegalStateException("Command is not specified");
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
public boolean isAssumeCommand() {
|
||||
return assumeCommand;
|
||||
}
|
||||
public void setCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void setAssumeCommand(boolean assumeCommand) {
|
||||
this.assumeCommand = assumeCommand;
|
||||
}
|
||||
public boolean isAssumeCommand() {
|
||||
return assumeCommand;
|
||||
}
|
||||
|
||||
public boolean isHasPosition() {
|
||||
return hasPosition;
|
||||
}
|
||||
public void setAssumeCommand(boolean assumeCommand) {
|
||||
this.assumeCommand = assumeCommand;
|
||||
}
|
||||
|
||||
public void setHasPosition(boolean hasPosition) {
|
||||
this.hasPosition = hasPosition;
|
||||
}
|
||||
public boolean isHasPosition() {
|
||||
return hasPosition;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return position;
|
||||
}
|
||||
public void setHasPosition(boolean hasPosition) {
|
||||
this.hasPosition = hasPosition;
|
||||
}
|
||||
|
||||
public void setPosition(long position) {
|
||||
this.position = position;
|
||||
}
|
||||
public long getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabCompleteRequest{" +
|
||||
"command='" + command + '\'' +
|
||||
", assumeCommand=" + assumeCommand +
|
||||
", hasPosition=" + hasPosition +
|
||||
", position=" + position +
|
||||
'}';
|
||||
}
|
||||
public void setPosition(long position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.command = ProtocolUtils.readString(buf);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
this.assumeCommand = buf.readBoolean();
|
||||
}
|
||||
this.hasPosition = buf.readBoolean();
|
||||
if (hasPosition) {
|
||||
this.position = buf.readLong();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabCompleteRequest{" +
|
||||
"command='" + command + '\'' +
|
||||
", assumeCommand=" + assumeCommand +
|
||||
", hasPosition=" + hasPosition +
|
||||
", position=" + position +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (command == null) {
|
||||
throw new IllegalStateException("Command is not specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, command);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
buf.writeBoolean(assumeCommand);
|
||||
}
|
||||
buf.writeBoolean(hasPosition);
|
||||
if (hasPosition) {
|
||||
buf.writeLong(position);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.command = ProtocolUtils.readString(buf);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
this.assumeCommand = buf.readBoolean();
|
||||
}
|
||||
this.hasPosition = buf.readBoolean();
|
||||
if (hasPosition) {
|
||||
this.position = buf.readLong();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (command == null) {
|
||||
throw new IllegalStateException("Command is not specified");
|
||||
}
|
||||
ProtocolUtils.writeString(buf, command);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
buf.writeBoolean(assumeCommand);
|
||||
}
|
||||
buf.writeBoolean(hasPosition);
|
||||
if (hasPosition) {
|
||||
buf.writeLong(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,42 +5,42 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TabCompleteResponse implements MinecraftPacket {
|
||||
private final List<String> offers = new ArrayList<>();
|
||||
|
||||
public List<String> getOffers() {
|
||||
return offers;
|
||||
}
|
||||
private final List<String> offers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabCompleteResponse{" +
|
||||
"offers=" + offers +
|
||||
'}';
|
||||
}
|
||||
public List<String> getOffers() {
|
||||
return offers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
int offersAvailable = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < offersAvailable; i++) {
|
||||
offers.add(ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabCompleteResponse{" +
|
||||
"offers=" + offers +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, offers.size());
|
||||
for (String offer : offers) {
|
||||
ProtocolUtils.writeString(buf, offer);
|
||||
}
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
int offersAvailable = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < offersAvailable; i++) {
|
||||
offers.add(ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, offers.size());
|
||||
for (String offer : offers) {
|
||||
ProtocolUtils.writeString(buf, offer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,146 +8,150 @@ import io.netty.buffer.ByteBuf;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
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 @Nullable String component;
|
||||
private int fadeIn;
|
||||
private int stay;
|
||||
private int fadeOut;
|
||||
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;
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException(); // encode only
|
||||
private int action;
|
||||
private @Nullable 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:
|
||||
if (component == null) {
|
||||
throw new IllegalStateException("No component found for " + action);
|
||||
}
|
||||
ProtocolUtils.writeString(buf, component);
|
||||
break;
|
||||
case SET_TIMES:
|
||||
buf.writeInt(fadeIn);
|
||||
buf.writeInt(stay);
|
||||
buf.writeInt(fadeOut);
|
||||
break;
|
||||
case HIDE:
|
||||
case RESET:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
} else {
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
if (component == null) {
|
||||
throw new IllegalStateException("No component found for " + action);
|
||||
}
|
||||
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;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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:
|
||||
if (component == null) {
|
||||
throw new IllegalStateException("No component found for " + action);
|
||||
}
|
||||
ProtocolUtils.writeString(buf, component);
|
||||
break;
|
||||
case SET_TIMES:
|
||||
buf.writeInt(fadeIn);
|
||||
buf.writeInt(stay);
|
||||
buf.writeInt(fadeOut);
|
||||
break;
|
||||
case HIDE:
|
||||
case RESET:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
} else {
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
if (component == null) {
|
||||
throw new IllegalStateException("No component found for " + action);
|
||||
}
|
||||
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;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown action " + action);
|
||||
}
|
||||
}
|
||||
}
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
public @Nullable String getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public @Nullable String getComponent() {
|
||||
return component;
|
||||
}
|
||||
public void setComponent(@Nullable String component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
public void setComponent(@Nullable String component) {
|
||||
this.component = component;
|
||||
}
|
||||
public int getFadeIn() {
|
||||
return fadeIn;
|
||||
}
|
||||
|
||||
public int getFadeIn() {
|
||||
return fadeIn;
|
||||
}
|
||||
public void setFadeIn(int fadeIn) {
|
||||
this.fadeIn = fadeIn;
|
||||
}
|
||||
|
||||
public void setFadeIn(int fadeIn) {
|
||||
this.fadeIn = fadeIn;
|
||||
}
|
||||
public int getStay() {
|
||||
return stay;
|
||||
}
|
||||
|
||||
public int getStay() {
|
||||
return stay;
|
||||
}
|
||||
public void setStay(int stay) {
|
||||
this.stay = stay;
|
||||
}
|
||||
|
||||
public void setStay(int stay) {
|
||||
this.stay = stay;
|
||||
}
|
||||
public int getFadeOut() {
|
||||
return fadeOut;
|
||||
}
|
||||
|
||||
public int getFadeOut() {
|
||||
return fadeOut;
|
||||
}
|
||||
public void setFadeOut(int fadeOut) {
|
||||
this.fadeOut = 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 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 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;
|
||||
}
|
||||
|
||||
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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TitlePacket{" +
|
||||
"action=" + action +
|
||||
", component='" + component + '\'' +
|
||||
", fadeIn=" + fadeIn +
|
||||
", stay=" + stay +
|
||||
", fadeOut=" + fadeOut +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler handler) {
|
||||
return handler.handle(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
package com.velocitypowered.proxy.protocol.util;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class FaviconSerializer implements JsonSerializer<Favicon>, JsonDeserializer<Favicon> {
|
||||
@Override
|
||||
public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
|
||||
return new Favicon(json.getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.getBase64Url());
|
||||
}
|
||||
@Override
|
||||
public Favicon deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
|
||||
return new Favicon(json.getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Favicon src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(src.getBase64Url());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,74 +7,80 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class PluginMessageUtil {
|
||||
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
|
||||
public static final String BRAND_CHANNEL = "minecraft:brand";
|
||||
public static final String REGISTER_CHANNEL_LEGACY = "REGISTER";
|
||||
public static final String REGISTER_CHANNEL = "minecraft:register";
|
||||
public static final String UNREGISTER_CHANNEL_LEGACY = "UNREGISTER";
|
||||
public static final String UNREGISTER_CHANNEL = "minecraft:unregister";
|
||||
|
||||
private PluginMessageUtil() {
|
||||
throw new AssertionError();
|
||||
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
|
||||
public static final String BRAND_CHANNEL = "minecraft:brand";
|
||||
public static final String REGISTER_CHANNEL_LEGACY = "REGISTER";
|
||||
public static final String REGISTER_CHANNEL = "minecraft:register";
|
||||
public static final String UNREGISTER_CHANNEL_LEGACY = "UNREGISTER";
|
||||
public static final String UNREGISTER_CHANNEL = "minecraft:unregister";
|
||||
|
||||
private PluginMessageUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static boolean isMCBrand(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals(BRAND_CHANNEL_LEGACY) || message.getChannel()
|
||||
.equals(BRAND_CHANNEL);
|
||||
}
|
||||
|
||||
public static boolean isMCRegister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals(REGISTER_CHANNEL_LEGACY) || message.getChannel()
|
||||
.equals(REGISTER_CHANNEL);
|
||||
}
|
||||
|
||||
public static boolean isMCUnregister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals(UNREGISTER_CHANNEL_LEGACY) || message.getChannel()
|
||||
.equals(UNREGISTER_CHANNEL);
|
||||
}
|
||||
|
||||
public static List<String> getChannels(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
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"));
|
||||
}
|
||||
|
||||
public static PluginMessage constructChannelsPacket(int protocolVersion,
|
||||
Collection<String> channels) {
|
||||
Preconditions.checkNotNull(channels, "channels");
|
||||
String channelName = protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ? REGISTER_CHANNEL
|
||||
: REGISTER_CHANNEL_LEGACY;
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(channelName);
|
||||
message.setData(String.join("\0", channels).getBytes(StandardCharsets.UTF_8));
|
||||
return message;
|
||||
}
|
||||
|
||||
public static PluginMessage rewriteMCBrand(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(isMCBrand(message), "message is not a MC Brand plugin message");
|
||||
|
||||
byte[] rewrittenData;
|
||||
ByteBuf rewrittenBuf = Unpooled.buffer();
|
||||
try {
|
||||
String currentBrand = ProtocolUtils.readString(Unpooled.wrappedBuffer(message.getData()));
|
||||
ProtocolUtils.writeString(rewrittenBuf, currentBrand + " (Velocity)");
|
||||
rewrittenData = new byte[rewrittenBuf.readableBytes()];
|
||||
rewrittenBuf.readBytes(rewrittenData);
|
||||
} finally {
|
||||
rewrittenBuf.release();
|
||||
}
|
||||
|
||||
public static boolean isMCBrand(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals(BRAND_CHANNEL_LEGACY) || message.getChannel().equals(BRAND_CHANNEL);
|
||||
}
|
||||
|
||||
public static boolean isMCRegister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals(REGISTER_CHANNEL_LEGACY) || message.getChannel().equals(REGISTER_CHANNEL);
|
||||
}
|
||||
|
||||
public static boolean isMCUnregister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals(UNREGISTER_CHANNEL_LEGACY) || message.getChannel().equals(UNREGISTER_CHANNEL);
|
||||
}
|
||||
|
||||
public static List<String> getChannels(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
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"));
|
||||
}
|
||||
|
||||
public static PluginMessage constructChannelsPacket(int protocolVersion, Collection<String> channels) {
|
||||
Preconditions.checkNotNull(channels, "channels");
|
||||
String channelName = protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ? REGISTER_CHANNEL : REGISTER_CHANNEL_LEGACY;
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(channelName);
|
||||
message.setData(String.join("\0", channels).getBytes(StandardCharsets.UTF_8));
|
||||
return message;
|
||||
}
|
||||
|
||||
public static PluginMessage rewriteMCBrand(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(isMCBrand(message), "message is not a MC Brand plugin message");
|
||||
|
||||
byte[] rewrittenData;
|
||||
ByteBuf rewrittenBuf = Unpooled.buffer();
|
||||
try {
|
||||
String currentBrand = ProtocolUtils.readString(Unpooled.wrappedBuffer(message.getData()));
|
||||
ProtocolUtils.writeString(rewrittenBuf, currentBrand + " (Velocity)");
|
||||
rewrittenData = new byte[rewrittenBuf.readableBytes()];
|
||||
rewrittenBuf.readBytes(rewrittenData);
|
||||
} finally {
|
||||
rewrittenBuf.release();
|
||||
}
|
||||
|
||||
PluginMessage newMsg = new PluginMessage();
|
||||
newMsg.setChannel(message.getChannel());
|
||||
newMsg.setData(rewrittenData);
|
||||
return newMsg;
|
||||
}
|
||||
PluginMessage newMsg = new PluginMessage();
|
||||
newMsg.setChannel(message.getChannel());
|
||||
newMsg.setData(rewrittenData);
|
||||
return newMsg;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,174 +9,184 @@ import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import com.velocitypowered.api.scheduler.Scheduler;
|
||||
import com.velocitypowered.api.scheduler.TaskStatus;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class VelocityScheduler implements Scheduler {
|
||||
private final PluginManager pluginManager;
|
||||
private final ExecutorService taskService;
|
||||
private final ScheduledExecutorService timerExecutionService;
|
||||
private final Multimap<Object, ScheduledTask> tasksByPlugin = Multimaps.synchronizedMultimap(
|
||||
Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new));
|
||||
|
||||
public VelocityScheduler(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.taskService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true)
|
||||
.setNameFormat("Velocity Task Scheduler - #%d").build());
|
||||
this.timerExecutionService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true)
|
||||
.setNameFormat("Velocity Task Scheduler Timer").build());
|
||||
private final PluginManager pluginManager;
|
||||
private final ExecutorService taskService;
|
||||
private final ScheduledExecutorService timerExecutionService;
|
||||
private final Multimap<Object, ScheduledTask> tasksByPlugin = Multimaps.synchronizedMultimap(
|
||||
Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new));
|
||||
|
||||
public VelocityScheduler(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.taskService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true)
|
||||
.setNameFormat("Velocity Task Scheduler - #%d").build());
|
||||
this.timerExecutionService = Executors
|
||||
.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true)
|
||||
.setNameFormat("Velocity Task Scheduler Timer").build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder buildTask(Object plugin, Runnable runnable) {
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
Preconditions.checkNotNull(runnable, "runnable");
|
||||
Preconditions
|
||||
.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "plugin is not registered");
|
||||
return new TaskBuilderImpl(plugin, runnable);
|
||||
}
|
||||
|
||||
public boolean shutdown() throws InterruptedException {
|
||||
Collection<ScheduledTask> terminating;
|
||||
synchronized (tasksByPlugin) {
|
||||
terminating = ImmutableList.copyOf(tasksByPlugin.values());
|
||||
}
|
||||
for (ScheduledTask task : terminating) {
|
||||
task.cancel();
|
||||
}
|
||||
timerExecutionService.shutdown();
|
||||
taskService.shutdown();
|
||||
return taskService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private class TaskBuilderImpl implements TaskBuilder {
|
||||
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private long delay; // ms
|
||||
private long repeat; // ms
|
||||
|
||||
private TaskBuilderImpl(Object plugin, Runnable runnable) {
|
||||
this.plugin = plugin;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder buildTask(Object plugin, Runnable runnable) {
|
||||
Preconditions.checkNotNull(plugin, "plugin");
|
||||
Preconditions.checkNotNull(runnable, "runnable");
|
||||
Preconditions.checkArgument(pluginManager.fromInstance(plugin).isPresent(), "plugin is not registered");
|
||||
return new TaskBuilderImpl(plugin, runnable);
|
||||
public TaskBuilder delay(long time, TimeUnit unit) {
|
||||
this.delay = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean shutdown() throws InterruptedException {
|
||||
Collection<ScheduledTask> terminating;
|
||||
synchronized (tasksByPlugin) {
|
||||
terminating = ImmutableList.copyOf(tasksByPlugin.values());
|
||||
}
|
||||
for (ScheduledTask task : terminating) {
|
||||
task.cancel();
|
||||
}
|
||||
timerExecutionService.shutdown();
|
||||
taskService.shutdown();
|
||||
return taskService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
@Override
|
||||
public TaskBuilder repeat(long time, TimeUnit unit) {
|
||||
this.repeat = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
|
||||
private class TaskBuilderImpl implements TaskBuilder {
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private long delay; // ms
|
||||
private long repeat; // ms
|
||||
|
||||
private TaskBuilderImpl(Object plugin, Runnable runnable) {
|
||||
this.plugin = plugin;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder delay(long time, TimeUnit unit) {
|
||||
this.delay = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder repeat(long time, TimeUnit unit) {
|
||||
this.repeat = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder clearDelay() {
|
||||
this.delay = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder clearRepeat() {
|
||||
this.repeat = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledTask schedule() {
|
||||
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
task.schedule();
|
||||
return task;
|
||||
}
|
||||
@Override
|
||||
public TaskBuilder clearDelay() {
|
||||
this.delay = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
private class VelocityTask implements Runnable, ScheduledTask {
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private final long delay;
|
||||
private final long repeat;
|
||||
private @Nullable ScheduledFuture<?> future;
|
||||
private volatile @Nullable Thread currentTaskThread;
|
||||
|
||||
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
|
||||
this.plugin = plugin;
|
||||
this.runnable = runnable;
|
||||
this.delay = delay;
|
||||
this.repeat = repeat;
|
||||
}
|
||||
|
||||
public void schedule() {
|
||||
if (repeat == 0) {
|
||||
this.future = timerExecutionService.schedule(this, delay, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
this.future = timerExecutionService.scheduleAtFixedRate(this, delay, repeat, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskStatus status() {
|
||||
if (future == null) {
|
||||
return TaskStatus.SCHEDULED;
|
||||
}
|
||||
|
||||
if (future.isCancelled()) {
|
||||
return TaskStatus.CANCELLED;
|
||||
}
|
||||
|
||||
if (future.isDone()) {
|
||||
return TaskStatus.FINISHED;
|
||||
}
|
||||
|
||||
return TaskStatus.SCHEDULED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
|
||||
Thread cur = currentTaskThread;
|
||||
if (cur != null) {
|
||||
cur.interrupt();
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
taskService.execute(() -> {
|
||||
currentTaskThread = Thread.currentThread();
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
|
||||
} finally {
|
||||
currentTaskThread = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onFinish() {
|
||||
tasksByPlugin.remove(plugin, this);
|
||||
}
|
||||
@Override
|
||||
public TaskBuilder clearRepeat() {
|
||||
this.repeat = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class Log {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityTask.class);
|
||||
@Override
|
||||
public ScheduledTask schedule() {
|
||||
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
task.schedule();
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
private class VelocityTask implements Runnable, ScheduledTask {
|
||||
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private final long delay;
|
||||
private final long repeat;
|
||||
private @Nullable ScheduledFuture<?> future;
|
||||
private volatile @Nullable Thread currentTaskThread;
|
||||
|
||||
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
|
||||
this.plugin = plugin;
|
||||
this.runnable = runnable;
|
||||
this.delay = delay;
|
||||
this.repeat = repeat;
|
||||
}
|
||||
|
||||
public void schedule() {
|
||||
if (repeat == 0) {
|
||||
this.future = timerExecutionService.schedule(this, delay, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
this.future = timerExecutionService
|
||||
.scheduleAtFixedRate(this, delay, repeat, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskStatus status() {
|
||||
if (future == null) {
|
||||
return TaskStatus.SCHEDULED;
|
||||
}
|
||||
|
||||
if (future.isCancelled()) {
|
||||
return TaskStatus.CANCELLED;
|
||||
}
|
||||
|
||||
if (future.isDone()) {
|
||||
return TaskStatus.FINISHED;
|
||||
}
|
||||
|
||||
return TaskStatus.SCHEDULED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
|
||||
Thread cur = currentTaskThread;
|
||||
if (cur != null) {
|
||||
cur.interrupt();
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
taskService.execute(() -> {
|
||||
currentTaskThread = Thread.currentThread();
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
|
||||
} finally {
|
||||
currentTaskThread = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onFinish() {
|
||||
tasksByPlugin.remove(plugin, this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Log {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(VelocityTask.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,55 +5,59 @@ 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 org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ServerMap {
|
||||
private final @Nullable VelocityServer server;
|
||||
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
|
||||
|
||||
public ServerMap(@Nullable VelocityServer server) {
|
||||
this.server = server;
|
||||
private final @Nullable VelocityServer server;
|
||||
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
|
||||
|
||||
public ServerMap(@Nullable 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);
|
||||
|
||||
RegisteredServer existing = servers.putIfAbsent(lowerName, rs);
|
||||
if (existing != null && !existing.getServerInfo().equals(serverInfo)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Server with name " + serverInfo.getName() + " already registered");
|
||||
} else if (existing == null) {
|
||||
return rs;
|
||||
} else {
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
RegisteredServer existing = servers.putIfAbsent(lowerName, rs);
|
||||
if (existing != null && !existing.getServerInfo().equals(serverInfo)) {
|
||||
throw new IllegalArgumentException("Server with name " + serverInfo.getName() + " already registered");
|
||||
} else if (existing == null) {
|
||||
return rs;
|
||||
} else {
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(ServerInfo serverInfo) {
|
||||
Preconditions.checkNotNull(serverInfo, "serverInfo");
|
||||
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
|
||||
RegisteredServer rs = servers.get(lowerName);
|
||||
if (rs == null) {
|
||||
throw new IllegalArgumentException("Server with name " + serverInfo.getName() + " is not registered!");
|
||||
}
|
||||
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());
|
||||
public void unregister(ServerInfo serverInfo) {
|
||||
Preconditions.checkNotNull(serverInfo, "serverInfo");
|
||||
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
|
||||
RegisteredServer rs = servers.get(lowerName);
|
||||
if (rs == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Server with name " + serverInfo.getName() + " is not registered!");
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package com.velocitypowered.proxy.server;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.HANDLER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
@@ -23,95 +30,98 @@ import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
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.*;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class VelocityRegisteredServer implements RegisteredServer {
|
||||
private final @Nullable VelocityServer server;
|
||||
private final ServerInfo serverInfo;
|
||||
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public VelocityRegisteredServer(@Nullable VelocityServer server, ServerInfo serverInfo) {
|
||||
this.server = server;
|
||||
this.serverInfo = Preconditions.checkNotNull(serverInfo, "serverInfo");
|
||||
private final @Nullable VelocityServer server;
|
||||
private final ServerInfo serverInfo;
|
||||
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public VelocityRegisteredServer(@Nullable VelocityServer server, ServerInfo serverInfo) {
|
||||
this.server = server;
|
||||
this.serverInfo = Preconditions.checkNotNull(serverInfo, "serverInfo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getPlayersConnected() {
|
||||
return ImmutableList.copyOf(players);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ServerPing> ping() {
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("No Velocity proxy instance available");
|
||||
}
|
||||
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));
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getPlayersConnected() {
|
||||
return ImmutableList.copyOf(players);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ServerPing> ping() {
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("No Velocity proxy instance available");
|
||||
}
|
||||
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) {
|
||||
ServerConnection connection = player.getConnectedServer();
|
||||
if (connection != null && connection.getServerInfo().equals(serverInfo)) {
|
||||
return connection.sendPluginMessage(identifier, data);
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
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) {
|
||||
ServerConnection connection = player.getConnectedServer();
|
||||
if (connection != null && connection.getServerInfo().equals(serverInfo)) {
|
||||
return connection.sendPluginMessage(identifier, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "registered server: " + serverInfo;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "registered server: " + serverInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,56 +10,57 @@ 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;
|
||||
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 boolean handle(StatusResponse packet) {
|
||||
// All good!
|
||||
completed = true;
|
||||
connection.close();
|
||||
|
||||
ServerPing ping = VelocityServer.GSON.fromJson(packet.getStatus(), ServerPing.class);
|
||||
result.complete(ping);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (!completed) {
|
||||
result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
|
||||
}
|
||||
}
|
||||
|
||||
@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 boolean handle(StatusResponse packet) {
|
||||
// All good!
|
||||
completed = true;
|
||||
connection.close();
|
||||
|
||||
ServerPing ping = VelocityServer.GSON.fromJson(packet.getStatus(), ServerPing.class);
|
||||
result.complete(ping);
|
||||
return true;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
completed = true;
|
||||
result.completeExceptionally(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,131 +8,142 @@ import com.velocitypowered.api.util.UuidUtils;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.kyori.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class VelocityTabList implements TabList {
|
||||
private final MinecraftConnection connection;
|
||||
private final Map<UUID, TabListEntry> entries = new ConcurrentHashMap<>();
|
||||
|
||||
public VelocityTabList(MinecraftConnection connection) {
|
||||
this.connection = connection;
|
||||
private final MinecraftConnection connection;
|
||||
private final Map<UUID, TabListEntry> entries = new ConcurrentHashMap<>();
|
||||
|
||||
public VelocityTabList(MinecraftConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaderAndFooter(Component header, Component footer) {
|
||||
Preconditions.checkNotNull(header, "header");
|
||||
Preconditions.checkNotNull(footer, "footer");
|
||||
connection.write(HeaderAndFooter.create(header, footer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaderAndFooter() {
|
||||
connection.write(HeaderAndFooter.reset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(TabListEntry entry) {
|
||||
Preconditions.checkNotNull(entry, "entry");
|
||||
Preconditions.checkArgument(entry.getTabList().equals(this),
|
||||
"The provided entry was not created by this tab list");
|
||||
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().idAsUuid()),
|
||||
"this TabList already contains an entry with the same uuid");
|
||||
|
||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||
connection.write(
|
||||
new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem)));
|
||||
entries.put(entry.getProfile().idAsUuid(), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TabListEntry> removeEntry(UUID uuid) {
|
||||
TabListEntry entry = entries.remove(uuid);
|
||||
if (entry != null) {
|
||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||
connection.write(
|
||||
new PlayerListItem(PlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaderAndFooter(Component header, Component footer) {
|
||||
Preconditions.checkNotNull(header, "header");
|
||||
Preconditions.checkNotNull(footer, "footer");
|
||||
connection.write(HeaderAndFooter.create(header, footer));
|
||||
return Optional.ofNullable(entry);
|
||||
}
|
||||
|
||||
public void clearAll() { // Note: this method is called upon server switch
|
||||
List<PlayerListItem.Item> items = new ArrayList<>();
|
||||
for (TabListEntry value : entries.values()) {
|
||||
items.add(PlayerListItem.Item.from(value));
|
||||
}
|
||||
entries.clear();
|
||||
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaderAndFooter() {
|
||||
connection.write(HeaderAndFooter.reset());
|
||||
}
|
||||
@Override
|
||||
public Collection<TabListEntry> getEntries() {
|
||||
return Collections.unmodifiableCollection(this.entries.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(TabListEntry entry) {
|
||||
Preconditions.checkNotNull(entry, "entry");
|
||||
Preconditions.checkArgument(entry.getTabList().equals(this), "The provided entry was not created by this tab list");
|
||||
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().idAsUuid()), "this TabList already contains an entry with the same uuid");
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
|
||||
int gameMode) {
|
||||
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode);
|
||||
}
|
||||
|
||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||
connection.write(new PlayerListItem(PlayerListItem.ADD_PLAYER, Collections.singletonList(packetItem)));
|
||||
entries.put(entry.getProfile().idAsUuid(), entry);
|
||||
}
|
||||
public void processBackendPacket(PlayerListItem packet) {
|
||||
//Packets are already forwarded on, so no need to do that here
|
||||
for (PlayerListItem.Item item : packet.getItems()) {
|
||||
UUID uuid = item.getUuid();
|
||||
if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) {
|
||||
//Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
|
||||
continue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TabListEntry> removeEntry(UUID uuid) {
|
||||
TabListEntry entry = entries.remove(uuid);
|
||||
if (entry != null) {
|
||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||
connection.write(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, Collections.singletonList(packetItem)));
|
||||
switch (packet.getAction()) {
|
||||
case PlayerListItem.ADD_PLAYER: {
|
||||
// ensure that name and properties are available
|
||||
String name = item.getName();
|
||||
List<GameProfile.Property> properties = item.getProperties();
|
||||
if (name == null || properties == null) {
|
||||
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
|
||||
}
|
||||
entries.put(item.getUuid(), TabListEntry.builder()
|
||||
.tabList(this)
|
||||
.profile(new GameProfile(UuidUtils.toUndashed(uuid), name, properties))
|
||||
.displayName(item.getDisplayName())
|
||||
.latency(item.getLatency())
|
||||
.gameMode(item.getGameMode())
|
||||
.build());
|
||||
break;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(entry);
|
||||
}
|
||||
|
||||
public void clearAll() { // Note: this method is called upon server switch
|
||||
List<PlayerListItem.Item> items = new ArrayList<>();
|
||||
for (TabListEntry value : entries.values()) {
|
||||
items.add(PlayerListItem.Item.from(value));
|
||||
case PlayerListItem.REMOVE_PLAYER:
|
||||
entries.remove(uuid);
|
||||
break;
|
||||
case PlayerListItem.UPDATE_DISPLAY_NAME: {
|
||||
TabListEntry entry = entries.get(uuid);
|
||||
if (entry != null) {
|
||||
entry.setDisplayName(item.getDisplayName());
|
||||
}
|
||||
break;
|
||||
}
|
||||
entries.clear();
|
||||
connection.delayedWrite(new PlayerListItem(PlayerListItem.REMOVE_PLAYER, items));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TabListEntry> getEntries() {
|
||||
return Collections.unmodifiableCollection(this.entries.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
|
||||
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode);
|
||||
}
|
||||
|
||||
public void processBackendPacket(PlayerListItem packet) {
|
||||
//Packets are already forwarded on, so no need to do that here
|
||||
for (PlayerListItem.Item item : packet.getItems()) {
|
||||
UUID uuid = item.getUuid();
|
||||
if (packet.getAction() != PlayerListItem.ADD_PLAYER && !entries.containsKey(uuid)) {
|
||||
//Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (packet.getAction()) {
|
||||
case PlayerListItem.ADD_PLAYER: {
|
||||
// ensure that name and properties are available
|
||||
String name = item.getName();
|
||||
List<GameProfile.Property> properties = item.getProperties();
|
||||
if (name == null || properties == null) {
|
||||
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
|
||||
}
|
||||
entries.put(item.getUuid(), TabListEntry.builder()
|
||||
.tabList(this)
|
||||
.profile(new GameProfile(UuidUtils.toUndashed(uuid), name, properties))
|
||||
.displayName(item.getDisplayName())
|
||||
.latency(item.getLatency())
|
||||
.gameMode(item.getGameMode())
|
||||
.build());
|
||||
break;
|
||||
}
|
||||
case PlayerListItem.REMOVE_PLAYER:
|
||||
entries.remove(uuid);
|
||||
break;
|
||||
case PlayerListItem.UPDATE_DISPLAY_NAME: {
|
||||
TabListEntry entry = entries.get(uuid);
|
||||
if (entry != null) {
|
||||
entry.setDisplayName(item.getDisplayName());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PlayerListItem.UPDATE_LATENCY: {
|
||||
TabListEntry entry = entries.get(uuid);
|
||||
if (entry != null) {
|
||||
entry.setLatency(item.getLatency());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PlayerListItem.UPDATE_GAMEMODE: {
|
||||
TabListEntry entry = entries.get(uuid);
|
||||
if (entry != null) {
|
||||
entry.setLatency(item.getGameMode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case PlayerListItem.UPDATE_LATENCY: {
|
||||
TabListEntry entry = entries.get(uuid);
|
||||
if (entry != null) {
|
||||
entry.setLatency(item.getLatency());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void updateEntry(int action, TabListEntry entry) {
|
||||
if (entries.containsKey(entry.getProfile().idAsUuid())) {
|
||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
|
||||
case PlayerListItem.UPDATE_GAMEMODE: {
|
||||
TabListEntry entry = entries.get(uuid);
|
||||
if (entry != null) {
|
||||
entry.setLatency(item.getGameMode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateEntry(int action, TabListEntry entry) {
|
||||
if (entries.containsKey(entry.getProfile().idAsUuid())) {
|
||||
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
|
||||
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,69 +4,70 @@ import com.velocitypowered.api.proxy.player.TabList;
|
||||
import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
|
||||
import java.util.Optional;
|
||||
import net.kyori.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class VelocityTabListEntry implements TabListEntry {
|
||||
private final VelocityTabList tabList;
|
||||
private final GameProfile profile;
|
||||
private @Nullable Component displayName;
|
||||
private int latency;
|
||||
private int gameMode;
|
||||
|
||||
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, @Nullable Component displayName, int latency, int gameMode) {
|
||||
this.tabList = tabList;
|
||||
this.profile = profile;
|
||||
this.displayName = displayName;
|
||||
this.latency = latency;
|
||||
this.gameMode = gameMode;
|
||||
}
|
||||
private final VelocityTabList tabList;
|
||||
private final GameProfile profile;
|
||||
private @Nullable Component displayName;
|
||||
private int latency;
|
||||
private int gameMode;
|
||||
|
||||
@Override
|
||||
public TabList getTabList() {
|
||||
return tabList;
|
||||
}
|
||||
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile,
|
||||
@Nullable Component displayName, int latency, int gameMode) {
|
||||
this.tabList = tabList;
|
||||
this.profile = profile;
|
||||
this.displayName = displayName;
|
||||
this.latency = latency;
|
||||
this.gameMode = gameMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
@Override
|
||||
public TabList getTabList() {
|
||||
return tabList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Component> getDisplayName() {
|
||||
return Optional.ofNullable(displayName);
|
||||
}
|
||||
@Override
|
||||
public GameProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry setDisplayName(@Nullable Component displayName) {
|
||||
this.displayName = displayName;
|
||||
tabList.updateEntry(PlayerListItem.UPDATE_DISPLAY_NAME, this);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public Optional<Component> getDisplayName() {
|
||||
return Optional.ofNullable(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLatency() {
|
||||
return latency;
|
||||
}
|
||||
@Override
|
||||
public TabListEntry setDisplayName(@Nullable Component displayName) {
|
||||
this.displayName = displayName;
|
||||
tabList.updateEntry(PlayerListItem.UPDATE_DISPLAY_NAME, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
tabList.updateEntry(PlayerListItem.UPDATE_LATENCY, this);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public int getLatency() {
|
||||
return latency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGameMode() {
|
||||
return gameMode;
|
||||
}
|
||||
@Override
|
||||
public TabListEntry setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
tabList.updateEntry(PlayerListItem.UPDATE_LATENCY, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry setGameMode(int gameMode) {
|
||||
this.gameMode = gameMode;
|
||||
tabList.updateEntry(PlayerListItem.UPDATE_GAMEMODE, this);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public int getGameMode() {
|
||||
return gameMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabListEntry setGameMode(int gameMode) {
|
||||
this.gameMode = gameMode;
|
||||
tabList.updateEntry(PlayerListItem.UPDATE_GAMEMODE, this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
|
||||
public enum AddressUtil {
|
||||
;
|
||||
;
|
||||
|
||||
public static InetSocketAddress parseAddress(String ip) {
|
||||
Preconditions.checkNotNull(ip, "ip");
|
||||
URI uri = URI.create("tcp://" + ip);
|
||||
return new InetSocketAddress(uri.getHost(), uri.getPort());
|
||||
}
|
||||
public static InetSocketAddress parseAddress(String ip) {
|
||||
Preconditions.checkNotNull(ip, "ip");
|
||||
URI uri = URI.create("tcp://" + ip);
|
||||
return new InetSocketAddress(uri.getHost(), uri.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public enum EncryptionUtils {
|
||||
;
|
||||
;
|
||||
|
||||
public static KeyPair createRsaKeyPair(final int keysize) {
|
||||
try {
|
||||
final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||
generator.initialize(keysize);
|
||||
return generator.generateKeyPair();
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Unable to generate RSA keypair", e);
|
||||
}
|
||||
public static KeyPair createRsaKeyPair(final int keysize) {
|
||||
try {
|
||||
final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||
generator.initialize(keysize);
|
||||
return generator.generateKeyPair();
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Unable to generate RSA keypair", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String twosComplementHexdigest(byte[] digest) {
|
||||
return new BigInteger(digest).toString(16);
|
||||
}
|
||||
public static String twosComplementHexdigest(byte[] digest) {
|
||||
return new BigInteger(digest).toString(16);
|
||||
}
|
||||
|
||||
public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException {
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
||||
return cipher.doFinal(bytes);
|
||||
}
|
||||
public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException {
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
||||
return cipher.doFinal(bytes);
|
||||
}
|
||||
|
||||
public static String generateServerId(byte[] sharedSecret, PublicKey key) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.update(sharedSecret);
|
||||
digest.update(key.getEncoded());
|
||||
return twosComplementHexdigest(digest.digest());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
public static String generateServerId(byte[] sharedSecret, PublicKey key) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.update(sharedSecret);
|
||||
digest.update(key.getEncoded());
|
||||
return twosComplementHexdigest(digest.digest());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user