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")); }