/// <summary>
        /// Allows you to insert a set of your own assembly instructions in the middle of a game function and have the game conditionally redirect
        /// to said function.
        ///
        /// The hook requires a minimum of 6 (X86) or 7 (X64) bytes and will therefore overwrite as many instructions as it needs until it can get enough space
        /// to place the hook. If you have ever used Cheat ENgine's assembly injection, you're already familliar on how to work with this class.
        /// </summary>
        /// <param name="hookAddress">
        ///     The memory address for which the hook is to be generated.
        /// </param>
        /// <param name="mnemonics">
        ///     X86-32 or X64 FASM compatible assembly code that starts with the line use16, use32 or use64 for identifying the architecture.
        /// </param>
        /// <param name="originalInstructionOptions">
        ///     Defines a switch for the assembly hook builder telling it what should be done with the original set of bytes that are going to be
        ///     replaced with the jump opcode.
        /// </param>
        /// <param name="hookLength">[Optional] The explicit amount of bytes to overwrite when inserting the actual hook.</param>
        public AssemblyHook(IntPtr hookAddress, string[] mnemonics, OriginalInstructionOptions originalInstructionOptions, int hookLength = -1)
        {
            // Set hook address.
            HookAddress = hookAddress;

            // Our 32bit and 64bit code paths will differ once we get the disassembler rolling.
            bool is64bit = mnemonics[0] == "use64";

            // Get the default for architecture, then true hook length.
            if (hookLength == -1)
            {
                hookLength = GetDefaultHookLength(is64bit);

                ArchitectureMode disassemblerMode = is64bit ? ArchitectureMode.x86_64 : ArchitectureMode.x86_32;
                hookLength = HookCommon.GetHookLength(HookAddress, hookLength, disassemblerMode);
            }

            // Assemble our custom ASM hook function to be jumped to and executed by the game, then write it to memory.
            IntPtr returnAddress = HookAddress + hookLength;

            byte[] assembledBytes = Assembler.Assembler.Assemble(mnemonics);
            assembledBytes = ProcessCustomInstructions(assembledBytes, OriginalBytes, originalInstructionOptions);
            assembledBytes = AppendJumpBack(assembledBytes, is64bit, returnAddress);
            AsmHookAddress = MemoryBufferManager.Add(assembledBytes);

            // Backup our bytes to hook.
            OriginalBytes = Bindings.TargetProcess.ReadMemory(HookAddress, hookLength);

            // Setup our new bytes to overwrite the old ones with (absolute jump).
            NewBytes = new byte[hookLength];
            Populate(NewBytes, (byte)0x90);                          // NOP
            byte[] hookJump = AssembleJump(is64bit, AsmHookAddress); // Assemble absolute jump and copy to array of NOPs.
            Array.Copy(hookJump, NewBytes, hookJump.Length);
        }
        /// <summary>
        /// Prepends, appends or leaves a set of original instruction bytes before or after our own custom assembled bytes.
        /// </summary>
        /// <param name="assembledBytes">Our own custom assembled bytes to which to append a jump back address.</param>
        /// <param name="originalInstructionBytes">The original instructions to which append or prepend.</param>
        /// <param name="originalInstructionOptions">Lets us know what to do with the said original instruction bytes.</param>
        /// <returns></returns>
        private byte[] ProcessCustomInstructions(byte[] assembledBytes, byte[] originalInstructionBytes, OriginalInstructionOptions originalInstructionOptions)
        {
            // Create a list of bytes and depending on the options, populate it.
            List <byte> newBytes = new List <byte>(assembledBytes.Length + originalInstructionBytes.Length);

            // Append bytes in the required order to the list.
            switch (originalInstructionOptions)
            {
            case OriginalInstructionOptions.DoNotInclude:
                newBytes.AddRange(assembledBytes);
                break;

            case OriginalInstructionOptions.ExecuteFirst:
                newBytes.AddRange(originalInstructionBytes);
                newBytes.AddRange(assembledBytes);
                break;

            case OriginalInstructionOptions.ExecuteLast:
                newBytes.AddRange(assembledBytes);
                newBytes.AddRange(originalInstructionBytes);
                break;
            }

            return(newBytes.ToArray());
        }