/// <summary>
    /// Reads the MODULEINFO struct of a process.
    /// </summary>
    /// <param name="process">The process to read.</param>
    /// <returns>Returns a 32 bit representation of the MODULEINFO struct.</returns>
    public static PInvokeStuff.MODULEINFO GetKernel32ModuleInfo(Process process)
    {
        // GetModuleHandle returns NULL when failed
        IntPtr moduleHandle = PInvokeStuff.GetModuleHandle("kernel32.dll");

        if (moduleHandle == null)
        {
            throw new Exceptions.PInvokeException(nameof(GetKernel32ModuleInfo), nameof(PInvokeStuff.GetModuleHandle), Marshal.GetLastWin32Error());
        }

        PInvokeStuff.MODULEINFO mi = new PInvokeStuff.MODULEINFO();

        // GetModuleInformation returns 0 when failed
        var result = PInvokeStuff.GetModuleInformation(process.Handle, moduleHandle, out mi, (uint)Marshal.SizeOf(mi));

        if (!result)
        {
            throw new Exceptions.PInvokeException(nameof(GetKernel32ModuleInfo), nameof(PInvokeStuff.GetModuleInformation), Marshal.GetLastWin32Error());
        }

        return(mi);
    }
    /// <summary>
    /// Returns the address of the ThreadStack0 special symbol.
    /// </summary>
    /// <param name="process">The target process</param>
    /// <returns>The memory address of ThreadStack0</returns>
    public static uint GetThreadStack0(Process process)
    {
        if (!Is32BitProcess(process.Handle))
        {
            throw new ArgumentException("Provided process is not 32 bit.");
        }

        const uint BytesToSample = 4096;                              // Arbitrary (?). 4096 should be enough for both x86 and x64; should be divisible by 4 (x86)

        PInvokeStuff.MODULEINFO mi  = GetKernel32ModuleInfo(process); // MODULEINFO delivers the Kernel32 module's load address and size (note that load address (lpBaseOfDll) is the same as the module handle)
        PInvokeStuff.NT_TIB     tib = GetTIB(process);                // NT_TIB delivers the stack base address of the process' main thread

        // Read sample byte array from the base of the main thread stack
        byte[] StackBaseSample = ReadToByteArray(process.Handle, (tib.StackBase - BytesToSample), BytesToSample);

        int i = 0; // Keep scope bigger than loop

        // ThreadStack0 is the first pointer in the main thread's stack that points inside the Kernel32 module.
        // To find it, we iterate through each 32 bit (4 byte) value in the stack sample, and check if it is in the target range.
        for (i = (StackBaseSample.Length / 4) - 1; i >= 0; --i)
        {
            UInt32 valueAtPosition = BitConverter.ToUInt32(StackBaseSample, i * 4);
            if (valueAtPosition >= (uint)mi.lpBaseOfDll &&
                valueAtPosition <= (uint)mi.lpBaseOfDll + mi.SizeOfImage)
            {
                break;
            }
        }

        if (i == 0) // If i reached zero, then iteration finished without finding a match
        {
            throw new Exceptions.LocalException(nameof(GetThreadStack0), "ThreadStack0 can't be found in the sampled " + BytesToSample + " bytes");
        }

        // Finally, calculate and return the actual ThreadStack0 address from index i
        return((uint)(tib.StackBase - BytesToSample + i * 4));
    }