PicoLimbo/src/main/java/ru/nanit/limbo/connection/ClientConnection.java
2020-11-29 15:00:17 +02:00

337 lines
12 KiB
Java

package ru.nanit.limbo.connection;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import ru.nanit.limbo.LimboConstants;
import ru.nanit.limbo.protocol.ByteMessage;
import ru.nanit.limbo.protocol.PreRenderedPacket;
import ru.nanit.limbo.protocol.packets.login.*;
import ru.nanit.limbo.protocol.packets.play.*;
import ru.nanit.limbo.protocol.pipeline.PacketDecoder;
import ru.nanit.limbo.protocol.pipeline.PacketEncoder;
import ru.nanit.limbo.protocol.packets.PacketHandshake;
import ru.nanit.limbo.protocol.packets.status.PacketStatusPing;
import ru.nanit.limbo.protocol.packets.status.PacketStatusRequest;
import ru.nanit.limbo.protocol.packets.status.PacketStatusResponse;
import ru.nanit.limbo.protocol.registry.State;
import ru.nanit.limbo.protocol.registry.Version;
import ru.nanit.limbo.server.LimboServer;
import ru.nanit.limbo.util.Logger;
import ru.nanit.limbo.util.UuidUtil;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
public class ClientConnection extends ChannelInboundHandlerAdapter {
private static PreRenderedPacket PACKET_LOGIN_SUCCESS;
private static PreRenderedPacket PACKET_JOIN_GAME;
private static PreRenderedPacket PACKET_PLAYER_ABILITIES;
private static PreRenderedPacket PACKET_PLAYER_INFO;
private static PreRenderedPacket PACKET_PLAYER_POS;
private static PreRenderedPacket PACKET_JOIN_MESSAGE;
private static PreRenderedPacket PACKET_BOSS_BAR;
private final LimboServer server;
private final Channel channel;
private final GameProfile gameProfile;
private State state;
private Version clientVersion;
private SocketAddress address;
private int velocityLoginMessageId = -1;
public ClientConnection(Channel channel, LimboServer server){
this.server = server;
this.channel = channel;
this.address = channel.remoteAddress();
this.gameProfile = new GameProfile();
}
public UUID getUuid() {
return gameProfile.getUuid();
}
public String getUsername(){
return gameProfile.getUsername();
}
public SocketAddress getAddress() {
return address;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (state.equals(State.PLAY)){
server.getConnections().removeConnection(this);
}
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (channel.isActive()){
Logger.error("Unhandled exception: %s", cause.getMessage());
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
handlePacket(msg);
}
public void handlePacket(Object packet){
if (packet instanceof PacketHandshake){
PacketHandshake handshake = (PacketHandshake) packet;
clientVersion = handshake.getVersion();
updateState(State.getById(handshake.getNextState()));
Logger.debug("Pinged from " + address);
if (server.getConfig().getInfoForwarding().isLegacy()){
String[] split = handshake.getHost().split("\00");
if (split.length == 3 || split.length == 4){
setAddress(split[1]);
gameProfile.setUuid(UuidUtil.fromString(split[2]));
} else {
disconnectLogin("You've enabled player info forwarding. You need to connect with proxy");
}
}
return;
}
if (packet instanceof PacketStatusRequest){
sendPacket(new PacketStatusResponse(server));
return;
}
if (packet instanceof PacketStatusPing){
sendPacketAndClose(packet);
return;
}
if (packet instanceof PacketLoginStart){
if (server.getConnections().getCount() >= server.getConfig().getMaxPlayers()){
disconnectLogin("Too many players connected");
return;
}
if (!clientVersion.equals(Version.getCurrentSupported())){
disconnectLogin("Incompatible client version");
return;
}
if (server.getConfig().getInfoForwarding().isModern()){
velocityLoginMessageId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
PacketLoginPluginRequest request = new PacketLoginPluginRequest();
request.setMessageId(velocityLoginMessageId);
request.setChannel(LimboConstants.VELOCITY_INFO_CHANNEL);
request.setData(Unpooled.EMPTY_BUFFER);
sendPacket(request);
return;
}
if (!server.getConfig().getInfoForwarding().isModern()){
gameProfile.setUsername(((PacketLoginStart)packet).getUsername());
gameProfile.setUuid(UuidUtil.getOfflineModeUuid(getUsername()));
}
fireLoginSuccess();
return;
}
if (packet instanceof PacketLoginPluginResponse){
PacketLoginPluginResponse response = (PacketLoginPluginResponse) packet;
if (server.getConfig().getInfoForwarding().isModern()
&& response.getMessageId() == velocityLoginMessageId){
if (!response.isSuccessful() || response.getData() == null){
disconnectLogin("You need to connect with Velocity");
return;
}
if (!checkVelocityKeyIntegrity(response.getData())) {
disconnectLogin("Can't verify forwarded player info");
return;
}
// Order is important
setAddress(response.getData().readString());
gameProfile.setUuid(response.getData().readUuid());
gameProfile.setUsername(response.getData().readString());
fireLoginSuccess();
}
}
}
private void fireLoginSuccess(){
if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1){
disconnectLogin("You need to connect with Velocity");
return;
}
writePacket(PACKET_LOGIN_SUCCESS);
updateState(State.PLAY);
server.getConnections().addConnection(this);
writePacket(PACKET_JOIN_GAME);
writePacket(PACKET_PLAYER_ABILITIES);
writePacket(PACKET_PLAYER_POS);
writePacket(PACKET_PLAYER_INFO);
if (PACKET_BOSS_BAR != null)
writePacket(PACKET_BOSS_BAR);
if (PACKET_JOIN_MESSAGE != null)
writePacket(PACKET_JOIN_MESSAGE);
sendKeepAlive();
}
public void disconnectLogin(String reason){
if (isConnected() && state == State.LOGIN){
PacketDisconnect disconnect = new PacketDisconnect();
disconnect.setReason(reason);
sendPacketAndClose(disconnect);
}
}
public void sendKeepAlive(){
if (state.equals(State.PLAY)){
PacketKeepAlive keepAlive = new PacketKeepAlive();
keepAlive.setId(ThreadLocalRandom.current().nextLong());
sendPacket(keepAlive);
}
}
public void sendPacket(Object packet){
if (isConnected())
channel.writeAndFlush(packet, channel.voidPromise());
}
public void sendPacketAndClose(Object packet){
if (isConnected())
channel.writeAndFlush(packet).addListener(ChannelFutureListener.CLOSE);
}
public void writePacket(Object packet){
if (isConnected()) channel.write(packet, channel.voidPromise());
}
public void flushPackets(){
if (isConnected()) channel.flush();
}
public boolean isConnected(){
return channel.isActive();
}
private void updateState(State state){
this.state = state;
channel.pipeline().get(PacketDecoder.class).updateState(state);
channel.pipeline().get(PacketEncoder.class).updateState(state);
}
private void setAddress(String host){
this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort());
}
private boolean checkVelocityKeyIntegrity(ByteMessage buf) {
byte[] signature = new byte[32];
buf.readBytes(signature);
byte[] data = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), data);
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(server.getConfig().getInfoForwarding().getSecretKey(), "HmacSHA256"));
byte[] mySignature = mac.doFinal(data);
if (!MessageDigest.isEqual(signature, mySignature))
return false;
} catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
int version = buf.readVarInt();
if (version != 1)
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001');
return true;
}
public static void preInitPackets(LimboServer server){
final String username = server.getConfig().getPingData().getVersion();
final UUID uuid = UuidUtil.getOfflineModeUuid(username);
PacketLoginSuccess loginSuccess = new PacketLoginSuccess();
loginSuccess.setUsername(username);
loginSuccess.setUuid(uuid);
PacketJoinGame joinGame = new PacketJoinGame();
joinGame.setEntityId(0);
joinGame.setEnableRespawnScreen(true);
joinGame.setFlat(false);
joinGame.setGameMode(server.getConfig().getGameMode());
joinGame.setHardcore(false);
joinGame.setMaxPlayers(server.getConfig().getMaxPlayers());
joinGame.setPreviousGameMode(-1);
joinGame.setReducedDebugInfo(true);
joinGame.setDebug(false);
joinGame.setViewDistance(2);
joinGame.setWorldName("minecraft:world");
joinGame.setWorldNames("minecraft:world");
joinGame.setHashedSeed(0);
joinGame.setDimensionCodec(server.getDimensionRegistry().getCodec());
joinGame.setDimension(server.getDimensionRegistry().getDefaultDimension());
PacketPlayerAbilities playerAbilities = new PacketPlayerAbilities();
playerAbilities.setFlyingSpeed(0.0F);
playerAbilities.setFlags(0x02);
playerAbilities.setFieldOfView(0.1F);
PacketPlayerPositionAndLook positionAndLook = new PacketPlayerPositionAndLook();
positionAndLook.setX(server.getConfig().getSpawnPosition().getX());
positionAndLook.setY(server.getConfig().getSpawnPosition().getY());
positionAndLook.setZ(server.getConfig().getSpawnPosition().getZ());
positionAndLook.setYaw(server.getConfig().getSpawnPosition().getYaw());
positionAndLook.setPitch(server.getConfig().getSpawnPosition().getPitch());
positionAndLook.setTeleportId(ThreadLocalRandom.current().nextInt());
PacketPlayerInfo info = new PacketPlayerInfo();
info.setUsername(username);
info.setGameMode(server.getConfig().getGameMode());
info.setUuid(uuid);
PACKET_LOGIN_SUCCESS = PreRenderedPacket.of(loginSuccess);
PACKET_JOIN_GAME = PreRenderedPacket.of(joinGame);
PACKET_PLAYER_ABILITIES = PreRenderedPacket.of(playerAbilities);
PACKET_PLAYER_POS = PreRenderedPacket.of(positionAndLook);
PACKET_PLAYER_INFO = PreRenderedPacket.of(info);
if (server.getConfig().isUseJoinMessage()){
PacketChatMessage joinMessage = new PacketChatMessage();
joinMessage.setJsonData(server.getConfig().getJoinMessage());
joinMessage.setPosition(PacketChatMessage.Position.CHAT);
joinMessage.setSender(UUID.randomUUID());
PACKET_JOIN_MESSAGE = PreRenderedPacket.of(joinMessage);
}
if (server.getConfig().isUseBossBar()){
PacketBossBar bossBar = new PacketBossBar();
bossBar.setBossBar(server.getConfig().getBossBar());
bossBar.setUuid(UUID.randomUUID());
PACKET_BOSS_BAR = PreRenderedPacket.of(bossBar);
}
}
}