public static void Process(string folderPath, Keyset keyset, Output Out) { var cnmtExtended = CnmtNca.GetCnmtExtended(folderPath, keyset, Out); if (cnmtExtended == null) { Out.Log( "[Info] Skiped fragemt trimming as no patch Cnmt was found\r\n"); return; } if (cnmtExtended.FragmentSets.Length == 0) { Out.Log( "[Info] Skiped fragemt trimming as no DeltaApplyInfos in the patch Cnmt were found\r\n"); return; } var DeltaContentID = 0; foreach (var deltaApplyInfo in cnmtExtended.FragmentSets) { long offset = 0; for (var deltaApplyInfoId = 0; deltaApplyInfoId < deltaApplyInfo.FragmentCount; ++deltaApplyInfoId) { var matching = $"{Utils.BytesToString(deltaApplyInfo.NcaIdNew).ToLower()}.nca"; var length = new FileInfo(Path.Combine(folderPath, matching)).Length; var hexLen = Math.Ceiling(Math.Log(length, 16.0)); if (deltaApplyInfo.FragmentCount > 1) { matching += $":{offset.ToString($"X{hexLen}")}:{0.ToString($"X{hexLen}")}"; } var lowerNcaID = Utils.BytesToString(cnmtExtended.DeltaContents[DeltaContentID].NcaId) .ToLower(); var ncaFileName = Path.Combine(folderPath, $"{lowerNcaID}.nca"); if (!File.Exists(ncaFileName)) { Out.Warn($"[WARN] File: {ncaFileName} not found!\r\n"); break; } Out.Log($"{ncaFileName}\r\n"); var ncaStorage = new StreamStorage(new FileStream(ncaFileName, FileMode.Open, FileAccess.Read), false); var DecryptedHeader = new byte[0xC00]; ncaStorage.Read(DecryptedHeader, 0, 0xC00, 0); var Header = new NcaHeader(new BinaryReader(new MemoryStream(DecryptedHeader)), keyset); var fragmentTrimmed = false; for (var sector = 0; sector < 4; ++sector) { var section = NcaParseSection.ParseSection(Header, sector); if (section == null || section.Header.Type != SectionType.Pfs0) { continue; } IStorage sectionStorage = ncaStorage.Slice(section.Offset, section.Size, false); IStorage pfs0Storage = sectionStorage.Slice(section.Header.Sha256Info.DataOffset, section.Header.Sha256Info.DataSize, false); var Pfs0Header = new PartitionFileSystemHeader(new BinaryReader(pfs0Storage.AsStream())); var FileDict = Pfs0Header.Files.ToDictionary(x => x.Name, x => x); var path = PathTools.Normalize("fragment").TrimStart('/'); if (FileDict.TryGetValue(path, out var fragmentFile)) { if (Pfs0Header.NumFiles != 1) { throw new InvalidDataException( "A fragment Pfs0 container should contain exactly 1 file"); } if (fragmentTrimmed) { throw new InvalidDataException( "Multiple fragments in NCA found!"); } IStorage fragmentStorage = pfs0Storage.Slice( Pfs0Header.HeaderSize + fragmentFile.Offset, fragmentFile.Size, false); var buffer = new byte[0xC00]; fragmentStorage.Read(buffer, 0, buffer.Length, 0); var writerPath = Path.Combine(folderPath, $"{lowerNcaID}.tca"); var writer = File.Open(writerPath, FileMode.Create); var offsetBefore = section.Offset + section.Header.Sha256Info.DataOffset + Pfs0Header.HeaderSize + fragmentFile.Offset; var offsetAfter = offsetBefore + fragmentFile.Size; IStorage ncaStorageBeforeFragment = ncaStorage.Slice(0, offsetBefore, false); IStorage ncaStorageAfterFragment = ncaStorage.Slice(offsetAfter, ncaStorage.GetSize() - offsetAfter, false); ncaStorageBeforeFragment.CopyToStream(writer); offset += SaveDeltaHeader.Save(fragmentStorage, writer, matching); ncaStorageAfterFragment.CopyToStream(writer); writer.Position = 0x200; writer.WriteByte(0x54); writer.Dispose(); fragmentTrimmed = true; } } ncaStorage.Dispose(); File.Delete(ncaFileName); ++DeltaContentID; } Out.Log("----------\r\n"); } }
public static void EncryptFunct( Stream Input, Stream Output, string ncaFilename, bool verifyEncrypted, Keyset keyset, Output Out) { var DecryptedKeys = Utils.CreateJaggedArray <byte[][]>(4, 0x10); var HeaderKey1 = new byte[16]; var HeaderKey2 = new byte[16]; Buffer.BlockCopy(keyset.HeaderKey, 0, HeaderKey1, 0, 16); Buffer.BlockCopy(keyset.HeaderKey, 16, HeaderKey2, 0, 16); var DecryptedHeader = new byte[0xC00]; Input.Read(DecryptedHeader, 0, 0xC00); var Header = new NcaHeader(new BinaryReader(new MemoryStream(DecryptedHeader)), keyset); var CryptoType = Math.Max(Header.CryptoType, Header.CryptoType2); if (CryptoType > 0) { CryptoType--; } var HasRightsId = !Header.RightsId.IsEmpty(); if (!HasRightsId) { Out.Log("Key Area (Encrypted):\r\n"); if (keyset.KeyAreaKeys[CryptoType][Header.KaekInd].IsEmpty()) { throw new ArgumentException($"key_area_key_{KakNames[Header.KaekInd]}_{CryptoType:x2}", "Missing area key!"); } Out.Log( $"key_area_key_{KakNames[Header.KaekInd]}_{CryptoType:x2}: {Utils.BytesToString(keyset.KeyAreaKeys[CryptoType][Header.KaekInd])}\r\n"); for (var i = 0; i < 4; ++i) { LibHac.Crypto.DecryptEcb(keyset.KeyAreaKeys[CryptoType][Header.KaekInd], Header.EncryptedKeys[i], DecryptedKeys[i], 0x10); Out.Log($"Key {i} (Encrypted): {Utils.BytesToString(Header.EncryptedKeys[i])}\r\n"); Out.Log($"Key {i} (Decrypted): {Utils.BytesToString(DecryptedKeys[i])}\r\n"); } } else { var titleKey = keyset.TitleKeys[Header.RightsId]; var TitleKeyDec = new byte[0x10]; LibHac.Crypto.DecryptEcb(keyset.TitleKeks[CryptoType], titleKey, TitleKeyDec, 0x10); Out.Log($"titleKey: {Utils.BytesToString(titleKey)}\r\n"); Out.Log($"TitleKeyDec: {Utils.BytesToString(TitleKeyDec)}\r\n"); DecryptedKeys[2] = TitleKeyDec; } var Sections = new NcaSection[4]; var SectionsByOffset = new Dictionary <long, int>(); var lowestOffset = long.MaxValue; for (var i = 0; i < 4; ++i) { var section = NcaParseSection.ParseSection(Header, i); if (section == null) { continue; } SectionsByOffset.Add(section.Offset, i); if (section.Offset < lowestOffset) { lowestOffset = section.Offset; } Sections[i] = section; } Out.Log($"HeaderKey: {Utils.BytesToString(keyset.HeaderKey)}\r\n"); Out.Log("Encrypting and writing header to NCA...\r\n"); SHA256Cng sha256NCA = null; if (verifyEncrypted) { sha256NCA = new SHA256Cng(); sha256NCA.Initialize(); } var encryptedHeader = CryptoInitialisers.AES_XTS(HeaderKey1, HeaderKey2, 0x200, DecryptedHeader, 0); if (Output != null) { Output.Write(encryptedHeader, 0, DecryptedHeader.Length); } if (verifyEncrypted) { sha256NCA.TransformBlock(encryptedHeader, 0, DecryptedHeader.Length, null, 0); } var dummyHeader = new byte[0xC00]; ulong dummyHeaderSector = 6; long dummyHeaderPos; for (dummyHeaderPos = 0xC00; dummyHeaderPos < lowestOffset; dummyHeaderPos += 0xC00) { var dummyHeaderWriteCount = (int)Math.Min(lowestOffset - dummyHeaderPos, DecryptedHeader.Length); Input.Read(dummyHeader, 0, dummyHeaderWriteCount); var dummyHeaderEncrypted = CryptoInitialisers.AES_XTS(HeaderKey1, HeaderKey2, 0x200, dummyHeader, dummyHeaderSector); if (Output != null) { Output.Write(dummyHeaderEncrypted, 0, dummyHeaderWriteCount); } if (verifyEncrypted) { sha256NCA.TransformBlock(dummyHeaderEncrypted, 0, dummyHeaderWriteCount, null, 0); } dummyHeaderSector += 6; } Out.Log("Encrypting and writing sectors to NCA...\r\n"); Out.Log("Sections:\r\n"); foreach (var i in SectionsByOffset.OrderBy(i => i.Key).Select(item => item.Value)) { var sect = Sections[i]; if (sect == null) { continue; } var isExefs = Header.ContentType == ContentType.Program && i == (int)ProgramPartitionType.Code; var PartitionType = isExefs ? "ExeFS" : sect.Type.ToString(); Out.Log($" Section {i}:\r\n"); Out.Log($" Offset: 0x{sect.Offset:x12}\r\n"); Out.Log($" Size: 0x{sect.Size:x12}\r\n"); Out.Log($" Partition Type: {PartitionType}\r\n"); Out.Log($" Section CTR: {Utils.BytesToString(sect.Header.Ctr)}\r\n"); var initialCounter = new byte[0x10]; if (sect.Header.Ctr != null) { Array.Copy(sect.Header.Ctr, initialCounter, 8); } Out.Log($"initialCounter: {Utils.BytesToString(initialCounter)}\r\n"); if (Input.Position != sect.Offset) { //Input.Seek(sect.Offset, SeekOrigin.Begin); //Output.Seek(sect.Offset, SeekOrigin.Begin); //Todo: sha256NCA Gap support throw new NotImplementedException("Gaps between NCA sections aren't implemented yet!"); } const int maxBS = 10485760; //10 MB int bs; var DecryptedSectionBlock = new byte[maxBS]; var sectOffsetEnd = sect.Offset + sect.Size; var AesCtrEncrypter = new Aes128CtrTransform(DecryptedKeys[2], initialCounter); switch (sect.Header.EncryptionType) { case NcaEncryptionType.None: while (Input.Position < sectOffsetEnd) { bs = (int)Math.Min(sectOffsetEnd - Input.Position, maxBS); Out.Print($"Encrypted: {Input.Position / 0x100000} MB\r\n"); Input.Read(DecryptedSectionBlock, 0, bs); if (Output != null) { Output.Write(DecryptedSectionBlock, 0, bs); } if (verifyEncrypted) { sha256NCA.TransformBlock(DecryptedSectionBlock, 0, bs, null, 0); } } break; case NcaEncryptionType.AesCtr: while (Input.Position < sectOffsetEnd) { SetCtrOffset(initialCounter, Input.Position); bs = (int)Math.Min(sectOffsetEnd - Input.Position, maxBS); Out.Print($"Encrypted: {Input.Position / 0x100000} MB\r\n"); Input.Read(DecryptedSectionBlock, 0, bs); AesCtrEncrypter.Counter = initialCounter; AesCtrEncrypter.TransformBlock(DecryptedSectionBlock); if (Output != null) { Output.Write(DecryptedSectionBlock, 0, bs); } if (verifyEncrypted) { sha256NCA.TransformBlock(DecryptedSectionBlock, 0, bs, null, 0); } } break; case NcaEncryptionType.AesCtrEx: var info = sect.Header.BktrInfo; var MyBucketTree = new MyBucketTree <AesSubsectionEntry>( new MemoryStream(sect.Header.BktrInfo.EncryptionHeader.Header), Input, sect.Offset + info.EncryptionHeader.Offset); var SubsectionEntries = MyBucketTree.GetEntryList(); var SubsectionOffsets = SubsectionEntries.Select(x => x.Offset).ToList(); var subsectionEntryCounter = new byte[0x10]; Array.Copy(initialCounter, subsectionEntryCounter, 0x10); foreach (var entry in SubsectionEntries) { do { bs = (int)Math.Min((sect.Offset + entry.OffsetEnd) - Input.Position, maxBS); SetCtrOffset(subsectionEntryCounter, Input.Position); subsectionEntryCounter[7] = (byte)entry.Counter; subsectionEntryCounter[6] = (byte)(entry.Counter >> 8); subsectionEntryCounter[5] = (byte)(entry.Counter >> 16); subsectionEntryCounter[4] = (byte)(entry.Counter >> 24); var DecryptedSectionBlockLUL = new byte[bs]; Out.Print($"Encrypted: {Input.Position / 0x100000} MB\r\n"); Out.Log($"{Input.Position}: {Utils.BytesToString(subsectionEntryCounter)}\r\n"); Input.Read(DecryptedSectionBlockLUL, 0, bs); AesCtrEncrypter.Counter = subsectionEntryCounter; AesCtrEncrypter.TransformBlock(DecryptedSectionBlockLUL); if (Output != null) { Output.Write(DecryptedSectionBlockLUL, 0, bs); } if (verifyEncrypted) { sha256NCA.TransformBlock(DecryptedSectionBlockLUL, 0, bs, null, 0); } } while (Input.Position < entry.OffsetEnd); } while (Input.Position < sectOffsetEnd) { SetCtrOffset(subsectionEntryCounter, Input.Position); bs = (int)Math.Min(sectOffsetEnd - Input.Position, maxBS); Out.Print($"EncryptedAfter: {Input.Position / 0x100000} MB\r\n"); Input.Read(DecryptedSectionBlock, 0, bs); Out.Log($"{Input.Position}: {Utils.BytesToString(subsectionEntryCounter)}\r\n"); AesCtrEncrypter.Counter = subsectionEntryCounter; AesCtrEncrypter.TransformBlock(DecryptedSectionBlock); if (Output != null) { Output.Write(DecryptedSectionBlock, 0, bs); } if (verifyEncrypted) { sha256NCA.TransformBlock(DecryptedSectionBlock, 0, bs, null, 0); } } break; default: throw new NotImplementedException(); } } if (verifyEncrypted) { sha256NCA.TransformFinalBlock(new byte[0], 0, 0); var sha256NCAHashString = Utils.BytesToString(sha256NCA.Hash).ToLower(); if (sha256NCAHashString.StartsWith(ncaFilename.Split('.')[0].ToLower())) { Out.Log($"[VERIFIED] {sha256NCAHashString}\r\n"); } else { throw new Exception($"[INVALID HASH] sha256({ncaFilename}) = {sha256NCAHashString}\r\n"); } } }
public static void Process(string folderPath, IFileSystem newBaseFolderFs, Keyset keyset, Output Out) { var dirDecrypted = new DirectoryInfo(folderPath); foreach (var inFile in dirDecrypted.GetFiles("*.tca")) { Out.Log($"{inFile}\r\n"); var ncaStorage = new StreamStorage(new FileStream(inFile.FullName, FileMode.Open, FileAccess.Read), false); var DecryptedHeader = new byte[0xC00]; ncaStorage.Read(DecryptedHeader, 0, 0xC00, 0); var Header = new NcaHeader(new BinaryReader(new MemoryStream(DecryptedHeader)), keyset); var fragmentTrimmed = false; for (var i = 0; i < 4; ++i) { var section = NcaParseSection.ParseSection(Header, i); if (section == null || section.Header.Type != SectionType.Pfs0) { continue; } if (fragmentTrimmed) { Out.Warn( "Multiple fragments in NCA found! Skip trimming this fragment.\r\n"); continue; } IStorage sectionStorage = ncaStorage.Slice(section.Offset, section.Size, false); IStorage pfs0Storage = sectionStorage.Slice(section.Header.Sha256Info.DataOffset, section.Header.Sha256Info.DataSize, false); var Pfs0Header = new PartitionFileSystemHeader(new BinaryReader(pfs0Storage.AsStream())); var FileDict = Pfs0Header.Files.ToDictionary(x => x.Name, x => x); var path = PathTools.Normalize(FragmentFileName).TrimStart('/'); if (Pfs0Header.NumFiles == 1 && FileDict.TryGetValue(path, out var fragmentFile)) { var inFileNameNoExtension = Path.GetFileNameWithoutExtension(inFile.Name); var writer = File.Open($"{folderPath}/{inFileNameNoExtension}.nca", FileMode.Create); var offsetBefore = section.Offset + section.Header.Sha256Info.DataOffset + Pfs0Header.HeaderSize + fragmentFile.Offset; IStorage ncaStorageBeforeFragment = ncaStorage.Slice(0, offsetBefore, false); IStorage fragmentStorageOverflow = ncaStorage.Slice(offsetBefore, ncaStorage.GetSize() - offsetBefore, false); ncaStorageBeforeFragment.CopyToStream(writer); var TDV0len = RecreateDelta.Recreate(fragmentStorageOverflow, writer, newBaseFolderFs); var offsetAfter = offsetBefore + TDV0len; IStorage fragmentStorageAfter = ncaStorage.Slice(offsetAfter, ncaStorage.GetSize() - offsetAfter, false); fragmentStorageAfter.CopyToStream(writer); writer.Position = 0x200; writer.WriteByte(0x4E); writer.Dispose(); fragmentTrimmed = true; } } ncaStorage.Dispose(); File.Delete(inFile.FullName); } }