Защита от копирования Unity-проекта с использованием библиотеки kernel32.dll

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Опубликовав свой первый проект в Steam «любовался» достаточно неплохим количеством скачиваний. Только какой в этом толк, если вся эта движуха происходила на торрент-трекерах...

Поэтому всерьез задумался о защите своих коммерческих проектов от пиратов.

Конечно, универсального способа защиты от пиратов не существует и тема защиты от пиратства является как-никак актуальной: темой постоянных дискуссий и споров.

В рамках данной статьи рассмотрим вариант дополнительной защиты Unity-проекта (под Windows) с использованием библиотеки kernel32.dll. А использовать данный способ защиты в своем проекте, либо не использовать – решать вам. И так приступим.

На нулевой нашей сцене создадим объект с названием SecurityManager и повесим на него скрипт с названием Security.

Подключаем необходимые библиотеки:

using UnityEngine;
using System;
using System.Text;
using System.IO;

Объявим необходимые переменные.

private string sn = "";
public string folder = "/Data";
public string fileName = "Settings.dat";
public string code = "AG7XpyPfmwN28193";

Значение переменной code придумываем любое. Желательно с помощью генератора паролей.

Создаем функцию с именем DebugSave() и пишем следующий код.

private void DebugSave()
    {

        try //обработка исключений
        {

            sn = code;

            //кодируем в двоичный код
            byte[] buf = Encoding.UTF8.GetBytes(sn);
            StringBuilder sb = new StringBuilder(buf.Length * 8);
            foreach (byte b in buf)
            {
                sb.Append(Convert.ToString(b, 2).PadLeft(8, '0'));
            }
            string binaryStr = sb.ToString();


            //создаем папку в директории проекта
            Directory.CreateDirectory(Application.dataPath + folder);

            //сохраняем в файл
            using (var stream = File.Open(Application.dataPath + folder + "/" + fileName, FileMode.Create))
            {
                using (var writer = new BinaryWriter(stream, Encoding.UTF8, false))
                {
                    
                    writer.Write(binaryStr);
                    writer.Close();
                }
            }


        }
        catch
        {
            Debug.Log(message: "Ошибка чтения в файл"); //выводим сообщение об ошибке
        }

    }

И вызовем ее при старте сцены:

    void Start()
    {
        DebugSave();
    }

При выполнении функции DebugSave() в директории проекта создается папка с именем Data. В папке Data создается бинарный файл с именем Settings.dat. Значение переменной code кодируется в двоичный код и записывается в файл Settings.dat. Кодируем в двоичный код для того, чтобы обычный юзер не смог прочитать значение в файле с помощью блокнота

Теперь приступим к реализации непосредственно самой защиты. Код работы с библиотекой kernel32.dll подсмотрел ТУТ.

Конечно, данную защиту можно реализовать с помощью стандартной юнитовской команды SystemInfo.deviceUniqueIdentifier, но в случае апгрейда ПК (замены процессора, прошивки BIOS) наш проект будет ругаться о пиратстве. Нас такой расклад не устраивает.

Подключаем следующую библиотеку:

using System.Runtime.InteropServices;

И объявляем необходимые переменные:

[DllImport("kernel32.dll")]
private static extern long GetVolumeInformation(
string PathName,
StringBuilder VolumeNameBuffer,
UInt32 VolumeNameSize,
ref UInt32 VolumeSerialNumber,
ref UInt32 MaximumComponentLength,
ref UInt32 FileSystemFlags,
StringBuilder FileSystemNameBuffer,
UInt32 FileSystemNameSize);

public string disk; // задать в инспекторе  "C:\"

Для переменной disk в инспекторе указываем "C:\". В самом скрипте не получается – ругается IDE.

Создаем функцию с именем Getvolumeinformation() и пишем следующий код:

    private void Getvolumeinformation() //считываем системную информацию
    {
        string drive_letter = disk;
        drive_letter = drive_letter.Substring(0, 1) + ":\\";

        uint serial_number = 0;
        uint max_component_length = 0;
        StringBuilder sb_volume_name = new StringBuilder(256);
        UInt32 file_system_flags = new UInt32();
        StringBuilder sb_file_system_name = new StringBuilder(256);

        if (GetVolumeInformation(drive_letter, sb_volume_name,
            (UInt32)sb_volume_name.Capacity, ref serial_number,
            ref max_component_length, ref file_system_flags,
            sb_file_system_name,
            (UInt32)sb_file_system_name.Capacity) == 0)
        {
            Debug.Log(message: "Error getting volume information.");
        }
        else
        {
            sn = serial_number.ToString(); //серийный номер
            Debug.Log(message: sn);
        }
    }

Сразу добавим функцию, отвечающую за вывод системных сообщений:

