private static SortedDictionary <string, string> ParseDecTitleKeysBin()
        {
            var ticketsDictionary = new SortedDictionary <string, string>();

            using (var reader = new BinaryReader(new FileStream(Files.DecryptedTitleKeysPath, FileMode.Open, FileAccess.Read)))
            {
                var numberOfKeys = new FileInfo(Files.DecryptedTitleKeysPath).Length / 32;

                reader.ReadBytes(16);                 // seek in

                for (var entry = 0; entry < numberOfKeys; entry++)
                {
                    reader.ReadBytes(8);                     // skip 8 bytes

                    var titleId  = BitConversion.BytesToHex(reader.ReadBytes(8));
                    var titleKey = BitConversion.BytesToHex(reader.ReadBytes(16));

                    ticketsDictionary[titleId] = titleKey;
                }
            }

            return(ticketsDictionary);
        }
        public static void Main(string[] args)
        {
            ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
            Console.OutputEncoding = Encoding.UTF8;
#if !DEBUG
            try
            {
#endif
            PrintProgramVersion();


            ProcessArgs(args);

            if (!File.Exists(Files.DecTitleKeysPath))
            {
                ConsoleUtils.PrintColorfulLine(
                    ConsoleColor.Red,
                    "decTitleKeys.bin not found! Get it from Decrypt9. Press any key to exit.");
                Console.ReadKey();
                Environment.Exit(1);
            }

            var decTitleKeysFile = File.ReadAllBytes(Files.DecTitleKeysPath);
            var computedHash     = BitConversion.BytesToHex(MD5.Create().ComputeHash(decTitleKeysFile));

            var shouldRecheck = false;

            if (!File.Exists(Files.DecTitleKeysMd5))
            {
                File.WriteAllText(Files.DecTitleKeysMd5, computedHash);
            }
            else
            {
                var savedHash = File.ReadAllText(Files.DecTitleKeysMd5);

                if (savedHash != computedHash)
                {
                    Console.WriteLine("Different decTitleKeys.bin found!");
                    Console.Write("Recheck titles? y/n: ");

                    var keyChosen = Console.ReadKey().Key;
                    if (keyChosen == ConsoleKey.Y)
                    {
                        shouldRecheck = true;
                        File.WriteAllText(Files.DecTitleKeysMd5, computedHash);
                    }
                }
            }

            if (args != null && args.Contains("-update"))
            {
                UpdateDependencies();
            }
            else
            {
                Files.CheckFor3dsDb();
                Files.CheckForGroovyCiaDb();
            }

            var tickets = new Dictionary <string, string>();

            if ((!File.Exists(Files.ValidTicketsPath) || new FileInfo(Files.ValidTicketsPath).Length == 0) || shouldRecheck)
            {
                Console.WriteLine("Decoding valid tickets...");
                tickets = DecodeTickets();
                var ticketsAsStrings = tickets.Select(ticket => ticket.Key + " " + ticket.Value).ToList();

                File.WriteAllText(Files.ValidTicketsPath, string.Join(Environment.NewLine, ticketsAsStrings));
                Console.WriteLine("Wrote valid tickets to ValidTickets.txt");
            }

            var parsedTickets = ParseTickets(tickets).ToArray();

            var titlesFound = ParseTicketsFromGroovyCiaDb(parsedTickets, Files.GroovyCiaPath);
            titlesFound = ParseTicketsFrom3DsDb(titlesFound);

            var longestTitleLength     = titlesFound.Max(a => a.Name.Length) + 2;
            var longestPublisherLength = titlesFound.Max(a => a.Publisher.Length) + 2;

            PrintNumberOfTicketsFound(titlesFound, parsedTickets);

            Console.WriteLine(PrintTitleLegend(longestTitleLength, longestPublisherLength));

            // titlesFound = titlesFound.OrderBy(r => Nintendo3DSRelease.GetTitleType(r.TitleId)).ThenBy(r => r.Name).ToList();
            foreach (var title in titlesFound)
            {
                var fullWidthExtraPadName      = GetFullWidthExtraPad(title.Name);
                var fullWidthExtraPadPublisher = GetFullWidthExtraPad(title.Publisher);

                Console.WriteLine(
                    $"{title.TitleId} {title.DecTitleKey} | {title.Name.PadRight(longestTitleLength - fullWidthExtraPadName)}{title.Publisher.PadRight(longestPublisherLength - fullWidthExtraPadPublisher)}{title.Region}");
            }

            Console.WriteLine(
                "\r\nTitles which 3dsdb or the GroovyCIA db couldn't find but we'll look up from the Nintendo CDN:");

            Console.WriteLine(PrintTitleLegend(longestTitleLength, longestPublisherLength));

            var remainingTitles = parsedTickets.Except(titlesFound).ToList();
            remainingTitles =
                LookUpRemainingTitles(remainingTitles, longestTitleLength, longestPublisherLength)
                .OrderBy(r => Nintendo3DSRelease.GetTitleType(r.TitleId))
                .ThenBy(r => r.Name)
                .ToList();

            WriteOutputToFile(longestTitleLength, longestPublisherLength, titlesFound, remainingTitles);
            WriteOutputToCsv(titlesFound, remainingTitles);

            Console.Write("Done! Tickets and titles exported to ");
            ConsoleUtils.PrintColorfulLine(ConsoleColor.Green, Files.OutputFile);

            Console.Write("Detailed info exported to ");
            ConsoleUtils.PrintColorfulLine(ConsoleColor.Green, Files.DetailedOutputFile);

            // #if !DEBUG
            Console.Write("Press any key to exit...");
            Console.ReadKey();
            // #endif
#if !DEBUG
        }

        catch (Exception ex)
        {
            ConsoleUtils.PrintColorfulLine(ConsoleColor.Red, "Fatal Error: " + ex.Message);

            Console.WriteLine(ex.StackTrace);
        }
#endif
        }
        /// <summary>
        /// offsets and other things from PlaiCDN
        /// </summary>
        public static bool TitleKeyIsValid(string titleId, string decTitleKey)
        {
            byte[] tmd;

            var cdnUrl = "http://nus.cdn.c.shop.nintendowifi.net/ccs/download/" + titleId;

            using (var client = new WebClient())
            {
                try
                {
                    tmd = client.DownloadData(cdnUrl + "/tmd");
                }
                catch (WebException)
                {
                    // likely a forbidden title (you can't download some system titles' TMD)
                    return(false);
                }
            }

            if (BitConverter.ToString(tmd.Take(4).ToArray()) != "00-01-00-04")
            {
                return(false);
            }

            // const int ContentOffset = 0xB04;
            // var contentId = BitConversion.BytesToHex(tmd.Skip(ContentOffset).Take(4));

            // const int TikOffset = 0x140;
            // var contentCount = Convert.ToInt32(BitConversion.BytesToHex(tmd.Skip(TikOffset + 0x9E).Take(2)), 16);
            var cOffs     = 0xB04 + 0x30;
            var contentId = BitConversion.BytesToHex(tmd.Skip(cOffs).Take(4));

            byte[] result;

            using (var client = new WebClient())
            {
                try
                {
                    using (var stream = client.OpenRead($"{cdnUrl}/{contentId}"))
                    {
                        result = new byte[272];

                        var bytesRead = 0;
                        while (bytesRead < 272)
                        {
                            result[bytesRead++] = (byte)stream.ReadByte();
                        }
                    }
                }
                catch (WebException)
                {
                    return(false);
                }
            }

            var titleKeyBytes = BitConversion.HexToBytes(decTitleKey);

            var decrypted = AesDecryptTmd(titleKeyBytes, result);

            return(decrypted.Contains("NCCH"));
        }