public RPF7File(Stream inputStream, String filname = "", String filePath = null) { if (!inputStream.CanRead) { throw new RPFParsingException("Stream isn't readable"); } this.Filename = filname; this.FilePath = filePath; Stream = inputStream; try { Info = new Structs.RPF7Header(Stream); } catch (Exception e) { throw new RPFParsingException(String.Format("Failed to read header: {0}", e.Message)); } if (new string(Info.Magic) != "RPF7") { throw new RPFParsingException("Invalid RPF Magic"); } if (GlobalOptions.Platform == Platform.PlatformType.PLAYSTATION3) { if (Info.PlatformBit != 0) { throw new RPFParsingException("Invalid platform bit (Are you sure that this RPF is for PlayStation 3?)"); } } else if (GlobalOptions.Platform == Platform.PlatformType.XBOX360) { if (Info.PlatformBit != 1) { throw new RPFParsingException("Invalid platform bit (Are you sure that this RPF is for Xbox 360?)"); } } if (Info.EntriesCount == 0) { throw new RPFParsingException("Empty RPF - no root directory"); } sixteenRoundsDecrypt = (Info.Flag >> 28) == 0xf; if (sixteenRoundsDecrypt) { throw new Exception("Needed to be tested first"); } using (BinaryReader binaryStream = new BinaryReader(AES.DecryptStream(new StreamKeeper(this.Stream), sixteenRoundsDecrypt))) { MemoryStream entriesInfo = new MemoryStream(binaryStream.ReadBytes(0x10 * Info.EntriesCount)); MemoryStream filenames = new MemoryStream(binaryStream.ReadBytes(Info.EntriesNamesLength)); Range <Boolean> fileUsage = new Range <Boolean>(Stream.Length); // Just mark the root entry info and the header as used fileUsage.AddItem(0, 0x10 + 0x10, true); this.Root = CreateFromHeader(new Structs.RPF7EntryInfoTemplate(entriesInfo), entriesInfo, filenames, fileUsage) as DirectoryEntry; } if (this.Root == null) { throw new RPFParsingException("Expected root to be a directory."); } }
private Entry CreateFromHeader(Structs.RPF7EntryInfoTemplate entryInfo, MemoryStream entriesInfo, MemoryStream filenames, Range <Boolean> fileUsage) { // Todo: check sizes and offsets bool isResource = entryInfo.Field1 == 1; long offset = (long)entryInfo.Field2; int compressedSize = (int)entryInfo.Field3; int filenameOffset = (int)entryInfo.Field4; filenameOffset <<= Info.ShiftNameAccessBy; if (filenameOffset < 0 || filenameOffset >= filenames.Length) { throw new RPFEntryParsingException("Reading entry name: Invalid offst"); } filenames.Seek(filenameOffset, SeekOrigin.Begin); String filename = ""; // Read null-terminated filename int currentChar; while ((currentChar = filenames.ReadByte()) != 0) { if (currentChar == -1) { throw new RPFEntryParsingException("Reading entry name: Unexpected EOF"); } filename += (char)currentChar; } // There may be duplicate names usage, so don't check it if (!fileUsage.AddItem(0x10 + entriesInfo.Length + filenameOffset, filename.Length + 1, true)) { throw new RPFEntryParsingException("Reading entry entry: Invalid duplicate usage of a name"); } if (offset == 0x7FFFFF) { // Is a Directory if (isResource) { throw new RPFEntryParsingException("Invalid entry type (directory and resource)"); } int subentriesStartIndex = (int)entryInfo.Field5; int subentriesCount = (int)entryInfo.Field6; if (subentriesStartIndex < 0 || (subentriesStartIndex + subentriesCount) * 0x10 > entriesInfo.Length) { throw new RPFEntryParsingException("Invalid directory entry: Invalid subentries info"); } List <Entry> entries = new List <Entry>(); // This will prevent recursion.. if (!fileUsage.AddItem(0x10 * (1 + subentriesStartIndex), 0x10 * subentriesCount, true, false)) { throw new RPFEntryParsingException("Reading directory entry: Duplicate usage of an entries - may lead to recursion"); } for (int i = 0; i < subentriesCount; ++i) { entriesInfo.Seek(0x10 * (i + subentriesStartIndex), SeekOrigin.Begin); entries.Add(CreateFromHeader(new Structs.RPF7EntryInfoTemplate(entriesInfo), entriesInfo, filenames, fileUsage)); } return(new DirectoryEntry(filename, entries)); } offset <<= 9; if (offset < 0 || offset > Stream.Length) { throw new RPFEntryParsingException("Invalid entry info: Invalid data offset"); } if (isResource) { if (compressedSize == 0xFFFFFF) { throw new RPFEntryParsingException("Resource with size -1, not supported (Contact developr if seen)"); } if (!fileUsage.AddItem(offset, compressedSize, true, false)) // Can't use the same data for two files { throw new RPFEntryParsingException("Reading entry data: Data position is wierd"); } uint systemFlag = entryInfo.Field5; uint graphicsFlag = entryInfo.Field6; return(new ResourceEntry(filename, new ResourceStreamCreator(Stream, offset, compressedSize, systemFlag, graphicsFlag, Path.GetExtension(filename).Substring(2)), systemFlag, graphicsFlag)); } // Regular file int uncompressedSize = (int)entryInfo.Field5; int isEncrypted = (int)entryInfo.Field6; if (compressedSize == 0) { // Uncompressed file if (isEncrypted != 0) { throw new RPFEntryParsingException("Unexcepted file - compressed but unencrypted (Contact developr if seen)"); } if (!fileUsage.AddItem(offset, uncompressedSize, true, false)) // Can't use the same data for two files { throw new RPFEntryParsingException("Reading entry data: Data position is wierd"); } return(new RegularFileEntry(filename, new FileStreamCreator(Stream, offset, uncompressedSize), false)); } else { if (!fileUsage.AddItem(offset, compressedSize, true, false)) // Can't use the same data for two files { throw new RPFEntryParsingException("Reading entry data: Data position is wierd"); } // Compressed file return(new RegularFileEntry(filename, new CompressedFileStreamCreator(Stream, offset, compressedSize, uncompressedSize, isEncrypted != 0), true)); } }