Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-17 05:20:14 +01:00
Merge pull request #122 from mikroskeem/feature/gs4-query-event
[WIP] GS4 query event
Dieser Commit ist enthalten in:
Commit
877b4f4738
@ -0,0 +1,83 @@
|
|||||||
|
package com.velocitypowered.api.event.query;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.api.proxy.server.QueryResponse;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is fired if proxy is getting queried over GS4 Query protocol
|
||||||
|
*/
|
||||||
|
public final class ProxyQueryEvent {
|
||||||
|
private final QueryType queryType;
|
||||||
|
private final InetAddress querierAddress;
|
||||||
|
private QueryResponse response;
|
||||||
|
|
||||||
|
public ProxyQueryEvent(QueryType queryType, InetAddress querierAddress, QueryResponse response) {
|
||||||
|
this.queryType = Preconditions.checkNotNull(queryType, "queryType");
|
||||||
|
this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress");
|
||||||
|
this.response = Preconditions.checkNotNull(response, "response");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query type
|
||||||
|
* @return query type
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public QueryType getQueryType() {
|
||||||
|
return queryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get querier address
|
||||||
|
* @return querier address
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public InetAddress getQuerierAddress() {
|
||||||
|
return querierAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query response
|
||||||
|
* @return query response
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public QueryResponse getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set query response
|
||||||
|
* @param response query response
|
||||||
|
*/
|
||||||
|
public void setResponse(@NonNull QueryResponse response) {
|
||||||
|
this.response = Preconditions.checkNotNull(response, "response");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ProxyQueryEvent{" +
|
||||||
|
"queryType=" + queryType +
|
||||||
|
", querierAddress=" + querierAddress +
|
||||||
|
", response=" + response +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of query
|
||||||
|
*/
|
||||||
|
public enum QueryType {
|
||||||
|
/**
|
||||||
|
* Basic query asks only a subset of information, such as hostname, game type (hardcoded to <pre>MINECRAFT</pre>), map,
|
||||||
|
* current players, max players, proxy port and proxy hostname
|
||||||
|
*/
|
||||||
|
BASIC,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full query asks pretty much everything present on this event (only hardcoded values cannot be modified here).
|
||||||
|
*/
|
||||||
|
FULL
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Provides events for handling GS4 queries.
|
||||||
|
*/
|
||||||
|
package com.velocitypowered.api.event.query;
|
@ -29,6 +29,13 @@ public interface ProxyConfig {
|
|||||||
*/
|
*/
|
||||||
String getQueryMap();
|
String getQueryMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether GameSpy 4 queries should show plugins installed on
|
||||||
|
* Velocity by default
|
||||||
|
* @return show plugins in query
|
||||||
|
*/
|
||||||
|
boolean shouldQueryShowPlugins();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the MOTD component shown in the tab list
|
* Get the MOTD component shown in the tab list
|
||||||
* @return the motd component
|
* @return the motd component
|
||||||
|
@ -0,0 +1,303 @@
|
|||||||
|
package com.velocitypowered.api.proxy.server;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GS4 query response. This class is immutable.
|
||||||
|
*/
|
||||||
|
public final class QueryResponse {
|
||||||
|
private final String hostname;
|
||||||
|
private final String gameVersion;
|
||||||
|
private final String map;
|
||||||
|
private final int currentPlayers;
|
||||||
|
private final int maxPlayers;
|
||||||
|
private final String proxyHost;
|
||||||
|
private final int proxyPort;
|
||||||
|
private final Collection<String> players;
|
||||||
|
private final String proxyVersion;
|
||||||
|
private final Collection<PluginInformation> plugins;
|
||||||
|
|
||||||
|
private QueryResponse(String hostname, String gameVersion, String map, int currentPlayers, int maxPlayers, String proxyHost, int proxyPort, Collection<String> players, String proxyVersion, Collection<PluginInformation> plugins) {
|
||||||
|
this.hostname = hostname;
|
||||||
|
this.gameVersion = gameVersion;
|
||||||
|
this.map = map;
|
||||||
|
this.currentPlayers = currentPlayers;
|
||||||
|
this.maxPlayers = maxPlayers;
|
||||||
|
this.proxyHost = proxyHost;
|
||||||
|
this.proxyPort = proxyPort;
|
||||||
|
this.players = players;
|
||||||
|
this.proxyVersion = proxyVersion;
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get hostname which will be used to reply to the query. By default it is {@link ProxyConfig#getMotdComponent()} in plain text without colour codes.
|
||||||
|
* @return hostname
|
||||||
|
*/
|
||||||
|
public String getHostname() {
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get game version which will be used to reply to the query. By default supported Minecraft versions range is sent.
|
||||||
|
* @return game version
|
||||||
|
*/
|
||||||
|
public String getGameVersion() {
|
||||||
|
return gameVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get map name which will be used to reply to the query. By default {@link ProxyConfig#getQueryMap()} is sent.
|
||||||
|
* @return map name
|
||||||
|
*/
|
||||||
|
public String getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current online player count which will be used to reply to the query.
|
||||||
|
* @return online player count
|
||||||
|
*/
|
||||||
|
public int getCurrentPlayers() {
|
||||||
|
return currentPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get max player count which will be used to reply to the query.
|
||||||
|
* @return max player count
|
||||||
|
*/
|
||||||
|
public int getMaxPlayers() {
|
||||||
|
return maxPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get proxy (public facing) hostname
|
||||||
|
* @return proxy hostname
|
||||||
|
*/
|
||||||
|
public String getProxyHost() {
|
||||||
|
return proxyHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get proxy (public facing) port
|
||||||
|
* @return proxy port
|
||||||
|
*/
|
||||||
|
public int getProxyPort() {
|
||||||
|
return proxyPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get collection of players which will be used to reply to the query.
|
||||||
|
* @return collection of players
|
||||||
|
*/
|
||||||
|
public Collection<String> getPlayers() {
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server software (name and version) which will be used to reply to the query.
|
||||||
|
* @return server software
|
||||||
|
*/
|
||||||
|
public String getProxyVersion() {
|
||||||
|
return proxyVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of plugins which will be used to reply to the query.
|
||||||
|
* @return collection of plugins
|
||||||
|
*/
|
||||||
|
public Collection<PluginInformation> getPlugins() {
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Builder} instance from data represented by this response
|
||||||
|
* @return {@link QueryResponse} builder
|
||||||
|
*/
|
||||||
|
public Builder toBuilder() {
|
||||||
|
return QueryResponse.builder()
|
||||||
|
.hostname(getHostname())
|
||||||
|
.gameVersion(getGameVersion())
|
||||||
|
.map(getMap())
|
||||||
|
.currentPlayers(getCurrentPlayers())
|
||||||
|
.maxPlayers(getMaxPlayers())
|
||||||
|
.proxyHost(getProxyHost())
|
||||||
|
.proxyPort(getProxyPort())
|
||||||
|
.players(getPlayers())
|
||||||
|
.proxyVersion(getProxyVersion())
|
||||||
|
.plugins(getPlugins());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Builder} instance
|
||||||
|
* @return {@link QueryResponse} builder
|
||||||
|
*/
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for {@link QueryResponse} objects.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
@MonotonicNonNull
|
||||||
|
private String hostname;
|
||||||
|
|
||||||
|
@MonotonicNonNull
|
||||||
|
private String gameVersion;
|
||||||
|
|
||||||
|
@MonotonicNonNull
|
||||||
|
private String map;
|
||||||
|
|
||||||
|
@MonotonicNonNull
|
||||||
|
private String proxyHost;
|
||||||
|
|
||||||
|
@MonotonicNonNull
|
||||||
|
private String proxyVersion;
|
||||||
|
|
||||||
|
private int currentPlayers;
|
||||||
|
private int maxPlayers;
|
||||||
|
private int proxyPort;
|
||||||
|
|
||||||
|
private List<String> players = new ArrayList<>();
|
||||||
|
private List<PluginInformation> plugins = new ArrayList<>();
|
||||||
|
|
||||||
|
private Builder() {}
|
||||||
|
|
||||||
|
public Builder hostname(String hostname) {
|
||||||
|
this.hostname = Preconditions.checkNotNull(hostname, "hostname");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder gameVersion(String gameVersion) {
|
||||||
|
this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder map(String map) {
|
||||||
|
this.map = Preconditions.checkNotNull(map, "map");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder currentPlayers(int currentPlayers) {
|
||||||
|
Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative");
|
||||||
|
this.currentPlayers = currentPlayers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder maxPlayers(int maxPlayers) {
|
||||||
|
Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative");
|
||||||
|
this.maxPlayers = maxPlayers;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder proxyHost(String proxyHost) {
|
||||||
|
this.proxyHost = Preconditions.checkNotNull(proxyHost, "proxyHost");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder proxyPort(int proxyPort) {
|
||||||
|
Preconditions.checkArgument(proxyPort >= 1 && proxyPort <= 65535, "proxyPort must be between 1-65535");
|
||||||
|
this.proxyPort = proxyPort;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder players(Collection<String> players) {
|
||||||
|
this.players.addAll(Preconditions.checkNotNull(players, "players"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder players(String... players) {
|
||||||
|
this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players")));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder clearPlayers() {
|
||||||
|
this.players.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder proxyVersion(String proxyVersion) {
|
||||||
|
this.proxyVersion = Preconditions.checkNotNull(proxyVersion, "proxyVersion");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder plugins(Collection<PluginInformation> plugins) {
|
||||||
|
this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder plugins(PluginInformation... plugins) {
|
||||||
|
this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins")));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder clearPlugins() {
|
||||||
|
this.plugins.clear();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds new {@link QueryResponse} with supplied data
|
||||||
|
* @return response
|
||||||
|
*/
|
||||||
|
public QueryResponse build() {
|
||||||
|
return new QueryResponse(
|
||||||
|
Preconditions.checkNotNull(hostname, "hostname"),
|
||||||
|
Preconditions.checkNotNull(gameVersion, "gameVersion"),
|
||||||
|
Preconditions.checkNotNull(map, "map"),
|
||||||
|
currentPlayers,
|
||||||
|
maxPlayers,
|
||||||
|
Preconditions.checkNotNull(proxyHost, "proxyHost"),
|
||||||
|
proxyPort,
|
||||||
|
ImmutableList.copyOf(players),
|
||||||
|
Preconditions.checkNotNull(proxyVersion, "proxyVersion"),
|
||||||
|
ImmutableList.copyOf(plugins)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin information
|
||||||
|
*/
|
||||||
|
public static class PluginInformation {
|
||||||
|
private String name;
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
public PluginInformation(String name, String version) {
|
||||||
|
this.name = Preconditions.checkNotNull(name, "name");
|
||||||
|
this.version = Preconditions.checkNotNull(version, "version");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(@Nullable String version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginInformation of(String name, @Nullable String version) {
|
||||||
|
return new PluginInformation(name, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -213,6 +213,11 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
return query.getQueryMap();
|
return query.getQueryMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldQueryShowPlugins() {
|
||||||
|
return query.shouldQueryShowPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
public String getMotd() {
|
public String getMotd() {
|
||||||
return motd;
|
return motd;
|
||||||
}
|
}
|
||||||
@ -504,6 +509,10 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
@ConfigKey("map")
|
@ConfigKey("map")
|
||||||
private String queryMap = "Velocity";
|
private String queryMap = "Velocity";
|
||||||
|
|
||||||
|
@Comment("Whether plugins should be shown in query response by default or not")
|
||||||
|
@ConfigKey("show-plugins")
|
||||||
|
private boolean showPlugins = false;
|
||||||
|
|
||||||
private Query() {
|
private Query() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,13 +542,18 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
|||||||
return queryMap;
|
return queryMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean shouldQueryShowPlugins() {
|
||||||
|
return showPlugins;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Query{"
|
return "Query{" +
|
||||||
+ "queryEnabled=" + queryEnabled
|
"queryEnabled=" + queryEnabled +
|
||||||
+ ", queryPort=" + queryPort
|
", queryPort=" + queryPort +
|
||||||
+ ", queryMap=" + queryMap
|
", queryMap='" + queryMap + '\'' +
|
||||||
+ '}';
|
", showPlugins=" + showPlugins +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ package com.velocitypowered.proxy.protocol.netty;
|
|||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.velocitypowered.api.event.query.ProxyQueryEvent;
|
||||||
|
import com.velocitypowered.api.plugin.PluginContainer;
|
||||||
import com.velocitypowered.api.proxy.Player;
|
import com.velocitypowered.api.proxy.Player;
|
||||||
|
import com.velocitypowered.api.proxy.server.QueryResponse;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
@ -13,13 +16,21 @@ import io.netty.channel.socket.DatagramPacket;
|
|||||||
import net.kyori.text.serializer.ComponentSerializers;
|
import net.kyori.text.serializer.ComponentSerializers;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.concurrent.TimeUnit;
|
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;
|
||||||
|
|
||||||
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket> {
|
||||||
private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class);
|
private static final Logger logger = LogManager.getLogger(GS4QueryHandler.class);
|
||||||
@ -46,6 +57,9 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@MonotonicNonNull
|
||||||
|
private volatile List<QueryResponse.PluginInformation> pluginInformationList = null;
|
||||||
|
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
|
|
||||||
public GS4QueryHandler(VelocityServer server) {
|
public GS4QueryHandler(VelocityServer server) {
|
||||||
@ -81,6 +95,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
|
queryResponse.writeByte(QUERY_TYPE_HANDSHAKE);
|
||||||
queryResponse.writeInt(sessionId);
|
queryResponse.writeInt(sessionId);
|
||||||
writeString(queryResponse, Integer.toString(challengeToken));
|
writeString(queryResponse, Integer.toString(challengeToken));
|
||||||
|
ctx.writeAndFlush(responsePacket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,28 +112,51 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
throw new IllegalStateException("Invalid query packet");
|
throw new IllegalStateException("Invalid query packet");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Packet header
|
// Build query response
|
||||||
queryResponse.writeByte(QUERY_TYPE_STAT);
|
QueryResponse response = QueryResponse.builder()
|
||||||
queryResponse.writeInt(sessionId);
|
.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();
|
||||||
|
|
||||||
// Start writing the response
|
boolean isBasic = queryMessage.readableBytes() == 0;
|
||||||
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
|
|
||||||
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
|
|
||||||
responseWriter.write("gametype", "SMP");
|
|
||||||
|
|
||||||
responseWriter.write("game_id", "MINECRAFT");
|
// Call event and write response
|
||||||
responseWriter.write("version", ProtocolConstants.SUPPORTED_GENERIC_VERSION_STRING);
|
server.getEventManager().fire(new ProxyQueryEvent(isBasic ? BASIC : FULL, senderAddress, response)).whenCompleteAsync((event, exc) -> {
|
||||||
responseWriter.write("plugins", "");
|
// Packet header
|
||||||
|
queryResponse.writeByte(QUERY_TYPE_STAT);
|
||||||
|
queryResponse.writeInt(sessionId);
|
||||||
|
|
||||||
responseWriter.write("map", server.getConfiguration().getQueryMap());
|
// Start writing the response
|
||||||
responseWriter.write("numplayers", server.getPlayerCount());
|
ResponseWriter responseWriter = new ResponseWriter(queryResponse, isBasic);
|
||||||
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
|
responseWriter.write("hostname", event.getResponse().getHostname());
|
||||||
responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
|
responseWriter.write("gametype", "SMP");
|
||||||
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
|
|
||||||
|
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());
|
||||||
|
|
||||||
if (!responseWriter.isBasic) {
|
|
||||||
responseWriter.writePlayers(server.getAllPlayers());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,9 +164,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
throw new IllegalStateException("Invalid query type: " + type);
|
throw new IllegalStateException("Invalid query type: " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the response
|
|
||||||
ctx.writeAndFlush(responsePacket);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
|
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
|
||||||
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
|
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
|
||||||
@ -142,6 +177,22 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
buf.writeByte(0x00);
|
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 static class ResponseWriter {
|
||||||
private final ByteBuf buf;
|
private final ByteBuf buf;
|
||||||
private final boolean isBasic;
|
private final boolean isBasic;
|
||||||
@ -180,7 +231,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
|
|
||||||
// Ends packet k/v body writing and writes stat player list to
|
// Ends packet k/v body writing and writes stat player list to
|
||||||
// the packet if this writer is initialized for full stat response
|
// the packet if this writer is initialized for full stat response
|
||||||
void writePlayers(Collection<Player> players) {
|
void writePlayers(Collection<String> players) {
|
||||||
if (isBasic) {
|
if (isBasic) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -189,8 +240,29 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
|||||||
buf.writeByte(0x00);
|
buf.writeByte(0x00);
|
||||||
|
|
||||||
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2);
|
buf.writeBytes(QUERY_RESPONSE_FULL_PADDING2);
|
||||||
players.forEach(player -> writeString(buf, player.getUsername()));
|
players.forEach(player -> writeString(buf, player));
|
||||||
buf.writeByte(0x00);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren