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