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 { /// /// Выполняет проверку лицензии при запуске приложения и пропускает ее в dev-среде. /// 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() }; 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()); } [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); } }