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