Danro/Assets/Scripts/Managers/StartupLicenseManager.cs
Danil Iarosh 942c5dd9fa chore(project): sync startup and tooling updates
- добавлены проектные инструкции 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:
  новые служебные файлы этих каталогов больше не будут засорять рабочее дерево
2026-05-15 14:31:53 +03:00

339 lines
10 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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