Войти или зарегистрироваться
  • Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.

Помогите Как я могу определить, использует ли игрок, пиратский или лиценцзионный клиент?

Ryazha_

Активный участник
Пользователь
Баллы
76
Имя в Minecraft
Ryazha_
Как я могу определить, использует ли игрок, подключающийся к серверу, пиратский (взломанный) или лицензионный клиент Minecraft? Мне известно, что для этого можно использовать Mojang API, но он работает только когда online-mode=true.
 
Как я могу определить, использует ли игрок, подключающийся к серверу, пиратский (взломанный) или лицензионный клиент Minecraft? Мне известно, что для этого можно использовать Mojang API, но он работает только когда online-mode=true.
Почитай этот пост.
 
Код:
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public static boolean isPremiumByAPI(String playerName) {
    try {
        URL url = new URL("https://api.ashcon.app/mojang/v2/user/" + playerName);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        return (connection.getResponseCode() == HttpURLConnection.HTTP_OK);
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

Это должно работать по сути в офлайн моде?
 
Код:
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

public static boolean isPremiumByAPI(String playerName) {
    try {
        URL url = new URL("https://api.ashcon.app/mojang/v2/user/" + playerName);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        return (connection.getResponseCode() == HttpURLConnection.HTTP_OK);
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

Это должно работать по сути в офлайн моде?
Так ты лишь проверишь, существует ли такой лицензионный ник в Mojang. Это вовсе не означает, что у игрока есть доступ к этому аккаунту, он вполне может входить и с пиратки с чужим ником.

Я относительно недавно описывал, как организовать авторизацию на сервере по лицензии:
https://rubukkit.org/threads/proverka-licenzii-na-piratke.193204/#post-1744881

Правда с тех пор закрылся wiki.vg, вот актуальные ссылки:

https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Login
https://minecraft.wiki/w/Microsoft_authentication
https://minecraft.wiki/w/Mojang_API
 
Последнее редактирование:
Так ты лишь проверишь, существует ли такой лицензионный ник в Mojang. Это вовсе не означает, что у игрока есть доступ к этому аккаунту, он вполне может входить и с пиратки с чужим ником.

Я относительно недавно описывал, как организовать авторизацию на сервере по лицензии:
https://rubukkit.org/threads/proverka-licenzii-na-piratke.193204/#post-1744881

Правда с тех пор закрылся wiki.vg, вот актуальные ссылки:

https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Login
https://minecraft.wiki/w/Microsoft_authentication
https://minecraft.wiki/w/Mojang_API

Можешь ещё раз пояснить в чём была идея?
 
Можешь ещё раз пояснить в чём была идея?
Ты по своей инициативе никак не узнаешь, есть ли у игрока лицензия. Инициатива должна исходить от игрока, он должен хотя бы раз переподключиться к серверу, чтобы ты смог включить ему онлайн мод и тем самым убедиться в наличии или отсутствии у него лицензии
 
Ты по своей инициативе никак не узнаешь, есть ли у игрока лицензия. Инициатива должна исходить от игрока, он должен хотя бы раз переподключиться к серверу, чтобы ты смог включить ему онлайн мод и тем самым убедиться в наличии или отсутствии у него лицензии
А нельзя ли сразу по пакетам проверять при первом входе, там вроде выше версии 1.20 какой-то новый способ есть?
 
Ты по своей инициативе никак не узнаешь, есть ли у игрока лицензия. Инициатива должна исходить от игрока, он должен хотя бы раз переподключиться к серверу, чтобы ты смог включить ему онлайн мод и тем самым убедиться в наличии или отсутствии у него лицензии
А при помощи NMS есть способ какой?
 
Ты по своей инициативе никак не узнаешь, есть ли у игрока лицензия. Инициатива должна исходить от игрока, он должен хотя бы раз переподключиться к серверу, чтобы ты смог включить ему онлайн мод и тем самым убедиться в наличии или отсутствии у него лицензии
https://github.com/yann1n/LizardAuth

Я накидал плагин, но к сожалению не удалось правильно реализовать проверку на лицензию.
 
А нельзя ли сразу по пакетам проверять при первом входе, там вроде выше версии 1.20 какой-то новый способ есть?
Какой способ? С transfer пакетом?
Это не способ, а лишь моё личное предположение. Ты можешь попробовать. Но получится или нет - неизвестно. На текущий момент я не слышал, чтобы кому-то действительно удавалось использовать это. Хотя не исключаю, что никто и не пытался.

А при помощи NMS есть способ какой?
НМС - это серверная сторона, а ограничения на стороне клиента.
Поэтому нет, твою задачу это не решит.
Ты можешь необходимые пакеты через нмс посылать, но смысла в этом мало, практически для всех действий сейчас уже есть апи.

Честно скажу - с твоим текущим уровнем понимания работы программирования и игры тебе придётся очень несладко делать авторизацию по лицензии, даже если ты прибегнешь к гарантированно рабочему способу, реализованному на других серверах.
Тебе предстоит ещё многое изучить и понять, чтобы лезть в эту тему.
Поэтому советую либо взять задачку попроще, либо начать серьёзно погружаться в принцип работы игры: и в нмс, и в протокол, и в логику работы клиента.
Благо основная документация по этому уже есть, ссылки кидал выше.

Если есть конкретные вопросы - спрашивай. Но пока всё выглядит так, будто эта задача для тебя сложновата, если не сам не начнёшь досконально вникать в каждую деталь
 
Последнее редактирование:
Какой способ? С transfer пакетом?
Это не способ, а лишь моё личное предположение. Ты можешь попробовать. Но получится или нет - неизвестно. На текущий момент я не слышал, чтобы кому-то действительно удавалось использовать это. Хотя не исключаю, что никто и не пытался.


НМС - это серверная сторона, а ограничения на стороне клиента.
Поэтому нет, твою задачу это не решит.
Ты можешь необходимые пакеты через нмс посылать, но смысла в этом мало, практически для всех действий сейчас уже есть апи.

Честно скажу - с твоим текущим уровнем понимания работы программирования и игры тебе придётся очень несладко делать авторизацию по лицензии, даже если ты прибегнешь к гарантированно рабочему способу, реализованному на других серверах.
Тебе предстоит ещё многое изучить и понять, чтобы лезть в эту тему.
Поэтому советую либо взять задачку попроще, либо начать серьёзно погружаться в принцип работы игры: и в нмс, и в протокол, и в логику работы клиента.
Благо основная документация по этому уже есть, ссылки кидал выше.

Если есть конкретные вопросы - спрашивай. Но пока всё выглядит так, будто эта задача для тебя сложновата, если не сам не начнёшь досконально вникать в каждую деталь


Из статье о протоколе, я понял что это можно реализовать за счет Энкриптинг пакетов и запросов. Но не понимаю как именно это реализовать, опыта не было.
 
Из статье о протоколе, я понял что это можно реализовать за счет Энкриптинг пакетов и запросов. Но не понимаю как именно это реализовать, опыта не было.
На банже можно использовать метод .setOnlineMode(true)
На пейпере, по-моему, подобного нет. Соответственно тебе нужно как-то приостанавливать подключение на этапе login, а после отправлять пакет вручную.
Ну, либо рефлексией как-то подменять оффлайн мод на онлайн, чтобы ядро отправило пакет самостоятельно. Но не уверен, что это возможно для отдельных игроков.
Короче говоря, нужно проводить ресерч. И в этом и состоит основная сложность для человека без опыта
 
На банже можно использовать метод .setOnlineMode(true)
На пейпере, по-моему, подобного нет. Соответственно тебе нужно как-то приостанавливать подключение на этапе login, а после отправлять пакет вручную.
Ну, либо рефлексией как-то подменять оффлайн мод на онлайн, чтобы ядро отправило пакет самостоятельно. Но не уверен, что это возможно для отдельных игроков.
Короче говоря, нужно проводить ресерч. И в этом и состоит основная сложность для человека без опыта
А почему НМС тут не поможет?, там же присутствует работа с пакетами.
 
Ты можешь и через нмс пакеты отправлять, но зачем?
Как я буду получать или создавать Server ID и ключи шифрования дла отправки пакетов, какие у них свойства, какое шифрование применять?

Вот код, который я реализовал на данный момент:


Код:
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class LizardAuth extends JavaPlugin implements Listener {

    private ProtocolManager protocolManager;
    private byte[] sharedSecret;
    private byte[] verifyToken;
    private final Random random = new SecureRandom();
    private PublicKey publicKey;
    private PrivateKey privateKey;
    private File privateKeyFile;
    private final String keyPass = "password";


    @Override
    public void onEnable() {
        getLogger().info("LizardAuth is enabling...");
        protocolManager = ProtocolLibrary.getProtocolManager();

        
        getServer().getPluginManager().registerEvents(this, this);
        getLogger().info("LizardAuth enabled successfully");

        try {
            loadOrCreateKeys();
        } catch (NoSuchAlgorithmException e) {
            getLogger().severe("Failed to generate RSA keys: " + e.getMessage());
            this.getServer().getPluginManager().disablePlugin(this);
            return;
        } catch (Exception e) {
             getLogger().severe("Failed to load or create RSA keys: " + e.getMessage());
            this.getServer().getPluginManager().disablePlugin(this);
            return;
        }
        registerPacketListeners();
    }

    @Override
    public void onDisable() {
        getLogger().info("LizardAuth is disabling...");
        protocolManager.removePacketListeners(this);
        getLogger().info("LizardAuth disabled successfully");
    }

    private void loadOrCreateKeys() throws Exception {
         privateKeyFile = new File(getDataFolder(), "private.key");

         if (privateKeyFile.exists()) {
             try {
                loadKeys();
             } catch (Exception e){
                 getLogger().severe("Failed to load keys, creating new one! Error: " + e.getMessage());
                 generateKeys();
                 saveKeys();
             }
         } else {
            generateKeys();
            saveKeys();
         }
    }

    private void generateKeys() throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair pair = keyGen.generateKeyPair();
        this.publicKey = pair.getPublic();
        this.privateKey = pair.getPrivate();
    }

    private void saveKeys() throws Exception {
        if (!getDataFolder().exists()) {
            getDataFolder().mkdirs();
        }

        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
        byte[] encryptedPrivateKey = encryptPrivateKey(privateKeySpec.getEncoded(), keyPass);


        FileOutputStream fos = new FileOutputStream(privateKeyFile);
        fos.write(encryptedPrivateKey);
        fos.close();

    }

    private void loadKeys() throws Exception{
      byte[] encryptedPrivateKey = Files.readAllBytes(Paths.get(privateKeyFile.getPath()));
      byte[] decryptedPrivateKey = decryptPrivateKey(encryptedPrivateKey, keyPass);
      PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decryptedPrivateKey);
      KeyFactory keyFactory = KeyFactory.getInstance("RSA");
      privateKey = keyFactory.generatePrivate(privateKeySpec);

        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
        publicKey = keyFactory.generatePublic(publicKeySpec);
    }


    private void registerPacketListeners() {
        protocolManager.addPacketListener(new PacketAdapter(
                this,
                ListenerPriority.NORMAL,
                Arrays.asList(PacketType.Handshake.Client.SET_PROTOCOL, PacketType.Login.Client.ENCRYPTION_BEGIN)
        ) {

            @Override
            public void onPacketReceiving(PacketEvent event) {
                if (event.getPacketType() == PacketType.Handshake.Client.SET_PROTOCOL) {
                    getLogger().info("C -> S: Handshake - Packet caught!");

                    // Create and send Encryption Begin
                    Player player = event.getPlayer();
                    sendEncryptionBeginPacket(player);

                    // Cancel handshake processing
                    event.setCancelled(true);
                } else if (event.getPacketType() == PacketType.Login.Client.ENCRYPTION_BEGIN) {
                    getLogger().info("C -> S: Encryption Response - Packet caught!");
                    // Получение данных из пакета Encryption Response (от клиента)
                    sharedSecret = event.getPacket().getByteArrays().read(0);
                    byte[] encryptedVerifyToken = event.getPacket().getByteArrays().read(1);
                    try {
                        byte[] decryptedVerifyToken = decryptData(encryptedVerifyToken);

                        if (Arrays.equals(decryptedVerifyToken, verifyToken)) {
                            getLogger().info("Decrypted Verify Token matches");
                            byte[] decryptedSharedSecret = decryptData(sharedSecret);
                           getLogger().info("Shared Secret decrypted: " + Arrays.toString(decryptedSharedSecret));
                           sharedSecret = decryptedSharedSecret;
                        } else {
                            getLogger().severe("Decrypted Verify Token doesnt match");
                            event.setCancelled(true);
                            return;
                        }
                    } catch (Exception e) {
                        getLogger().severe("Failed to decrypt verify token: " + e.getMessage());
                        event.setCancelled(true);
                        return;
                    }
                    getLogger().info("Shared Secret: " + Arrays.toString(sharedSecret));
                   //  event.getPlayer().kickPlayer("Debug!");
                }

            }
        });
    }

    public void sendEncryptionBeginPacket(Player player) {
        verifyToken = generateVerifyToken();
        com.comphenix.protocol.wrappers.PacketContainer encryptionBeginPacket = new com.comphenix.protocol.wrappers.PacketContainer(PacketType.Login.Server.ENCRYPTION_BEGIN);
        encryptionBeginPacket.getSpecificModifier(String.class).write(0, ""); // Server ID
        encryptionBeginPacket.getByteArrays().write(0, publicKey.getEncoded()); // Public Key
        encryptionBeginPacket.getByteArrays().write(1, verifyToken); // Verify Token
        try {
            protocolManager.sendServerPacket(player, encryptionBeginPacket);
        } catch (Exception e) {
            getLogger().severe("Failed to send Encryption Begin packet: " + e.getMessage());
        }
        getLogger().info("S -> C: Encryption Request - Packet sent!");
        getLogger().info("Public key: " + Arrays.toString(publicKey.getEncoded()));
        getLogger().info("Verify token: " + Arrays.toString(verifyToken));
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        getLogger().info("Player " + event.getPlayer().getName() + " has joined (PlayerJoinEvent)");
    }

    @EventHandler
    public void onPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
        getLogger().info("Player " + event.getName() + " is attempting to log in (Async)");
        getLogger().info("UUID: " + event.getUniqueId());
        getLogger().info("IP Address: " + event.getAddress().getHostAddress());
        getLogger().info("Hostname: " + event.getHostname());

        CompletableFuture.runAsync(() -> {
            try {
                if (sharedSecret == null || verifyToken == null) {
                    event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Failed to get shared secret and verify token.");
                    return;
                }
                getLogger().info("Starting authentication process...");
                String accessToken = authenticateWithMinecraft(event.getUniqueId(), event.getName(), sharedSecret, verifyToken);

                if (accessToken != null) {
                    boolean hasLicense = checkGameOwnership(accessToken);
                    if (!hasLicense) {
                        event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "You do not own Minecraft.");
                    } else {
                        getLogger().info("Player " + event.getName() + " has a valid license.");
                    }

                } else {
                    event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Failed to authenticate with Minecraft.");
                }
            } catch (Exception e) {
                getLogger().severe("An error occurred during authentication: " + e.getMessage());
                event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, "Authentication error.");
            }
        });

    }

    private String authenticateWithMinecraft(UUID playerUUID, String playerName, byte[] sharedSecret, byte[] verifyToken) throws IOException, InterruptedException {
        // Заглушка для будущей проверки.
        return "access_token";
    }

    private byte[] generateVerifyToken() {
        byte[] token = new byte[4];
        random.nextBytes(token);
        return token;
    }

    private boolean checkGameOwnership(String accessToken) throws IOException, InterruptedException {
        // Заглушка для будущей проверки.
        return true;
    }

    private byte[] decryptData(byte[] encryptedData) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(encryptedData);
    }

    private byte[] encryptPrivateKey(byte[] privateKeyBytes, String keyPass) throws Exception {
        SecretKeySpec key = new SecretKeySpec(keyPass.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(privateKeyBytes);
    }

   private byte[] decryptPrivateKey(byte[] encryptedPrivateKey, String keyPass) throws Exception {
        SecretKeySpec key = new SecretKeySpec(keyPass.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(encryptedPrivateKey);
    }
}
 
Последнее редактирование:
Как я могу определить, использует ли игрок, подключающийся к серверу, пиратский (взломанный) или лицензионный клиент Minecraft? Мне известно, что для этого можно использовать Mojang API, но он работает только когда online-mode=true.
Заставить клиент войти с врубленным онлайн модом, иначе игра не переключиться в режим енкрипшен.
 
Заставить клиент войти с врубленным онлайн модом, иначе игра не переключиться в режим енкрипшен.
Я вот не понимаю как это сделать? Саморучно энкрипт пакет кидать?
 
Как я буду получать или создавать Server ID и ключи шифрования дла отправки пакетов, какие у них свойства, какое шифрование применять?
Ответы на эти вопросы есть по ссылкам, которые уже кидал выше. Нужно было лишь чуть копнуть вглубь: https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_Encryption

Я вот не понимаю как это сделать? Саморучно энкрипт пакет кидать?
Либо саморучно, либо заставить ядро отправить. Но для этого придётся лезть в НМС/рефлексию. В апи Paper этого по-моему, нет. Только на банже и велосити присутствует.
Теоретически отправить пакет было бы проще, т.к. можно использовать какой-нибудь ProtocolLib. Но проблема в другом.
Даже если ты отправишь пакет, то ядро сервера знать об этом не будет. А значит будет нарушен процесс подключения к серверу. Будет рассинхро между тем, что ожидаешь ты и тем, что ожидает ядро. Скорее всего, тип протокола изменится на play раньше времени. Это всё тоже нужно учитывать.

Забегая вперёд - на твой вопрос "а что конкретно нужно сделать?" не существует ответа, которые бы тебе понравился. Ответ звучит так: "изучай ядро и пытайся понять, как с ним взаимодействовать".
Навык разбираться - это то, что делает программиста программистом.
Кто-то из форумчан вряд ли станет тратить существенное время, чтобы лезть в ядро вместо тебя, чтобы предложить конкретное решение.
Ты можешь попытаться его нагуглить, если кто-то уже делал подобное. Но учти, что на разных версиях игры и на разных ядрах всё может быть совершенно по-разному. Даже между Spigot и Paper может быть существенная разница в этом вопросе, хотя обычно плагины от Paper совместимы со Spigot на 99%. Но тебе придётся залеть в саму реализацию ядра, а тут ошибки уже не прощаются
 
Последнее редактирование:
Ответы на эти вопросы есть по ссылкам, которые уже кидал выше. Нужно было лишь чуть копнуть вглубь: https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_Encryption


Либо саморучно, либо заставить ядро отправить. Но для этого придётся лезть в НМС/рефлексию. В апи Paper этого по-моему, нет. Только на банже и велосити присутствует.
Теоретически отправить пакет было бы проще, т.к. можно использовать какой-нибудь ProtocolLib. Но проблема в другом.
Даже если ты отправишь пакет, то ядро сервера знать об этом не будет. А значит будет нарушен процесс подключения к серверу. Будет рассинхро между тем, что ожидаешь ты и тем, что ожидает ядро. Скорее всего, тип протокола изменится на play раньше времени. Это всё тоже нужно учитывать.

Забегая вперёд - на твой вопрос "а что конкретно нужно сделать?" не существует ответа, которые бы тебе понравился. Ответ звучит так: "изучай ядро и пытайся понять, как с ним взаимодействовать".
Навык разбираться - это то, что делает программиста программистом.
Кто-то из форумчан вряд ли станет тратить существенное время, чтобы лезть в ядро вместо тебя, чтобы предложить конкретное решение.
Ты можешь попытаться его нагуглить, если кто-то уже делал подобное. Но учти, что на разных версиях игры и на разных ядрах всё может быть совершенно по-разному. Даже между Spigot и Paper может быть существенная разница в этом вопросе, хотя обычно плагины от Paper совместимы со Spigot на 99%. Но тебе придётся залеть в саму реализацию ядра, а тут ошибки уже не прощаются
Получается мне нужно залезть в НМС или ядро, найти там то как генерировать айди сервера, и как отправлять пакеты?
 

Поделиться этой страницей

Назад
Верх