const int EncryptedBufferSize = 256; //?? TODO: check - may be value 16 will be better for performance public FPakFile(BinaryReader Reader, FPakEntry Info, byte[] key = null) { Reader.BaseStream.Seek(Info.Offset + Info.StructSize, SeekOrigin.Begin); if (Info.Encrypted) { long encSize = (Info.Size & 15) == 0 ? Info.Size : ((Info.Size / 16) + 1) * 16; byte[] encBuffer = Reader.ReadBytes((int)encSize); data = AESDecryptor.DecryptAES(encBuffer, key).SubArray(0, (int)Info.UncompressedSize); if (encSize != Info.Size) { data = data.SubArray(0, (int)Info.UncompressedSize); } } else { data = Reader.ReadBytes((int)Info.UncompressedSize); } //File.WriteAllBytes(Path.GetFileName(info.Name), data); /* * if (info.CompressionMethod != 0) * { * Console.WriteLine("compressed"); * while (size > 0) * { * if ((UncompressedBuffer == null) || (ArPos < UncompressedBufferPos) || (ArPos >= UncompressedBufferPos + Info.CompressionBlockSize)) * { * // buffer is not ready * if (UncompressedBuffer == null) * { * UncompressedBuffer = new byte[Info.CompressionBlockSize]; * } * // prepare buffer * int BlockIndex = ArPos / Info.CompressionBlockSize; * UncompressedBufferPos = Info.CompressionBlockSize * BlockIndex; * * FPakCompressedBlock Block = Info.CompressionBlocks[BlockIndex]; * int CompressedBlockSize = (int)(Block.CompressedEnd - Block.CompressedStart); * int UncompressedBlockSize = Math.Min(Info.CompressionBlockSize, (int)Info.UncompressedSize - UncompressedBufferPos); // don't pass file end * * byte[] CompressedData; * if (Info.bEncrypted == 0) * { * Reader.BaseStream.Seek(Block.CompressedStart, SeekOrigin.Begin); * CompressedData = Reader.ReadBytes(CompressedBlockSize); * } * else * { * int EncryptedSize = Align(CompressedBlockSize, EncryptionAlign); * Reader.BaseStream.Seek(Block.CompressedStart, SeekOrigin.Begin); * CompressedData = Reader.ReadBytes(EncryptedSize); * CompressedData = AESDecryptor.DecryptAES(CompressedData, EncryptedSize, key, key.Length); * } * // appDecompress(CompressedData, CompressedBlockSize, UncompressedBuffer, UncompressedBlockSize, Info.CompressionMethod); * // https://github.com/gildor2/UModel/blob/39641f9e58cb4c286dde5f191e5db9a24b19f503/Unreal/UnCoreCompression.cpp#L183 * throw new NotImplementedException("Decompressing of files aren't written yet"); * } * * // data is in buffer, copy it * int BytesToCopy = UncompressedBufferPos + Info.CompressionBlockSize - ArPos; // number of bytes until end of the buffer * if (BytesToCopy > size) BytesToCopy = size; * if (BytesToCopy <= 0) * { * throw new ArgumentOutOfRangeException("Bytes to copy is invalid"); * } * * // copy uncompressed data * int OffsetInBuffer = ArPos - UncompressedBufferPos; * Buffer.BlockCopy(UncompressedBuffer, OffsetInBuffer, data, dataOffset, BytesToCopy); * * // advance pointers * * // ArPos += BytesToCopy; * size -= BytesToCopy; * dataOffset += BytesToCopy; * } * throw new NotImplementedException("Decompressing of files aren't written yet"); * } * else if (Info.bEncrypted != 0) * { * Console.WriteLine("Encrypted"); * // Uncompressed encrypted data. Reuse compression fields to handle decryption efficiently * if (UncompressedBuffer == null) * { * UncompressedBuffer = new byte[EncryptedBufferSize]; * UncompressedBufferPos = 0x40000000; // some invalid value * } * * while (size > 0) * { * if ((ArPos < UncompressedBufferPos) || (ArPos >= UncompressedBufferPos + EncryptedBufferSize)) * { * // Should fetch block and decrypt it. * // Note: AES is block encryption, so we should always align read requests for correct decryption. * UncompressedBufferPos = ArPos & ~(EncryptionAlign - 1); * Reader.BaseStream.Seek(Info.Pos + Info.StructSize + UncompressedBufferPos, SeekOrigin.Begin); * int RemainingSize = (int)Info.Size; * if (RemainingSize >= 0) * { * if (RemainingSize > EncryptedBufferSize) * RemainingSize = EncryptedBufferSize; * RemainingSize = Align(RemainingSize, EncryptionAlign); // align for AES, pak contains aligned data * UncompressedBuffer = Reader.ReadBytes(RemainingSize); * UncompressedBuffer = AESDecryptor.DecryptAES(UncompressedBuffer, RemainingSize, key, key.Length); * } * } * * // Now copy decrypted data from UncompressedBuffer (code is very similar to those used in decompression above) * int BytesToCopy = UncompressedBufferPos + EncryptedBufferSize - ArPos; // number of bytes until end of the buffer * if (BytesToCopy > size) BytesToCopy = size; * if (BytesToCopy <= 0) * { * throw new ArgumentOutOfRangeException("Bytes to copy is invalid"); * } * * // copy uncompressed data * int OffsetInBuffer = ArPos - UncompressedBufferPos; * Buffer.BlockCopy(UncompressedBuffer, OffsetInBuffer, data, dataOffset, BytesToCopy); * * // advance pointers * * // ArPos += BytesToCopy; * size -= BytesToCopy; * dataOffset += BytesToCopy; * } * throw new NotImplementedException("Decryption of files aren't written yet"); * } * else * { * // Pure data * // seek every time in a case if the same 'Reader' was used by different FPakFile * // (this is a lightweight operation for buffered FArchive) * Reader.BaseStream.Seek(Info.Pos + Info.StructSize, SeekOrigin.Begin); * data = Reader.ReadBytes(size); * // ArPos += size; * }*/ }
private void ReadIndexUpdated(BinaryReader reader, FPakInfo info, byte[] aesKey) { MountPoint = reader.ReadFString(); if (MountPoint.StartsWith("../../..")) { MountPoint = MountPoint.Substring(8); } else { MountPoint = "/"; } var filesNum = reader.ReadInt32(); reader.ReadUInt64(); if (reader.ReadInt32() == 0) { throw new FileLoadException("No path hash index"); } //reader.ReadInt64(); //reader.ReadInt64(); reader.BaseStream.Position += 20L + 8L + 8L; if (reader.ReadInt32() == 0) { throw new FileLoadException("No directory index"); } var position = reader.ReadInt64(); var directoryIndexSize = reader.ReadInt64(); reader.BaseStream.Position += 20L; var encodedPakEntries = reader.ReadTArray(reader.ReadByte); var files = reader.ReadInt32(); if (files < 0) { throw new FileLoadException("Corrupt pak PrimaryIndex detected!"); } Reader.BaseStream.Position = position; var directoryIndexData = Reader.ReadBytes((int)directoryIndexSize); if (info.bEncryptedIndex != 0) { directoryIndexData = AESDecryptor.DecryptAES(directoryIndexData, aesKey); } var directoryIndexReader = new BinaryReader(new MemoryStream(directoryIndexData)); var directoryEntries = directoryIndexReader.ReadTArray(() => new FPakDirectoryEntry(directoryIndexReader)); var entries = new List <FPakEntry>(filesNum); foreach (var directoryEntry in directoryEntries) { foreach (var hashIndexEntry in directoryEntry.Entries) { string path = MountPoint + directoryEntry.Directory + hashIndexEntry.Filename; entries.Add(GetEntry(path, hashIndexEntry.Location, encodedPakEntries)); } } this.FileInfos = entries.ToArray(); }
public PakReader(Stream stream, string name, byte[] aes = null, bool ParseFiles = true) { Aes = aes; Stream = stream; Name = name; Reader = new BinaryReader(Stream); Stream.Seek(-FPakInfo.Size, SeekOrigin.End); FPakInfo info = new FPakInfo(Reader); if (info.Magic != FPakInfo.PAK_FILE_MAGIC) { DebugHelper.WriteLine(".PAKs: The file magic is invalid"); throw new FileLoadException("The file magic is invalid"); } if (info.Version > (int)PAK_VERSION.PAK_LATEST) { DebugHelper.WriteLine($".PAKs: WARNING: Pak file \"{Name}\" has unsupported version {info.Version}"); } if (info.bEncryptedIndex != 0) { if (Aes == null) { DebugHelper.WriteLine(".PAKs: The file has an encrypted index"); throw new FileLoadException("The file has an encrypted index"); } } // Read pak index Stream.Seek(info.IndexOffset, SeekOrigin.Begin); // Manage pak files with encrypted index BinaryReader infoReader = Reader; if (info.bEncryptedIndex != 0) { var InfoBlock = Reader.ReadBytes((int)info.IndexSize); InfoBlock = AESDecryptor.DecryptAES(InfoBlock, Aes); infoReader = new BinaryReader(new MemoryStream(InfoBlock)); int stringLen = infoReader.ReadInt32(); if (stringLen > 512 || stringLen < -512) { DebugHelper.WriteLine(".PAKs: The AES key is invalid"); throw new FileLoadException("The AES key is invalid"); } if (stringLen < 0) { infoReader.BaseStream.Seek((stringLen - 1) * 2, SeekOrigin.Current); ushort c = infoReader.ReadUInt16(); if (c != 0) { DebugHelper.WriteLine(".PAKs: The AES key is invalid"); throw new FileLoadException("The AES key is invalid"); } } else { infoReader.BaseStream.Seek(stringLen - 1, SeekOrigin.Current); byte c = infoReader.ReadByte(); if (c != 0) { DebugHelper.WriteLine(".PAKs: The AES key is invalid"); throw new FileLoadException("The AES key is invalid"); } } infoReader.BaseStream.Seek(0, SeekOrigin.Begin); } if (!ParseFiles) { return; } if (info.Version >= (int)PAK_VERSION.PAK_PATH_HASH_INDEX) { ReadIndexUpdated(infoReader, info, Aes); } else { MountPoint = infoReader.ReadFString(); if (MountPoint.StartsWith("../../..")) { MountPoint = MountPoint.Substring(8); } else { MountPoint = "/"; } FileInfos = new FPakEntry[infoReader.ReadInt32()]; for (int i = 0; i < FileInfos.Length; i++) { FileInfos[i] = new FPakEntry(infoReader, MountPoint, (PAK_VERSION)info.Version); } } }
public PakReader(Stream stream, string name, byte[] aes = null, bool ParseFiles = true) { Aes = aes; Stream = stream; Name = name; Reader = new BinaryReader(Stream); Stream.Seek(-FPakInfo.Size, SeekOrigin.End); FPakInfo info = new FPakInfo(Reader); if (info.Magic != FPakInfo.PAK_FILE_MAGIC) { throw new FileLoadException("The file magic is invalid"); } if (info.Version > (int)PAK_VERSION.PAK_LATEST) { Console.Error.WriteLine($"WARNING: Pak file \"{Name}\" has unsupported version {info.Version}"); } if (info.bEncryptedIndex != 0) { if (Aes == null) { throw new FileLoadException("The file has an encrypted index"); } } // Read pak index Stream.Seek(info.IndexOffset, SeekOrigin.Begin); // Manage pak files with encrypted index BinaryReader infoReader = Reader; if (info.bEncryptedIndex != 0) { var InfoBlock = Reader.ReadBytes((int)info.IndexSize); InfoBlock = AESDecryptor.DecryptAES(InfoBlock, (int)info.IndexSize, Aes, Aes.Length); infoReader = new BinaryReader(new MemoryStream(InfoBlock)); int stringLen = infoReader.ReadInt32(); if (stringLen > 512 || stringLen < -512) { throw new FileLoadException("The AES key is invalid"); } if (stringLen < 0) { infoReader.BaseStream.Seek((stringLen - 1) * 2, SeekOrigin.Current); ushort c = infoReader.ReadUInt16(); if (c != 0) { throw new FileLoadException("The AES key is invalid"); } } else { infoReader.BaseStream.Seek(stringLen - 1, SeekOrigin.Current); byte c = infoReader.ReadByte(); if (c != 0) { throw new FileLoadException("The AES key is invalid"); } } } if (!ParseFiles) { return; } // Pak index reading time :) infoReader.BaseStream.Seek(0, SeekOrigin.Begin); MountPoint = infoReader.ReadString(FPakInfo.MAX_PACKAGE_PATH); bool badMountPoint = false; if (!MountPoint.StartsWith("../../..")) { badMountPoint = true; } else { MountPoint = MountPoint.Substring(8); } if (MountPoint[0] != '/' || ((MountPoint.Length > 1) && (MountPoint[1] == '.'))) { badMountPoint = true; } if (badMountPoint) { Console.Error.WriteLine($"WARNING: Pak \"{Name}\" has strange mount point \"{MountPoint}\", mounting to root"); MountPoint = "/"; } FileInfos = new FPakEntry[infoReader.ReadInt32()]; for (int i = 0; i < FileInfos.Length; i++) { FileInfos[i] = new FPakEntry(infoReader, MountPoint, info.Version); } }