Exemple #1
0
 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);
             }
         }
     }
 }
Exemple #2
0
        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");
        }
Exemple #3
0
        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");
        }
Exemple #4
0
        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;
                }
            }
        }
Exemple #5
0
        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");
                }
            }
        }
Exemple #6
0
        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));
                            }
            }
        }