private void Decompress(Stream stream, byte[] key, string[] compressionMethods, byte[] outData) { if (compressionMethods == null || compressionMethods.Length == 0) { throw new ArgumentOutOfRangeException(nameof(compressionMethods), "CompressionMethods are null or empty"); } string compressionMethod = compressionMethods[CompressionMethodIndex - 1]; // -1 because we dont have 'NAME_None' in the array int bytesRead = 0; for (int i = 0; i < CompressionBlocks.Length; i++) { stream.Position = Offset + CompressionBlocks[i].CompressedStart; int uncompressedSize = (int)Math.Min(CompressionBlockSize, outData.Length - bytesRead); byte[] blockBbuffer; if (Encrypted) { blockBbuffer = new byte[BinaryHelper.Align(CompressionBlocks[i].Size, AESDecryptor.BLOCK_SIZE)]; stream.Read(blockBbuffer, 0, blockBbuffer.Length); blockBbuffer = AESDecryptor.DecryptAES(blockBbuffer, key); } else { blockBbuffer = new byte[CompressionBlocks[i].Size]; stream.Read(blockBbuffer, 0, blockBbuffer.Length); } using var blockMs = new MemoryStream(blockBbuffer, false); using Stream compressionStream = compressionMethod switch { "Zlib" => new ZlibStream(blockMs, CompressionMode.Decompress), "Gzip" => new GZipStream(blockMs, CompressionMode.Decompress), "Oodle" => new OodleStream(blockBbuffer, uncompressedSize), _ => throw new NotImplementedException($"Decompression not yet implemented ({compressionMethod})") }; bytesRead += compressionStream.Read(outData, bytesRead, uncompressedSize); } }
FPakEntry GetEntry(string name, int pakLocation, byte[] encodedPakEntries) { if (pakLocation >= 0) { // Grab the big bitfield value: // Bit 31 = Offset 32-bit safe? // Bit 30 = Uncompressed size 32-bit safe? // Bit 29 = Size 32-bit safe? // Bits 28-23 = Compression method // Bit 22 = Encrypted // Bits 21-6 = Compression blocks count // Bits 5-0 = Compression block size // Filter out the CompressionMethod. long Offset, UncompressedSize, Size; uint CompressionMethodIndex, CompressionBlockSize; bool Encrypted, Deleted; uint Value = BitConverter.ToUInt32(encodedPakEntries, pakLocation); pakLocation += sizeof(uint); CompressionMethodIndex = ((Value >> 23) & 0x3f); // Test for 32-bit safe values. Grab it, or memcpy the 64-bit value // to avoid alignment exceptions on platforms requiring 64-bit alignment // for 64-bit variables. // // Read the Offset. bool bIsOffset32BitSafe = (Value & (1 << 31)) != 0; if (bIsOffset32BitSafe) { Offset = BitConverter.ToUInt32(encodedPakEntries, pakLocation); pakLocation += sizeof(uint); } else { Offset = BitConverter.ToInt64(encodedPakEntries, pakLocation); pakLocation += sizeof(long); } // Read the UncompressedSize. bool bIsUncompressedSize32BitSafe = (Value & (1 << 30)) != 0; if (bIsUncompressedSize32BitSafe) { UncompressedSize = BitConverter.ToUInt32(encodedPakEntries, pakLocation); pakLocation += sizeof(uint); } else { UncompressedSize = BitConverter.ToInt64(encodedPakEntries, pakLocation); pakLocation += sizeof(long); } // Fill in the Size. if (CompressionMethodIndex != 0) { // Size is only present if compression is applied. bool bIsSize32BitSafe = (Value & (1 << 29)) != 0; if (bIsSize32BitSafe) { Size = BitConverter.ToUInt32(encodedPakEntries, pakLocation); pakLocation += sizeof(uint); } else { Size = BitConverter.ToInt64(encodedPakEntries, pakLocation); pakLocation += sizeof(long); } } else { // The Size is the same thing as the UncompressedSize when // CompressionMethod == COMPRESS_None. Size = UncompressedSize; } // Filter the encrypted flag. Encrypted = (Value & (1 << 22)) != 0; // This should clear out any excess CompressionBlocks that may be valid in the user's // passed in entry. var CompressionBlocksCount = (Value >> 6) & 0xffff; FPakCompressedBlock[] CompressionBlocks = new FPakCompressedBlock[CompressionBlocksCount]; // Filter the compression block size or use the UncompressedSize if less that 64k. CompressionBlockSize = 0; if (CompressionBlocksCount > 0) { CompressionBlockSize = UncompressedSize < 65536 ? (uint)UncompressedSize : ((Value & 0x3f) << 11); } // Set bDeleteRecord to false, because it obviously isn't deleted if we are here. Deleted = false; // Base offset to the compressed data long BaseOffset = true ? 0 : Offset; // HasRelativeCompressedChunkOffsets -> Version >= PakFile_Version_RelativeChunkOffsets // Handle building of the CompressionBlocks array. if (CompressionBlocks.Length == 1 && !Encrypted) { // If the number of CompressionBlocks is 1, we didn't store any extra information. // Derive what we can from the entry's file offset and size. var start = BaseOffset + FPakEntry.GetSize(EPakVersion.LATEST, CompressionMethodIndex, CompressionBlocksCount); CompressionBlocks[0] = new FPakCompressedBlock(start, start + Size); } else if (CompressionBlocks.Length > 0) { // Get the right pointer to start copying the CompressionBlocks information from. // Alignment of the compressed blocks var CompressedBlockAlignment = Encrypted ? 16 : 1; // CompressedBlockOffset is the starting offset. Everything else can be derived from there. long CompressedBlockOffset = BaseOffset + FPakEntry.GetSize(EPakVersion.LATEST, CompressionMethodIndex, CompressionBlocksCount); for (int CompressionBlockIndex = 0; CompressionBlockIndex < CompressionBlocks.Length; ++CompressionBlockIndex) { FPakCompressedBlock CompressionBlock = new FPakCompressedBlock(CompressedBlockOffset, CompressedBlockOffset + BitConverter.ToUInt32(encodedPakEntries, pakLocation)); CompressionBlocks[CompressionBlockIndex] = CompressionBlock; CompressedBlockOffset += BinaryHelper.Align(CompressionBlock.CompressedEnd - CompressionBlock.CompressedStart, CompressedBlockAlignment); pakLocation += 4; } } return(new FPakEntry(this.FileName, name, Offset, Size, UncompressedSize, CompressionBlocks, CompressionBlockSize, CompressionMethodIndex, (byte)((Encrypted ? 0x01 : 0x00) | (Deleted ? 0x02 : 0x00)))); } else { throw new FileLoadException("list indexes aren't supported"); } }