Convert into a multi-module project.
For now, the API module only contains a few assorted utilities. More will be added later.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package com.velocitypowered.network;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.network.Connections.CLIENT_READ_TIMEOUT_SECONDS;
|
||||
import static com.velocitypowered.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.LEGACY_PING_DECODER;
|
||||
import static com.velocitypowered.network.Connections.LEGACY_PING_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.READ_TIMEOUT;
|
||||
|
||||
public final class ConnectionManager {
|
||||
private static final Logger logger = LogManager.getLogger(ConnectionManager.class);
|
||||
|
||||
private static final String DISABLE_EPOLL_PROPERTY = "velocity.connection.disable-epoll";
|
||||
private static final boolean DISABLE_EPOLL = Boolean.getBoolean(DISABLE_EPOLL_PROPERTY);
|
||||
private final Set<Channel> endpoints = new HashSet<>();
|
||||
private final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||
private final Class<? extends SocketChannel> socketChannelClass;
|
||||
private final EventLoopGroup bossGroup;
|
||||
private final EventLoopGroup workerGroup;
|
||||
|
||||
public ConnectionManager() {
|
||||
final boolean epoll = canUseEpoll();
|
||||
if (epoll) {
|
||||
this.serverSocketChannelClass = EpollServerSocketChannel.class;
|
||||
this.socketChannelClass = EpollSocketChannel.class;
|
||||
this.bossGroup = new EpollEventLoopGroup(0, createThreadFactory("Netty Epoll Boss #%d"));
|
||||
this.workerGroup = new EpollEventLoopGroup(0, createThreadFactory("Netty Epoll Worker #%d"));
|
||||
} else {
|
||||
this.serverSocketChannelClass = NioServerSocketChannel.class;
|
||||
this.socketChannelClass = NioSocketChannel.class;
|
||||
this.bossGroup = new NioEventLoopGroup(0, createThreadFactory("Netty Nio Boss #%d"));
|
||||
this.workerGroup = new NioEventLoopGroup(0, createThreadFactory("Netty Nio Worker #%d"));
|
||||
}
|
||||
this.logChannelInformation(epoll);
|
||||
}
|
||||
|
||||
private void logChannelInformation(final boolean epoll) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Using channel type ");
|
||||
sb.append(epoll ? "epoll": "nio");
|
||||
if(DISABLE_EPOLL) {
|
||||
sb.append(String.format(" - epoll explicitly disabled using -D%s=true", DISABLE_EPOLL_PROPERTY));
|
||||
}
|
||||
logger.info(sb.toString()); // TODO: move to logger
|
||||
}
|
||||
|
||||
public void bind(final InetSocketAddress address) {
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(this.serverSocketChannelClass)
|
||||
.group(this.bossGroup, this.workerGroup)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(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);
|
||||
connection.setState(StateRegistry.HANDSHAKE);
|
||||
connection.setSessionHandler(new HandshakeSessionHandler(connection));
|
||||
ch.pipeline().addLast(Connections.HANDLER, connection);
|
||||
}
|
||||
})
|
||||
.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.socketChannelClass)
|
||||
.group(this.workerGroup);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canUseEpoll() {
|
||||
return Epoll.isAvailable() && !DISABLE_EPOLL;
|
||||
}
|
||||
|
||||
private static ThreadFactory createThreadFactory(final String nameFormat) {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(nameFormat)
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.velocitypowered.network;
|
||||
|
||||
public interface Connections {
|
||||
String CIPHER_DECODER = "cipher-decoder";
|
||||
String CIPHER_ENCODER = "cipher-encoder";
|
||||
String COMPRESSION_DECODER = "compression-decoder";
|
||||
String COMPRESSION_ENCODER = "compression-encoder";
|
||||
String FRAME_DECODER = "frame-decoder";
|
||||
String FRAME_ENCODER = "frame-encoder";
|
||||
String HANDLER = "handler";
|
||||
String LEGACY_PING_DECODER = "legacy-ping-decoder";
|
||||
String LEGACY_PING_ENCODER = "legacy-ping-encoder";
|
||||
String MINECRAFT_DECODER = "minecraft-decoder";
|
||||
String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||
String READ_TIMEOUT = "read-timeout";
|
||||
|
||||
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
|
||||
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
|
||||
}
|
12
proxy/src/main/java/com.velocitypowered/proxy/Velocity.java
Normal file
12
proxy/src/main/java/com.velocitypowered/proxy/Velocity.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.velocitypowered.proxy;
|
||||
|
||||
public class Velocity {
|
||||
public static void main(String... args) throws InterruptedException {
|
||||
final VelocityServer server = VelocityServer.getServer();
|
||||
server.start();
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread"));
|
||||
|
||||
Thread.currentThread().join();
|
||||
}
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
package com.velocitypowered.proxy;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.velocitypowered.network.ConnectionManager;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.http.NettyHttpClient;
|
||||
import com.velocitypowered.api.servers.ServerInfo;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.util.ServerMap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public class VelocityServer {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
|
||||
private static final VelocityServer INSTANCE = new VelocityServer();
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
||||
.create();
|
||||
|
||||
private final ConnectionManager cm = new ConnectionManager();
|
||||
private VelocityConfiguration configuration;
|
||||
private NettyHttpClient httpClient;
|
||||
private KeyPair serverKeyPair;
|
||||
private final ServerMap servers = new ServerMap();
|
||||
|
||||
private VelocityServer() {
|
||||
}
|
||||
|
||||
public static VelocityServer getServer() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public KeyPair getServerKeyPair() {
|
||||
return serverKeyPair;
|
||||
}
|
||||
|
||||
public VelocityConfiguration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
// Create a key pair
|
||||
logger.info("Booting up Velocity...");
|
||||
try {
|
||||
Path configPath = Paths.get("velocity.toml");
|
||||
try {
|
||||
configuration = VelocityConfiguration.read(configPath);
|
||||
} catch (NoSuchFileException e) {
|
||||
logger.info("No velocity.toml found, creating one for you...");
|
||||
Files.copy(VelocityServer.class.getResourceAsStream("/velocity.toml"), configPath);
|
||||
configuration = VelocityConfiguration.read(configPath);
|
||||
}
|
||||
|
||||
if (!configuration.validate()) {
|
||||
logger.error("Your configuration is invalid. Velocity will refuse to start up until the errors are resolved.");
|
||||
System.exit(1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Unable to load your velocity.toml. The server will shut down.", e);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
|
||||
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
|
||||
}
|
||||
|
||||
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
|
||||
|
||||
httpClient = new NettyHttpClient(this);
|
||||
|
||||
this.cm.bind(configuration.getBind());
|
||||
}
|
||||
|
||||
public ServerMap getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public Bootstrap initializeGenericBootstrap() {
|
||||
return this.cm.createWorker();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.cm.shutdown();
|
||||
}
|
||||
|
||||
public NettyHttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
public enum IPForwardingMode {
|
||||
NONE,
|
||||
LEGACY,
|
||||
MODERN
|
||||
}
|
@@ -0,0 +1,185 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.api.util.LegacyChatColorUtils;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class VelocityConfiguration {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class);
|
||||
|
||||
private final String bind;
|
||||
private final String motd;
|
||||
private final int showMaxPlayers;
|
||||
private final boolean onlineMode;
|
||||
private final IPForwardingMode ipForwardingMode;
|
||||
private final Map<String, String> servers;
|
||||
private final List<String> attemptConnectionOrder;
|
||||
|
||||
private Component motdAsComponent;
|
||||
|
||||
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
|
||||
IPForwardingMode ipForwardingMode, Map<String, String> servers,
|
||||
List<String> attemptConnectionOrder) {
|
||||
this.bind = bind;
|
||||
this.motd = motd;
|
||||
this.showMaxPlayers = showMaxPlayers;
|
||||
this.onlineMode = onlineMode;
|
||||
this.ipForwardingMode = ipForwardingMode;
|
||||
this.servers = servers;
|
||||
this.attemptConnectionOrder = attemptConnectionOrder;
|
||||
}
|
||||
|
||||
public boolean validate() {
|
||||
boolean valid = true;
|
||||
|
||||
if (bind.isEmpty()) {
|
||||
logger.error("'bind' option is empty.");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
try {
|
||||
AddressUtil.parseAddress(bind);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("'bind' option does not specify a valid IP address.", e);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!onlineMode) {
|
||||
logger.info("Proxy is running in offline mode!");
|
||||
}
|
||||
|
||||
switch (ipForwardingMode) {
|
||||
case NONE:
|
||||
logger.info("IP forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (servers.isEmpty()) {
|
||||
logger.error("You have no servers configured. :(");
|
||||
valid = false;
|
||||
} else {
|
||||
if (attemptConnectionOrder.isEmpty()) {
|
||||
logger.error("No fallback servers are configured!");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : servers.entrySet()) {
|
||||
try {
|
||||
AddressUtil.parseAddress(entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (String s : attemptConnectionOrder) {
|
||||
if (!servers.containsKey(s)) {
|
||||
logger.error("Fallback server " + s + " doesn't exist!");
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
getMotdComponent();
|
||||
} catch (Exception e) {
|
||||
logger.error("Can't parse your MOTD", e);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
public InetSocketAddress getBind() {
|
||||
return AddressUtil.parseAddress(bind);
|
||||
}
|
||||
|
||||
public String getMotd() {
|
||||
return motd;
|
||||
}
|
||||
|
||||
public Component getMotdComponent() {
|
||||
if (motdAsComponent == null) {
|
||||
if (motd.startsWith("{")) {
|
||||
motdAsComponent = ComponentSerializers.JSON.deserialize(motd);
|
||||
} else {
|
||||
motdAsComponent = ComponentSerializers.LEGACY.deserialize(LegacyChatColorUtils.translate('&', motd));
|
||||
}
|
||||
}
|
||||
return motdAsComponent;
|
||||
}
|
||||
|
||||
public int getShowMaxPlayers() {
|
||||
return showMaxPlayers;
|
||||
}
|
||||
|
||||
public boolean isOnlineMode() {
|
||||
return onlineMode;
|
||||
}
|
||||
|
||||
public IPForwardingMode getIpForwardingMode() {
|
||||
return ipForwardingMode;
|
||||
}
|
||||
|
||||
public Map<String, String> getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public List<String> getAttemptConnectionOrder() {
|
||||
return attemptConnectionOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityConfiguration{" +
|
||||
"bind='" + bind + '\'' +
|
||||
", motd='" + motd + '\'' +
|
||||
", showMaxPlayers=" + showMaxPlayers +
|
||||
", onlineMode=" + onlineMode +
|
||||
", ipForwardingMode=" + ipForwardingMode +
|
||||
", servers=" + servers +
|
||||
", attemptConnectionOrder=" + attemptConnectionOrder +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static VelocityConfiguration read(Path path) throws IOException {
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
Toml toml = new Toml().read(reader);
|
||||
|
||||
Map<String, String> servers = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : toml.getTable("servers").entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
servers.put(entry.getKey(), (String) entry.getValue());
|
||||
} else {
|
||||
if (!entry.getKey().equalsIgnoreCase("try")) {
|
||||
throw new IllegalArgumentException("Server entry " + entry.getKey() + " is not a string!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new VelocityConfiguration(
|
||||
toml.getString("bind"),
|
||||
toml.getString("motd"),
|
||||
toml.getLong("show-max-players").intValue(),
|
||||
toml.getBoolean("online-mode"),
|
||||
IPForwardingMode.valueOf(toml.getString("ip-forwarding").toUpperCase()),
|
||||
ImmutableMap.copyOf(servers),
|
||||
toml.getTable("servers").getList("try"));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.PacketWrapper;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.compression.JavaVelocityCompressor;
|
||||
import com.velocitypowered.proxy.protocol.encryption.JavaVelocityCipher;
|
||||
import com.velocitypowered.proxy.protocol.encryption.VelocityCipher;
|
||||
import com.velocitypowered.proxy.protocol.netty.*;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static com.velocitypowered.network.Connections.CIPHER_DECODER;
|
||||
import static com.velocitypowered.network.Connections.CIPHER_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.COMPRESSION_DECODER;
|
||||
import static com.velocitypowered.network.Connections.COMPRESSION_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER;
|
||||
|
||||
/**
|
||||
* 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 boolean closed;
|
||||
private StateRegistry state;
|
||||
private MinecraftSessionHandler sessionHandler;
|
||||
private int protocolVersion;
|
||||
private MinecraftConnectionAssociation association;
|
||||
|
||||
public MinecraftConnection(Channel channel) {
|
||||
this.channel = channel;
|
||||
this.closed = false;
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
}
|
||||
|
||||
@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 (msg instanceof PacketWrapper) {
|
||||
PacketWrapper pw = (PacketWrapper) msg;
|
||||
try {
|
||||
if (sessionHandler != null) {
|
||||
if (pw.getPacket() == null) {
|
||||
sessionHandler.handleUnknown(pw.getBuffer());
|
||||
} else {
|
||||
sessionHandler.handle(pw.getPacket());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(pw.getBuffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
} else {
|
||||
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
|
||||
}
|
||||
|
||||
closed = true;
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void write(Object msg) {
|
||||
ensureOpen();
|
||||
channel.writeAndFlush(msg, channel.voidPromise());
|
||||
}
|
||||
|
||||
public void delayedWrite(Object msg) {
|
||||
ensureOpen();
|
||||
channel.write(msg, channel.voidPromise());
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
ensureOpen();
|
||||
channel.flush();
|
||||
}
|
||||
|
||||
public void closeWith(Object msg) {
|
||||
ensureOpen();
|
||||
teardown();
|
||||
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
ensureOpen();
|
||||
teardown();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
public void teardown() {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
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.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion);
|
||||
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion);
|
||||
}
|
||||
|
||||
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(!closed, "Connection is closed.");
|
||||
}
|
||||
|
||||
public void setCompressionThreshold(int threshold) {
|
||||
if (threshold == -1) {
|
||||
channel.pipeline().remove(COMPRESSION_DECODER);
|
||||
channel.pipeline().remove(COMPRESSION_ENCODER);
|
||||
return;
|
||||
}
|
||||
|
||||
JavaVelocityCompressor compressor = new JavaVelocityCompressor();
|
||||
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 {
|
||||
SecretKey key = new SecretKeySpec(secret, "AES");
|
||||
|
||||
VelocityCipher decryptionCipher = new JavaVelocityCipher(false, key);
|
||||
VelocityCipher encryptionCipher = new JavaVelocityCipher(true, key);
|
||||
channel.pipeline().addBefore(FRAME_DECODER, CIPHER_DECODER, new MinecraftCipherDecoder(decryptionCipher));
|
||||
channel.pipeline().addBefore(FRAME_ENCODER, CIPHER_ENCODER, new MinecraftCipherEncoder(encryptionCipher));
|
||||
}
|
||||
|
||||
public MinecraftConnectionAssociation getAssociation() {
|
||||
return association;
|
||||
}
|
||||
|
||||
public void setAssociation(MinecraftConnectionAssociation association) {
|
||||
this.association = association;
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
public interface MinecraftConnectionAssociation {
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public interface MinecraftSessionHandler {
|
||||
void handle(MinecraftPacket packet) throws Exception;
|
||||
|
||||
default void handleUnknown(ByteBuf buf) {
|
||||
// No-op: we'll release the buffer later.
|
||||
}
|
||||
|
||||
default void connected() {
|
||||
|
||||
}
|
||||
|
||||
default void disconnected() {
|
||||
|
||||
}
|
||||
|
||||
default void activated() {
|
||||
|
||||
}
|
||||
|
||||
default void deactivated() {
|
||||
|
||||
}
|
||||
|
||||
default void exception(Throwable throwable) {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
public class VelocityConstants {
|
||||
private VelocityConstants() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packets.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private final ServerConnection connection;
|
||||
|
||||
public BackendPlaySessionHandler(ServerConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
ClientPlaySessionHandler playerHandler =
|
||||
(ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler();
|
||||
if (packet instanceof KeepAlive) {
|
||||
// Forward onto the server
|
||||
connection.getMinecraftConnection().write(packet);
|
||||
} else if (packet instanceof Disconnect) {
|
||||
Disconnect original = (Disconnect) packet;
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), original);
|
||||
} else if (packet instanceof JoinGame) {
|
||||
playerHandler.handleBackendJoinGame((JoinGame) packet);
|
||||
} else if (packet instanceof Respawn) {
|
||||
// Record the dimension switch, and then forward the packet on.
|
||||
playerHandler.setCurrentDimension(((Respawn) packet).getDimension());
|
||||
connection.getProxyPlayer().getConnection().write(packet);
|
||||
} else if (packet instanceof BossBar) {
|
||||
BossBar bossBar = (BossBar) packet;
|
||||
switch (bossBar.getAction()) {
|
||||
case 0: // add
|
||||
playerHandler.getServerBossBars().add(bossBar.getUuid());
|
||||
break;
|
||||
case 1: // remove
|
||||
playerHandler.getServerBossBars().remove(bossBar.getUuid());
|
||||
break;
|
||||
}
|
||||
connection.getProxyPlayer().getConnection().write(packet);
|
||||
} else if (packet instanceof PluginMessage) {
|
||||
PluginMessage pm = (PluginMessage) packet;
|
||||
try {
|
||||
PluginMessage newPacket = pm;
|
||||
if (!canForwardPluginMessage(newPacket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPacket.getChannel().equals("MC|Brand")) {
|
||||
newPacket = PluginMessageUtil.rewriteMCBrand(pm);
|
||||
}
|
||||
|
||||
if (newPacket == pm) {
|
||||
// we'll decrement this thrice: once when writing to the server, once just below this block,
|
||||
// and once in the MinecraftConnection (since this is a slice)
|
||||
pm.getData().retain();
|
||||
}
|
||||
connection.getProxyPlayer().getConnection().write(newPacket);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(pm.getData());
|
||||
}
|
||||
} else {
|
||||
// Just forward the packet on. We don't have anything to handle at this time.
|
||||
if (packet instanceof ScoreboardTeam ||
|
||||
packet instanceof ScoreboardObjective ||
|
||||
packet instanceof ScoreboardSetScore ||
|
||||
packet instanceof ScoreboardDisplay) {
|
||||
playerHandler.handleServerScoreboardPacket(packet);
|
||||
}
|
||||
connection.getProxyPlayer().getConnection().write(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
ClientPlaySessionHandler playerHandler =
|
||||
(ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler();
|
||||
ByteBuf remapped = playerHandler.getIdRemapper().remap(buf, ProtocolConstants.Direction.CLIENTBOUND);
|
||||
connection.getProxyPlayer().getConnection().write(remapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable);
|
||||
}
|
||||
|
||||
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||
ClientPlaySessionHandler playerHandler =
|
||||
(ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler();
|
||||
if (connection.getMinecraftConnection().getProtocolVersion() <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
return message.getChannel().startsWith("MC|") ||
|
||||
playerHandler.getClientPluginMsgChannels().contains(message.getChannel());
|
||||
} else {
|
||||
return message.getChannel().startsWith("minecraft:") ||
|
||||
playerHandler.getClientPluginMsgChannels().contains(message.getChannel());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.IPForwardingMode;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packets.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.text.TextComponent;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
private final ServerConnection connection;
|
||||
private ScheduledFuture<?> forwardingCheckTask;
|
||||
|
||||
public LoginSessionHandler(ServerConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN) {
|
||||
forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> {
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(),
|
||||
TextComponent.of("Your server did not send the forwarding request in time. Is it set up correctly?"));
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
if (packet instanceof EncryptionRequest) {
|
||||
throw new IllegalStateException("Backend server is online-mode!");
|
||||
} else if (packet instanceof LoginPluginMessage) {
|
||||
LoginPluginMessage message = (LoginPluginMessage) packet;
|
||||
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN &&
|
||||
message.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) {
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(true);
|
||||
response.setId(message.getId());
|
||||
response.setData(createForwardingData(connection.getProxyPlayer().getRemoteAddress().getHostString(),
|
||||
connection.getProxyPlayer().getProfile()));
|
||||
connection.getMinecraftConnection().write(response);
|
||||
cancelForwardingCheck();
|
||||
|
||||
ServerLogin login = new ServerLogin();
|
||||
login.setUsername(connection.getProxyPlayer().getUsername());
|
||||
connection.getMinecraftConnection().write(login);
|
||||
} else {
|
||||
// Don't understand
|
||||
LoginPluginResponse response = new LoginPluginResponse();
|
||||
response.setSuccess(false);
|
||||
response.setId(message.getId());
|
||||
response.setData(Unpooled.EMPTY_BUFFER);
|
||||
connection.getMinecraftConnection().write(response);
|
||||
}
|
||||
} else if (packet instanceof Disconnect) {
|
||||
Disconnect disconnect = (Disconnect) packet;
|
||||
connection.disconnect();
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect);
|
||||
} else if (packet instanceof SetCompression) {
|
||||
SetCompression sc = (SetCompression) packet;
|
||||
connection.getMinecraftConnection().setCompressionThreshold(sc.getThreshold());
|
||||
} else if (packet instanceof ServerLoginSuccess) {
|
||||
// The player has been logged on to the backend server.
|
||||
connection.getMinecraftConnection().setState(StateRegistry.PLAY);
|
||||
ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer();
|
||||
if (existingConnection == null) {
|
||||
// Strap on the play session handler
|
||||
connection.getProxyPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(connection.getProxyPlayer()));
|
||||
} else {
|
||||
// The previous server connection should become obsolete.
|
||||
existingConnection.disconnect();
|
||||
}
|
||||
connection.getMinecraftConnection().setSessionHandler(new BackendPlaySessionHandler(connection));
|
||||
connection.getProxyPlayer().setConnectedServer(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivated() {
|
||||
cancelForwardingCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), throwable);
|
||||
}
|
||||
|
||||
private void cancelForwardingCheck() {
|
||||
if (forwardingCheckTask != null) {
|
||||
forwardingCheckTask.cancel(false);
|
||||
forwardingCheckTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(String address, GameProfile profile) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ProtocolUtils.writeString(buf, address);
|
||||
ProtocolUtils.writeUuid(buf, profile.idAsUuid());
|
||||
ProtocolUtils.writeString(buf, profile.getName());
|
||||
ProtocolUtils.writeVarInt(buf, profile.getProperties().size());
|
||||
for (GameProfile.Property property : profile.getProperties()) {
|
||||
ProtocolUtils.writeString(buf, property.getName());
|
||||
ProtocolUtils.writeString(buf, property.getValue());
|
||||
String signature = property.getSignature();
|
||||
if (signature != null) {
|
||||
buf.writeBoolean(true);
|
||||
ProtocolUtils.writeString(buf, signature);
|
||||
} else {
|
||||
buf.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
@@ -0,0 +1,134 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.proxy.config.IPForwardingMode;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.packets.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packets.ServerLogin;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.api.servers.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.HANDLER;
|
||||
import static com.velocitypowered.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.network.Connections.READ_TIMEOUT;
|
||||
import static com.velocitypowered.network.Connections.SERVER_READ_TIMEOUT_SECONDS;
|
||||
|
||||
public class ServerConnection implements MinecraftConnectionAssociation {
|
||||
private final ServerInfo serverInfo;
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
private MinecraftConnection minecraftConnection;
|
||||
|
||||
public ServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.serverInfo = target;
|
||||
this.proxyPlayer = proxyPlayer;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
server.initializeGenericBootstrap()
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(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);
|
||||
connection.setState(StateRegistry.HANDSHAKE);
|
||||
connection.setAssociation(ServerConnection.this);
|
||||
ch.pipeline().addLast(HANDLER, connection);
|
||||
}
|
||||
})
|
||||
.connect(serverInfo.getAddress())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
minecraftConnection = future.channel().pipeline().get(MinecraftConnection.class);
|
||||
|
||||
// Kick off the connection process
|
||||
minecraftConnection.setSessionHandler(new LoginSessionHandler(ServerConnection.this));
|
||||
startHandshake();
|
||||
} else {
|
||||
proxyPlayer.handleConnectionException(serverInfo, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 serverInfo.getAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getProfile().getId() + "\0" +
|
||||
VelocityServer.GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||
}
|
||||
|
||||
private void startHandshake() {
|
||||
// Initiate a handshake.
|
||||
Handshake handshake = new Handshake();
|
||||
handshake.setNextStatus(StateRegistry.LOGIN_ID);
|
||||
handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion());
|
||||
if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.LEGACY) {
|
||||
handshake.setServerAddress(createBungeeForwardingAddress());
|
||||
} else {
|
||||
handshake.setServerAddress(serverInfo.getAddress().getHostString());
|
||||
}
|
||||
handshake.setPort(serverInfo.getAddress().getPort());
|
||||
minecraftConnection.write(handshake);
|
||||
|
||||
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
|
||||
minecraftConnection.setProtocolVersion(protocolVersion);
|
||||
minecraftConnection.setState(StateRegistry.LOGIN);
|
||||
|
||||
// Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding.
|
||||
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ||
|
||||
VelocityServer.getServer().getConfiguration().getIpForwardingMode() != IPForwardingMode.MODERN) {
|
||||
ServerLogin login = new ServerLogin();
|
||||
login.setUsername(proxyPlayer.getUsername());
|
||||
minecraftConnection.write(login);
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectedPlayer getProxyPlayer() {
|
||||
return proxyPlayer;
|
||||
}
|
||||
|
||||
public MinecraftConnection getMinecraftConnection() {
|
||||
return minecraftConnection;
|
||||
}
|
||||
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName();
|
||||
}
|
||||
}
|
@@ -0,0 +1,338 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
||||
import com.velocitypowered.api.servers.ServerInfo;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Objective;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Score;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Scoreboard;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Team;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packets.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.remap.EntityIdRemapper;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class);
|
||||
private static final int MAX_PLUGIN_CHANNELS = 128;
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private ScheduledFuture<?> pingTask;
|
||||
private long lastPing = -1;
|
||||
private boolean spawned = false;
|
||||
private final List<UUID> serverBossBars = new ArrayList<>();
|
||||
private final Set<String> clientPluginMsgChannels = new HashSet<>();
|
||||
private PluginMessage brandMessage;
|
||||
private int currentDimension;
|
||||
private Scoreboard serverScoreboard = new Scoreboard();
|
||||
private EntityIdRemapper idRemapper;
|
||||
|
||||
public ClientPlaySessionHandler(ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
EventLoop loop = player.getConnection().getChannel().eventLoop();
|
||||
pingTask = loop.scheduleAtFixedRate(this::ping, 5, 15, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void ping() {
|
||||
long randomId = ThreadLocalRandom.current().nextInt();
|
||||
lastPing = randomId;
|
||||
KeepAlive keepAlive = new KeepAlive();
|
||||
keepAlive.setRandomId(randomId);
|
||||
player.getConnection().write(keepAlive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
if (packet instanceof KeepAlive) {
|
||||
KeepAlive keepAlive = (KeepAlive) packet;
|
||||
if (keepAlive.getRandomId() != lastPing) {
|
||||
throw new IllegalStateException("Client sent invalid keepAlive; expected " + lastPing + ", got " + keepAlive.getRandomId());
|
||||
}
|
||||
|
||||
// Do not forward the packet to the player's server, because we handle pings for all servers already.
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof ClientSettings) {
|
||||
player.setClientSettings((ClientSettings) packet);
|
||||
// forward it on
|
||||
}
|
||||
|
||||
if (packet instanceof Chat) {
|
||||
Chat chat = (Chat) packet;
|
||||
if (chat.getMessage().equals("/connect")) {
|
||||
ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25566));
|
||||
ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer());
|
||||
connection.connect();
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof PluginMessage) {
|
||||
handleClientPluginMessage((PluginMessage) packet);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't want to handle this packet, just forward it on.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
ByteBuf remapped = idRemapper.remap(buf, ProtocolConstants.Direction.SERVERBOUND);
|
||||
player.getConnectedServer().getMinecraftConnection().write(remapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
player.teardown();
|
||||
|
||||
if (pingTask != null && !pingTask.isCancelled()) {
|
||||
pingTask.cancel(false);
|
||||
pingTask = null;
|
||||
}
|
||||
|
||||
if (brandMessage != null) {
|
||||
brandMessage.getData().release();
|
||||
}
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
||||
if (!spawned) {
|
||||
// nothing special to do here
|
||||
spawned = true;
|
||||
currentDimension = joinGame.getDimension();
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
idRemapper = EntityIdRemapper.getMapper(joinGame.getEntityId(), player.getConnection().getProtocolVersion());
|
||||
} else {
|
||||
// In order to handle switching to another server we will need send three packets:
|
||||
// - The join game packet from the backend server
|
||||
// - A respawn packet with a different dimension
|
||||
// - Another respawn with the correct dimension
|
||||
// We can't simply ignore the packet with the different dimension. If you try to be smart about it it doesn't
|
||||
// work.
|
||||
idRemapper.setServerEntityId(joinGame.getEntityId());
|
||||
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()));
|
||||
currentDimension = joinGame.getDimension();
|
||||
}
|
||||
|
||||
// Resend client settings packet to remote server if we have it, this preserves client settings across
|
||||
// transitions.
|
||||
if (player.getClientSettings() != null) {
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(player.getClientSettings());
|
||||
}
|
||||
|
||||
// Remove old boss bars.
|
||||
for (UUID serverBossBar : serverBossBars) {
|
||||
BossBar deletePacket = new BossBar();
|
||||
deletePacket.setUuid(serverBossBar);
|
||||
deletePacket.setAction(1); // remove
|
||||
player.getConnection().delayedWrite(deletePacket);
|
||||
}
|
||||
serverBossBars.clear();
|
||||
|
||||
// Remove scoreboard junk.
|
||||
clearServerScoreboard();
|
||||
|
||||
// Tell the server about this client's plugin messages. Velocity will forward them on to the client.
|
||||
if (!clientPluginMsgChannels.isEmpty()) {
|
||||
String channel = player.getConnection().getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13 ?
|
||||
"minecraft:register" : "REGISTER";
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(
|
||||
PluginMessageUtil.constructChannelsPacket(channel, clientPluginMsgChannels));
|
||||
}
|
||||
|
||||
// Tell the server the client's brand
|
||||
if (brandMessage != null) {
|
||||
brandMessage.getData().retain();
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(brandMessage);
|
||||
}
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
player.getConnectedServer().getMinecraftConnection().flush();
|
||||
}
|
||||
|
||||
public void setCurrentDimension(int currentDimension) {
|
||||
this.currentDimension = currentDimension;
|
||||
}
|
||||
|
||||
public List<UUID> getServerBossBars() {
|
||||
return serverBossBars;
|
||||
}
|
||||
|
||||
public void handleClientPluginMessage(PluginMessage packet) {
|
||||
logger.info("Got client plugin message packet {}", packet);
|
||||
|
||||
PluginMessage original = packet;
|
||||
try {
|
||||
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) {
|
||||
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.size() > 0) {
|
||||
logger.info("Rewritten register packet: {}", actuallyRegistered);
|
||||
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
|
||||
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
clientPluginMsgChannels.removeAll(channels);
|
||||
}
|
||||
|
||||
if (packet.getChannel().equals("MC|Brand") || packet.getChannel().equals("minecraft:brand")) {
|
||||
if (this.brandMessage != null) {
|
||||
// Rewrite this packet to indicate that Velocity is running. Hurrah!
|
||||
packet = PluginMessageUtil.rewriteMCBrand(packet);
|
||||
this.brandMessage = packet;
|
||||
} else {
|
||||
// Already have the brand packet and don't need this one.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No other special handling?
|
||||
if (packet == original) {
|
||||
// we'll decrement this thrice: once when writing to the server, once just below this block,
|
||||
// and once in the MinecraftConnection (since this is a slice)
|
||||
packet.getData().retain();
|
||||
}
|
||||
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(original.getData());
|
||||
}
|
||||
}
|
||||
|
||||
public void handleServerScoreboardPacket(MinecraftPacket packet) {
|
||||
if (packet instanceof ScoreboardDisplay) {
|
||||
ScoreboardDisplay sd = (ScoreboardDisplay) packet;
|
||||
serverScoreboard.setPosition(sd.getPosition());
|
||||
serverScoreboard.setDisplayName(sd.getDisplayName());
|
||||
}
|
||||
|
||||
if (packet instanceof ScoreboardObjective) {
|
||||
ScoreboardObjective so = (ScoreboardObjective) packet;
|
||||
switch (so.getMode()) {
|
||||
case ScoreboardObjective.ADD:
|
||||
Objective o = new Objective(so.getId());
|
||||
o.setDisplayName(so.getDisplayName());
|
||||
o.setType(so.getType());
|
||||
serverScoreboard.getObjectives().put(so.getId(), o);
|
||||
break;
|
||||
case ScoreboardObjective.REMOVE:
|
||||
serverScoreboard.getObjectives().remove(so.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof ScoreboardSetScore) {
|
||||
ScoreboardSetScore sss = (ScoreboardSetScore) packet;
|
||||
Objective objective = serverScoreboard.getObjectives().get(sss.getObjective());
|
||||
if (objective == null) {
|
||||
return;
|
||||
}
|
||||
switch (sss.getAction()) {
|
||||
case ScoreboardSetScore.CHANGE:
|
||||
Score score = new Score(sss.getEntity(), sss.getValue());
|
||||
objective.getScores().put(sss.getEntity(), score);
|
||||
break;
|
||||
case ScoreboardSetScore.REMOVE:
|
||||
objective.getScores().remove(sss.getEntity());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet instanceof ScoreboardTeam) {
|
||||
ScoreboardTeam st = (ScoreboardTeam) packet;
|
||||
switch (st.getMode()) {
|
||||
case ScoreboardTeam.ADD:
|
||||
// TODO: Preserve other team information? We might not need to...
|
||||
Team team = new Team(st.getId());
|
||||
serverScoreboard.getTeams().put(st.getId(), team);
|
||||
break;
|
||||
case ScoreboardTeam.REMOVE:
|
||||
serverScoreboard.getTeams().remove(st.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearServerScoreboard() {
|
||||
for (Objective objective : serverScoreboard.getObjectives().values()) {
|
||||
for (Score score : objective.getScores().values()) {
|
||||
ScoreboardSetScore sss = new ScoreboardSetScore();
|
||||
sss.setObjective(objective.getId());
|
||||
sss.setAction(ScoreboardSetScore.REMOVE);
|
||||
sss.setEntity(score.getTarget());
|
||||
player.getConnection().delayedWrite(sss);
|
||||
}
|
||||
|
||||
ScoreboardObjective so = new ScoreboardObjective();
|
||||
so.setId(objective.getId());
|
||||
so.setMode(ScoreboardObjective.REMOVE);
|
||||
player.getConnection().delayedWrite(so);
|
||||
}
|
||||
|
||||
for (Team team : serverScoreboard.getTeams().values()) {
|
||||
ScoreboardTeam st = new ScoreboardTeam();
|
||||
st.setId(team.getId());
|
||||
st.setMode(ScoreboardTeam.REMOVE);
|
||||
player.getConnection().delayedWrite(st);
|
||||
}
|
||||
|
||||
serverScoreboard = new Scoreboard();
|
||||
}
|
||||
|
||||
public Set<String> getClientPluginMsgChannels() {
|
||||
return clientPluginMsgChannels;
|
||||
}
|
||||
|
||||
public EntityIdRemapper getIdRemapper() {
|
||||
return idRemapper;
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.packets.Chat;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.packets.ClientSettings;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import com.velocitypowered.api.servers.ServerInfo;
|
||||
import com.velocitypowered.proxy.protocol.packets.Disconnect;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import net.kyori.text.serializer.PlainComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation {
|
||||
private static final PlainComponentSerializer PASS_THRU_TRANSLATE = new PlainComponentSerializer((c) -> "", TranslatableComponent::key);
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(ConnectedPlayer.class);
|
||||
|
||||
private final GameProfile profile;
|
||||
private final MinecraftConnection connection;
|
||||
private int tryIndex = 0;
|
||||
private ServerConnection connectedServer;
|
||||
private ClientSettings clientSettings;
|
||||
private ServerConnection connectionInFlight;
|
||||
|
||||
public ConnectedPlayer(GameProfile profile, MinecraftConnection connection) {
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
public UUID getUniqueId() {
|
||||
return profile.idAsUuid();
|
||||
}
|
||||
|
||||
public GameProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public MinecraftConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) connection.getChannel().remoteAddress();
|
||||
}
|
||||
|
||||
public ServerConnection getConnectedServer() {
|
||||
return connectedServer;
|
||||
}
|
||||
|
||||
public ClientSettings getClientSettings() {
|
||||
return clientSettings;
|
||||
}
|
||||
|
||||
public void setClientSettings(ClientSettings clientSettings) {
|
||||
this.clientSettings = clientSettings;
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Throwable throwable) {
|
||||
String error = ThrowableUtils.briefDescription(throwable);
|
||||
String userMessage;
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
logger.error("{}: exception occurred in connection to {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception in server " + info.getName();
|
||||
} else {
|
||||
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + info.getName();
|
||||
}
|
||||
handleConnectionException(info, TextComponent.builder()
|
||||
.content(userMessage + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(TextComponent.of(error, TextColor.WHITE))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Disconnect disconnect) {
|
||||
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason);
|
||||
} else {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason);
|
||||
}
|
||||
handleConnectionException(info, disconnectReason);
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Component disconnectReason) {
|
||||
if (connectedServer == null || connectedServer.getServerInfo().equals(info)) {
|
||||
// The player isn't yet connected to a server or they are already connected to the server
|
||||
// they're disconnected from.
|
||||
connection.closeWith(Disconnect.create(disconnectReason));
|
||||
} else {
|
||||
connection.write(Chat.create(disconnectReason));
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ServerInfo> getNextServerToTry() {
|
||||
List<String> serversToTry = VelocityServer.getServer().getConfiguration().getAttemptConnectionOrder();
|
||||
if (tryIndex >= serversToTry.size()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String toTryName = serversToTry.get(tryIndex);
|
||||
tryIndex++;
|
||||
return VelocityServer.getServer().getServers().getServer(toTryName);
|
||||
}
|
||||
|
||||
public void connect(ServerInfo info) {
|
||||
Preconditions.checkNotNull(info, "info");
|
||||
Preconditions.checkState(connectionInFlight == null, "A connection is already active!");
|
||||
ServerConnection connection = new ServerConnection(info, this, VelocityServer.getServer());
|
||||
connectionInFlight = connection;
|
||||
connection.connect();
|
||||
}
|
||||
|
||||
public void setConnectedServer(ServerConnection serverConnection) {
|
||||
if (this.connectedServer != null && !serverConnection.getServerInfo().equals(connectedServer.getServerInfo())) {
|
||||
this.tryIndex = 0;
|
||||
}
|
||||
this.connectedServer = serverConnection;
|
||||
}
|
||||
|
||||
public void close(TextComponent reason) {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
public void teardown() {
|
||||
if (connectionInFlight != null) {
|
||||
connectionInFlight.disconnect();
|
||||
}
|
||||
if (connectedServer != null) {
|
||||
connectedServer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[connected player] " + getProfile().getName() + " (" + getRemoteAddress() + ")";
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packets.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packets.Handshake;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
|
||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
private final MinecraftConnection connection;
|
||||
|
||||
public HandshakeSessionHandler(MinecraftConnection connection) {
|
||||
this.connection = Preconditions.checkNotNull(connection, "connection");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
if (!(packet instanceof Handshake)) {
|
||||
throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName());
|
||||
}
|
||||
|
||||
Handshake handshake = (Handshake) packet;
|
||||
switch (handshake.getNextStatus()) {
|
||||
case StateRegistry.STATUS_ID:
|
||||
connection.setState(StateRegistry.STATUS);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
connection.setSessionHandler(new StatusSessionHandler(connection));
|
||||
break;
|
||||
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;
|
||||
} else {
|
||||
connection.setSessionHandler(new LoginSessionHandler(connection));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
|
||||
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
||||
private final ConnectedPlayer player;
|
||||
|
||||
public InitialConnectSessionHandler(ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
// No-op: will never handle packets
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
// the user cancelled the login process
|
||||
player.teardown();
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packets.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.api.servers.ServerInfo;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||
private static final String MOJANG_SERVER_AUTH_URL =
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
|
||||
|
||||
private final MinecraftConnection inbound;
|
||||
private ServerLogin login;
|
||||
private byte[] verify;
|
||||
private int playerInfoId;
|
||||
|
||||
public LoginSessionHandler(MinecraftConnection inbound) {
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
if (inbound.getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
LoginPluginMessage message = new LoginPluginMessage();
|
||||
playerInfoId = ThreadLocalRandom.current().nextInt();
|
||||
message.setId(playerInfoId);
|
||||
message.setChannel(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL);
|
||||
message.setData(Unpooled.EMPTY_BUFFER);
|
||||
inbound.write(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) throws Exception {
|
||||
if (packet instanceof LoginPluginResponse) {
|
||||
LoginPluginResponse lpr = (LoginPluginResponse) packet;
|
||||
if (lpr.getId() == playerInfoId && lpr.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 if (packet instanceof ServerLogin) {
|
||||
this.login = (ServerLogin) packet;
|
||||
|
||||
if (VelocityServer.getServer().getConfiguration().isOnlineMode()) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
inbound.write(request);
|
||||
} else {
|
||||
// Offline-mode, don't try to request encryption.
|
||||
handleSuccessfulLogin(GameProfile.forOfflinePlayer(login.getUsername()));
|
||||
}
|
||||
} else if (packet instanceof EncryptionResponse) {
|
||||
KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair();
|
||||
EncryptionResponse response = (EncryptionResponse) packet;
|
||||
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken());
|
||||
if (!Arrays.equals(verify, decryptedVerifyToken)) {
|
||||
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
|
||||
}
|
||||
|
||||
byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, response.getSharedSecret());
|
||||
String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
|
||||
|
||||
String playerIp = ((InetSocketAddress) inbound.getChannel().remoteAddress()).getHostString();
|
||||
VelocityServer.getServer().getHttpClient()
|
||||
.get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
|
||||
.thenAcceptAsync(profileResponse -> {
|
||||
try {
|
||||
inbound.enableEncryption(decryptedSharedSecret);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class);
|
||||
handleSuccessfulLogin(profile);
|
||||
}, inbound.getChannel().eventLoop())
|
||||
.exceptionally(exception -> {
|
||||
logger.error("Unable to enable encryption", exception);
|
||||
inbound.close();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private EncryptionRequest generateRequest() {
|
||||
byte[] verify = new byte[4];
|
||||
ThreadLocalRandom.current().nextBytes(verify);
|
||||
|
||||
EncryptionRequest request = new EncryptionRequest();
|
||||
request.setPublicKey(VelocityServer.getServer().getServerKeyPair().getPublic().getEncoded());
|
||||
request.setVerifyToken(verify);
|
||||
return request;
|
||||
}
|
||||
|
||||
private void handleSuccessfulLogin(GameProfile profile) {
|
||||
// Initiate a regular connection and move over to it.
|
||||
ConnectedPlayer player = new ConnectedPlayer(profile, inbound);
|
||||
Optional<ServerInfo> toTry = player.getNextServerToTry();
|
||||
if (!toTry.isPresent()) {
|
||||
player.close(TextComponent.of("No available servers", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
inbound.write(new SetCompression(256));
|
||||
inbound.setCompressionThreshold(256);
|
||||
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(profile.getName());
|
||||
success.setUuid(profile.idAsUuid());
|
||||
inbound.write(success);
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
inbound.setAssociation(player);
|
||||
inbound.setState(StateRegistry.PLAY);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
player.connect(toTry.get());
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.packets.KeepAlive;
|
||||
import com.velocitypowered.proxy.protocol.packets.StatusPing;
|
||||
import com.velocitypowered.proxy.protocol.packets.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packets.StatusResponse;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.data.ServerPing;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
private final MinecraftConnection connection;
|
||||
|
||||
public StatusSessionHandler(MinecraftConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
Preconditions.checkArgument(packet instanceof StatusPing|| packet instanceof StatusRequest,
|
||||
"Unrecognized packet type " + packet.getClass().getName());
|
||||
|
||||
if (packet instanceof StatusPing) {
|
||||
// Just send back the client's packet, no processing to do here.
|
||||
connection.closeWith(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration();
|
||||
|
||||
// Status request
|
||||
ServerPing ping = new ServerPing(
|
||||
new ServerPing.Version(connection.getProtocolVersion(), "Velocity 1.9-1.13"),
|
||||
new ServerPing.Players(0, configuration.getShowMaxPlayers()),
|
||||
configuration.getMotdComponent(),
|
||||
null
|
||||
);
|
||||
StatusResponse response = new StatusResponse();
|
||||
response.setStatus(VelocityServer.GSON.toJson(ping));
|
||||
connection.write(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
throw new IllegalStateException("Unknown data " + ByteBufUtil.hexDump(buf));
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package com.velocitypowered.proxy.connection.http;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.codec.http.*;
|
||||
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.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class NettyHttpClient {
|
||||
private final VelocityServer server;
|
||||
|
||||
public NettyHttpClient(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public CompletableFuture<String> 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<String> reply = new CompletableFuture<>();
|
||||
server.initializeGenericBootstrap()
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
if (ssl) {
|
||||
SslContext context = SslContextBuilder.forClient().build();
|
||||
SSLEngine engine = context.newEngine(ch.alloc());
|
||||
ch.pipeline().addLast(new SslHandler(engine));
|
||||
}
|
||||
ch.pipeline().addLast(new HttpClientCodec());
|
||||
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
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");
|
||||
ctx.writeAndFlush(request);
|
||||
}
|
||||
});
|
||||
ch.pipeline().addLast(new SimpleHttpResponseCollector(reply));
|
||||
}
|
||||
})
|
||||
.connect(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
reply.completeExceptionally(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
return reply;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.velocitypowered.proxy.connection.http;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
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.LastHttpContent;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
|
||||
private final StringBuilder buffer = new StringBuilder(1024);
|
||||
private final CompletableFuture<String> reply;
|
||||
|
||||
SimpleHttpResponseCollector(CompletableFuture<String> reply) {
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof HttpResponse) {
|
||||
HttpResponseStatus status = ((HttpResponse) msg).status();
|
||||
if (status != HttpResponseStatus.OK) {
|
||||
ctx.close();
|
||||
reply.completeExceptionally(new RuntimeException("Unexpected status code " + status.code()));
|
||||
}
|
||||
}
|
||||
|
||||
if (msg instanceof HttpContent) {
|
||||
buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8));
|
||||
((HttpContent) msg).release();
|
||||
|
||||
if (msg instanceof LastHttpContent) {
|
||||
ctx.close();
|
||||
reply.complete(buffer.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
package com.velocitypowered.proxy.data;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.proxy.util.UuidUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class GameProfile {
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final List<Property> properties;
|
||||
|
||||
public GameProfile(String id, String name, List<Property> properties) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.properties = ImmutableList.copyOf(properties);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public UUID idAsUuid() {
|
||||
return UuidUtils.fromUndashed(id);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<Property> getProperties() {
|
||||
return ImmutableList.copyOf(properties);
|
||||
}
|
||||
|
||||
public static GameProfile forOfflinePlayer(String username) {
|
||||
Preconditions.checkNotNull(username, "username");
|
||||
String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username));
|
||||
return new GameProfile(id, username, ImmutableList.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GameProfile{" +
|
||||
"id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", properties=" + properties +
|
||||
'}';
|
||||
}
|
||||
|
||||
public class Property {
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String signature;
|
||||
|
||||
public Property(String name, String value, String signature) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Property{" +
|
||||
"name='" + name + '\'' +
|
||||
", value='" + value + '\'' +
|
||||
", signature='" + signature + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
package com.velocitypowered.proxy.data;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
|
||||
public class ServerPing {
|
||||
private final Version version;
|
||||
private final Players players;
|
||||
private final Component description;
|
||||
private final String favicon;
|
||||
|
||||
public ServerPing(Version version, Players players, Component description, String favicon) {
|
||||
this.version = version;
|
||||
this.players = players;
|
||||
this.description = description;
|
||||
this.favicon = favicon;
|
||||
}
|
||||
|
||||
public Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Players getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public Component getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getFavicon() {
|
||||
return favicon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerPing{" +
|
||||
"version=" + version +
|
||||
", players=" + players +
|
||||
", description=" + description +
|
||||
", favicon='" + favicon + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static class Version {
|
||||
private final int protocol;
|
||||
private final String name;
|
||||
|
||||
public Version(int protocol, String name) {
|
||||
this.protocol = protocol;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Version{" +
|
||||
"protocol=" + protocol +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Players {
|
||||
private final int online;
|
||||
private final int max;
|
||||
|
||||
public Players(int online, int max) {
|
||||
this.online = online;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public int getOnline() {
|
||||
return online;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Players{" +
|
||||
"online=" + online +
|
||||
", max=" + max +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package com.velocitypowered.proxy.data.scoreboard;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Objective {
|
||||
private final String id;
|
||||
private Component displayName;
|
||||
private ObjectiveMode type;
|
||||
private final List<Team> teams = new ArrayList<>();
|
||||
private final Map<String, Score> scores = new HashMap<>();
|
||||
|
||||
public Objective(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public ObjectiveMode getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ObjectiveMode type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Team> getTeams() {
|
||||
return teams;
|
||||
}
|
||||
|
||||
public Map<String, Score> getScores() {
|
||||
return scores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Objective{" +
|
||||
"id='" + id + '\'' +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
", teams=" + teams +
|
||||
", scores=" + scores +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package com.velocitypowered.proxy.data.scoreboard;
|
||||
|
||||
public enum ObjectiveMode {
|
||||
INTEGER,
|
||||
HEARTS
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package com.velocitypowered.proxy.data.scoreboard;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Score {
|
||||
private final String target;
|
||||
private final int value;
|
||||
|
||||
public Score(String target, int value) {
|
||||
this.target = target;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Score score = (Score) o;
|
||||
return value == score.value &&
|
||||
Objects.equals(target, score.target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(target, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Score{" +
|
||||
"target='" + target + '\'' +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.velocitypowered.proxy.data.scoreboard;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Scoreboard {
|
||||
private String displayName;
|
||||
private byte position;
|
||||
private final Map<String, Objective> objectives = new HashMap<>();
|
||||
private final Map<String, Team> teams = new HashMap<>();
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public byte getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(byte position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public Map<String, Objective> getObjectives() {
|
||||
return objectives;
|
||||
}
|
||||
|
||||
public Map<String, Team> getTeams() {
|
||||
return teams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Scoreboard{" +
|
||||
"displayName='" + displayName + '\'' +
|
||||
", position=" + position +
|
||||
", objectives=" + objectives +
|
||||
", teams=" + teams +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
package com.velocitypowered.proxy.data.scoreboard;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Team {
|
||||
private final String id;
|
||||
private String prefix;
|
||||
private String suffix;
|
||||
private byte flags;
|
||||
private String nameTagVisibility;
|
||||
private String collision;
|
||||
private byte color;
|
||||
private final Collection<String> players = new HashSet<>();
|
||||
|
||||
public Team(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public void setSuffix(String suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public byte getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(byte flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public String getNameTagVisibility() {
|
||||
return nameTagVisibility;
|
||||
}
|
||||
|
||||
public void setNameTagVisibility(String nameTagVisibility) {
|
||||
this.nameTagVisibility = nameTagVisibility;
|
||||
}
|
||||
|
||||
public String getCollision() {
|
||||
return collision;
|
||||
}
|
||||
|
||||
public void setCollision(String collision) {
|
||||
this.collision = collision;
|
||||
}
|
||||
|
||||
public byte getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(byte color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Collection<String> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Team{" +
|
||||
"id='" + id + '\'' +
|
||||
", prefix='" + prefix + '\'' +
|
||||
", suffix='" + suffix + '\'' +
|
||||
", flags=" + flags +
|
||||
", nameTagVisibility='" + nameTagVisibility + '\'' +
|
||||
", collision='" + collision + '\'' +
|
||||
", color=" + color +
|
||||
", players=" + players +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
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);
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
public class PacketWrapper {
|
||||
private final MinecraftPacket packet;
|
||||
private final ByteBuf buffer;
|
||||
|
||||
public PacketWrapper(MinecraftPacket packet, ByteBuf buffer) {
|
||||
this.packet = packet;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public MinecraftPacket getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public ByteBuf getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketWrapper{" +
|
||||
"packet=" + packet +
|
||||
", buffer=" + ByteBufUtil.hexDump(buffer) +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum ProtocolConstants { ;
|
||||
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 MINIMUM_GENERIC_VERSION = MINECRAFT_1_9;
|
||||
|
||||
public static final int[] SUPPORTED_VERSIONS = new int[] {
|
||||
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
|
||||
};
|
||||
|
||||
public static boolean isSupported(int version) {
|
||||
return Arrays.binarySearch(SUPPORTED_VERSIONS, version) >= 0;
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
SERVERBOUND,
|
||||
CLIENTBOUND
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializer;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
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 void writeVarInt(ByteBuf buf, int value) {
|
||||
while (true) {
|
||||
if ((value & 0xFFFFFF80) == 0) {
|
||||
buf.writeByte(value);
|
||||
return;
|
||||
}
|
||||
|
||||
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, 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 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 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 void writeUuid(ByteBuf buf, UUID uuid) {
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
buf.writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static Component readScoreboardTextComponent(ByteBuf buf, int protocolVersion) {
|
||||
String toDeserialize = readString(buf);
|
||||
ComponentSerializer<Component, ? extends Component, String> serializer =
|
||||
protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ? ComponentSerializers.JSON : ComponentSerializers.LEGACY;
|
||||
return serializer.deserialize(toDeserialize);
|
||||
}
|
||||
|
||||
public static void writeScoreboardTextComponent(ByteBuf buf, int protocolVersion, Component component) {
|
||||
ComponentSerializer<Component, ? extends Component, String> serializer =
|
||||
protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ? ComponentSerializers.JSON : ComponentSerializers.LEGACY;
|
||||
writeString(buf, serializer.serialize(component));
|
||||
}
|
||||
}
|
@@ -0,0 +1,274 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.packets.*;
|
||||
import io.netty.util.collection.IntObjectHashMap;
|
||||
import io.netty.util.collection.IntObjectMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
|
||||
|
||||
public enum StateRegistry {
|
||||
HANDSHAKE {
|
||||
{
|
||||
SERVERBOUND.register(Handshake.class, Handshake::new,
|
||||
genericMappings(0x00));
|
||||
}
|
||||
},
|
||||
STATUS {
|
||||
{
|
||||
SERVERBOUND.register(StatusRequest.class, StatusRequest::new,
|
||||
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.register(Chat.class, Chat::new,
|
||||
map(0x02, MINECRAFT_1_9),
|
||||
map(0x03, MINECRAFT_1_12),
|
||||
map(0x02, MINECRAFT_1_12_2),
|
||||
map(0x02, MINECRAFT_1_13));
|
||||
SERVERBOUND.register(ClientSettings.class, ClientSettings::new,
|
||||
map(0x04, MINECRAFT_1_9),
|
||||
map(0x05, MINECRAFT_1_12),
|
||||
map(0x04, MINECRAFT_1_12_1),
|
||||
map(0x04, MINECRAFT_1_13));
|
||||
SERVERBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x09, MINECRAFT_1_9),
|
||||
map(0x0A, MINECRAFT_1_12),
|
||||
map(0x09, MINECRAFT_1_12_1),
|
||||
map(0x0A, MINECRAFT_1_13));
|
||||
SERVERBOUND.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x0B, MINECRAFT_1_9),
|
||||
map(0x0C, MINECRAFT_1_12),
|
||||
map(0x0B, MINECRAFT_1_12_1),
|
||||
map(0x0E, MINECRAFT_1_13));
|
||||
|
||||
CLIENTBOUND.register(BossBar.class, BossBar::new,
|
||||
map(0x0C, MINECRAFT_1_9),
|
||||
map(0x0C, MINECRAFT_1_12));
|
||||
CLIENTBOUND.register(Chat.class, Chat::new,
|
||||
map(0x0F, MINECRAFT_1_9),
|
||||
map(0x0F, MINECRAFT_1_12),
|
||||
map(0x0E, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x18, MINECRAFT_1_9),
|
||||
map(0x18, MINECRAFT_1_12),
|
||||
map(0x19, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(Disconnect.class, Disconnect::new,
|
||||
map(0x1A, MINECRAFT_1_9),
|
||||
map(0x1A, MINECRAFT_1_12),
|
||||
map(0x1B, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x1F, MINECRAFT_1_9),
|
||||
map(0x1F, MINECRAFT_1_12),
|
||||
map(0x21, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(JoinGame.class, JoinGame::new,
|
||||
map(0x23, MINECRAFT_1_9),
|
||||
map(0x23, MINECRAFT_1_12),
|
||||
map(0x25, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(Respawn.class, Respawn::new,
|
||||
map(0x33, MINECRAFT_1_9),
|
||||
map(0x34, MINECRAFT_1_12),
|
||||
map(0x35, MINECRAFT_1_12_2),
|
||||
map(0x38, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new,
|
||||
map(0x38, MINECRAFT_1_9),
|
||||
map(0x3A, MINECRAFT_1_12),
|
||||
map(0x3B, MINECRAFT_1_12_1),
|
||||
map(0x3E, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(ScoreboardObjective.class, ScoreboardObjective::new,
|
||||
map(0x3F, MINECRAFT_1_9),
|
||||
map(0x41, MINECRAFT_1_12),
|
||||
map(0x42, MINECRAFT_1_12_1),
|
||||
map(0x45, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(ScoreboardTeam.class, ScoreboardTeam::new,
|
||||
map(0x41, MINECRAFT_1_9),
|
||||
map(0x43, MINECRAFT_1_12),
|
||||
map(0x44, MINECRAFT_1_12_1),
|
||||
map(0x47, MINECRAFT_1_13));
|
||||
CLIENTBOUND.register(ScoreboardSetScore.class, ScoreboardSetScore::new,
|
||||
map(0x42, MINECRAFT_1_9),
|
||||
map(0x44, MINECRAFT_1_12),
|
||||
map(0x45, MINECRAFT_1_12_1),
|
||||
map(0x48, MINECRAFT_1_13));
|
||||
}
|
||||
},
|
||||
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));
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
public static final int STATUS_ID = 1;
|
||||
public static final int LOGIN_ID = 2;
|
||||
public final PacketRegistry CLIENTBOUND = new PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, this);
|
||||
public final PacketRegistry SERVERBOUND = new PacketRegistry(ProtocolConstants.Direction.SERVERBOUND, this);
|
||||
|
||||
public static class PacketRegistry {
|
||||
private static final IntObjectMap<int[]> LINKED_PROTOCOL_VERSIONS = new IntObjectHashMap<>();
|
||||
|
||||
static {
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9, new int[] { MINECRAFT_1_9_1, MINECRAFT_1_9_2, MINECRAFT_1_9_4 });
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_9_4, new int[] { MINECRAFT_1_10, MINECRAFT_1_11, MINECRAFT_1_11_1 });
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12, new int[] { MINECRAFT_1_12_1 });
|
||||
LINKED_PROTOCOL_VERSIONS.put(MINECRAFT_1_12_1, new int[] { MINECRAFT_1_12_2 });
|
||||
}
|
||||
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private final StateRegistry state;
|
||||
private final IntObjectMap<ProtocolVersion> versions = new IntObjectHashMap<>();
|
||||
|
||||
public PacketRegistry(Direction direction, StateRegistry state) {
|
||||
this.direction = direction;
|
||||
this.state = state;
|
||||
for (int version : ProtocolConstants.SUPPORTED_VERSIONS) {
|
||||
versions.put(version, new ProtocolVersion(version));
|
||||
}
|
||||
versions.put(MINIMUM_GENERIC_VERSION, new ProtocolVersion(MINIMUM_GENERIC_VERSION));
|
||||
}
|
||||
|
||||
public ProtocolVersion getVersion(final int version) {
|
||||
ProtocolVersion result = versions.get(version);
|
||||
if (result == null) {
|
||||
if (state != PLAY) {
|
||||
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);
|
||||
}
|
||||
|
||||
version.packetIdToSupplier.put(mapping.id, packetSupplier);
|
||||
version.packetClassToId.put(clazz, mapping.id);
|
||||
|
||||
int[] linked = LINKED_PROTOCOL_VERSIONS.get(mapping.protocolVersion);
|
||||
if (linked != null) {
|
||||
links: for (int i : linked) {
|
||||
// Make sure that later mappings override this one.
|
||||
for (PacketMapping m : mappings) {
|
||||
if (i == m.protocolVersion) continue links;
|
||||
}
|
||||
register(clazz, packetSupplier, map(mapping.id, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProtocolVersion {
|
||||
public final int id;
|
||||
final IntObjectMap<Supplier<? extends MinecraftPacket>> packetIdToSupplier = new IntObjectHashMap<>();
|
||||
final Map<Class<? extends MinecraftPacket>, Integer> packetClassToId = new HashMap<>();
|
||||
|
||||
ProtocolVersion(final int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public 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 Integer id = this.packetClassToId.get(packet.getClass());
|
||||
if (id == null) {
|
||||
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.id
|
||||
));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProtocolVersion{" +
|
||||
"id=" + id +
|
||||
", packetClassToId=" + packetClassToId +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PacketMapping {
|
||||
private final int id;
|
||||
private final int protocolVersion;
|
||||
|
||||
public PacketMapping(int id, int protocolVersion) {
|
||||
this.id = id;
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketMapping{" +
|
||||
"id=" + id +
|
||||
", protocolVersion=" + protocolVersion +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, protocolVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private static PacketMapping map(int id, int version) {
|
||||
return new PacketMapping(id, version);
|
||||
}
|
||||
|
||||
private static PacketMapping[] genericMappings(int id) {
|
||||
return new PacketMapping[]{
|
||||
map(id, MINECRAFT_1_9),
|
||||
map(id, MINECRAFT_1_12),
|
||||
map(id, MINECRAFT_1_13)
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.velocitypowered.proxy.protocol.compression;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
private final Deflater deflater;
|
||||
private final Inflater inflater;
|
||||
private final byte[] buf;
|
||||
private boolean disposed = false;
|
||||
|
||||
public JavaVelocityCompressor() {
|
||||
this.deflater = new Deflater();
|
||||
this.inflater = new Inflater();
|
||||
this.buf = new byte[8192];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] inData = new byte[source.readableBytes()];
|
||||
source.readBytes(inData);
|
||||
inflater.setInput(inData);
|
||||
while (!inflater.finished()) {
|
||||
int read = inflater.inflate(buf);
|
||||
destination.writeBytes(buf, 0, read);
|
||||
}
|
||||
inflater.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] inData = new byte[source.readableBytes()];
|
||||
source.readBytes(inData);
|
||||
deflater.setInput(inData);
|
||||
deflater.finish();
|
||||
while (!deflater.finished()) {
|
||||
int bytes = deflater.deflate(buf);
|
||||
destination.writeBytes(buf, 0, bytes);
|
||||
}
|
||||
deflater.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ensureNotDisposed();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.velocitypowered.proxy.protocol.compression;
|
||||
|
||||
import com.velocitypowered.proxy.util.Disposable;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
public interface VelocityCompressor extends Disposable {
|
||||
void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
|
||||
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package com.velocitypowered.proxy.protocol.encryption;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class JavaVelocityCipher implements VelocityCipher {
|
||||
private final Cipher cipher;
|
||||
private boolean disposed = false;
|
||||
|
||||
public JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
|
||||
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
|
||||
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(key.getEncoded()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||
ensureNotDisposed();
|
||||
|
||||
byte[] sourceAsBytes = new byte[source.readableBytes()];
|
||||
source.readBytes(sourceAsBytes);
|
||||
|
||||
int outputSize = cipher.getOutputSize(sourceAsBytes.length);
|
||||
byte[] destinationBytes = new byte[outputSize];
|
||||
cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes);
|
||||
destination.writeBytes(destinationBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ensureNotDisposed();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.velocitypowered.proxy.protocol.encryption;
|
||||
|
||||
import com.velocitypowered.proxy.util.Disposable;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
public interface VelocityCipher extends Disposable {
|
||||
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.packets.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());
|
||||
}
|
||||
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.proxy.protocol.packets.LegacyPingResponse;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class LegacyPingEncoder extends MessageToByteEncoder<LegacyPingResponse> {
|
||||
public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder();
|
||||
|
||||
private LegacyPingEncoder() {}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, LegacyPingResponse msg, ByteBuf out) throws Exception {
|
||||
out.writeByte(0xff);
|
||||
String serializedResponse = serialize(msg);
|
||||
out.writeShort(serializedResponse.length());
|
||||
out.writeBytes(serializedResponse.getBytes(StandardCharsets.UTF_16BE));
|
||||
}
|
||||
|
||||
private String serialize(LegacyPingResponse response) {
|
||||
List<String> parts = ImmutableList.of(
|
||||
"§1",
|
||||
Integer.toString(response.getProtocolVersion()),
|
||||
response.getServerVersion(),
|
||||
response.getMotd(),
|
||||
Integer.toString(response.getPlayersOnline()),
|
||||
Integer.toString(response.getPlayersMax())
|
||||
);
|
||||
return Joiner.on('\0').join(parts);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.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");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
ByteBuf decrypted = ctx.alloc().buffer();
|
||||
try {
|
||||
cipher.process(in, decrypted);
|
||||
out.add(decrypted);
|
||||
} catch (Exception e) {
|
||||
decrypted.release();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.encryption.VelocityCipher;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
cipher.process(msg, out);
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.compression.VelocityCompressor;
|
||||
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;
|
||||
|
||||
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.slice().retain());
|
||||
msg.skipBytes(msg.readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
Preconditions.checkState(uncompressedSize >= threshold, "Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.compression.VelocityCompressor;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
if (msg.readableBytes() <= threshold) {
|
||||
// Under the threshold, there is nothing to do.
|
||||
ProtocolUtils.writeVarInt(out, 0);
|
||||
out.writeBytes(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuf compressedBuffer = ctx.alloc().buffer();
|
||||
try {
|
||||
int uncompressed = msg.readableBytes();
|
||||
compressor.deflate(msg, compressedBuffer);
|
||||
ProtocolUtils.writeVarInt(out, uncompressed);
|
||||
out.writeBytes(compressedBuffer);
|
||||
} finally {
|
||||
compressedBuffer.release();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.*;
|
||||
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 StateRegistry state;
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
|
||||
|
||||
public MinecraftDecoder(ProtocolConstants.Direction direction) {
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
this.direction = Preconditions.checkNotNull(direction, "direction");
|
||||
this.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
if (!msg.isReadable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuf slice = msg.slice().retain();
|
||||
|
||||
int packetId = ProtocolUtils.readVarInt(msg);
|
||||
MinecraftPacket packet = this.protocolVersion.createPacket(packetId);
|
||||
if (packet == null) {
|
||||
msg.skipBytes(msg.readableBytes());
|
||||
out.add(new PacketWrapper(null, slice));
|
||||
} else {
|
||||
try {
|
||||
packet.decode(msg, direction, protocolVersion.id);
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId), e);
|
||||
}
|
||||
out.add(new PacketWrapper(packet, slice));
|
||||
}
|
||||
}
|
||||
|
||||
public StateRegistry.PacketRegistry.ProtocolVersion getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = (this.direction == ProtocolConstants.Direction.CLIENTBOUND ? this.state.CLIENTBOUND : this.state.SERVERBOUND).getVersion(protocolVersion);
|
||||
}
|
||||
|
||||
public StateRegistry getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.setProtocolVersion(protocolVersion.id);
|
||||
}
|
||||
|
||||
public ProtocolConstants.Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
|
||||
private StateRegistry state;
|
||||
private final ProtocolConstants.Direction direction;
|
||||
private StateRegistry.PacketRegistry.ProtocolVersion protocolVersion;
|
||||
|
||||
public MinecraftEncoder(ProtocolConstants.Direction direction) {
|
||||
this.state = StateRegistry.HANDSHAKE;
|
||||
this.direction = Preconditions.checkNotNull(direction, "direction");
|
||||
this.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
}
|
||||
|
||||
@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.id);
|
||||
}
|
||||
|
||||
public StateRegistry.PacketRegistry.ProtocolVersion getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(final int protocolVersion) {
|
||||
this.protocolVersion = (this.direction == ProtocolConstants.Direction.CLIENTBOUND ? this.state.CLIENTBOUND : this.state.SERVERBOUND).getVersion(protocolVersion);
|
||||
}
|
||||
|
||||
public StateRegistry getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(StateRegistry state) {
|
||||
this.state = state;
|
||||
this.setProtocolVersion(protocolVersion.id);
|
||||
}
|
||||
|
||||
public ProtocolConstants.Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (in.readableBytes() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
in.markReaderIndex();
|
||||
int packetLength = ProtocolUtils.readVarInt(in);
|
||||
if (in.readableBytes() < packetLength) {
|
||||
in.resetReaderIndex();
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(in.slice(in.readerIndex(), packetLength).retain());
|
||||
in.skipBytes(packetLength);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class MinecraftVarintLengthEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||
|
||||
private MinecraftVarintLengthEncoder() { }
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||
out.ensureWritable(msg.readableBytes() + 5);
|
||||
ProtocolUtils.writeVarInt(out, msg.readableBytes());
|
||||
out.writeBytes(msg);
|
||||
}
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
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;
|
||||
|
||||
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 UUID uuid;
|
||||
private int action;
|
||||
private String name;
|
||||
private float percent;
|
||||
private int color;
|
||||
private int overlay;
|
||||
private short flags;
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeUuid(buf, uuid);
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
switch (action) {
|
||||
case ADD:
|
||||
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:
|
||||
ProtocolUtils.writeString(buf, name);
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeVarInt(buf, overlay);
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
buf.writeByte(flags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
public class Chat implements MinecraftPacket {
|
||||
public static final byte CHAT = (byte) 0;
|
||||
private String message;
|
||||
private byte type;
|
||||
|
||||
public Chat() {
|
||||
}
|
||||
|
||||
public Chat(String message, byte type) {
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public byte getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(byte type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Chat{" +
|
||||
"message='" + message + '\'' +
|
||||
", 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 void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, message);
|
||||
if (direction == ProtocolConstants.Direction.CLIENTBOUND) {
|
||||
buf.writeByte(type);
|
||||
}
|
||||
}
|
||||
|
||||
public static Chat create(Component component) {
|
||||
return create(component, CHAT);
|
||||
}
|
||||
|
||||
public static Chat create(Component component, byte type) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Chat(ComponentSerializers.JSON.serialize(component), type);
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ClientSettings implements MinecraftPacket {
|
||||
private String locale;
|
||||
private byte viewDistance;
|
||||
private int chatVisibility;
|
||||
private boolean chatColors;
|
||||
private short skinParts;
|
||||
private int mainHand;
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
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();
|
||||
this.mainHand = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, locale);
|
||||
buf.writeByte(viewDistance);
|
||||
ProtocolUtils.writeVarInt(buf, chatVisibility);
|
||||
buf.writeBoolean(chatColors);
|
||||
buf.writeByte(skinParts);
|
||||
ProtocolUtils.writeVarInt(buf, mainHand);
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
public class Disconnect implements MinecraftPacket {
|
||||
private String reason;
|
||||
|
||||
public Disconnect() {
|
||||
}
|
||||
|
||||
public Disconnect(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Disconnect{" +
|
||||
"reason='" + reason + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
reason = ProtocolUtils.readString(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, reason);
|
||||
}
|
||||
|
||||
public static Disconnect create(Component component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Disconnect(ComponentSerializers.JSON.serialize(component));
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
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;
|
||||
|
||||
public class EncryptionRequest implements MinecraftPacket {
|
||||
private byte[] publicKey;
|
||||
private byte[] verifyToken;
|
||||
|
||||
public byte[] getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(byte[] publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
|
||||
public void setVerifyToken(byte[] verifyToken) {
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EncryptionRequest{" +
|
||||
"publicKey=" + Arrays.toString(publicKey) +
|
||||
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.readString(buf); // Server ID, can be ignored since it is an empty string
|
||||
publicKey = ProtocolUtils.readByteArray(buf, 256);
|
||||
verifyToken = ProtocolUtils.readByteArray(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, ""); // Server ID
|
||||
ProtocolUtils.writeByteArray(buf, publicKey);
|
||||
ProtocolUtils.writeByteArray(buf, verifyToken);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
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;
|
||||
|
||||
public class EncryptionResponse implements MinecraftPacket {
|
||||
private byte[] sharedSecret;
|
||||
private byte[] verifyToken;
|
||||
|
||||
public byte[] getSharedSecret() {
|
||||
return sharedSecret;
|
||||
}
|
||||
|
||||
public void setSharedSecret(byte[] sharedSecret) {
|
||||
this.sharedSecret = sharedSecret;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
|
||||
public void setVerifyToken(byte[] verifyToken) {
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EncryptionResponse{" +
|
||||
"sharedSecret=" + Arrays.toString(sharedSecret) +
|
||||
", verifyToken=" + Arrays.toString(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 void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeByteArray(buf, sharedSecret);
|
||||
ProtocolUtils.writeByteArray(buf, verifyToken);
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
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;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
public String getServerAddress() {
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
public void setServerAddress(String serverAddress) {
|
||||
this.serverAddress = serverAddress;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getNextStatus() {
|
||||
return nextStatus;
|
||||
}
|
||||
|
||||
public void setNextStatus(int nextStatus) {
|
||||
this.nextStatus = nextStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Handshake{" +
|
||||
"protocolVersion=" + protocolVersion +
|
||||
", serverAddress='" + serverAddress + '\'' +
|
||||
", port=" + port +
|
||||
", nextStatus=" + nextStatus +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.protocolVersion = ProtocolUtils.readVarInt(buf);
|
||||
this.serverAddress = ProtocolUtils.readString(buf, 255);
|
||||
this.port = buf.readUnsignedShort();
|
||||
this.nextStatus = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class JoinGame implements MinecraftPacket {
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private short difficulty;
|
||||
private short maxPlayers;
|
||||
private String levelType;
|
||||
private boolean reducedDebugInfo;
|
||||
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
public void setEntityId(int entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
public short getGamemode() {
|
||||
return gamemode;
|
||||
}
|
||||
|
||||
public void setGamemode(short gamemode) {
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public void setDimension(int dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public void setDifficulty(short difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
public short getMaxPlayers() {
|
||||
return maxPlayers;
|
||||
}
|
||||
|
||||
public void setMaxPlayers(short maxPlayers) {
|
||||
this.maxPlayers = maxPlayers;
|
||||
}
|
||||
|
||||
public String getLevelType() {
|
||||
return levelType;
|
||||
}
|
||||
|
||||
public void setLevelType(String levelType) {
|
||||
this.levelType = levelType;
|
||||
}
|
||||
|
||||
public boolean isReducedDebugInfo() {
|
||||
return reducedDebugInfo;
|
||||
}
|
||||
|
||||
public void setReducedDebugInfo(boolean reducedDebugInfo) {
|
||||
this.reducedDebugInfo = reducedDebugInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JoinGame{" +
|
||||
"entityId=" + entityId +
|
||||
", gamemode=" + gamemode +
|
||||
", dimension=" + dimension +
|
||||
", difficulty=" + difficulty +
|
||||
", maxPlayers=" + maxPlayers +
|
||||
", levelType='" + levelType + '\'' +
|
||||
", reducedDebugInfo=" + reducedDebugInfo +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.entityId = buf.readInt();
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
this.dimension = buf.readInt();
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
this.maxPlayers = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
this.reducedDebugInfo = buf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeByte(gamemode);
|
||||
buf.writeInt(dimension);
|
||||
buf.writeByte(difficulty);
|
||||
buf.writeByte(maxPlayers);
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
buf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
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;
|
||||
}
|
||||
|
||||
public void setRandomId(long randomId) {
|
||||
this.randomId = randomId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeepAlive{" +
|
||||
"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 void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_12_2) {
|
||||
buf.writeLong(randomId);
|
||||
} else {
|
||||
ProtocolUtils.writeVarInt(buf, (int) randomId);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
public class LegacyPing {
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.data.ServerPing;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
public class LegacyPingResponse {
|
||||
private int protocolVersion;
|
||||
private String serverVersion;
|
||||
private String motd;
|
||||
private int playersOnline;
|
||||
private int playersMax;
|
||||
|
||||
public LegacyPingResponse() {
|
||||
}
|
||||
|
||||
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 int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
return serverVersion;
|
||||
}
|
||||
|
||||
public void setServerVersion(String serverVersion) {
|
||||
this.serverVersion = serverVersion;
|
||||
}
|
||||
|
||||
public String getMotd() {
|
||||
return motd;
|
||||
}
|
||||
|
||||
public void setMotd(String motd) {
|
||||
this.motd = motd;
|
||||
}
|
||||
|
||||
public int getPlayersOnline() {
|
||||
return playersOnline;
|
||||
}
|
||||
|
||||
public void setPlayersOnline(int playersOnline) {
|
||||
this.playersOnline = playersOnline;
|
||||
}
|
||||
|
||||
public int getPlayersMax() {
|
||||
return playersMax;
|
||||
}
|
||||
|
||||
public void setPlayersMax(int playersMax) {
|
||||
this.playersMax = playersMax;
|
||||
}
|
||||
|
||||
@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().getOnline(),
|
||||
ping.getPlayers().getMax());
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class LoginPluginMessage implements MinecraftPacket {
|
||||
private int id;
|
||||
private String channel;
|
||||
private ByteBuf data;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(ByteBuf data) {
|
||||
this.data = 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.readRetainedSlice(buf.readableBytes());
|
||||
} else {
|
||||
this.data = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
ProtocolUtils.writeString(buf, channel);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
public class LoginPluginResponse implements MinecraftPacket {
|
||||
private int id;
|
||||
private boolean success;
|
||||
private ByteBuf data;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(ByteBuf data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginPluginResponse{" +
|
||||
"id=" + id +
|
||||
", success=" + success +
|
||||
", 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.readRetainedSlice(buf.readableBytes());
|
||||
} else {
|
||||
this.data = Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, id);
|
||||
buf.writeBoolean(success);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
public class PluginMessage implements MinecraftPacket {
|
||||
private String channel;
|
||||
private ByteBuf data;
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public void setChannel(String channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public ByteBuf getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(ByteBuf data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PluginMessage{" +
|
||||
"channel='" + channel + '\'' +
|
||||
", data=" + ByteBufUtil.hexDump(data) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.channel = ProtocolUtils.readString(buf);
|
||||
this.data = buf.readRetainedSlice(buf.readableBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, channel);
|
||||
buf.writeBytes(data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class Respawn implements MinecraftPacket {
|
||||
private int dimension;
|
||||
private short difficulty;
|
||||
private short gamemode;
|
||||
private String levelType;
|
||||
|
||||
public Respawn() {
|
||||
}
|
||||
|
||||
public Respawn(int dimension, short difficulty, short gamemode, String levelType) {
|
||||
this.dimension = dimension;
|
||||
this.difficulty = difficulty;
|
||||
this.gamemode = gamemode;
|
||||
this.levelType = levelType;
|
||||
}
|
||||
|
||||
public int getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public void setDimension(int dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public void setDifficulty(short difficulty) {
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
public short getGamemode() {
|
||||
return gamemode;
|
||||
}
|
||||
|
||||
public void setGamemode(short gamemode) {
|
||||
this.gamemode = gamemode;
|
||||
}
|
||||
|
||||
public String getLevelType() {
|
||||
return levelType;
|
||||
}
|
||||
|
||||
public void setLevelType(String levelType) {
|
||||
this.levelType = levelType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Respawn{" +
|
||||
"dimension=" + dimension +
|
||||
", difficulty=" + difficulty +
|
||||
", gamemode=" + gamemode +
|
||||
", 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 void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeInt(dimension);
|
||||
buf.writeByte(difficulty);
|
||||
buf.writeByte(gamemode);
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ScoreboardDisplay implements MinecraftPacket {
|
||||
private byte position;
|
||||
private String displayName;
|
||||
|
||||
public byte getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(byte position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardDisplay{" +
|
||||
"position=" + position +
|
||||
", displayName='" + displayName + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.position = buf.readByte();
|
||||
this.displayName = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeByte(position);
|
||||
ProtocolUtils.writeString(buf, displayName);
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.data.scoreboard.ObjectiveMode;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.util.ScoreboardProtocolUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
||||
public class ScoreboardObjective implements MinecraftPacket {
|
||||
public static final byte ADD = (byte) 0;
|
||||
public static final byte REMOVE = (byte) 1;
|
||||
public static final byte CHANGE = (byte) 2;
|
||||
private String id;
|
||||
private byte mode;
|
||||
private Component displayName;
|
||||
private ObjectiveMode type;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(byte mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public ObjectiveMode getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ObjectiveMode type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardObjective{" +
|
||||
"id='" + id + '\'' +
|
||||
", mode=" + mode +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readString(buf, 16);
|
||||
this.mode = buf.readByte();
|
||||
if (this.mode != REMOVE) {
|
||||
this.displayName = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
this.type = ScoreboardProtocolUtil.getMode(ProtocolUtils.readVarInt(buf));
|
||||
} else {
|
||||
this.type = ScoreboardProtocolUtil.getMode(ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, id);
|
||||
buf.writeByte(mode);
|
||||
if (this.mode != REMOVE) {
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, displayName);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
ProtocolUtils.writeVarInt(buf, type.ordinal());
|
||||
} else {
|
||||
ProtocolUtils.writeString(buf, type.name().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ScoreboardSetScore implements MinecraftPacket {
|
||||
public static final byte CHANGE = (byte) 0;
|
||||
public static final byte REMOVE = (byte) 1;
|
||||
private String entity;
|
||||
private byte action;
|
||||
private String objective;
|
||||
private int value;
|
||||
|
||||
public String getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void setEntity(String entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public byte getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(byte action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getObjective() {
|
||||
return objective;
|
||||
}
|
||||
|
||||
public void setObjective(String objective) {
|
||||
this.objective = objective;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardSetScore{" +
|
||||
"entity='" + entity + '\'' +
|
||||
", action=" + action +
|
||||
", objective='" + objective + '\'' +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.entity = ProtocolUtils.readString(buf, 40);
|
||||
this.action = buf.readByte();
|
||||
this.objective = ProtocolUtils.readString(buf, 16);
|
||||
if (this.action != REMOVE) {
|
||||
value = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, entity);
|
||||
buf.writeByte(action);
|
||||
ProtocolUtils.writeString(buf, objective);
|
||||
if (this.action != REMOVE) {
|
||||
ProtocolUtils.writeVarInt(buf, value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,211 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ScoreboardTeam implements MinecraftPacket {
|
||||
public static final byte ADD = (byte) 0;
|
||||
public static final byte REMOVE = (byte) 1;
|
||||
public static final byte UPDATE = (byte) 2;
|
||||
public static final byte ADD_PLAYER = (byte) 3;
|
||||
public static final byte REMOVE_PLAYER = (byte) 4;
|
||||
private String id;
|
||||
private byte mode;
|
||||
|
||||
private Component displayName;
|
||||
private Component prefix;
|
||||
private Component suffix;
|
||||
private byte flags;
|
||||
private String nameTagVisibility;
|
||||
private String collisionRule;
|
||||
private int color;
|
||||
private List<String> entities;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(byte mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public Component getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(Component prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public Component getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public void setSuffix(Component suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public byte getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(byte flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public String getNameTagVisibility() {
|
||||
return nameTagVisibility;
|
||||
}
|
||||
|
||||
public void setNameTagVisibility(String nameTagVisibility) {
|
||||
this.nameTagVisibility = nameTagVisibility;
|
||||
}
|
||||
|
||||
public String getCollisionRule() {
|
||||
return collisionRule;
|
||||
}
|
||||
|
||||
public void setCollisionRule(String collisionRule) {
|
||||
this.collisionRule = collisionRule;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public List<String> getEntities() {
|
||||
return entities;
|
||||
}
|
||||
|
||||
public void setEntities(List<String> entities) {
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardTeam{" +
|
||||
"id='" + id + '\'' +
|
||||
", mode=" + mode +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", prefix='" + prefix + '\'' +
|
||||
", suffix='" + suffix + '\'' +
|
||||
", flags=" + flags +
|
||||
", nameTagVisibility='" + nameTagVisibility + '\'' +
|
||||
", collisionRule='" + collisionRule + '\'' +
|
||||
", color=" + color +
|
||||
", entities=" + entities +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readString(buf, 16);
|
||||
this.mode = buf.readByte();
|
||||
|
||||
switch (mode) {
|
||||
case ADD:
|
||||
case UPDATE:
|
||||
this.displayName = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
this.prefix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
this.suffix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
}
|
||||
this.flags = buf.readByte();
|
||||
this.nameTagVisibility = ProtocolUtils.readString(buf, 32);
|
||||
this.collisionRule = ProtocolUtils.readString(buf, 32);
|
||||
this.color = protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ? buf.readByte() :
|
||||
ProtocolUtils.readVarInt(buf);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
this.prefix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
this.suffix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
}
|
||||
if (mode == ADD) {
|
||||
this.entities = readEntities(buf);
|
||||
}
|
||||
break;
|
||||
case REMOVE: // remove
|
||||
break;
|
||||
case ADD_PLAYER: // add player
|
||||
case REMOVE_PLAYER: // remove player
|
||||
this.entities = readEntities(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, id);
|
||||
buf.writeByte(mode);
|
||||
switch (mode) {
|
||||
case ADD:
|
||||
case UPDATE:
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, displayName);
|
||||
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix);
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, suffix);
|
||||
}
|
||||
buf.writeByte(flags);
|
||||
ProtocolUtils.writeString(buf, nameTagVisibility);
|
||||
ProtocolUtils.writeString(buf, collisionRule);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix);
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, suffix);
|
||||
} else {
|
||||
buf.writeByte(color);
|
||||
}
|
||||
if (mode == ADD) {
|
||||
writeEntities(buf, entities);
|
||||
}
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case ADD_PLAYER:
|
||||
case REMOVE_PLAYER:
|
||||
writeEntities(buf, entities);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> readEntities(ByteBuf buf) {
|
||||
List<String> entities = new ArrayList<>();
|
||||
int size = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
entities.add(ProtocolUtils.readString(buf, 40));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static void writeEntities(ByteBuf buf, List<String> entities) {
|
||||
ProtocolUtils.writeVarInt(buf, entities.size());
|
||||
for (String entity : entities) {
|
||||
ProtocolUtils.writeString(buf, entity);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ServerLogin implements MinecraftPacket {
|
||||
private String username;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.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 encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, username);
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
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;
|
||||
|
||||
public class ServerLoginSuccess implements MinecraftPacket {
|
||||
private UUID uuid;
|
||||
private String username;
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerLoginSuccess{" +
|
||||
"uuid=" + uuid +
|
||||
", 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 void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, uuid.toString());
|
||||
ProtocolUtils.writeString(buf, username);
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class SetCompression implements MinecraftPacket {
|
||||
private int threshold;
|
||||
|
||||
public SetCompression() {}
|
||||
|
||||
public SetCompression(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public void setThreshold(int threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SetCompression{" +
|
||||
"threshold=" + threshold +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.threshold = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, threshold);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeLong(randomId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusRequest implements MinecraftPacket {
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.velocitypowered.proxy.protocol.packets;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusResponse implements MinecraftPacket {
|
||||
private String status;
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.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 encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, status);
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.velocitypowered.proxy.protocol.remap;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Represents a protocol-specific entity ID remapper for certain Minecraft packets. This is mostly required to support
|
||||
* old versions of Minecraft. For Minecraft 1.9 clients and above, Velocity can use a more efficient method based on
|
||||
* sending JoinGame packets multiple times.
|
||||
*/
|
||||
public interface EntityIdRemapper {
|
||||
/**
|
||||
* Remaps the entity IDs in this packet so that they apply to the player.
|
||||
* @param original the packet to remap
|
||||
* @param direction the direction of the packet
|
||||
* @return a remapped packet, which may either be a retained version of the original buffer or an entirely new buffer
|
||||
*/
|
||||
ByteBuf remap(ByteBuf original, ProtocolConstants.Direction direction);
|
||||
|
||||
int getClientEntityId();
|
||||
|
||||
int getServerEntityId();
|
||||
|
||||
void setServerEntityId(int id);
|
||||
|
||||
static EntityIdRemapper getMapper(int eid, int protocolVersion) {
|
||||
return NoopEntityIdRemapper.INSTANCE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.velocitypowered.proxy.protocol.remap;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class Minecraft18EntityIdRemapper implements EntityIdRemapper {
|
||||
private final int clientId;
|
||||
private int serverId;
|
||||
|
||||
public Minecraft18EntityIdRemapper(int clientId, int serverId) {
|
||||
this.clientId = clientId;
|
||||
this.serverId = serverId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf remap(ByteBuf original, ProtocolConstants.Direction direction) {
|
||||
if (clientId == serverId) {
|
||||
// If these are equal (i.e. first connection), no remapping is required.
|
||||
return original.retain();
|
||||
}
|
||||
|
||||
// TODO: Implement.
|
||||
throw new UnsupportedOperationException("1.8 doesn't allow switching servers.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientEntityId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getServerEntityId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServerEntityId(int id) {
|
||||
this.serverId = id;
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.velocitypowered.proxy.protocol.remap;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class NoopEntityIdRemapper implements EntityIdRemapper {
|
||||
public static final NoopEntityIdRemapper INSTANCE = new NoopEntityIdRemapper();
|
||||
|
||||
private NoopEntityIdRemapper() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf remap(ByteBuf original, ProtocolConstants.Direction direction) {
|
||||
return original.retain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClientEntityId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getServerEntityId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServerEntityId(int id) {
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
package com.velocitypowered.proxy.protocol.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packets.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public enum PluginMessageUtil {
|
||||
;
|
||||
|
||||
public static List<String> getChannels(PluginMessage message) {
|
||||
Preconditions.checkArgument(message.getChannel().equals("REGISTER") ||
|
||||
message.getChannel().equals("UNREGISTER") ||
|
||||
message.getChannel().equals("minecraft:register") ||
|
||||
message.getChannel().equals("minecraft:unregister"),
|
||||
"Unknown channel type " + message.getChannel());
|
||||
String channels = message.getData().toString(StandardCharsets.UTF_8);
|
||||
return ImmutableList.copyOf(channels.split("\0"));
|
||||
}
|
||||
|
||||
public static PluginMessage constructChannelsPacket(String channel, Collection<String> channels) {
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(channel);
|
||||
|
||||
ByteBuf data = Unpooled.buffer();
|
||||
for (String s : channels) {
|
||||
ByteBufUtil.writeUtf8(data, s);
|
||||
data.writeByte(0);
|
||||
}
|
||||
data.writerIndex(data.writerIndex() - 1);
|
||||
|
||||
message.setData(data);
|
||||
return message;
|
||||
}
|
||||
|
||||
public static PluginMessage rewriteMCBrand(PluginMessage message) {
|
||||
ByteBuf rewrittenBuf = Unpooled.buffer();
|
||||
String currentBrand = ProtocolUtils.readString(message.getData());
|
||||
ProtocolUtils.writeString(rewrittenBuf, currentBrand + " (Velocity)");
|
||||
|
||||
PluginMessage newMsg = new PluginMessage();
|
||||
newMsg.setChannel(message.getChannel());
|
||||
newMsg.setData(rewrittenBuf);
|
||||
return newMsg;
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.velocitypowered.proxy.protocol.util;
|
||||
|
||||
import com.velocitypowered.proxy.data.scoreboard.ObjectiveMode;
|
||||
|
||||
public class ScoreboardProtocolUtil {
|
||||
private ScoreboardProtocolUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static ObjectiveMode getMode(String mode) {
|
||||
return ObjectiveMode.valueOf(mode.toUpperCase());
|
||||
}
|
||||
|
||||
public static ObjectiveMode getMode(int enumVal) {
|
||||
switch (enumVal) {
|
||||
case 0:
|
||||
return ObjectiveMode.INTEGER;
|
||||
case 1:
|
||||
return ObjectiveMode.HEARTS;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown mode " + enumVal);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
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());
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
/**
|
||||
* This marker interface indicates that this object should be explicitly disposed before the object can no longer be used.
|
||||
* Not disposing these objects will likely leak native resources and eventually lead to resource exhaustion.
|
||||
*/
|
||||
public interface Disposable {
|
||||
void dispose();
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
|
||||
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 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 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.servers.ServerInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ServerMap {
|
||||
private final Map<String, ServerInfo> servers = new HashMap<>();
|
||||
|
||||
public Optional<ServerInfo> getServer(String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
return Optional.ofNullable(servers.get(name.toLowerCase()));
|
||||
}
|
||||
|
||||
public Collection<ServerInfo> getAllServers() {
|
||||
return ImmutableList.copyOf(servers.values());
|
||||
}
|
||||
|
||||
public void register(ServerInfo info) {
|
||||
Preconditions.checkNotNull(info, "info");
|
||||
servers.put(info.getName(), info);
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
public enum ThrowableUtils {
|
||||
;
|
||||
|
||||
public static String briefDescription(Throwable throwable) {
|
||||
return throwable.getClass().getName() + ": " + throwable.getMessage();
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public enum UuidUtils {
|
||||
;
|
||||
|
||||
public static UUID fromUndashed(final String string) {
|
||||
Objects.requireNonNull(string, "string");
|
||||
Preconditions.checkArgument(string.length() == 32, "Length is incorrect");
|
||||
return new UUID(
|
||||
Long.parseUnsignedLong(string.substring(0, 16), 16),
|
||||
Long.parseUnsignedLong(string.substring(16), 16)
|
||||
);
|
||||
}
|
||||
|
||||
public static String toUndashed(final UUID uuid) {
|
||||
Preconditions.checkNotNull(uuid, "uuid");
|
||||
return Long.toUnsignedString(uuid.getMostSignificantBits(), 16) + Long.toUnsignedString(uuid.getLeastSignificantBits(), 16);
|
||||
}
|
||||
|
||||
public static UUID generateOfflinePlayerUuid(String username) {
|
||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
23
proxy/src/main/resources/log4j2.xml
Normal file
23
proxy/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
|
||||
</Console>
|
||||
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz"
|
||||
immediateFlush="false">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n"/>
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy/>
|
||||
<OnStartupTriggeringPolicy/>
|
||||
</Policies>
|
||||
</RollingRandomAccessFile>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="File"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
@@ -0,0 +1,53 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.packets.Handshake;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PacketRegistryTest {
|
||||
private StateRegistry.PacketRegistry setupRegistry() {
|
||||
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE);
|
||||
registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12));
|
||||
return registry;
|
||||
}
|
||||
|
||||
@Test
|
||||
void packetRegistryWorks() {
|
||||
StateRegistry.PacketRegistry registry = setupRegistry();
|
||||
MinecraftPacket packet = registry.getVersion(MINECRAFT_1_12).createPacket(0);
|
||||
assertNotNull(packet, "Packet was not found in registry");
|
||||
assertEquals(Handshake.class, packet.getClass(), "Registry returned wrong class");
|
||||
|
||||
assertEquals(0, registry.getVersion(MINECRAFT_1_12).getPacketId(packet), "Registry did not return the correct packet ID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void packetRegistryLinkingWorks() {
|
||||
StateRegistry.PacketRegistry registry = setupRegistry();
|
||||
MinecraftPacket packet = registry.getVersion(MINECRAFT_1_12_1).createPacket(0);
|
||||
assertNotNull(packet, "Packet was not found in registry");
|
||||
assertEquals(Handshake.class, packet.getClass(), "Registry returned wrong class");
|
||||
assertEquals(0, registry.getVersion(MINECRAFT_1_12_1).getPacketId(packet), "Registry did not return the correct packet ID");
|
||||
}
|
||||
|
||||
@Test
|
||||
void failOnNoMappings() {
|
||||
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE);
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.register(Handshake.class, Handshake::new));
|
||||
assertThrows(IllegalArgumentException.class, () -> registry.getVersion(0).getPacketId(new Handshake()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrySuppliesCorrectPacketsByProtocol() {
|
||||
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(ProtocolConstants.Direction.CLIENTBOUND, StateRegistry.HANDSHAKE);
|
||||
registry.register(Handshake.class, Handshake::new, new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12),
|
||||
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1));
|
||||
assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12).createPacket(0x00).getClass());
|
||||
assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_1).createPacket(0x01).getClass());
|
||||
assertEquals(Handshake.class, registry.getVersion(MINECRAFT_1_12_2).createPacket(0x01).getClass());
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class EncryptionUtilsTest {
|
||||
@Test
|
||||
void twosComplementHexdigest() throws Exception {
|
||||
String notchHash = mojangLoginSha1("Notch");
|
||||
assertEquals("4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48", notchHash);
|
||||
|
||||
String jebHash = mojangLoginSha1("jeb_");
|
||||
assertEquals("-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1", jebHash);
|
||||
}
|
||||
|
||||
private String mojangLoginSha1(String str) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.update(str.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] digested = digest.digest();
|
||||
return EncryptionUtils.twosComplementHexdigest(digested);
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class UuidUtilsTest {
|
||||
private static final UUID EXPECTED_DASHED_UUID = UUID.fromString("6b501978-d3be-4f33-bcf6-6e7808f37a0d");
|
||||
private static final String ACTUAL_UNDASHED_UUID = EXPECTED_DASHED_UUID.toString().replace("-", "");
|
||||
|
||||
private static final UUID TEST_OFFLINE_PLAYER_UUID = UUID.fromString("708f6260-183d-3912-bbde-5e279a5e739a");
|
||||
private static final String TEST_OFFLINE_PLAYER = "tuxed";
|
||||
|
||||
@Test
|
||||
void generateOfflinePlayerUuid() {
|
||||
assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER), "UUIDs do not match");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromUndashed() {
|
||||
assertEquals(EXPECTED_DASHED_UUID, UuidUtils.fromUndashed(ACTUAL_UNDASHED_UUID), "UUIDs do not match");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toUndashed() {
|
||||
assertEquals(ACTUAL_UNDASHED_UUID, UuidUtils.toUndashed(EXPECTED_DASHED_UUID), "UUIDs do not match");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user