// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using SharpEmu.Core.Cpu.Disasm;
using SharpEmu.HLE;

namespace SharpEmu.Core.Cpu.Native;

public sealed partial class DirectExecutionBackend
{
	private const ulong LazyCommitWindowBytes = 0x0200_0000UL;
	private static int _lazyCommitTraceCount;

	private unsafe void SetupExceptionHandler()
	{
		if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal))
		{
			_rawExceptionHandlerStub = CreateExceptionHandlerTrampoline(RawVectoredHandlerPtrManaged);
			if (_rawExceptionHandlerStub == 0)
			{
				throw new InvalidOperationException("Failed to create raw exception handler trampoline");
			}
			_rawExceptionHandler = (nint)AddVectoredExceptionHandler(1u, _rawExceptionHandlerStub);
			Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}");
		}
		else
		{
			Console.Error.WriteLine("[LOADER][INFO] Raw exception handler disabled by SHARPEMU_DISABLE_RAW_HANDLER=1");
		}

		_handlerDelegate = VectoredHandler;
		_handlerHandle = GCHandle.Alloc(_handlerDelegate);
		_exceptionHandlerStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_handlerDelegate));
		if (_exceptionHandlerStub == 0)
		{
			throw new InvalidOperationException("Failed to create exception handler trampoline");
		}
		_exceptionHandler = (nint)AddVectoredExceptionHandler(1u, _exceptionHandlerStub);
		Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}");

		_unhandledFilterDelegate = UnhandledExceptionFilter;
		_unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate);
		_unhandledFilterStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate));
		if (_unhandledFilterStub == 0)
		{
			throw new InvalidOperationException("Failed to create unhandled exception filter trampoline");
		}
		SetUnhandledExceptionFilter(_unhandledFilterStub);
	}

	private unsafe int UnhandledExceptionFilter(void* exceptionInfo)
	{
		try
		{
			EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
			ulong rip = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, 248);
			ulong rsp = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, 152);
			Console.Error.WriteLine("[LOADER][FATAL] Unhandled exception filter fired.");
			Console.Error.WriteLine($"[LOADER][FATAL]   Code: 0x{exceptionRecord->ExceptionCode:X8}");
			Console.Error.WriteLine($"[LOADER][FATAL]   Exception Address: 0x{(ulong)(nint)exceptionRecord->ExceptionAddress:X16}");
			Console.Error.WriteLine($"[LOADER][FATAL]   RIP: 0x{rip:X16}");
			Console.Error.WriteLine($"[LOADER][FATAL]   RSP: 0x{rsp:X16}");
			Console.Error.Flush();
		}
		catch
		{
		}

		return 0;
	}

	private unsafe int VectoredHandler(void* exceptionInfo)
	{
		if (_vectoredHandlerDepth > 0)
		{
			LogNestedVectoredException(exceptionInfo);
			Console.Error.Flush();
			return 0;
		}

		_vectoredHandlerDepth++;
		try
		{
			EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
			uint exceptionCode = exceptionRecord->ExceptionCode;
			uint exceptionFlags = exceptionRecord->ExceptionFlags;
			ulong exceptionAddress = (ulong)exceptionRecord->ExceptionAddress;
			void* contextRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord;
			if (contextRecord == null)
			{
				Console.Error.WriteLine("[LOADER][FATAL] ContextRecord is null!");
				Console.Error.Flush();
				return 0;
			}

			ulong rip = ReadCtxU64(contextRecord, 248);
			ulong rsp = ReadCtxU64(contextRecord, 152);

			if (exceptionCode == 3221225477u && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp))
			{
				return -1;
			}
			if (IsBenignHostDebugException(exceptionCode))
			{
				return -1;
			}
			if (exceptionCode == MSVC_CPP_EXCEPTION)
			{
				return 0;
			}

			switch (exceptionCode)
			{
				case 3221225477u:
					LogAccessViolationTrace(exceptionAddress, exceptionRecord);
					break;
				case 3221226505u:
					{
						ulong p0 = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0;
						ulong p1 = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0;
						Console.Error.WriteLine($"[LOADER][TRACE] VEH_FASTFAIL code=0x{exceptionCode:X8} ex=0x{exceptionAddress:X16} rip=0x{rip:X16} rsp=0x{rsp:X16} p0=0x{p0:X16} p1=0x{p1:X16}");
						Console.Error.Flush();
						break;
					}
			}

			ulong rax = ReadCtxU64(contextRecord, 120);
			ulong rbx = ReadCtxU64(contextRecord, 144);
			ulong rcx = ReadCtxU64(contextRecord, 128);
			ulong rdx = ReadCtxU64(contextRecord, 136);
			ulong rsi = ReadCtxU64(contextRecord, 168);
			ulong rdi = ReadCtxU64(contextRecord, 176);
			ulong rbp = ReadCtxU64(contextRecord, 160);
			ulong r8 = ReadCtxU64(contextRecord, 184);
			ulong r9 = ReadCtxU64(contextRecord, 192);
			ulong r10 = ReadCtxU64(contextRecord, 200);
			ulong r11 = ReadCtxU64(contextRecord, 208);
			ulong r12 = ReadCtxU64(contextRecord, 216);
			ulong r13 = ReadCtxU64(contextRecord, 224);
			ulong r14 = ReadCtxU64(contextRecord, 232);
			ulong r15 = ReadCtxU64(contextRecord, 240);

			Console.Error.WriteLine("[LOADER][INFO] =========================================");
			Console.Error.WriteLine("[LOADER][INFO] NATIVE EXCEPTION CAUGHT!");
			Console.Error.WriteLine($"[LOADER][INFO]   Code: 0x{exceptionCode:X8}");
			Console.Error.WriteLine($"[LOADER][INFO]   Exception Address: 0x{exceptionAddress:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RIP: 0x{rip:X16}");
			if (TryFormatNearestRuntimeSymbol(rip, out string symbol))
			{
				Console.Error.WriteLine("[LOADER][INFO]   RIP symbol: " + symbol);
			}
			Console.Error.WriteLine($"[LOADER][INFO]   RAX: 0x{rax:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RBX: 0x{rbx:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RCX: 0x{rcx:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RDX: 0x{rdx:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RSI: 0x{rsi:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RDI: 0x{rdi:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RBP: 0x{rbp:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   RSP: 0x{rsp:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R8 : 0x{r8:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R9 : 0x{r9:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R10: 0x{r10:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R11: 0x{r11:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R12: 0x{r12:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R13: 0x{r13:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R14: 0x{r14:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   R15: 0x{r15:X16}");
			Console.Error.WriteLine($"[LOADER][INFO]   Flags: 0x{exceptionFlags:X8}");

			ulong accessType = 0;
			ulong target = 0;
			if (exceptionCode == 3221225477u && exceptionRecord->NumberParameters >= 2)
			{
				accessType = *exceptionRecord->ExceptionInformation;
				target = exceptionRecord->ExceptionInformation[1];
				string accessText = accessType switch
				{
					0uL => "read",
					1uL => "write",
					8uL => "execute",
					_ => $"unknown({accessType})"
				};
				Console.Error.WriteLine("[LOADER][INFO]   AV access: " + accessText);
				Console.Error.WriteLine($"[LOADER][INFO]   AV target: 0x{target:X16}");
				if (VirtualQuery((void*)target, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0)
				{
					Console.Error.WriteLine($"[LOADER][INFO]   AV target region: base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} state=0x{mbi.State:X08} protect=0x{mbi.Protect:X08}");
				}

			}

			try
			{
				Console.Error.WriteLine("[LOADER][INFO]   Stack qwords (RSP..):");
				for (int i = 0; i < 16; i++)
				{
					ulong stackAddr = rsp + (ulong)(i * 8);
					ulong value = (ulong)Marshal.ReadInt64((nint)stackAddr);
					Console.Error.WriteLine($"[LOADER][INFO]     [rsp+0x{i * 8:X2}] @0x{stackAddr:X16} = 0x{value:X16}");
				}
			}
			catch
			{
				Console.Error.WriteLine("[LOADER][WARNING]   Could not read stack qwords.");
			}

			try
			{
				Console.Error.WriteLine("[LOADER][INFO]   Frame chain (RBP walk):");
				ulong frame = rbp;
				for (int i = 0; i < 12; i++)
				{
					if (frame < 140733193388032L || frame > 140737488355327L)
					{
						break;
					}
					ulong next = (ulong)Marshal.ReadInt64((nint)frame);
					ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8));
					string extra = TryFormatNearestRuntimeSymbol(ret, out string retSym) ? $" [{retSym}]" : string.Empty;
					Console.Error.WriteLine($"[LOADER][INFO]     frame#{i}: rbp=0x{frame:X16} ret=0x{ret:X16}{extra} next=0x{next:X16}");
					if (next <= frame)
					{
						break;
					}
					frame = next;
				}
			}
			catch
			{
				Console.Error.WriteLine("[LOADER][WARNING]   Could not walk RBP frame chain.");
			}

			switch (exceptionCode)
			{
				case 3221225477u:
					Console.Error.WriteLine("[LOADER][ERROR]   Type: Access Violation");
					Console.Error.WriteLine("[LOADER][ERROR]   This usually means:");
					Console.Error.WriteLine("[LOADER][ERROR]     - Guest code called an unmapped import");
					Console.Error.WriteLine("[LOADER][ERROR]     - Guest code accessed unmapped memory");
					Console.Error.WriteLine("[LOADER][ERROR]     - Need to implement HLE for this NID");
					try
					{
						byte[] code = new byte[16];
						Marshal.Copy((nint)rip, code, 0, code.Length);
						Console.Error.WriteLine("[LOADER][INFO]   Code at RIP: " + BitConverter.ToString(code).Replace("-", " "));
						if (code[0] == 100)
						{
							Console.Error.WriteLine("[LOADER][ERROR]   Detected FS segment prefix - TLS access not patched!");
						}
						else if (code[0] == 101)
						{
							Console.Error.WriteLine("[LOADER][ERROR]   Detected GS segment prefix - TLS access not patched!");
						}
						else if (code[0] == 197 || code[0] == 196)
						{
							Console.Error.WriteLine("[LOADER][INFO]   Detected AVX instruction - check CPU support!");
							Console.Error.WriteLine($"[LOADER][INFO]   RBP: 0x{rbp:X16} (mod 16 = {rbp % 16})");
							Console.Error.WriteLine($"[LOADER][INFO]   RSP: 0x{rsp:X16} (mod 16 = {rsp % 16})");
						}
						if (rip > 16)
						{
							byte[] before = new byte[16];
							Marshal.Copy((nint)(rip - 16), before, 0, before.Length);
							Console.Error.WriteLine("[LOADER][INFO]   Code before RIP: " + BitConverter.ToString(before).Replace("-", " "));
						}
						if (rip > 32)
						{
							byte[] window = new byte[64];
							Marshal.Copy((nint)(rip - 32), window, 0, window.Length);
							Console.Error.WriteLine("[LOADER][INFO]   Code window [RIP-0x20..]: " + BitConverter.ToString(window).Replace("-", " "));
						}
					}
					catch
					{
						Console.Error.WriteLine("[LOADER][ERROR]   Could not read code at RIP");
					}
					DumpRecentImportTrace();
					DumpGuestDisasmDiagnostics(rip, rbp);
					DumpGuestReferenceDiagnostics();
					DumpGuestPointerWindowDiagnostics();
					break;
				case 2147483651u:
					Console.Error.WriteLine("[LOADER][WARNING]   Type: Breakpoint (int3)");
					Console.Error.WriteLine("[LOADER][WARNING]   Unexpected breakpoint in direct-bridge mode");
					break;
				case 3221225501u:
					Console.Error.WriteLine("[LOADER][INFO]   Type: Illegal Instruction");
					break;
			}

			Console.Error.WriteLine("[LOADER][INFO] =========================================");
			Console.Error.Flush();
			return 0;
		}
		finally
		{
			_vectoredHandlerDepth--;
		}
	}

	private static bool IsBenignHostDebugException(uint exceptionCode)
	{
		return exceptionCode is DBG_PRINTEXCEPTION_C or DBG_PRINTEXCEPTION_WIDE_C or MS_VC_THREADNAME_EXCEPTION;
	}

	private unsafe static void LogNestedVectoredException(void* exceptionInfo)
	{
		int count = Interlocked.Increment(ref _nestedVehTraceCount);
		if (count > 16 && count % 128 != 0)
		{
			return;
		}

		try
		{
			EXCEPTION_POINTERS* pointers = (EXCEPTION_POINTERS*)exceptionInfo;
			EXCEPTION_RECORD* record = pointers->ExceptionRecord;
			void* contextRecord = pointers->ContextRecord;
			ulong rip = contextRecord != null ? ReadCtxU64(contextRecord, 248) : 0;
			ulong rsp = contextRecord != null ? ReadCtxU64(contextRecord, 152) : 0;
			ulong accessType = record->NumberParameters >= 1 ? *record->ExceptionInformation : 0;
			ulong target = record->NumberParameters >= 2 ? record->ExceptionInformation[1] : 0;
			Console.Error.WriteLine(
				$"[LOADER][TRACE] Nested VEH exception#{count}: code=0x{record->ExceptionCode:X8} ex=0x{(ulong)record->ExceptionAddress:X16} rip=0x{rip:X16} rsp=0x{rsp:X16} type={accessType} target=0x{target:X16}; passing through.");
		}
		catch
		{
			Console.Error.WriteLine($"[LOADER][TRACE] Nested VEH exception#{count}; passing through.");
		}
	}

	private unsafe void LogAccessViolationTrace(ulong exceptionAddress, EXCEPTION_RECORD* exceptionRecord)
	{
		ulong accessType = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0;
		ulong target = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0;
		if (_lastAvTraceRip == exceptionAddress && _lastAvTraceType == accessType && _lastAvTraceTarget == target)
		{
			_lastAvTraceRepeatCount++;
			if (_lastAvTraceRepeatCount > 4 && _lastAvTraceRepeatCount % 128 != 0)
			{
				return;
			}
			Console.Error.WriteLine($"[LOADER][TRACE] VEH_AV repeat#{_lastAvTraceRepeatCount} at 0x{exceptionAddress:X16} type={accessType} target=0x{target:X16}");
			Console.Error.Flush();
			return;
		}

		_lastAvTraceRip = exceptionAddress;
		_lastAvTraceType = accessType;
		_lastAvTraceTarget = target;
		_lastAvTraceRepeatCount = 1;
		Console.Error.WriteLine($"[LOADER][TRACE] VEH_AV first-chance at 0x{exceptionAddress:X16} type={accessType} target=0x{target:X16}");
		Console.Error.Flush();
	}

	private void DumpGuestInstructionStream(string name, ulong startRip, int maxInstructions)
	{
		if (_cpuContext == null || startRip < 0x10000 || maxInstructions <= 0)
		{
			return;
		}

		Console.Error.WriteLine($"[LOADER][INFO]   {name} disasm @0x{startRip:X16}:");
		ulong rip = startRip;
		for (int i = 0; i < maxInstructions; i++)
		{
			if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
				!IcedDecoder.TryDecode(rip, bytes, out var instruction))
			{
				Console.Error.WriteLine($"[LOADER][INFO]     0x{rip:X16}: <decode-failed>");
				break;
			}

			Console.Error.WriteLine(
				$"[LOADER][INFO]     0x{instruction.Rip:X16}: {instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
			rip += (ulong)instruction.Length;
		}
	}

	private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp)
	{
		if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM"), "1", StringComparison.Ordinal))
		{
			return;
		}

		if (rip >= 0x20)
		{
			DumpGuestInstructionStream("fault-prelude", rip - 0x20, 24);
		}

		try
		{
			ulong frame = rbp;
			for (int i = 0; i < 3; i++)
			{
				if (frame < 140733193388032L || frame > 140737488355327L)
				{
					break;
				}

				ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8));
				if (ret >= 0x40)
				{
					DumpGuestInstructionStream($"frame#{i}-ret-prelude", ret - 0x40, 24);
				}

				ulong next = (ulong)Marshal.ReadInt64((nint)frame);
				if (next <= frame)
				{
					break;
				}

				frame = next;
			}
		}
		catch
		{
			Console.Error.WriteLine("[LOADER][WARNING]   Could not dump disasm diagnostics.");
		}

		var extraAddresses = Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM_ADDRS");
		if (string.IsNullOrWhiteSpace(extraAddresses))
		{
			return;
		}

		foreach (var token in extraAddresses.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
		{
			var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
				? token[2..]
				: token;
			if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address) || address < 0x20)
			{
				continue;
			}

			DumpGuestInstructionStream($"extra-0x{address:X16}", address, 48);
		}
	}

	private unsafe void DumpGuestReferenceDiagnostics()
	{
		var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_REFSCAN_ADDRS"));
		if (targetList.Count == 0 || _cpuContext == null)
		{
			return;
		}

		const ulong scanBase = 0x0000000800000000UL;
		const ulong scanEnd = 0x0000000810000000UL;
		const int maxHitsPerTarget = 24;

		Console.Error.WriteLine(
			$"[LOADER][INFO]   Ref scan targets: {string.Join(", ", targetList.ConvertAll(static addr => $"0x{addr:X16}"))}");

		var hitCounts = new Dictionary<ulong, int>(targetList.Count);
		for (var i = 0; i < targetList.Count; i++)
		{
			hitCounts[targetList[i]] = 0;
		}

		ulong address = scanBase;
		while (address < scanEnd)
		{
			if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
			{
				break;
			}

			ulong regionBase = mbi.BaseAddress;
			ulong regionEnd = regionBase + mbi.RegionSize;
			if (regionEnd <= address)
			{
				break;
			}

			if (mbi.State == MEM_COMMIT &&
				IsReadableProtection(mbi.Protect) &&
				IsExecutableProtection(mbi.Protect))
			{
				ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget);
			}

			var allTargetsSatisfied = true;
			for (var i = 0; i < targetList.Count; i++)
			{
				if (hitCounts[targetList[i]] < maxHitsPerTarget)
				{
					allTargetsSatisfied = false;
					break;
				}
			}

			if (allTargetsSatisfied)
			{
				break;
			}

			address = regionEnd;
		}

		for (var i = 0; i < targetList.Count; i++)
		{
			var target = targetList[i];
			if (!hitCounts.TryGetValue(target, out var count) || count == 0)
			{
				Console.Error.WriteLine($"[LOADER][INFO]   Ref scan 0x{target:X16}: none");
			}
		}
	}

	private void DumpGuestPointerWindowDiagnostics()
	{
		var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOWS"));
		if (targetList.Count == 0)
		{
			return;
		}

		var windowSize = 0x80;
		var rawWindowSize = Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOW_SIZE");
		if (!string.IsNullOrWhiteSpace(rawWindowSize))
		{
			var normalized = rawWindowSize.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
				? rawWindowSize[2..]
				: rawWindowSize;
			if (int.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsedWindowSize) &&
				parsedWindowSize > 0)
			{
				windowSize = parsedWindowSize;
			}
		}

		foreach (var target in targetList)
		{
			DumpPointerWindow($"ptrwin-0x{target:X16}", target, windowSize);
		}
	}

	private void ScanExecutableRegionForTargetReferences(
		ulong regionBase,
		ulong regionEnd,
		IReadOnlyList<ulong> targets,
		IDictionary<ulong, int> hitCounts,
		int maxHitsPerTarget)
	{
		if (_cpuContext == null || regionEnd <= regionBase)
		{
			return;
		}

		ulong rip = regionBase;
		while (rip < regionEnd)
		{
			if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
				!IcedDecoder.TryDecode(rip, bytes, out var instruction) ||
				instruction.Length <= 0)
			{
				rip++;
				continue;
			}

			if (instruction.MemoryAddress is { } memoryAddress)
			{
				for (var i = 0; i < targets.Count; i++)
				{
					var target = targets[i];
					if (memoryAddress != target ||
						!hitCounts.TryGetValue(target, out var count) ||
						count >= maxHitsPerTarget)
					{
						continue;
					}

					hitCounts[target] = count + 1;
					Console.Error.WriteLine(
						$"[LOADER][INFO]   Ref scan hit target=0x{target:X16} rip=0x{instruction.Rip:X16} text={instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
				}
			}

			rip += (ulong)instruction.Length;
		}
	}

	private static List<ulong> ParseDiagnosticAddresses(string? rawValue)
	{
		var result = new List<ulong>();
		if (string.IsNullOrWhiteSpace(rawValue))
		{
			return result;
		}

		foreach (var token in rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
		{
			var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
				? token[2..]
				: token;
			if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
			{
				continue;
			}

			if (!result.Contains(address))
			{
				result.Add(address);
			}
		}

		return result;
	}

	private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size)
	{
		if (baseAddress < 0x10000 || size <= 0)
		{
			return;
		}

		ulong scanStart = baseAddress;
		ulong scanEnd = baseAddress + (ulong)size;
		List<ulong> hits = ScanSuspiciousResolverPointers(scanStart, scanEnd);
		if (hits.Count == 0)
		{
			Console.Error.WriteLine($"[LOADER][INFO]   {name} unresolved scan: none");
			return;
		}

		Console.Error.WriteLine($"[LOADER][INFO]   {name} unresolved scan hits: {hits.Count}");
		for (int i = 0; i < hits.Count && i < 8; i++)
		{
			ulong slotAddress = hits[i];
			if (TryReadQword(slotAddress, out var value))
			{
				Console.Error.WriteLine($"[LOADER][INFO]     hit#{i}: slot=0x{slotAddress:X16} value=0x{value:X16}");
			}
		}
	}

	private void DumpSentinelPatternWindow(string name, ulong baseAddress, int size)
	{
		if (_cpuContext == null || baseAddress < 0x10000 || size <= 0)
		{
			return;
		}

		byte[] buffer = new byte[size];
		if (!_cpuContext.Memory.TryRead(baseAddress, buffer))
		{
			Console.Error.WriteLine($"[LOADER][INFO]   {name} sentinel-pattern scan: unreadable");
			return;
		}

		List<string> hits = new();
		for (int offset = 0; offset + 2 <= buffer.Length; offset++)
		{
			if (BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset, 2)) == 0xFFFE)
			{
				hits.Add($"+0x{offset:X}:u16");
			}

			if (offset + 4 <= buffer.Length &&
				BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(offset, 4)) == 0xFFFFFFFEu)
			{
				hits.Add($"+0x{offset:X}:u32");
			}

			if (offset + 8 <= buffer.Length &&
				BinaryPrimitives.ReadUInt64LittleEndian(buffer.AsSpan(offset, 8)) == 0xFFFFFFFFFFFFFFFEuL)
			{
				hits.Add($"+0x{offset:X}:u64");
			}
		}

		if (hits.Count == 0)
		{
			Console.Error.WriteLine($"[LOADER][INFO]   {name} sentinel-pattern scan: none");
			return;
		}

		Console.Error.WriteLine($"[LOADER][INFO]   {name} sentinel-pattern hits: {string.Join(", ", hits.GetRange(0, Math.Min(hits.Count, 12)))}");
	}

	private void DumpReturnTargetCandidates(ulong rsp)
	{
		if (rsp < 0x10000)
		{
			return;
		}

		ulong start = rsp >= 0x10 ? rsp - 0x10 : rsp;
		Console.Error.WriteLine($"[LOADER][INFO]   Return-target candidates near RSP=0x{rsp:X16}:");
		for (int offset = 0; offset <= 0x20; offset++)
		{
			ulong address = start + (ulong)offset;
			try
			{
				ulong value = (ulong)Marshal.ReadInt64((nint)address);
				Console.Error.WriteLine($"[LOADER][INFO]     [0x{address:X16}] -> 0x{value:X16}");
			}
			catch
			{
				Console.Error.WriteLine($"[LOADER][INFO]     [0x{address:X16}] -> <unreadable>");
				break;
			}
		}
	}

	private void DumpObjectFieldTargets(string name, ulong objectAddress, int[] offsets, int windowSize)
	{
		if (objectAddress < 0x10000 || offsets.Length == 0)
		{
			return;
		}

		foreach (int offset in offsets)
		{
			ulong slotAddress = objectAddress + (ulong)offset;
			if (!TryReadQword(slotAddress, out var target) || target < 0x10000)
			{
				continue;
			}

			Console.Error.WriteLine($"[LOADER][INFO]   {name}+0x{offset:X2} target = {FormatPointerWithNearestSymbol(target)}");
			DumpPointerWindow($"{name}+0x{offset:X2}", target, windowSize);
			DumpUnresolvedSentinelWindow($"{name}+0x{offset:X2}", target, 0x80);
		}
	}

	private void DumpSuspiciousGlobalSlots()
	{
		DumpAbsoluteSlot("callback_slot[0x80293BD08]", 0x000000080293BD08uL);
		DumpAbsoluteSlot("callback_arg[0x8030FBBE8]", 0x00000008030FBBE8uL);
		DumpAbsoluteSlot("tsc_freq_global[0x8030FD590]", 0x00000008030FD590uL);
		DumpAbsoluteSlot("tsc_base_global[0x8030FD598]", 0x00000008030FD598uL);
		DumpAbsoluteSlot("plt_got[0x8028F6100]", 0x00000008028F6100uL);
		DumpAbsoluteSlot("plt_got[0x8028F6158]", 0x00000008028F6158uL);
		DumpAbsoluteSlot("plt_got[0x8028F6160]", 0x00000008028F6160uL);
		DumpAbsoluteSlot("plt_got[0x8028F64C0]", 0x00000008028F64C0uL);
		DumpAbsoluteSlot("plt_got[0x8028F64C8]", 0x00000008028F64C8uL);
		DumpAbsoluteSlot("plt_got[0x8028F6590]", 0x00000008028F6590uL);
		DumpAbsoluteSlot("plt_got[0x8028F6708]", 0x00000008028F6708uL);
		DumpUnresolvedSentinelWindow("PLT-GOT", 0x00000008028F6100uL, 0x700);
	}

	private void DumpAbsoluteSlot(string name, ulong slotAddress)
	{
		if (!TryReadQword(slotAddress, out var value))
		{
			Console.Error.WriteLine($"[LOADER][INFO]   {name} @0x{slotAddress:X16} = <unreadable>");
			return;
		}

		Console.Error.WriteLine($"[LOADER][INFO]   {name} @0x{slotAddress:X16} = {FormatPointerWithNearestSymbol(value)}");
	}
	private void DumpPointerWindow(string name, ulong baseAddress, int size)
	{
		if (baseAddress < 0x10000 || size <= 0)
		{
			return;
		}

		Console.Error.WriteLine($"[LOADER][INFO]   {name} window @0x{baseAddress:X16}:");
		for (int offset = 0; offset < size; offset += 8)
		{
			ulong slotAddress = baseAddress + (ulong)offset;
			if (!TryReadQword(slotAddress, out var value))
			{
				Console.Error.WriteLine($"[LOADER][INFO]     +0x{offset:X2}: <unreadable>");
				break;
			}

			Console.Error.WriteLine($"[LOADER][INFO]     +0x{offset:X2}: {FormatPointerWithNearestSymbol(value)}");
		}
	}

	private unsafe bool TryReadQword(ulong address, out ulong value)
	{
		value = 0;
		if (address < 0x10000)
		{
			return false;
		}

		if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
		{
			return false;
		}

		ulong regionEnd = mbi.BaseAddress + mbi.RegionSize;
		if (mbi.State != MEM_COMMIT || !IsReadableProtection(mbi.Protect) || regionEnd <= address || address > regionEnd - 8)
		{
			return false;
		}

		try
		{
			value = (ulong)Marshal.ReadInt64((nint)address);
			return true;
		}
		catch
		{
			value = 0;
			return false;
		}
	}

	private string FormatPointerWithNearestSymbol(ulong value)
	{
		string text = $"0x{value:X16}";
		if (TryFormatNearestRuntimeSymbol(value, out string symbol))
		{
			text += $" [{symbol}]";
		}

		return text;
	}

	private void InitializeRuntimeSymbolIndex(IReadOnlyDictionary<string, ulong> runtimeSymbols)
	{
		_runtimeSymbolsByName.Clear();
		if (runtimeSymbols.Count == 0)
		{
			_runtimeSymbolsByAddress = Array.Empty<KeyValuePair<string, ulong>>();
			return;
		}

		List<KeyValuePair<string, ulong>> list = new(runtimeSymbols.Count);
		foreach (KeyValuePair<string, ulong> runtimeSymbol in runtimeSymbols)
		{
			if (runtimeSymbol.Value != 0L && !string.IsNullOrWhiteSpace(runtimeSymbol.Key))
			{
				list.Add(runtimeSymbol);
				_runtimeSymbolsByName[runtimeSymbol.Key] = runtimeSymbol.Value;
			}
		}

		list.Sort((a, b) => a.Value.CompareTo(b.Value));
		_runtimeSymbolsByAddress = list.ToArray();
	}

	private bool TryFormatNearestRuntimeSymbol(ulong address, out string text)
	{
		text = string.Empty;
		KeyValuePair<string, ulong>[] runtimeSymbolsByAddress = _runtimeSymbolsByAddress;
		if (runtimeSymbolsByAddress.Length == 0)
		{
			return false;
		}

		int low = 0;
		int high = runtimeSymbolsByAddress.Length - 1;
		int best = -1;
		while (low <= high)
		{
			int mid = low + ((high - low) >> 1);
			ulong value = runtimeSymbolsByAddress[mid].Value;
			if (value <= address)
			{
				best = mid;
				low = mid + 1;
			}
			else
			{
				high = mid - 1;
			}
		}

		if (best < 0)
		{
			return false;
		}

		KeyValuePair<string, ulong> symbol = runtimeSymbolsByAddress[best];
		ulong delta = address - symbol.Value;
		text = delta == 0
			? $"{symbol.Key} (0x{symbol.Value:X16})"
			: $"{symbol.Key}+0x{delta:X} (0x{symbol.Value:X16})";
		return true;
	}

	private unsafe bool TryHandleLazyCommittedPage(EXCEPTION_RECORD* exceptionRecord, ulong rip, ulong rsp)
	{
		if (exceptionRecord->NumberParameters < 2)
		{
			return false;
		}

		ulong accessType = *exceptionRecord->ExceptionInformation;
		ulong faultAddress = exceptionRecord->ExceptionInformation[1];
		if (accessType == 8 && faultAddress < 4294967296L)
		{
			return false;
		}
		if (faultAddress < 65536 || faultAddress >= 140737488355328L)
		{
			return false;
		}
		if (!IsGuestOwnedLazyCommitAddress(faultAddress, out var owner))
		{
			return false;
		}
		if (VirtualQuery((void*)faultAddress, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
		{
			return false;
		}

		ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL;
		uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.AllocationProtect);
		int traceIndex = Interlocked.Increment(ref _lazyCommitTraceCount);
		bool traceLazyCommit = ShouldTraceLazyCommit(traceIndex);
		if (traceLazyCommit)
		{
			Console.Error.WriteLine($"[LOADER][TRACE] lazy-query#{traceIndex}: fault=0x{faultAddress:X16} owner={owner} rip=0x{rip:X16} rsp=0x{rsp:X16} state=0x{mbi.State:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.AllocationProtect:X08} prot=0x{mbi.Protect:X08}");
		}

		if (mbi.State == 4096 && IsAccessCompatible(accessType, mbi.Protect))
		{
			if (traceLazyCommit)
			{
				Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit-race#{traceIndex}: fault=0x{faultAddress:X16} protect=0x{mbi.Protect:X08}");
			}
			return true;
		}

		bool committed = false;
		ulong committedBase = 0;
		ulong committedSize = 0;

		if (mbi.State == 65536)
		{
			if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var windowBase, out var windowSize) &&
				TryReserveThenCommit(windowBase, windowSize, windowBase, windowSize, commitProtect))
			{
				committed = true;
				committedBase = windowBase;
				committedSize = windowSize;
			}
			else
			{
				ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL;
				if (TryReserveThenCommit(largeBase, 2097152uL, largeBase, 2097152uL, commitProtect))
				{
					committed = true;
					committedBase = largeBase;
					committedSize = 2097152uL;
				}
			}

			if (!committed)
			{
				ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL;
				if (TryReserveThenCommit(region64kBase, 65536uL, region64kBase, 65536uL, commitProtect))
				{
					committed = true;
					committedBase = region64kBase;
					committedSize = 65536uL;
				}
				else if (TryReserveThenCommit(pageBase, 4096uL, pageBase, 4096uL, commitProtect))
				{
					committed = true;
					committedBase = pageBase;
					committedSize = 4096uL;
				}
			}

			if (!committed)
			{
				return false;
			}

			TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
			if (traceLazyCommit)
			{
				Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
			}
			return true;
		}

		if (mbi.State != 8192)
		{
			return false;
		}

		if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var commitWindowBase, out var commitWindowSize) &&
			TryCommitRange(commitWindowBase, commitWindowSize, commitProtect))
		{
			committed = true;
			committedBase = commitWindowBase;
			committedSize = commitWindowSize;
		}
		else
		{
			ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL;
			if (TryCommitRange(largeCommitBase, 2097152uL, commitProtect))
			{
				committed = true;
				committedBase = largeCommitBase;
				committedSize = 2097152uL;
			}
		}

		if (!committed)
		{
			ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL;
			if (TryCommitRange(region64kBase, 65536uL, commitProtect))
			{
				committed = true;
				committedBase = region64kBase;
				committedSize = 65536uL;
			}
			else if (TryCommitRange(pageBase, 8192uL, commitProtect))
			{
				committed = true;
				committedBase = pageBase;
				committedSize = 8192uL;
			}
			else if (TryCommitRange(pageBase, 4096uL, commitProtect))
			{
				committed = true;
				committedBase = pageBase;
				committedSize = 4096uL;
			}
		}

		if (!committed)
		{
			return false;
		}

		TryCommitRange(pageBase + 4096, 4096uL, commitProtect);
		if (traceLazyCommit)
		{
			Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}");
		}
		return true;

		static bool TryGetLazyCommitWindow(ulong fault, ulong regionBase, ulong regionSize, out ulong baseAddress, out ulong length)
		{
			baseAddress = 0;
			length = 0;
			if (regionSize == 0 || ulong.MaxValue - regionBase < regionSize)
			{
				return false;
			}

			ulong regionEnd = regionBase + regionSize;
			ulong windowBase = fault & ~(LazyCommitWindowBytes - 1);
			if (windowBase < regionBase)
			{
				windowBase = regionBase;
			}

			if (windowBase >= regionEnd)
			{
				return false;
			}

			ulong windowEnd = Math.Min(regionEnd, windowBase + LazyCommitWindowBytes);
			ulong windowSize = windowEnd - windowBase;
			windowSize &= 0xFFFFFFFFFFFFF000uL;
			if (windowSize == 0)
			{
				return false;
			}

			baseAddress = windowBase;
			length = windowSize;
			return true;
		}

		static unsafe bool TryCommitRange(ulong baseAddress, ulong length, uint protection)
		{
			if (length == 0)
			{
				return false;
			}
			return VirtualAlloc((void*)baseAddress, (nuint)length, 4096u, protection) != null;
		}

		static unsafe bool TryReserveRange(ulong baseAddress, ulong length)
		{
			if (length == 0)
			{
				return false;
			}
			return VirtualAlloc((void*)baseAddress, (nuint)length, 8192u, 4u) != null;
		}

		static bool TryReserveThenCommit(ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection)
		{
			if (!TryReserveRange(reserveAddress, reserveSize))
			{
				return false;
			}
			return TryCommitRange(commitAddress, commitSize, protection);
		}

		static bool IsAccessCompatible(ulong accessType, uint protection)
		{
			const uint pageNoAccess = 0x01;
			const uint pageReadOnly = 0x02;
			const uint pageReadWrite = 0x04;
			const uint pageWriteCopy = 0x08;
			const uint pageExecute = 0x10;
			const uint pageExecuteRead = 0x20;
			const uint pageExecuteReadWrite = 0x40;
			const uint pageExecuteWriteCopy = 0x80;
			const uint pageGuard = 0x100;
			const uint accessMask = 0xFF;

			if ((protection & pageGuard) != 0)
			{
				return false;
			}

			uint access = protection & accessMask;
			if (access == pageNoAccess)
			{
				return false;
			}

			return accessType switch
			{
				0 => access is pageReadOnly or pageReadWrite or pageWriteCopy or pageExecuteRead or pageExecuteReadWrite or pageExecuteWriteCopy,
				1 => access is pageReadWrite or pageWriteCopy or pageExecuteReadWrite or pageExecuteWriteCopy,
				8 => access is pageExecute or pageExecuteRead or pageExecuteReadWrite or pageExecuteWriteCopy,
				_ => false
			};
		}
	}

	private static bool ShouldTraceLazyCommit(int traceIndex)
	{
		if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_LAZY_COMMIT"), "1", StringComparison.Ordinal))
		{
			return true;
		}

		return traceIndex <= 16 || traceIndex % 256 == 0;
	}

	private static uint ResolveLazyCommitProtection(ulong accessType, uint allocationProtect)
	{
		if (accessType == 8 || (allocationProtect & 0xF0) != 0)
		{
			return 64u;
		}
		return 4u;
	}
}
