Unity project copy protection using kernel32.dll

By publishing your first project in Steam “Admired” quite a good number of downloads. But what’s the use of it, if all this movement took place on torrent trackers …

Therefore, he seriously thought about protecting his commercial projects from pirates.

Of course, there is no universal way to protect against piracy, and the topic of protection against piracy is, after all, relevant: the topic of constant discussions and disputes.

In the framework of this article, we will consider the option of additional protection of a Unity project (under Windows) using the library kernel32.dll. And it is up to you to use this method of protection in your project, or not to use it. And so let’s get started.

On our zero scene, we will create an object with the name Security Manager and hang a script on it with the name security.

We connect the necessary libraries:

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

Let’s declare the necessary variables.

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

Variable value code think of any. Preferably with a password generator.

Create a function named DebugSave() and write the following code.

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: "Ошибка чтения в файл"); //выводим сообщение об ошибке
        }

    }

And call it at the start of the scene:

    void Start()
    {
        DebugSave();
    }

When executing the function DebugSave() a folder is created in the project directory with the name Data. In the folder Data a binary file is created with the name Settings.dat. Variable value code encoded into binary code and written to a file Settings.dat. We encode to binary code so that a normal user cannot read the value in the file using notepad

Now let’s proceed to the implementation of the protection itself. Code for working with the library kernel32.dll spied HERE.

Of course, this protection can be implemented using the standard unit command SystemInfo.deviceUniqueIdentifier, but in case of a PC upgrade (replacement of the processor, firmware BIOS) our project will swear about piracy. We are not satisfied with this arrangement.

We include the following library:

using System.Runtime.InteropServices;

And we declare the necessary variables:

[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:"

For a variable disk in the inspector specify “C:“. It doesn’t work in the script itself – it swears IDE.

Create a function named getvolumeinformation() and write the following code:

    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);
        }
    }

Immediately add a function responsible for displaying system messages:

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) { }
        }
    }

The code of the function responsible for displaying system messages was borrowed HERE.

Create a function named Save() and write the following code:

    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: "Ошибка чтения в файл"); //выводим сообщение об ошибке
        }

    }

Function to convert binary when to text:

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());
    }

Create a function named Set() and write the following code:

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");
        }

    }

When executing the function Set() First check if the file exists Settings.datif it is not present, then we display a message about piracy (call the function NativeWinAlert). If the file Settings.dat exists, read it, convert the binary code to text, read the value of the volume serial number. And we perform another check if the value is not equal to the value of the variable code or the value of the serial number of the volume WITHthen display a message about piracy (call the function NativeWinAlert), otherwise to the file Settings.dat write down the value of the serial number of the volume WITH.

In function Start() comment out the function call DebugSave() (we only need it) and add a function call set().

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

Now the first time we run our project, the value in the file Settings.dat overwritten with the value of the volume serial number WITH. After this procedure, a copy of the project cannot be run on another machine.

The source file of the project is located on the GitHub service at the following link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *