mirror of
https://github.com/Nan1t/NanoLimbo.git
synced 2025-07-09 03:30:12 +02:00
337 lines
12 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|