示例#1
0
        public static async Task DownloadTitle(HttpClient client, string downloadDir, string titleId, params int[] metadataVersions)
        {
            string titleDir = Path.Combine(downloadDir, titleId);

            Directory.CreateDirectory(titleDir);

            // Download Common ETicKet (CETK)
            await DownloadTitleFile(client, titleDir, titleId, TicketFileName);

            // Download Title MetaData (TMD)
            string[] metadataNames = DownloadSuffixlessMetadata ?
                                     metadataVersions.Select(v => $"{MetadataFileName}.{v}").Union(new[] { MetadataFileName }).ToArray() :
                                     metadataVersions.Select(v => $"{MetadataFileName}.{v}").ToArray();
            foreach (string metadataName in metadataNames)
            {
                string metadataDir = Path.Combine(titleDir, metadataName);
                Directory.CreateDirectory(metadataDir);

                string metadataPath = await DownloadTitleFile(client, metadataDir, titleId, metadataName);

                if (metadataPath == null)
                {
                    continue;
                }

                // Parse out the content IDs from the metadata file, and download them
                TitleMetadata metadata = new TitleMetadata(metadataPath);
                for (int i = 0; i < metadata.NumContents; i++)
                {
                    await DownloadTitleFile(client, metadataDir, titleId, metadata.ContentInfo[i].Id.ToString("X8"));
                }
            }
        }
示例#2
0
        public static bool VerifyMetadataContent(TitleMetadata metadata, string decPath, int contentIndex, string titleDir, string contentIdStr)
        {
            byte[] decHash = Hasher.CalcSha1Hash(decPath);
            if (!metadata.ContentInfo[contentIndex].DecSha1Hash.SequenceEqual(decHash))
            {
                Log.Instance.Error(
                    $"Hash for '{decPath}' does not match the recorded hash in '{Path.Combine(titleDir, metadata.FileName)}' (content index {metadata.ContentInfo[contentIndex].Index}). " +
                    $"(`{metadata.ContentInfo[contentIndex].DecSha1Hash.ToHexString()}` != `{decHash.ToHexString()}`)");
                return(false);
            }

            Log.Instance.Trace(
                $"Hash for '{decPath}' matches the recorded hash in '{Path.Combine(titleDir, metadata.FileName)}' (content index {metadata.ContentInfo[contentIndex].Index}). " +
                $"(`{metadata.ContentInfo[contentIndex].DecSha1Hash.ToHexString()}`)");
            return(true);
        }
示例#3
0
        public static async Task <(TicketBooth.Ticket ticket, List <string> contentsList)> MakeTicketAndDecryptMetadataContents(byte[] titleIdBytes, TitleMetadata metadata, string titleDir, bool makeQolFiles = false)
        {
            if (metadata.NumContents <= 0)
            {
                return(null, new List <string>());
            }

            List <string> contentsList = new List <string>();

            TicketBooth.Ticket ticket = null;
            bool   success            = false;
            string contentPath        = Path.Combine(titleDir, metadata.ContentInfo[0].Id.ToString("x8"));
            string appPath            = "";

            // TODO: Not needed for any known DSiWare titles, but could be a decent idea to test the gamecode of the game as a password
            foreach (string tryPass in KeyGenTryPasswords)
            {
                byte[] titleKey = TitleKeyGen.Derive(titleIdBytes, tryPass);
                ticket  = new TicketBooth.Ticket(titleKey);
                appPath = await ticket.DecryptContent(metadata.ContentInfo[0].Index, contentPath);

                RomInfo cInfo = new RomInfo(appPath, makeQolFiles);
                success = cInfo.ValidContent;
                if (!success)
                {
                    continue;
                }
                Log.Instance.Trace($"'{tryPass}' is the password for the title key of '{contentPath}'!");

                // Somewhat redundant given the CRC validation of the decrypted ROM in RomInfo, but still good to check
                if (VerifyMetadataContent(metadata, appPath, 0, titleDir, metadata.ContentInfo[0].Id.ToString("x8")))
                {
                    await File.WriteAllTextAsync(Path.Combine(titleDir, TitlePasswordFileName), tryPass);

                    await File.WriteAllBytesAsync(Path.Combine(titleDir, TitleKeyFileName), titleKey);
                }

                contentsList.Add(metadata.ContentInfo[0].Id.ToString("x8"));
                if (makeQolFiles)
                {
                    MakeQolFiles(cInfo, titleDir);
                }
                break;
            }

            if (!success)
            {
                Log.Instance.Error($"Unable to find the password for the title key of '{contentPath}'.");
                File.Delete(appPath);
                return(null, new List <string>());
            }

            for (int i = 1; i < metadata.NumContents; i++)
            {
                string contentName = metadata.ContentInfo[i].Id.ToString("x8");
                contentsList.Add(contentName);
                contentPath = Path.Combine(titleDir, contentName);
                string decPath = await ticket.DecryptContent(metadata.ContentInfo[i].Index, contentPath);

                VerifyMetadataContent(metadata, decPath, i, titleDir, contentName);
            }

            return(ticket, contentsList);
        }
示例#4
0
        public static async Task <List <string> > DecryptMetadataContents(byte[] titleIdBytes, TicketBooth.Ticket ticket, TitleMetadata metadata, string titleDir, bool makeQolFiles = false)
        {
            if (metadata.NumContents <= 0)
            {
                return(new List <string>());
            }

            List <string> contentsList = new List <string>();

            for (int i = 0; i < metadata.NumContents; i++)
            {
                string contentName = metadata.ContentInfo[i].Id.ToString("x8");
                contentsList.Add(contentName);
                string contentPath = Path.Combine(titleDir, contentName);
                string appPath     = await ticket.DecryptContent(metadata.ContentInfo[i].Index, contentPath);

                // TODO: Could verify enc and dec file sizes match metadata recorded size, but at this point I don't think it's worth the additional processing time.

                if (VerifyMetadataContent(metadata, appPath, i, titleDir, contentName))
                {
                    string titlePass = DeriveTitlePassFromKey(titleIdBytes, ticket.TitleKey);
                    if (titlePass != null)
                    {
                        await File.WriteAllTextAsync(Path.Combine(titleDir, TitlePasswordFileName), titlePass);
                    }
                    else
                    {
                        Log.Instance.Error($"Unable to find the title pass for title '{titleDir}'.");
                    }
                    await File.WriteAllBytesAsync(Path.Combine(titleDir, TitleKeyFileName), ticket.TitleKey);
                }

                if (i != 0 || !makeQolFiles)
                {
                    continue;
                }
                RomInfo cInfo = new RomInfo(appPath, makeQolFiles);
                MakeQolFiles(cInfo, titleDir);
            }

            return(contentsList);
        }