public static void Encrypt(IFileSystem sourceFs, IFileSystem destFs, bool verifyEncrypted, Keyset keyset, Output Out) { foreach (var decryptedNcaEntry in sourceFs.EnumerateEntries().Where(item => item.Name.EndsWith(".nca"))) { Out.Log($"Input: {decryptedNcaEntry.Name}\r\n"); using (var decryptedNca = sourceFs.OpenFile(decryptedNcaEntry.FullPath, OpenMode.Read)) { if (destFs != null) { Out.Log("Opened NCA for writing...\r\n"); using (IFile outputFile = FolderTools.CreateAndOpen(decryptedNcaEntry, destFs, decryptedNcaEntry.Name, decryptedNca.GetSize())) { EncryptFunct(decryptedNca.AsStream(), outputFile.AsStream(), decryptedNcaEntry.Name, verifyEncrypted, keyset, Out); } } else { EncryptFunct(decryptedNca.AsStream(), null, decryptedNcaEntry.Name, verifyEncrypted, keyset, Out); } } } }
public static void ExtractKey(Stream TicketFile, string filename, Keyset keyset, Output Out) { var titleKey = new byte[0x10]; TicketFile.Seek(0x180, SeekOrigin.Begin); TicketFile.Read(titleKey, 0, 0x10); var ticketNameWithoutAnyExtensions = filename.Split('.')[0]; if (!ticketNameWithoutAnyExtensions.TryToBytes(out var rightsId)) { throw new InvalidDataException( $"Invalid rights ID \"{ticketNameWithoutAnyExtensions}\" as ticket file name"); } keyset.TitleKeys[rightsId] = titleKey; Out.Log($"titleKey: {Utils.BytesToString(rightsId)},{Utils.BytesToString(titleKey)}\r\n"); }
public MainWindow() { InitializeComponent(); Out = new Output(); MainGrid.Visibility = Visibility.Visible; MainGridBusy.Visibility = Visibility.Hidden; SelectNspzDialog.Filter = "Compressed Switch File (*.nspz)|*.nspz|" + "XCIZ to not-installable NSP (*.xciz)|*.xciz"; SelectNspzDialog.Multiselect = true; SelectNspzDialog.Title = "Select input nspz files..."; SelectInputFileDialog.Filter = "All Switch Games (*.nsp;*.xci;*.nspz;*.xciz)|*.nsp;*.xci;*.nspz;*.xciz|" + "Switch Package (*.nsp)|*.nsp|Switch Cartridge (*.xci)|*.xci|" + "Compressed Switch File (*.nspz)|*.nspz|" + "Compressed Switch Cart (*.xciz)|*.xciz"; SelectInputFileDialog.Multiselect = true; SelectInputFileDialog.Title = "Select input files..."; SelectNspXciDialog.Filter = "Switch Games (*.nsp;*.xci)|*.nsp;*.xci|" + "Switch Package (*.nsp)|*.ns|Switch Cartridge (*.xci)|*.xci"; SelectNspXciDialog.Multiselect = true; SelectNspXciDialog.Title = "Select input NSP files..."; SelectOutputDictionaryDialog.RootFolder = Environment.SpecialFolder.MyComputer; OutputFolderTextBox.Text = Settings.Default.OutputFolder != "" ? Settings.Default.OutputFolder : Environment.GetFolderPath(Environment.SpecialFolder.Desktop); VerificationComboBox.SelectedIndex = Settings.Default.Verification ? 0 : 1; CheckForUpdatesComboBox.SelectedIndex = Settings.Default.CheckForUpdates ? 0 : 1; foreach (var item in CompressionLevelComboBox.Items.Cast <ComboBoxItem>()) { if ((int)item.Tag == Settings.Default.CompressionLevel) { CompressionLevelComboBox.SelectedItem = item; break; } } foreach (var item in BlockSizeComboBox.Items.Cast <ComboBoxItem>()) { if ((int)item.Tag == Settings.Default.BlockSize) { BlockSizeComboBox.SelectedItem = item; break; } } SelectTempDictionaryDialog.RootFolder = Environment.SpecialFolder.MyComputer; if (Settings.Default.TempFolder != "") { TempFolderTextBox.Text = Settings.Default.TempFolder; } else { TempFolderTextBox.Text = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); } KeepTempFilesAfterTaskComboBox.SelectedIndex = Settings.Default.KeepTempFiles ? 0 : 1; StandByWhenTaskDoneComboBox.SelectedIndex = Settings.Default.StandbyWhenDone; try { LicenseTextBox.Text = File.ReadAllText(@"LICENSE"); } catch (Exception ex) { Out.Log("LICENSE file not found!\r\n"); } Out.Log("nsZip initialized\r\n"); }
private void RunTask() { if (TaskQueue.Items.Count == 0) { Out.Log("Nothing to do - TaskQueue empty! Please add an NSP or NSPZ!\r\n"); return; } Dispatcher.Invoke(() => { BusyTextBlock.Text = "Working..."; MainGrid.Visibility = Visibility.Hidden; MainGridBusy.Visibility = Visibility.Visible; }); try { do { var inFile = (string)TaskQueue.Items[0]; var infileLowerCase = inFile.ToLower(); var inFileNoExtension = Path.GetFileNameWithoutExtension(inFile); Dispatcher.Invoke(() => { BusyTextBlock.Text = $"Task \"{Path.GetFileNameWithoutExtension(inFile)}\" in progress...\r\nThis might take quite some time.\r\n" + $"Please take a look at the console window for more information."; TaskQueue.Items.RemoveAt(0); }); var tl = new TaskLogic(OutputFolderPath, TempFolderPath, VerifyHashes, BlockSize, ZstdLevel, Out); if (tl.checkIfAlreadyExist(inFile)) { continue; } tl.cleanFolders(); try { if (infileLowerCase.EndsWith("nsp")) { tl.CompressNSP(inFile); } else if (infileLowerCase.EndsWith("xci")) { tl.CompressXCI(inFile); } else if (infileLowerCase.EndsWith("nspz")) { tl.DecompressNSPZ(inFile); } else if (infileLowerCase.EndsWith("xciz")) { tl.DecompressXCIZ(inFile); } else { throw new InvalidDataException($"Invalid file type {inFile}"); } } catch (Exception ex) { Out.Error(ex.StackTrace + "\r\n"); Out.Error(ex.Message + "\r\n\r\n"); } finally { if (!KeepTempFilesAfterTask && tl != null) { tl.cleanFolders(); } } } while (TaskQueue.Items.Count > 0); } catch (Exception ex) { Out.Log(ex.StackTrace + "\r\n"); Out.Log(ex.Message); throw ex; } finally { Dispatcher.Invoke(() => { MainGrid.Visibility = Visibility.Visible; MainGridBusy.Visibility = Visibility.Hidden; }); switch (StandByWhenTaskDone) { case TaskDonePowerState.Suspend: Out.Log("Activate standby mode...\r\n"); Thread.Sleep(1000); System.Windows.Forms.Application.SetSuspendState(PowerState.Suspend, false, false); break; case TaskDonePowerState.Hibernate: Out.Log("Activate hibernate mode...\r\n"); Thread.Sleep(1000); System.Windows.Forms.Application.SetSuspendState(PowerState.Hibernate, false, false); break; } } }
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"); } } }
private void CompressFunct() { var CompressionIO = new byte[104857600]; var blocksPerChunk = CompressionIO.Length / bs + (CompressionIO.Length % bs > 0 ? 1 : 0); var sourceFs = new LocalFileSystem(inFolderPath); var destFs = new LocalFileSystem(outFolderPath); foreach (var file in sourceFs.EnumerateEntries().Where(item => item.Type == DirectoryEntryType.File)) { Out.Log($"{file.FullPath}\r\n"); var outFileName = $"{file.Name}.nsz"; using (var outputFileBase = FolderTools.CreateAndOpen(file, destFs, outFileName)) using (var outputFile = new FilePositionStorage(outputFileBase)) using (var inputFileBase = sourceFs.OpenFile(file.FullPath, OpenMode.Read)) using (var inputFile = new FilePositionStorage(inputFileBase)) { amountOfBlocks = (int)Math.Ceiling((decimal)inputFile.GetSize() / bs); sizeOfSize = (int)Math.Ceiling(Math.Log(bs, 2) / 8); var perBlockHeaderSize = sizeOfSize + 1; var headerSize = 0x15 + perBlockHeaderSize * amountOfBlocks; outputFile.Seek(headerSize); var nsZipMagic = new byte[] { 0x6e, 0x73, 0x5a, 0x69, 0x70 }; var nsZipMagicRandomKey = new byte[5]; secureRNG.GetBytes(nsZipMagicRandomKey); Util.XorArrays(nsZipMagic, nsZipMagicRandomKey); var chunkIndex = 0; nsZipHeader = new byte[headerSize]; Array.Copy(nsZipMagic, 0x00, nsZipHeader, 0x00, 0x05); Array.Copy(nsZipMagicRandomKey, 0x00, nsZipHeader, 0x05, 0x05); nsZipHeader[0x0A] = 0x00; //Version nsZipHeader[0x0B] = 0x01; //Type nsZipHeader[0x0C] = (byte)(bs >> 32); nsZipHeader[0x0D] = (byte)(bs >> 24); nsZipHeader[0x0E] = (byte)(bs >> 16); nsZipHeader[0x0F] = (byte)(bs >> 8); nsZipHeader[0x10] = (byte)bs; nsZipHeader[0x11] = (byte)(amountOfBlocks >> 24); nsZipHeader[0x12] = (byte)(amountOfBlocks >> 16); nsZipHeader[0x13] = (byte)(amountOfBlocks >> 8); nsZipHeader[0x14] = (byte)amountOfBlocks; sha256Compressed = new SHA256Cng(); long maxPos = inputFile.GetSize(); int blocksLeft; int blocksInThisChunk; do { var outputLen = new int[blocksPerChunk]; //Filled with 0 inputFile.Read(CompressionIO); blocksLeft = amountOfBlocks - chunkIndex * blocksPerChunk; blocksInThisChunk = Math.Min(blocksPerChunk, blocksLeft); var opt = new ParallelOptions() { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }; //for(int index = 0; index < blocksInThisChunk; ++index) Parallel.For(0, blocksInThisChunk, opt, index => { var currentBlockID = chunkIndex * blocksPerChunk + index; var startPosRelative = index * bs; //Don't directly cast bytesLeft to int or sectors over 2 GB will overflow into negative size long startPos = (long)currentBlockID * (long)bs; long bytesLeft = maxPos - startPos; var blockSize = bs < bytesLeft ? bs : (int)bytesLeft; Out.Print($"Block: {currentBlockID + 1}/{amountOfBlocks} ({opt.MaxDegreeOfParallelism})\r\n"); CompressionAlgorithm compressionAlgorithm; outputLen[index] = CompressBlock(ref CompressionIO, startPosRelative, blockSize, out compressionAlgorithm); //Out.Log($"inputLen[{currentBlockID}]: {blockSize}\r\n"); //Out.Log($"outputLen[{currentBlockID}]: {outputLen[index]} bytesLeft={bytesLeft}\r\n"); var offset = currentBlockID * (sizeOfSize + 1); switch (compressionAlgorithm) { case CompressionAlgorithm.None: nsZipHeader[0x15 + offset] = 0x00; break; case CompressionAlgorithm.Zstandard: nsZipHeader[0x15 + offset] = 0x01; break; case CompressionAlgorithm.LZMA: nsZipHeader[0x15 + offset] = 0x02; break; default: throw new ArgumentOutOfRangeException(); } for (var j = 0; j < sizeOfSize; ++j) { nsZipHeader[0x16 + offset + j] = (byte)(outputLen[index] >> ((sizeOfSize - j - 1) * 8)); } }); for (int index = 0; index < blocksInThisChunk; ++index) { var startPos = index * bs; sha256Compressed.TransformBlock(CompressionIO, startPos, outputLen[index], null, 0); var dataToWrite = CompressionIO.AsSpan().Slice(startPos, outputLen[index]); outputFile.Write(dataToWrite); } ++chunkIndex; } while (blocksLeft - blocksInThisChunk > 0); outputFile.Write(nsZipHeader, 0); sha256Header = new SHA256Cng(); sha256Header.ComputeHash(nsZipHeader); var sha256Hash = new byte[0x20]; Array.Copy(sha256Header.Hash, sha256Hash, 0x20); sha256Compressed.TransformFinalBlock(new byte[0], 0, 0); Util.XorArrays(sha256Hash, sha256Compressed.Hash); //Console.WriteLine(sha256Header.Hash.ToHexString()); //Console.WriteLine(sha256Compressed.Hash.ToHexString()); outputFile.Seek(0, SeekOrigin.End); outputFile.Write(sha256Hash.AsSpan().Slice(0, 0x10)); } } }