Beispiel #1
0
        private bool DecryptTOC()
        {
            if (VolumeHeader is null)
            {
                throw new InvalidOperationException("Header was not yet loaded");
            }

            if (IsPatchVolume)
            {
                string path = PDIPFSPathResolver.GetPathFromSeed(VolumeHeader.TOCEntryIndex);

                string localPath = Path.Combine(this.PatchVolumeFolder, path);
                Program.Log($"[!] Volume Patch Path Table of contents located at: {localPath}", true);
                if (!File.Exists(localPath))
                {
                    Program.Log($"[X] Error: Unable to locate PDIPFS main TOC file on local filesystem. ({path})", true);
                    return(false);
                }

                using var fs = new FileStream(localPath, FileMode.Open);
                byte[] data = new byte[VolumeHeader.CompressedTOCSize];
                fs.Read(data);

                // Accessing a new file, we need to decrypt the header again
                Program.Log($"[-] TOC Entry is {VolumeHeader.TOCEntryIndex} which is at {path} - decrypting it", true);
                Keyset.CryptData(data, VolumeHeader.TOCEntryIndex);

                Program.Log($"[-] Decompressing TOC file..", true);
                if (!MiscUtils.TryInflateInMemory(data, VolumeHeader.TOCSize, out byte[] deflatedData))
Beispiel #2
0
        public static void List(ListVerbs options)
        {
            bool isFile = File.Exists(options.InputPath);
            bool isDir  = false;

            if (!isFile)
            {
                isDir = Directory.Exists(options.InputPath);
                if (!isDir)
                {
                    Console.WriteLine($"[X] Volume file or PDIPFS folder \"{options.InputPath}\" does not exist.");
                    return;
                }
            }


            Keyset[] keyset = CheckKeys();
            if (keyset is null)
            {
                return;
            }

            bool     found = false;
            GTVolume vol   = null;

            foreach (var k in keyset)
            {
                vol = GTVolume.Load(k, options.InputPath, isDir, Syroot.BinaryData.Core.Endian.Big);
                if (vol != null)
                {
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                Console.WriteLine($"[X] Could not unpack volume. Make sure that you have a valid game key/seed in your key.json.");
                return;
            }

            using var sw = new StreamWriter(options.OutputPath);
            var entries = vol.TableOfContents.GetAllRegisteredFileMap();

            foreach (var entry in entries)
            {
                if (vol.IsPatchVolume)
                {
                    sw.WriteLine($"{entry.Key} ({entry.Value.EntryIndex}) - {PDIPFSPathResolver.GetPathFromSeed(entry.Value.EntryIndex)}");
                }
                else
                {
                    sw.WriteLine($"{entry.Key} ({entry.Value.EntryIndex})");
                }
            }

            sw.WriteLine($"[!] Wrote {entries.Count} at {options.OutputPath}.");
        }
Beispiel #3
0
        /// <summary>
        /// Saves the table of contents to a Patch File System file.
        /// </summary>
        /// <param name="outputDir"></param>
        /// <param name="compressedTocSize"></param>
        /// <param name="uncompressedSize"></param>
        public void SaveToPatchFileSystem(string outputDir, out uint compressedTocSize, out uint uncompressedSize)
        {
            byte[] tocSerialized = Serialize();
            byte[] compressedToc = MiscUtils.ZlibCompress(tocSerialized);

            uncompressedSize  = (uint)tocSerialized.Length;
            compressedTocSize = (uint)compressedToc.Length;

            ParentVolume.Keyset.CryptData(compressedToc, ParentHeader.TOCEntryIndex);

            string path = Path.Combine(outputDir, PDIPFSPathResolver.GetPathFromSeed(ParentHeader.TOCEntryIndex));

            Directory.CreateDirectory(Path.GetDirectoryName(path));
            File.WriteAllBytes(path, compressedToc);
        }
Beispiel #4
0
        private void PackFile(PackCache packCache, string outputDir, bool packAllAsNewEntries, PackCache newCache, FileEntryKey tocFile, InputPackEntry file)
        {
            Program.Log($"[:] Pack: Processing {file.VolumeDirPath}");
            FileInfoKey key = FileInfos.GetByFileIndex(tocFile.EntryIndex);

            if (packAllAsNewEntries && !file.IsAddedFile)
            {
                uint oldEntryFileIndex = key.FileIndex;
                key = ModifyExistingEntryAsNew(key, file.VolumeDirPath);
                Program.Log($"[:] Entry key for {file.VolumeDirPath} changed as new: {oldEntryFileIndex} -> {key.FileIndex}");
            }

            uint   newUncompressedSize = (uint)file.FileSize;
            uint   newCompressedSize   = (uint)file.FileSize;
            string pfsFilePath         = PDIPFSPathResolver.GetPathFromSeed(tocFile.EntryIndex);

            // Check for cached file
            if (ParentVolume.UsePackingCache && packCache.HasValidCachedEntry(file, key.FileIndex, out PackedCacheEntry validCacheEntry))
            {
                string oldFilePath = Path.Combine(outputDir, pfsFilePath);
                if (File.Exists(oldFilePath))
                {
                    newCache.Entries.Add(file.VolumeDirPath, validCacheEntry);
                    Program.Log($"[:] Pack: {file.VolumeDirPath} found in cache file, does not need compressing/encrypting");

                    string movePath = Path.Combine($"{outputDir}_temp", pfsFilePath);
                    Directory.CreateDirectory(Path.GetDirectoryName(movePath));
                    File.Move(oldFilePath, Path.Combine($"{outputDir}_temp", pfsFilePath));
                    UpdateKeyAndRetroactiveAdjustSegments(key, (uint)validCacheEntry.CompressedFileSize, (uint)validCacheEntry.FileSize);
                    return;
                }
                else
                {
                    Program.Log($"[:] Pack: {file.VolumeDirPath} found in cache file but actual file is missing ({pfsFilePath}) - recreating it");
                }
            }

            byte[] fileData = File.ReadAllBytes(file.FullPath);
            if (ParentVolume.NoCompress)
            {
                key.Flags &= ~FileInfoFlags.Compressed;
            }
            else if (key.Flags.HasFlag(FileInfoFlags.Compressed))
            {
                Program.Log($"[:] Pack: Compressing {file.VolumeDirPath}");
                fileData          = MiscUtils.ZlibCompress(fileData);
                newCompressedSize = (uint)fileData.Length;
            }

            Program.Log($"[:] Pack: Saving and encrypting {file.VolumeDirPath} -> {pfsFilePath}");

            // Will also update the ones we pre-registered
            UpdateKeyAndRetroactiveAdjustSegments(key, newCompressedSize, newUncompressedSize);
            ParentVolume.Keyset.CryptBytes(fileData, fileData, key.FileIndex);

            string outputFile = Path.Combine($"{outputDir}_temp", pfsFilePath);

            Directory.CreateDirectory(Path.GetDirectoryName(outputFile));

            File.WriteAllBytes(outputFile, fileData);

            if (ParentVolume.UsePackingCache)
            {
                // Add to our new cache
                var newCacheEntry = new PackedCacheEntry()
                {
                    FileIndex          = tocFile.EntryIndex,
                    FileSize           = newUncompressedSize,
                    LastModified       = file.LastModified,
                    VolumePath         = file.VolumeDirPath,
                    CompressedFileSize = newCompressedSize,
                };

                newCache.Entries.Add(file.VolumeDirPath, newCacheEntry);
            }
        }
Beispiel #5
0
        public bool UnpackNode(FileInfoKey nodeKey, string filePath)
        {
            ulong offset           = DataOffset + (ulong)nodeKey.SegmentIndex * GTVolumeTOC.SEGMENT_SIZE;
            uint  uncompressedSize = nodeKey.UncompressedSize;

            if (!IsPatchVolume)
            {
                if (NoUnpack)
                {
                    return(false);
                }

                Stream.Position = (long)offset;
                if (nodeKey.Flags.HasFlag(FileInfoFlags.Compressed))
                {
                    if (!MiscUtils.DecryptCheckCompression(Stream, Keyset, nodeKey.FileIndex, uncompressedSize))
                    {
                        Program.Log($"[X] Failed to decompress file ({filePath}) while unpacking file info key {nodeKey.FileIndex}", forceConsolePrint: true);
                        return(false);
                    }

                    Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                    Stream.Position -= 8;
                    MiscUtils.DecryptAndInflateToFile(Keyset, Stream, nodeKey.FileIndex, uncompressedSize, filePath, false);
                }
                else
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                    MiscUtils.DecryptToFile(Keyset, Stream, nodeKey.FileIndex, uncompressedSize, filePath, false);
                }
            }
            else
            {
                string patchFilePath = PDIPFSPathResolver.GetPathFromSeed(nodeKey.FileIndex);
                string localPath     = this.PatchVolumeFolder + "/" + patchFilePath;

                if (NoUnpack)
                {
                    return(false);
                }

                /* I'm really not sure if there's a better way to do this.
                 * Volume files, at least nodes don't seem to even store any special flag whether
                 * it is located within an actual volume file or a patch volume. The only thing that is different is the sector index.. Sometimes node index when it's updated
                 * It's slow, but somewhat works I guess..
                 * */
                if (!File.Exists(localPath))
                {
                    return(false);
                }

                Program.Log($"[:] Unpacking: {patchFilePath} -> {filePath}");

                using var fs = new FileStream(localPath, FileMode.Open);
                if (fs.Length >= 7)
                {
                    Span <byte> magic = stackalloc byte[6];
                    fs.Read(magic);
                    if (Encoding.ASCII.GetString(magic).StartsWith("BSDIFF"))
                    {
                        Program.Log($"[X] Detected BSDIFF file for {filePath} ({patchFilePath}), can not unpack yet. (fileID {nodeKey.FileIndex})", forceConsolePrint: true);
                        return(false);
                    }

                    fs.Position = 0;
                }


                if (nodeKey.Flags.HasFlag(FileInfoFlags.Compressed))
                {
                    if (!MiscUtils.DecryptCheckCompression(fs, Keyset, nodeKey.FileIndex, uncompressedSize))
                    {
                        Program.Log($"[X] Failed to decompress file {filePath} ({patchFilePath}) while unpacking file info key {nodeKey.FileIndex}", forceConsolePrint: true);
                        return(false);
                    }

                    Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                    fs.Position = 0;
                    MiscUtils.DecryptAndInflateToFile(Keyset, fs, nodeKey.FileIndex, filePath);
                }
                else
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(filePath));
                    MiscUtils.DecryptToFile(Keyset, fs, nodeKey.FileIndex, filePath);
                }
            }

            return(true);
        }