public static class NativeWinAlert
    {
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern System.IntPtr GetActiveWindow();

        public static System.IntPtr GetWindowHandle()
        {
            return GetActiveWindow();
        }

        [DllImport("user32.dll", SetLastError = true)]
        static extern int MessageBox(IntPtr hwnd, String lpText, String lpCaption, uint uType);

        /// <summary>
        /// Shows Error alert box with OK button.
        /// </summary>
        /// <param name="text">Main alert text / content.</param>
        /// <param name="caption">Message box title.</param>
        public static void Error(string text, string caption)
        {
            try
            {
                MessageBox(GetWindowHandle(), text, caption, (uint)(0x00000000L | 0x00000010L));
                Debug.Log("Игра закрылась");
                Application.Quit();    // закрыть приложение
            }
            catch (Exception ex) { }
        }
    }

Код функции, отвечающую за вывод системных сообщений, позаимствовал ТУТ.

Создаем функцию с именем Save() и пишем следующий код:

    private void Save()
    {

        try //обработка исключений
        {

            //кодируем в двоичный код
            byte[] buf = Encoding.UTF8.GetBytes(sn);
            StringBuilder sb = new StringBuilder(buf.Length * 8);
            foreach (byte b in buf)
            {
                sb.Append(Convert.ToString(b, 2).PadLeft(8, '0'));
            }
            string binaryStr = sb.ToString();

            //сохраняем в файл
            using (var stream = File.Open(Application.dataPath + folder + "/" + fileName, FileMode.Create))
            {
                using (var writer = new BinaryWriter(stream, Encoding.UTF8, false))
                {

                    writer.Write(binaryStr);
                    writer.Close(); //закрываем файл
                }
            }


        }
        catch
        {
            Debug.Log(message: "Ошибка чтения в файл"); //выводим сообщение об ошибке
        }

    }

Функция перевода двоичного когда в текст:

public static string BinaryToString(string data) 
    {
        List<Byte> byteList = new List<Byte>();

        for (int i = 0; i < data.Length; i += 8)
        {
            byteList.Add(Convert.ToByte(data.Substring(i, 8), 2));
        }

        return Encoding.ASCII.GetString(byteList.ToArray());
    }

Создаем функцию с именем Set() и пишем следующий код:

private void Set()
    {
        if (File.Exists(Application.dataPath + folder + "/" + fileName)) //проверяем наличие файла, если его нет выводим сообщение о приатстве.
        {
            using (var stream = File.Open(Application.dataPath + folder + "/" + fileName, FileMode.Open))
            {
                using (var reader = new BinaryReader(stream, Encoding.UTF8, false))
                {
                    string binaryStr = reader.ReadString();
                    reader.Close(); //закрываем файл

                    //двоичный код переобразовываем в строку
                    string resultText = BinaryToString(binaryStr);
                    
                    Getvolumeinformation(); //читаем серийный номер диска

                    if ((resultText == code) || (resultText == sn))
                    {
                        if (resultText == code)
                        {
                            Save();
                        }
                            

                    }
                    else
                    {
                        Debug.Log(message: "Пират!"); //выводим сообщение об ошибке
                        NativeWinAlert.Error("This copy of game is not genuine.", "Error");
                    }

                }
            }

        }
        else
        {
            Debug.Log(message: "Пират!"); //выводим сообщение об ошибке
            NativeWinAlert.Error("This copy of game is not genuine.", "Error");
        }

    }

При выполнении функции Set() вначале проверяем наличие файла Settings.dat, если его нет, то выводим сообщение о пиратстве (вызываем функцию NativeWinAlert). Если файл Settings.dat существует, читаем его, двоичный код преобразовываем в текст, считываем значение серийного номера тома. И выполняем очередную проверку, если значение не равняется значению переменной code или значению серийному номеру тома С, то выводим сообщение о пиратстве (вызываем функцию NativeWinAlert), в противном случае в файл Settings.dat записываем значение серийного номера тома С.

В функции Start() закоментируем вызов функции DebugSave() (она нужна только нам) и добавим вызов функции Set().

    void Start()
    {
        Set(); 
        //DebugSave(); //записываем в файл значение переменной code
    }

Теперь при первом запуске нашего проекта, значение в файле Settings.dat перезаписывается на значение серийного номера тома С. После данной процедуры копию проекта нельзя будет запустить на другой машине.

Исходный файл проекта располагается на сервисе GitHub по следующей ссылке.

Источник: https://habr.com/ru/post/648543/


Интересные статьи

Интересные статьи

Данная статья - это не научный прорыв, а лишь помощник быстрее понять как работает стандартный функционал в BitrixДавайте представим, что в разделе каталога у нас 150 запросов к БД. Вроде бы немного п...
В основном я предпочитаю использовать реляционные базы данных (SQL), поскольку они предоставляют несколько возможностей, которые весьма полезны при работе с данными. SQLi...
В статье пойдет речь об опыте разработки программы для составления эффективного портфеля облигаций с точки зрения минимизации его дюрации. Возможно, я не буду оригинален и для всех, кто инвестиру...
Всем привет! В этой заметке я хотел бы поделиться своим подходом к организации и тестированию кода с использованием Redux Thunk в проекте на React. Путь к нему был долог и тернист, поэтому пост...