content: package emu.grasscutter.server.packet.recv;
  
  import emu.grasscutter.Grasscutter;
  import emu.grasscutter.database.DatabaseHelper;
  import emu.grasscutter.game.Account;
  import emu.grasscutter.game.player.Player;
  import emu.grasscutter.net.packet.Opcodes;
  import emu.grasscutter.net.packet.PacketOpcodes;
  import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq;
  import emu.grasscutter.net.packet.PacketHandler;
  import emu.grasscutter.server.event.game.PlayerCreationEvent;
  import emu.grasscutter.server.game.GameSession;
  import emu.grasscutter.server.game.GameSession.SessionState;
  import emu.grasscutter.server.packet.send.PacketGetPlayerTokenRsp;
  import emu.grasscutter.utils.ByteHelper;
  import emu.grasscutter.utils.Crypto;
  import emu.grasscutter.utils.Utils;
  
  import javax.crypto.Cipher;
  
  import static emu.grasscutter.config.Configuration.ACCOUNT;
  import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
  
  import java.nio.ByteBuffer;
  import java.security.Signature;
  
  @Opcodes(PacketOpcodes.GetPlayerTokenReq)
  public class HandlerGetPlayerTokenReq extends PacketHandler {
  
      @Override
      public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
          GetPlayerTokenReq req = GetPlayerTokenReq.parseFrom(payload);
  
          // Authenticate
          Account account = DatabaseHelper.getAccountById(req.getAccountUid());
          if (account == null || !account.getToken().equals(req.getAccountToken())) {
              return;
          }
  
          // Set account
          session.setAccount(account);
  
          // Check if player object exists in server
          // NOTE: CHECKING MUST SITUATED HERE (BEFORE getPlayerByUid)! because to save firstly ,to load secondly !!!
          // TODO - optimize
          boolean kicked = false;
          Player exists = Grasscutter.getGameServer().getPlayerByAccountId(account.getId());
          if (exists != null) {
              GameSession existsSession = exists.getSession();
              if (existsSession != session) {// No self-kicking
                  exists.onLogout();//must save immediately , or the below will load old data
                  existsSession.close();
                  Grasscutter.getLogger().warn("Player {} was kicked due to duplicated login", account.getUsername());
                  kicked = true;
              }
          }
  
          //NOTE: If there are 5 online players, max count of player is 5,
          // a new client want to login by kicking one of them ,
          // I think it should be allowed
          if (!kicked) {
              // Max players limit
              if (ACCOUNT.maxPlayer > -1 && Grasscutter.getGameServer().getPlayers().size() >= ACCOUNT.maxPlayer) {
                  session.close();
                  return;
              }
          }
  
          // Call creation event.
          PlayerCreationEvent event = new PlayerCreationEvent(session, Player.class);
          event.call();
  
          // Get player.
          Player player = DatabaseHelper.getPlayerByAccount(account, event.getPlayerClass());
  
          if (player == null) {
              int nextPlayerUid = DatabaseHelper.getNextPlayerId(session.getAccount().getReservedPlayerUid());
  
              // Create player instance from event.
              player = event.getPlayerClass().getDeclaredConstructor(GameSession.class).newInstance(session);
  
              // Save to db
              DatabaseHelper.generatePlayerUid(player, nextPlayerUid);
          }
  
          // Set player object for session
          session.setPlayer(player);
  
          // Checks if the player is banned
          if (session.getAccount().isBanned()) {
              session.setState(SessionState.ACCOUNT_BANNED);
              session.send(new PacketGetPlayerTokenRsp(session, 21, "FORBID_CHEATING_PLUGINS", session.getAccount().getBanEndTime()));
              return;
          }
  
          // Load player from database
          player.loadFromDatabase();
  
          // Set session state
          session.setUseSecretKey(true);
          session.setState(SessionState.WAITING_FOR_LOGIN);
  
          // Only >= 2.7.50 has this
          if (req.getKeyId() > 0) {
              try {
                  Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                  cipher.init(Cipher.DECRYPT_MODE, Crypto.CUR_SIGNING_KEY);
  
                  var client_seed_encrypted = Utils.base64Decode(req.getClientRandKey());
                  var client_seed = ByteBuffer.wrap(cipher.doFinal(client_seed_encrypted))
                      .getLong();
  
                  byte[] seed_bytes = ByteBuffer.wrap(new byte[8])
                      .putLong(Crypto.ENCRYPT_SEED ^ client_seed)
                      .array();
  
                  cipher.init(Cipher.ENCRYPT_MODE, Crypto.EncryptionKeys.get(req.getKeyId()));
                  var seed_encrypted = cipher.doFinal(seed_bytes);
  
                  Signature privateSignature = Signature.getInstance("SHA256withRSA");
                  privateSignature.initSign(Crypto.CUR_SIGNING_KEY);
                  privateSignature.update(seed_bytes);
  
                  session.send(new PacketGetPlayerTokenRsp(session, Utils.base64Encode(seed_encrypted), Utils.base64Encode(privateSignature.sign())));
              } catch (Exception ignore) {
                  // Only UA Patch users will have exception
                  byte[] clientBytes = Utils.base64Decode(req.getClientRandKey());
                  byte[] seed = ByteHelper.longToBytes(Crypto.ENCRYPT_SEED);
                  Crypto.xor(clientBytes, seed);
  
                  String base64str = Utils.base64Encode(clientBytes);
  
                  session.send(new PacketGetPlayerTokenRsp(session, base64str, "bm90aGluZyBoZXJl"));
              }
          } else {
              // Send packet
              session.send(new PacketGetPlayerTokenRsp(session));
          }
      }
  }
  