mirror of
https://github.com/GeyserMC/GeyserConnect.git
synced 2025-06-26 14:15:22 +02:00
195 lines
7.6 KiB
Java
195 lines
7.6 KiB
Java
package org.geysermc.multi;
|
|
|
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
|
import com.nimbusds.jose.JWSObject;
|
|
import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory;
|
|
import com.nukkitx.network.util.DisconnectReason;
|
|
import com.nukkitx.protocol.bedrock.BedrockServerSession;
|
|
import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler;
|
|
import com.nukkitx.protocol.bedrock.packet.*;
|
|
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
|
import org.geysermc.common.window.FormWindow;
|
|
import org.geysermc.common.window.response.CustomFormResponse;
|
|
import org.geysermc.common.window.response.SimpleFormResponse;
|
|
import org.geysermc.multi.ui.FormID;
|
|
import org.geysermc.multi.ui.UIHandler;
|
|
import org.geysermc.multi.utils.Player;
|
|
|
|
import java.io.IOException;
|
|
import java.security.interfaces.ECPublicKey;
|
|
|
|
public class PacketHandler implements BedrockPacketHandler {
|
|
|
|
private BedrockServerSession session;
|
|
private MasterServer masterServer;
|
|
|
|
private Player player;
|
|
|
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);;
|
|
|
|
public PacketHandler(BedrockServerSession session, MasterServer masterServer) {
|
|
this.session = session;
|
|
this.masterServer = masterServer;
|
|
|
|
session.addDisconnectHandler((reason) -> disconnect(reason));
|
|
}
|
|
|
|
public void disconnect(DisconnectReason reason) {
|
|
if (player != null) {
|
|
masterServer.getLogger().info(player.getDisplayName() + " has disconnected from the master server (" + reason + ")");
|
|
masterServer.getStorageManager().saveServers(player);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean handle(LoginPacket packet) {
|
|
masterServer.getLogger().debug("Login: " + packet.toString());
|
|
|
|
// Check the protocol version is correct
|
|
int protocol = packet.getProtocolVersion();
|
|
if (protocol != MasterServer.CODEC.getProtocolVersion()) {
|
|
PlayStatusPacket status = new PlayStatusPacket();
|
|
if (protocol > MasterServer.CODEC.getProtocolVersion()) {
|
|
status.setStatus(PlayStatusPacket.Status.FAILED_SERVER);
|
|
} else {
|
|
status.setStatus(PlayStatusPacket.Status.FAILED_CLIENT);
|
|
}
|
|
session.sendPacket(status);
|
|
}
|
|
|
|
// Set the session codec
|
|
session.setPacketCodec(MasterServer.CODEC);
|
|
|
|
// Read the raw chain data
|
|
JsonNode rawChainData;
|
|
try {
|
|
rawChainData = OBJECT_MAPPER.readTree(packet.getChainData().toByteArray());
|
|
} catch (IOException e) {
|
|
throw new AssertionError("Unable to read chain data!");
|
|
}
|
|
|
|
// Get the parsed chain data
|
|
JsonNode chainData = rawChainData.get("chain");
|
|
if (chainData.getNodeType() != JsonNodeType.ARRAY) {
|
|
throw new AssertionError("Invalid chain data!");
|
|
}
|
|
|
|
try {
|
|
// Parse the signed jws object
|
|
JWSObject jwsObject;
|
|
jwsObject = JWSObject.parse(chainData.get(chainData.size() - 1).asText());
|
|
|
|
// Read the JWS payload
|
|
JsonNode payload = OBJECT_MAPPER.readTree(jwsObject.getPayload().toBytes());
|
|
|
|
// Check the identityPublicKey is there
|
|
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
|
|
throw new AssertionError("Missing identity public key!");
|
|
}
|
|
|
|
// Create an ECPublicKey from the identityPublicKey
|
|
ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue());
|
|
|
|
// Get the skin data to validate the JWS token
|
|
JWSObject skinData = JWSObject.parse(packet.getSkinData().toString());
|
|
if (skinData.verify(new DefaultJWSVerifierFactory().createJWSVerifier(skinData.getHeader(), identityPublicKey))) {
|
|
// Make sure the client sent over the username, xuid and other info
|
|
if (payload.get("extraData").getNodeType() != JsonNodeType.OBJECT) {
|
|
throw new AssertionError("Missing client data");
|
|
}
|
|
|
|
// Fetch the client data
|
|
JsonNode extraData = payload.get("extraData");
|
|
|
|
// Create a new player and add it to the players list
|
|
player = new Player(extraData, session);
|
|
masterServer.getPlayers().put(session.getAddress(), player);
|
|
|
|
// Tell the client we have logged in successfully
|
|
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
|
|
playStatusPacket.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
|
|
session.sendPacket(playStatusPacket);
|
|
|
|
// Tell the client there are no resourcepacks
|
|
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
|
session.sendPacket(resourcePacksInfo);
|
|
} else {
|
|
throw new AssertionError("Invalid identity public key!");
|
|
}
|
|
} catch (Exception e) {
|
|
// Disconnect the client
|
|
session.disconnect("disconnectionScreen.internalError.cantConnect");
|
|
throw new AssertionError("Failed to login", e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean handle(ResourcePackClientResponsePacket packet) {
|
|
switch (packet.getStatus()) {
|
|
case COMPLETED:
|
|
masterServer.getLogger().info("Logged in " + player.getDisplayName() + " (" + player.getXuid() + ", " + player.getIdentity() + ")");
|
|
player.sendStartGame();
|
|
break;
|
|
case HAVE_ALL_PACKS:
|
|
ResourcePackStackPacket stack = new ResourcePackStackPacket();
|
|
stack.setExperimental(false);
|
|
stack.setForcedToAccept(false);
|
|
stack.setGameVersion("*");
|
|
session.sendPacket(stack);
|
|
break;
|
|
default:
|
|
session.disconnect("disconnectionScreen.resourcePack");
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
|
|
masterServer.getLogger().debug("Player initialized: " + player.getDisplayName());
|
|
|
|
player.sendWindow(FormID.MAIN, UIHandler.getServerList(player.getServers()));;
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean handle(ModalFormResponsePacket packet) {
|
|
// Make sure the form is valid
|
|
FormID id = FormID.fromId(packet.getFormId());
|
|
if (id != player.getCurrentWindowId())
|
|
return false;
|
|
|
|
// Fetch the form and parse the response
|
|
FormWindow window = player.getCurrentWindow();
|
|
window.setResponse(packet.getFormData().trim());
|
|
|
|
// Resend the form if they closed it
|
|
if (window.getResponse() == null && !id.isHandlesNull()) {
|
|
player.resendWindow();
|
|
} else {
|
|
// Send the response to the correct response function
|
|
switch (id) {
|
|
case MAIN:
|
|
UIHandler.handleServerListResponse(player, (SimpleFormResponse) window.getResponse());
|
|
break;
|
|
|
|
case DIRECT_CONNECT:
|
|
UIHandler.handleDirectConnectResponse(player, (CustomFormResponse) window.getResponse());
|
|
break;
|
|
|
|
default:
|
|
player.resendWindow();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|