Beispiel #6
0
        public void PackFiles(string outrepackDir, string[] filesToRemove, bool packAllAsNew, string customTitleID)
        {
            if (FilesToPack.Count == 0 && filesToRemove.Length == 0)
            {
                Program.Log("[X] Found no files to pack or remove from volume.", forceConsolePrint: true);
                Console.WriteLine("[?] Continue? (Y/N)");
                if (Console.ReadKey().Key != ConsoleKey.Y)
                {
                    return;
                }
            }

            // Leftover?
            if (Directory.Exists($"{outrepackDir}_temp"))
            {
                Directory.Delete($"{outrepackDir}_temp", true);
            }

            // Create temp to make sure we aren't transfering user leftovers
            Directory.CreateDirectory($"{outrepackDir}_temp");

            Program.Log($"[-] Preparing to pack {FilesToPack.Count} files, and remove {filesToRemove.Length} files");
            PackCache newCache = TableOfContents.PackFilesForPatchFileSystem(FilesToPack, _packCache, filesToRemove, outrepackDir, packAllAsNew);

            if (UsePackingCache)
            {
                newCache.Save(".pack_cache");
            }

            // Delete main one if needed
            if (Directory.Exists(outrepackDir))
            {
                Directory.Delete(outrepackDir, true);
            }

            Directory.Move($"{outrepackDir}_temp", outrepackDir);

            Program.Log($"[-] Verifying and fixing Table of Contents segment sizes if needed");
            if (!TableOfContents.TryCheckAndFixInvalidSegmentIndexes())
            {
                Program.Log($"[-] Re-ordered segment indexes.");
            }
            else
            {
                Program.Log($"[/] Segment sizes are correct.");
            }

            if (packAllAsNew)
            {
                Program.Log($"[-] Packing as new: New TOC Entry Index is {VolumeHeader.TOCEntryIndex}.");
            }

            Program.Log($"[-] Saving Table of Contents ({PDIPFSPathResolver.GetPathFromSeed(VolumeHeader.TOCEntryIndex)})");
            TableOfContents.SaveToPatchFileSystem(outrepackDir, out uint compressedSize, out uint uncompressedSize);

            if (!string.IsNullOrEmpty(customTitleID) && customTitleID.Length <= 128)
            {
                VolumeHeader.HasCustomGameID = true;
                VolumeHeader.TitleID         = customTitleID;
            }

            VolumeHeader.CompressedTOCSize = compressedSize;
            VolumeHeader.TOCSize           = uncompressedSize;
            VolumeHeader.TotalVolumeSize   = TableOfContents.GetTotalPatchFileSystemSize(compressedSize);

            Program.Log($"[-] Saving main volume header ({PDIPFSPathResolver.Default})");
            byte[] header = VolumeHeader.Serialize();

            Span <uint> headerBlocks = MemoryMarshal.Cast <byte, uint>(header);

            Keyset.EncryptBlocks(headerBlocks, headerBlocks);
            Keyset.CryptData(header, BASE_VOLUME_ENTRY_INDEX);

            string headerPath = Path.Combine(outrepackDir, PDIPFSPathResolver.Default);

            Directory.CreateDirectory(Path.GetDirectoryName(headerPath));

            File.WriteAllBytes(headerPath, header);

            Program.Log($"[/] Done packing.", forceConsolePrint: true);
        }