示例#1
0
        /// <summary>
        /// Unpacks an NCZ file, specified by nczPath, into a new NCA file.
        /// Doesn't currently work, because the NSZ tool doesn't support directly decompressing NCZs.
        /// </summary>
        /// <param name="nczPath">Path to the input NCZ file.</param>
        /// <param name="outDir">Directory in which to output the NCA file.</param>
        /// <returns>Decompressed file path if the operation succeeded, otherwise null.</returns>
        public static async Task <string> UnpackNCZ(string nczPath, string outDir)
        {
            string fName   = Path.GetFileNameWithoutExtension(nczPath);
            string outfile = FileUtils.BuildPath(outDir, fName, ".nca");

            string exe = (exePath);

            // NOTE: Using single quotes here instead of single quotes f***s up windows, it CANNOT handle single quotes
            // Anything surrounded in single quotes will throw an error because the file/folder isn't found
            // Must use escaped double quotes!
            string commandLine = $" -D --overwrite --verify" +
                                 $" --output \"{outDir}\"" +
                                 $" \"{nczPath}\"";


            try
            {
                return(await Task.Run(delegate
                {
                    ProcessStartInfo psi = new ProcessStartInfo()
                    {
                        FileName = exe,
                        WorkingDirectory = System.IO.Directory.GetCurrentDirectory(),
                        Arguments = commandLine,
                        UseShellExecute = false,
                        //RedirectStandardOutput = true,
                        //RedirectStandardError = true,
                        CreateNoWindow = true,
                    };
                    Process process = Process.Start(psi);

                    //string errors = hactool.StandardError.ReadToEnd();
                    //string output = hactool.StandardOutput.ReadToEnd();

                    process.WaitForExit();

                    if (!File.Exists(outfile))
                    {
                        throw new Exception($"Decompressing NCZ failed, {outfile} is missing!");
                    }
                    return outfile;
                }));
            }
            catch (Exception e)
            {
                throw new Exception("Decompressing NCZ failed!", e);
            }
        }
