- добавлены проектные инструкции AGENTS.md: зафиксированы правила работы с документацией, сообщениями коммитов, MCP-инструментами и Unity MCP workflow для дальнейшей разработки - проверка лицензии вынесена из GameManager в отдельный StartupLicenseManager: код WinAPI, запуск RRLauncher.exe, обработка таймаута, возврат фокуса, установка fullscreen-режима и клик в центр перенесены в отдельный долгоживущий компонент - обновлена инициализация глобальных сервисов: GlobalsManager теперь добавляет и инициализирует StartupLicenseManager, а Globals.prefab содержит новый компонент рядом с остальными runtime-сервисами - обновлены Unity Editor и зависимости проекта: версия проекта поднята до 6000.3.14f1, обновлены ai.inference, collab-proxy, inputsystem, timeline, visualscripting и связанные lock-записи - синхронизированы проектные настройки и generated assets после обновления Unity: обновлены ProjectSettings, URP global settings, TMP/font assets, tile/prefab meta-данные и связанные ресурсы сцен - скорректирован MainMenu UI: символ валюты заменен на текстовое обозначение руб., а также сохранены небольшие Unity-сериализованные изменения layout/scrollbar-значений - добавлены правила .gitignore для локальных служебных артефактов: Codex/RAG-индекса, .NET SDK telemetry/cache и Unity recovery-сцен - удалены из отслеживания ранее попавшие в репозиторий файлы .dotnet и Assets/_Recovery: новые служебные файлы этих каталогов больше не будут засорять рабочее дерево
339 lines
10 KiB
C#
339 lines
10 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.IO;
|
||
using System.Runtime.InteropServices;
|
||
using System.Threading;
|
||
using UnityEngine;
|
||
#if UNITY_EDITOR
|
||
using UnityEditor;
|
||
#endif
|
||
|
||
namespace RR.Game2D.Core.Managers
|
||
{
|
||
/// <summary>
|
||
/// Выполняет проверку лицензии при запуске приложения и пропускает ее в dev-среде.
|
||
/// </summary>
|
||
public class StartupLicenseManager : MonoBehaviour
|
||
{
|
||
private const string LauncherName = "RRLauncher.exe";
|
||
private const float CheckTimeoutSeconds = 30f;
|
||
private const float QuitDelaySeconds = 3f;
|
||
private const uint WaitTimeout = 0x00000102;
|
||
|
||
private bool _isInitialized;
|
||
|
||
public void Initialize()
|
||
{
|
||
if (_isInitialized) return;
|
||
|
||
_isInitialized = true;
|
||
StartCoroutine(CheckLicenseCoroutine());
|
||
}
|
||
|
||
private IEnumerator CheckLicenseCoroutine()
|
||
{
|
||
// Ждем один кадр, чтобы Unity завершил базовую инициализацию.
|
||
yield return null;
|
||
|
||
if (ShouldSkipLicenseCheck())
|
||
{
|
||
ApplyLicensedStartupState();
|
||
yield break;
|
||
}
|
||
|
||
bool licenseValid = false;
|
||
bool checkCompleted = false;
|
||
Exception licenseCheckException = null;
|
||
|
||
Thread thread = new Thread(() =>
|
||
{
|
||
try
|
||
{
|
||
licenseValid = CheckLicense();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
licenseCheckException = ex;
|
||
}
|
||
finally
|
||
{
|
||
checkCompleted = true;
|
||
}
|
||
})
|
||
{
|
||
IsBackground = true
|
||
};
|
||
|
||
thread.Start();
|
||
|
||
float timeLeft = CheckTimeoutSeconds;
|
||
while (!checkCompleted && timeLeft > 0f)
|
||
{
|
||
timeLeft -= Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
if (!checkCompleted)
|
||
{
|
||
Debug.LogError("[License] License check timed out.");
|
||
licenseValid = false;
|
||
}
|
||
|
||
if (licenseCheckException != null)
|
||
{
|
||
Debug.LogError($"[License] License check failed: {licenseCheckException.Message}");
|
||
Debug.LogError($"[License] Stack trace: {licenseCheckException.StackTrace}");
|
||
licenseValid = false;
|
||
}
|
||
|
||
if (!licenseValid)
|
||
{
|
||
yield return new WaitForSeconds(QuitDelaySeconds);
|
||
QuitApplication();
|
||
yield break;
|
||
}
|
||
|
||
ApplyLicensedStartupState();
|
||
}
|
||
|
||
private bool ShouldSkipLicenseCheck()
|
||
{
|
||
if (Application.isEditor)
|
||
{
|
||
Debug.Log("[License] Unity Editor detected. License check skipped.");
|
||
return true;
|
||
}
|
||
|
||
if (Debug.isDebugBuild)
|
||
{
|
||
Debug.Log("[License] Development build detected. License check skipped.");
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void ApplyLicensedStartupState()
|
||
{
|
||
Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
|
||
Screen.SetResolution(1920, 1080, true);
|
||
ClickCenter();
|
||
}
|
||
|
||
private void QuitApplication()
|
||
{
|
||
#if UNITY_EDITOR
|
||
EditorApplication.isPlaying = false;
|
||
#else
|
||
Application.Quit();
|
||
#endif
|
||
}
|
||
|
||
private bool CheckLicense()
|
||
{
|
||
try
|
||
{
|
||
string launcherPath = FindLauncherPath();
|
||
if (string.IsNullOrEmpty(launcherPath))
|
||
{
|
||
Debug.LogError("[License] RRLauncher.exe not found.");
|
||
return false;
|
||
}
|
||
|
||
STARTUPINFO startupInfo = new STARTUPINFO
|
||
{
|
||
cb = Marshal.SizeOf<STARTUPINFO>()
|
||
};
|
||
|
||
bool success = CreateProcess(
|
||
launcherPath,
|
||
null,
|
||
IntPtr.Zero,
|
||
IntPtr.Zero,
|
||
false,
|
||
0,
|
||
IntPtr.Zero,
|
||
Path.GetDirectoryName(launcherPath),
|
||
ref startupInfo,
|
||
out PROCESS_INFORMATION processInfo);
|
||
|
||
if (!success) return false;
|
||
|
||
try
|
||
{
|
||
uint waitResult = WaitForSingleObject(processInfo.hProcess, (uint)(CheckTimeoutSeconds * 1000f));
|
||
if (waitResult == WaitTimeout) return false;
|
||
|
||
if (!GetExitCodeProcess(processInfo.hProcess, out uint exitCode)) return false;
|
||
|
||
if (exitCode == 0)
|
||
{
|
||
ForceToFront();
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
CloseHandle(processInfo.hProcess);
|
||
CloseHandle(processInfo.hThread);
|
||
}
|
||
}
|
||
catch (Exception outerEx)
|
||
{
|
||
Debug.LogError($"[License] Critical error in CheckLicense: {outerEx.Message}");
|
||
Debug.LogError($"[License] Stack trace: {outerEx.StackTrace}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private static string FindLauncherPath()
|
||
{
|
||
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||
string pathInBase = Path.Combine(baseDirectory, LauncherName);
|
||
if (File.Exists(pathInBase)) return pathInBase;
|
||
|
||
string pathInStreamingAssets = Path.Combine(Application.streamingAssetsPath, LauncherName);
|
||
if (File.Exists(pathInStreamingAssets)) return pathInStreamingAssets;
|
||
|
||
return null;
|
||
}
|
||
|
||
private void ForceToFront()
|
||
{
|
||
IntPtr handle = GetActiveWindow();
|
||
SetForegroundWindow(handle);
|
||
}
|
||
|
||
private static void ClickCenter()
|
||
{
|
||
int screenWidth = Screen.currentResolution.width;
|
||
int screenHeight = Screen.currentResolution.height;
|
||
if (screenWidth <= 0 || screenHeight <= 0) return;
|
||
|
||
int absoluteX = (int)(screenWidth * 0.5f * 65535 / screenWidth);
|
||
int absoluteY = (int)(screenHeight * 0.5f * 65535 / screenHeight);
|
||
|
||
INPUT[] inputs =
|
||
{
|
||
new INPUT
|
||
{
|
||
type = InputMouse,
|
||
mi = new MOUSEINPUT
|
||
{
|
||
dx = absoluteX,
|
||
dy = absoluteY,
|
||
dwFlags = MouseEventfMove | MouseEventfAbsolute
|
||
}
|
||
},
|
||
new INPUT
|
||
{
|
||
type = InputMouse,
|
||
mi = new MOUSEINPUT
|
||
{
|
||
dwFlags = MouseEventfLeftDown
|
||
}
|
||
},
|
||
new INPUT
|
||
{
|
||
type = InputMouse,
|
||
mi = new MOUSEINPUT
|
||
{
|
||
dwFlags = MouseEventfLeftUp
|
||
}
|
||
}
|
||
};
|
||
|
||
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
private struct PROCESS_INFORMATION
|
||
{
|
||
public IntPtr hProcess;
|
||
public IntPtr hThread;
|
||
public int dwProcessId;
|
||
public int dwThreadId;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||
private struct STARTUPINFO
|
||
{
|
||
public int cb;
|
||
public string lpReserved;
|
||
public string lpDesktop;
|
||
public string lpTitle;
|
||
public int dwX;
|
||
public int dwY;
|
||
public int dwXSize;
|
||
public int dwYSize;
|
||
public int dwXCountChars;
|
||
public int dwYCountChars;
|
||
public int dwFillAttribute;
|
||
public int dwFlags;
|
||
public short wShowWindow;
|
||
public short cbReserved2;
|
||
public IntPtr lpReserved2;
|
||
public IntPtr hStdInput;
|
||
public IntPtr hStdOutput;
|
||
public IntPtr hStdError;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
private struct INPUT
|
||
{
|
||
public uint type;
|
||
public MOUSEINPUT mi;
|
||
}
|
||
|
||
[StructLayout(LayoutKind.Sequential)]
|
||
private struct MOUSEINPUT
|
||
{
|
||
public int dx;
|
||
public int dy;
|
||
public uint mouseData;
|
||
public uint dwFlags;
|
||
public uint time;
|
||
public IntPtr dwExtraInfo;
|
||
}
|
||
|
||
private const uint InputMouse = 0;
|
||
private const uint MouseEventfMove = 0x0001;
|
||
private const uint MouseEventfAbsolute = 0x8000;
|
||
private const uint MouseEventfLeftDown = 0x0002;
|
||
private const uint MouseEventfLeftUp = 0x0004;
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||
private static extern bool CreateProcess(
|
||
string lpApplicationName,
|
||
string lpCommandLine,
|
||
IntPtr lpProcessAttributes,
|
||
IntPtr lpThreadAttributes,
|
||
bool bInheritHandles,
|
||
uint dwCreationFlags,
|
||
IntPtr lpEnvironment,
|
||
string lpCurrentDirectory,
|
||
ref STARTUPINFO lpStartupInfo,
|
||
out PROCESS_INFORMATION lpProcessInformation);
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
private static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
private static extern bool CloseHandle(IntPtr hObject);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern bool SetForegroundWindow(IntPtr hwnd);
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern IntPtr GetActiveWindow();
|
||
|
||
[DllImport("user32.dll")]
|
||
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||
}
|
||
}
|