namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter21.Listing21_20
{
    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    public class Program
    {
        public unsafe delegate void MethodInvoker(byte* buffer);

        public unsafe static int Main()
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                unsafe
                {
                    byte[] codeBytes = new byte[] {
                    0x49, 0x89, 0xd8,       // mov    %rbx,%r8
                    0x49, 0x89, 0xc9,       // mov    %rcx,%r9
                    0x48, 0x31, 0xc0,       // xor    %rax,%rax
                    0x0f, 0xa2,             // cpuid
                    0x4c, 0x89, 0xc8,       // mov    %r9,%rax
                    0x89, 0x18,             // mov    %ebx,0x0(%rax)
                    0x89, 0x50, 0x04,       // mov    %edx,0x4(%rax)
                    0x89, 0x48, 0x08,       // mov    %ecx,0x8(%rax)
                    0x4c, 0x89, 0xc3,       // mov    %r8,%rbx
                    0xc3                    // retq
            };

                    byte[] buffer = new byte[12];

                    using (VirtualMemoryPtr codeBytesPtr =
                        new VirtualMemoryPtr(codeBytes.Length))
                    {
                        Marshal.Copy(
                            codeBytes, 0,
                            codeBytesPtr, codeBytes.Length);

                        MethodInvoker method = Marshal.GetDelegateForFunctionPointer<MethodInvoker>(codeBytesPtr);
                        fixed (byte* newBuffer = &buffer[0])
                        {
                            method(newBuffer);
                        }
                    }
                    Console.Write("Identyfikator procesora: ");
                    Console.WriteLine(ASCIIEncoding.ASCII.GetChars(buffer));
                } // Niezabezpieczony kod.
            }
            else
            {
                Console.WriteLine("Ten przykład jest poprawny tylko w systemie Windows");
            }
            return 0;
        }
    }

    public class VirtualMemoryPtr : System.Runtime.InteropServices.SafeHandle
    {
        public VirtualMemoryPtr(int memorySize) :
            base(IntPtr.Zero, true)
        {
            ProcessHandle =
                VirtualMemoryManager.GetCurrentProcessHandle();
            MemorySize = (IntPtr)memorySize;
            AllocatedPointer =
                VirtualMemoryManager.AllocExecutionBlock(
                memorySize, ProcessHandle);
            Disposed = false;
        }

        public readonly IntPtr AllocatedPointer;
        readonly IntPtr ProcessHandle;
        readonly IntPtr MemorySize;
        bool Disposed;

        public static implicit operator IntPtr(
            VirtualMemoryPtr virtualMemoryPointer)
        {
            return virtualMemoryPointer.AllocatedPointer;
        }

        // Abstrakcyjna składowa z typu SafeHandle.
        public override bool IsInvalid
        {
            get
            {
                return Disposed;
            }
        }

        // Abstrakcyjna składowa z typu SafeHandle.
        protected override bool ReleaseHandle()
        {
            if (!Disposed)
            {
                Disposed = true;
                GC.SuppressFinalize(this);
                VirtualMemoryManager.VirtualFreeEx(ProcessHandle,
                    AllocatedPointer, MemorySize);
            }
            return true;
        }
    }

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

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool VirtualProtectEx(
            IntPtr hProcess, IntPtr lpAddress,
            IntPtr dwSize, uint flNewProtect,
            ref uint lpflOldProtect);

        [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")]
        internal static extern IntPtr GetCurrentProcessHandle();

        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool VirtualFreeEx(
            IntPtr hProcess, IntPtr lpAddress,
            IntPtr dwSize, IntPtr dwFreeType);

        public static bool VirtualFreeEx(
            IntPtr hProcess, IntPtr lpAddress,
            IntPtr dwSize)
        {
            bool result = VirtualFreeEx(
                hProcess, lpAddress, dwSize,
                (IntPtr)MemoryFreeType.Decommit);
            if (!result)
            {
                throw new System.ComponentModel.Win32Exception();
            }
            return result;
        }

        public static bool VirtualFreeEx(
            IntPtr lpAddress, IntPtr dwSize)
        {
            return VirtualFreeEx(
                GetCurrentProcessHandle(), lpAddress, dwSize);
        }



        /// <summary>
        /// Typ alokacji pamięci. Parameter musi
        /// zawierać jedną z podanych wartości.
        /// </summary>
        [Flags]
        private enum AllocationType : uint
        {
            /// <summary>
            /// Alokuje fizycznie obszar na określone zarezerwowane 
            /// strony w pamięci lub w pliku stronicowania na dysku.
            /// Inicjuje tę pamięć wartością zero. 
            /// </summary>

            Commit = 0x1000,
            /// <summary>
            /// Rezerwuje zakres przestrzeni adresów wirtualnych 
            /// procesu bez alokowania fizycznego obszaru w
            /// pamięci lub w pliku stronicowania na dysku. 
            /// </summary>
            Reserve = 0x2000,
            /// <summary>
            /// Informuje, że dane z obszaru pamięci określanego przez
            /// parametry lpAddress i dwSize nie są już istotne. Tych 
            /// stron nie należy wczytywać ani zapisywać w pliku 
            /// stronicowania. Jednak dany blok pamięci może być 
            /// jeszcze używany, dlatego nie należy go zwalniać. Tej
            /// wartości nie można łączyć z innymi.
            /// </summary>
            Reset = 0x80000,
            /// <summary>
            /// Alokuje fizyczną pamięć z dostępem do odczytu i do zapisu. 
            /// Ta wartość jest dostępna tylko dla pamięci 
            /// AWE (ang. Address Windowing Extensions).
            /// </summary>
            Physical = 0x400000,
            /// <summary>
            /// Alokuje pamięć o najwyższym możliwym adresie. 
            /// </summary>
            TopDown = 0x100000,
        }

        /// <summary>
        /// Określa poziom ochrony alokowanego obszaru pamięci ze stronami.
        /// </summary>
        [Flags]
        private enum ProtectionOptions : uint
        {
            /// <summary>
            /// Umożliwia wykonywanie zajętego obszaru ze stronami. 
            /// Próba odczytu lub zapisu w tym obszarze
            /// spowoduje błąd naruszenia poziomu dostępu.
            /// </summary>
            Execute = 0x10,
            /// <summary>
            /// Zapewnia możliwość wykonywania i odczytu zajętego
            /// obszaru ze stronami. Próba zapisu w tym
            /// obszarze spowoduje błąd naruszenia poziomu dostępu.
            /// </summary>
            PageExecuteRead = 0x20,
            /// <summary>
            /// Umożliwia wykonywanie, odczyt i zapis w
            /// zajętym obszarze ze stronami.
            /// </summary>
            PageExecuteReadWrite = 0x40,
            // ...
        }

        /// <summary>
        /// Typ operacji zwalniającej pamięć.
        /// </summary>
        [Flags]
        private enum MemoryFreeType : uint
        {
            /// <summary>
            /// Zwalnia określony zajęty obszar stron. 
            /// Po tej operacji strony są uznawane za zarezerwowane (stan Reserved). 
            /// </summary>
            Decommit = 0x4000,
            /// <summary>
            /// Zwalnia określony obszar ze stronami. Po wykonaniu 
            /// tej operacji strony są uznawane za zwolnione (stan Free). 
            /// </summary>
            Release = 0x8000
        }

        public static IntPtr AllocExecutionBlock(
           int size, IntPtr hProcess)
        {
            IntPtr codeBytesPtr;
            codeBytesPtr = VirtualAllocEx(
                hProcess, IntPtr.Zero,
                (IntPtr)size,
                AllocationType.Reserve | AllocationType.Commit,
                (uint)ProtectionOptions.PageExecuteReadWrite);

            if (codeBytesPtr == IntPtr.Zero)
            {
                throw new System.ComponentModel.Win32Exception();
            }

            uint lpflOldProtect = 0;
            if (!VirtualProtectEx(
                hProcess, codeBytesPtr,
                (IntPtr)size,
                (uint)ProtectionOptions.PageExecuteReadWrite,
                ref lpflOldProtect))
            {
                throw new System.ComponentModel.Win32Exception();
            }
            return codeBytesPtr;
        }

        public static IntPtr AllocExecutionBlock(int size)
        {
            return AllocExecutionBlock(
                size, GetCurrentProcessHandle());
        }
    }
}