private bool InitKeysOffsets()
        {
            CryptoPatches.Clear();

            // "N3TableBase - Can't open file(read) File Handle..."
            var  pattern = Pattern.Transform("4E 33 54 61 62 6C 65 42 61 73 65 20 2D 20 43"); // "N3TableBase - C"
            long offsetLogWrite;

            if (!Pattern.Find(_data, pattern, out offsetLogWrite))
            {
                Console.WriteLine("[TargetPE::InitKeysOffsets]: Unable to find offset.");
                return(false);
            }

            // Generate pattern for finding all references to that string
            string logWriteVA         = Calculator.OffsetToVA((ulong)offsetLogWrite).ToString("X8");
            string logWriteGenPattern = "68"; // push

            for (int i = logWriteVA.Length - 1; i >= 0; --i)
            {
                logWriteGenPattern += i % 2 == 0 ? " " + logWriteVA.Substring(i, 2) : "";
            }
            logWriteGenPattern += " E8"; // call

            Console.WriteLine($"[TargetPE::InitKeysOffsets]: Generated pattern result: {logWriteGenPattern}");
            var         patternStrRefRegion = Pattern.Transform(logWriteGenPattern);
            List <long> regionStrRefOffsets;

            if (!Pattern.FindAll(_data, patternStrRefRegion, out regionStrRefOffsets))
            {
                Console.WriteLine("[TargetPE::InitKeysOffsets]: No offsets were found.");
                return(false);
            }

            Console.WriteLine($"[TargetPE::InitKeysOffsets]: Found {regionStrRefOffsets.Count} region offsets.");

            var patDecryptRegions = new Pattern.Byte[][] {
                // push ?? <- DWORD as short
                // push ?? <- DWORD as short
                // push ?? <- DWORD as short
                Pattern.Transform("68 ?? ?? 00 00 68 ?? ?? 00 00 68 ?? ?? 00 00 ?? ?? E8"),

                //Pattern.Transform("FF ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 55 FF"),
                //Pattern.Transform("FF ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 56 FF"),
                //Pattern.Transform("FF ?? ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 57 FF"),

                // call ?? <- ReadFile
                // push ebp|esi|edi
                // call ?? <- CloseHandle
                // xor reg,reg
                Pattern.Transform("FF ?? ?? ?? ?? ?? 55 FF"),
                Pattern.Transform("FF ?? ?? ?? ?? ?? 56 FF"),
                Pattern.Transform("FF ?? ?? ?? ?? ?? 57 FF"),
            };

            const int ENCRYPT_REGION_MIN        = 50;
            const int ENCRYPT_REGION_MAX        = 700;
            const int ESTIMATED_DES_RANGE_BYTES = 280;

            bool detectedDesEncryption = false;

            foreach (long regionStrRefOffset in regionStrRefOffsets)
            {
                long decryptRegionOffset = -1;
                bool isInlinedFunction   = false;
                for (int i = 0; i < patDecryptRegions.Length; i++)
                {
                    if (!Pattern.Find(_data, patDecryptRegions[i], out decryptRegionOffset,
                                      regionStrRefOffset + ENCRYPT_REGION_MIN, regionStrRefOffset + ENCRYPT_REGION_MAX))
                    {
                        continue;
                    }

                    // Estimate average distance by bytes, to know whether there is more code for DES encryption before XOR.
                    if (!detectedDesEncryption && (decryptRegionOffset - regionStrRefOffset) > ESTIMATED_DES_RANGE_BYTES)
                    {
                        detectedDesEncryption = true;
                    }

                    // First pattern: we know it's a function that got inlined by the compiler.
                    // +5 for ptr & +2 for xor reg,reg
                    isInlinedFunction = i != 0;
                    if (isInlinedFunction)
                    {
                        CanUpdateKey2        = false;
                        decryptRegionOffset += patDecryptRegions[i].Length + 5 + 2;
                    }

                    break;
                }

                if (decryptRegionOffset == -1)
                {
                    Console.WriteLine($"[TargetPE::InitKeysOffsets]: Unable to find decryption region offset.");
                    continue;
                }

                CryptoKey[] keys = new CryptoKey[CryptoPatch.KEYS_COUNT];

                MemoryStream ms = (MemoryStream)PE.GetStream();
                ms.Seek(decryptRegionOffset, SeekOrigin.Begin);

                if (isInlinedFunction)
                {
                    if (!InitInlinedKeys(keys, ms))
                    {
                        return(false);
                    }
                }
                else
                {
                    for (int i = CryptoPatch.KEYS_COUNT - 1; i >= 0; i--)
                    {
                        DWORD dword;
                        ms.Position++;
                        dword   = ms.ReadStructure <DWORD>();
                        keys[i] = new CryptoKey((ms.Position - 4), (ushort)dword.sValue1,
                                                (long)Calculator.OffsetToVA((ulong)(ms.Position - 4)));
                    }
                }
                long keyVA = (long)Calculator.OffsetToVA((ulong)decryptRegionOffset);
                var  patch = new CryptoPatch(decryptRegionOffset, keyVA, keys, isInlinedFunction);
                CryptoPatches.Add(patch);
            }

            // Update the container cryption type, so we know what we deal with
            CryptoPatches.CryptoType = detectedDesEncryption ? CryptoType.DES_XOR : CryptoType.XOR;

            if (CryptoPatches.Count != regionStrRefOffsets.Count)
            {
                return(false);
            }

            if (!VerifyAllMatchedKeys(CryptoPatches))
            {
                return(false);
            }

            return(true);
        }
        private bool InitClientVersion()
        {
            const int MAX_STEPS_FORWARD  = 4;
            const int MIN_CLIENT_VERSION = 1000;
            const int MAX_CLIENT_VERSION = 5000;

            var  pattern = Pattern.Transform("68 C3 12 00 00");
            long offset;

            if (!Pattern.Find(_data, pattern, out offset))
            {
                Console.WriteLine("[TargetPE::InitClientVersion]: Failed to find resource offset.");
                return(false);
            }

            MemoryStream ms = (MemoryStream)PE.GetStream();

            ms.Seek(offset, SeekOrigin.Begin);
            byte[] buff = new byte[4];
            ms.Read(buff, 0, 4, 1);
            int resourceId = BitConverter.ToInt32(buff, 0);

            if (resourceId != 4803)
            {
                Console.WriteLine($"[TargetPE::InitClientVersion]: Invalid resource id: {resourceId}." +
                                  $"Are we at the right location? :)");
                return(false);
            }

            // https://c9x.me/x86/html/file_module_x86_id_35.html
            byte[] cmps = { 0x81, 0x3D };

            long originOffset = ms.Position;
            long stepsBack    = 72;
            long startOffset  = originOffset - stepsBack;

            foreach (byte cmp in cmps)
            {
                ms.Position = startOffset;

                // while it's not a compare instruction, keep reading byte bites ;)
                do
                {
                    ms.Read(buff, 0, 1);
                    if (ms.Position > originOffset)
                    {
                        return(false);
                    }
                } while (buff[0] != cmp);

                for (int i = 0; i < MAX_STEPS_FORWARD; i++)
                {
                    ms.Read(buff, 0, 4);
                    int version = BitConverter.ToInt32(buff, 0);
                    if (version > MIN_CLIENT_VERSION && version < MAX_CLIENT_VERSION)
                    {
                        ClientVersion = version;
                        Console.WriteLine("[TargetPE::InitClientVersion]: Found client version v" + ClientVersion);
                        return(true);
                    }
                    ms.Position -= 3;
                }
            }

            return(false);
        }