Опубликовав свой первый проект в 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 по следующей ссылке.