示例#2
0
        /// <summary>
        /// </summary>
        /// <param name="nspPath">Path to the NSP file you want to extract.</param>
        /// <param name="outputDirectory">Path to a directory where the extracted file will be placed.
        /// If null, the NSP will be extracted to a new directory named after the NSP file and with the same parent as the NSP file. </param>
        /// <param name="specificFile">A specific file within the NSP to extract, or null to extract all files.</param>
        public static async Task <NSP> ParseNSP(string nspPath, string outputDirectory = null, string specificFile = null)
        {
            if (string.IsNullOrWhiteSpace(nspPath))
            {
                logger.Error("Empty path passed to NSP.Unpack.");
                return(null);
            }

            FileInfo finfo = new FileInfo(nspPath);

            if (!finfo.Exists)
            {
                logger.Error($"Non-existent file passed to NSP.Unpack: {nspPath}");
                return(null);
            }

            using (JobFileStream nspReadStream = new JobFileStream(nspPath, "NSP unpack of " + nspPath, finfo.Length, 0))
            {
                using (BinaryReader br = new BinaryReader(nspReadStream))
                {
                    if (br.ReadChar() != 'P')
                    {
                        throw new InvalidNspException("Wrong header");
                    }
                    if (br.ReadChar() != 'F')
                    {
                        throw new InvalidNspException("Wrong header");
                    }
                    if (br.ReadChar() != 'S')
                    {
                        throw new InvalidNspException("Wrong header");
                    }
                    if (br.ReadChar() != '0')
                    {
                        throw new InvalidNspException("Wrong header");
                    }

                    // 0x4 + 0x4 number of files
                    int numFiles = br.ReadInt32();
                    if (numFiles < 1)
                    {
                        throw new InvalidNspException("No files inside NSP");
                    }

                    // 0x8 + 0x4  size of string table (plus remainder so it reaches a multple of 0x10)
                    int stringTableSize = br.ReadInt32();
                    if (stringTableSize < 1)
                    {
                        throw new InvalidNspException("Invalid or zero string table size");
                    }

                    // 0xC + 0x4 Zero/Reserved
                    br.ReadUInt32();

                    long[] fileOffsets        = new long[numFiles];
                    long[] fileSizes          = new long[numFiles];
                    int[]  stringTableOffsets = new int[numFiles];

                    // 0x10 + 0x18 * nFiles File Entry Table
                    // One File Entry for each file
                    for (int i = 0; i < numFiles; i++)
                    {
                        // 0x0 + 0x8 Offset of this file from start of file data block
                        fileOffsets[i] = br.ReadInt64();

                        // 0x8 + 0x8 Size of this specific file within the file data block
                        fileSizes[i] = br.ReadInt64();

                        // 0x10 + 0x4 Offset of this file's filename within the string table
                        stringTableOffsets[i] = br.ReadInt32();

                        // 0x14 + 0x4 Zero?
                        br.ReadInt32();
                    }

                    // (0x10 + X) + Y string table, where X is file table size and Y is string table size
                    // Encode every string in UTF8, then terminate with a 0
                    byte[] strBytes = br.ReadBytes(stringTableSize);
                    var    files    = new string[numFiles];
                    for (int i = 0; i < numFiles; i++)
                    {
                        // Start of the string is in the string table offsets table
                        int thisOffset = stringTableOffsets[i];

                        // Decode UTF8 string and assign to files array
                        string name = strBytes.DecodeUTF8NullTerminated(thisOffset);
                        //string name = Encoding.UTF8.GetString(strBytes, thisOffset, thisLength);
                        files[i] = name;
                    }

                    // The header is always aligned to a multiple of 0x10 bytes
                    // It is padded with 0s until the header size is a multiple of 0x10.
                    // However, these 0s are INCLUDED as part of the string table. Thus, they've already been
                    // read (and skipped)

                    if (outputDirectory == null)
                    {
                        // Create a directory right next to the NSP, using the NSP's file name (no extension)
                        DirectoryInfo parentDir = finfo.Directory;
                        DirectoryInfo nspDir    = parentDir.CreateSubdirectory(Path.GetFileNameWithoutExtension(finfo.Name));
                        outputDirectory = nspDir.FullName;
                    }

                    NSP           nsp  = new NSP(outputDirectory);
                    List <string> ncas = new List <string>();
                    List <string> nczs = new List <string>();

                    long dataPosition = nspReadStream.Position;

                    // Copy each file in the NSP to a new file.
                    for (int i = 0; i < files.Length; i++)
                    {
                        string currentFile = files[i];

                        // If specificFile is null, extract all, otherwise only extract matching filename
                        if (specificFile == null || currentFile.ToLower().Contains(specificFile.ToLower()))
                        {
                            // NSPs are just groups of files, but switch titles have very specific files in them
                            // So we allow quick reference to these files
                            string filePath = FileUtils.BuildPath(outputDirectory, currentFile);
                            if (filePath.ToLower().EndsWith(".cnmt.xml"))
                            {
                                nsp.CnmtXML = filePath;
                            }
                            else if (filePath.ToLower().EndsWith(".programinfo.xml"))
                            {
                                nsp.PrograminfoXML = filePath;
                            }
                            else if (filePath.ToLower().EndsWith(".legalinfo.xml"))
                            {
                                nsp.LegalinfoXML = filePath;
                            }
                            else if (filePath.ToLower().EndsWith(".nacp.xml"))
                            {
                                nsp.ControlXML = filePath;
                            }
                            else if (filePath.ToLower().EndsWith(".cert"))
                            {
                                nsp.Certificate = filePath;
                            }
                            else if (filePath.ToLower().EndsWith(".tik"))
                            {
                                nsp.Ticket = filePath;
                            }
                            else if (filePath.ToLower().StartsWith("icon_") || filePath.ToLower().EndsWith(".jpg"))
                            {
                                nsp.AddImage(filePath);
                            }
                            else if (filePath.ToLower().EndsWith(".nca"))
                            {
                                if (filePath.ToLower().EndsWith(".cnmt.nca"))
                                {
                                    nsp.CnmtNCA = filePath;
                                }
                                ncas.Add(filePath);
                            }
                            else if (filePath.ToLower().EndsWith(".ncz"))
                            {
                                nczs.Add(filePath);
                            }
                            else
                            {
                                logger.Warn($"Unknown file type found in NSP, {filePath}");
                                nsp.AddFile(filePath);
                            }

                            using (FileStream fs = FileUtils.OpenWriteStream(filePath))
                            {
                                logger.Info($"Unpacking NSP from file {nspPath}.");

                                long fileOffset = fileOffsets[i];
                                long fileSize   = fileSizes[i];
                                nspReadStream.Seek(dataPosition + fileOffset, SeekOrigin.Begin);
                                await nspReadStream.CopyToAsync(fs, fileSize).ConfigureAwait(false);

                                logger.Info($"Copied NSP contents to file {filePath}");
                            }
                        }
                    }
                    CNMT cnmt = nsp.CNMT = await nsp.ReadCNMT().ConfigureAwait(false);

                    var cnmtNcas = cnmt.ParseContent();
                    foreach (var ncafile in ncas)
                    {
                        // Intentionally two calls, .cnmt.nca is two extensions
                        string ncaid = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(ncafile));
                        var    entry = cnmtNcas[ncaid];
                        nsp.AddNCAByID(entry.Type, ncaid);
                    }

                    // Ugh, handle compressed NCZ files by decrypting them and deleting the original
                    foreach (var nczfile in nczs)
                    {
                        string nczFullPath = FileUtils.BuildPath(outputDirectory, nczfile);
                        string ncaid       = Path.GetFileNameWithoutExtension(nczfile);
                        string ncafile     = await Compression.UnpackNCZ(nczFullPath, outputDirectory).ConfigureAwait(false);

                        if (!string.IsNullOrWhiteSpace(ncafile))
                        {
                            FileUtils.DeleteFile(nczFullPath);
                            var entry = cnmtNcas[ncaid];
                            nsp.AddNCAByID(entry.Type, ncaid);
                        }
                    }
                    return(nsp);
                }
            }
        }