Example #1
0
        public MemoryStream DecompressEntry(FileEntryStruct e)
        {
            //Debug.WriteLine("Decompressing " + e.FileName);
            MemoryStream result = new MemoryStream();
            uint         count  = 0;

            byte[]     inputBlock;
            long       left = e.RealUncompressedSize;
            FileStream fs   = new FileStream(FileName, FileMode.Open, FileAccess.Read);

            fs.Seek(e.BlockOffsets[0], SeekOrigin.Begin);
            byte[] buff;
            if (e.BlockSizeTableIndex == 0xFFFFFFFF)
            {
                buff = new byte[e.RealUncompressedSize];
                fs.Read(buff, 0, buff.Length);
                result.Write(buff, 0, buff.Length);
            }
            else
            {
                while (left > 0)
                {
                    uint compressedBlockSize = e.BlockSizes[count];
                    if (compressedBlockSize == 0)
                    {
                        compressedBlockSize = Header.MaxBlockSize;
                    }
                    if (compressedBlockSize == Header.MaxBlockSize || compressedBlockSize == left)
                    {
                        //uncompressed?
                        buff = new byte[compressedBlockSize];
                        fs.Read(buff, 0, buff.Length);

                        result.Write(buff, 0, buff.Length);
                        left -= compressedBlockSize;
                    }
                    else
                    {
                        var uncompressedBlockSize = (uint)Math.Min(left, Header.MaxBlockSize);
                        if (compressedBlockSize < 5)
                        {
                            throw new Exception("compressed block size smaller than 5");
                        }

                        inputBlock = new byte[compressedBlockSize];
                        //Debug.WriteLine($"Decompressing at 0x{fs.Position:X8}");
                        fs.Read(inputBlock, 0, (int)compressedBlockSize);
                        uint actualUncompressedBlockSize = uncompressedBlockSize;
                        if (Header.CompressionScheme == "amzl" /* PC */ || Header.CompressionScheme == "lzma" /* PS3 (it doesn't appear to actually be LZMA!), WiiU */)
                        {
                            //if (Header.CompressionScheme == "lzma")
                            //{
                            //PS3 - This doesn't work. I'm not sure what kind of LZMA this uses but it has seemingly no header
                            //var attachedHeader = new byte[inputBlock.Length + 5];
                            //attachedHeader[0] = 0x5D;
                            ////attachedHeader[1] = (byte) (Header.Version >> 24);
                            ////attachedHeader[2] = (byte)(Header.Version >> 16);
                            ////attachedHeader[3] = (byte)(Header.Version >> 8);
                            ////attachedHeader[4] = (byte) Header.Version;
                            //attachedHeader[1] = (byte)Header.Version;
                            //attachedHeader[2] = (byte)(Header.Version >> 8);
                            //attachedHeader[3] = (byte)(Header.Version >> 16);
                            //attachedHeader[4] = (byte)(Header.Version >> 24);
                            //Buffer.BlockCopy(inputBlock,0,attachedHeader,5, inputBlock.Length);
                            //inputBlock = attachedHeader;
                            //}

                            var outputBlock = LZMA.Decompress(inputBlock, actualUncompressedBlockSize);
                            if (outputBlock.Length != actualUncompressedBlockSize)
                            {
                                throw new Exception("SFAR LZMA Decompression Error");
                            }
                            result.Write(outputBlock, 0, (int)actualUncompressedBlockSize);
                            left -= uncompressedBlockSize;
                            //continue;
                        }

                        if (Header.CompressionScheme == "lzx") //Xbox
                        {
                            var outputBlock  = new byte[actualUncompressedBlockSize];
                            var decompResult = LZX.Decompress(inputBlock, (uint)inputBlock.Length, outputBlock);
                            if (decompResult != 0)
                            {
                                throw new Exception("SFAR LZX Decompression Error");
                            }
                            result.Write(outputBlock, 0, (int)actualUncompressedBlockSize);
                            left -= uncompressedBlockSize;
                        }
                    }
                    count++;
                }
            }
            fs.Close();
            result.Position = 0;
            return(result);
        }
        //        #region Decompression


        //        // REMOVE THIS METHOD AND USE THE STANDARDIZED ONE
        //        /// <summary>
        //        ///     decompress an entire ME3, 2, or 1 package file.
        //        /// </summary>
        //        /// <param name="pccFileName">pcc file's name to open.</param>
        //        /// <returns>a decompressed array of bytes.</returns>
        //        //public static Stream Decompress(string pccFileName)
        //        //{
        //        //    using (FileStream input = File.OpenRead(pccFileName))
        //        //    {
        //        //        EndianReader packageReader = EndianReader.SetupForPackageReading(input);
        //        //        packageReader.SkipInt32(); //skip package tag
        //        //        var versionLicenseePacked = packageReader.ReadUInt32();
        //        //        var unrealVersion = (ushort)(versionLicenseePacked & 0xFFFF);
        //        //        var licenseeVersion = (ushort)(versionLicenseePacked >> 16);

        //        //        //ME3
        //        //        if ((unrealVersion == MEPackage.ME3UnrealVersion || unrealVersion == MEPackage.ME3WiiUUnrealVersion) && licenseeVersion == MEPackage.ME3LicenseeVersion)
        //        //        {
        //        //            return DecompressME3(packageReader);
        //        //        }
        //        //        //Support other platforms
        //        //        //ME2 || ME1
        //        //        else if (unrealVersion == 512 && licenseeVersion == 130 || unrealVersion == 491 && licenseeVersion == 1008)
        //        //        {
        //        //            return DecompressME1orME2(input);
        //        //        }
        //        //        else
        //        //        {
        //        //            throw new FormatException("Not an ME1, ME2, or ME3 package file.");
        //        //        }
        //        //    }
        //        //}

        //        // REMOVE THIS METHOD AND USE THE STANDARDIZED ONE
        //        /// <summary>
        //        ///     decompress an entire ME1 or 2 pcc file.
        //        /// </summary>
        //        /// <param name="raw">pcc file passed in stream format</param>
        //        /// <returns>a decompressed stream.</returns>
        //        //public static MemoryStream DecompressME1orME2(Stream raw)
        //        //{
        //        //    raw.Seek(4, SeekOrigin.Begin);
        //        //    ushort versionLo = raw.ReadUInt16();
        //        //    ushort versionHi = raw.ReadUInt16();
        //        //    raw.Seek(12, SeekOrigin.Begin);
        //        //    int tempNameSize = raw.ReadInt32();
        //        //    raw.Seek(64 + tempNameSize, SeekOrigin.Begin);
        //        //    int tempGenerations = raw.ReadInt32();
        //        //    raw.Seek(32 + tempGenerations * 12, SeekOrigin.Current);

        //        //    //if ME1
        //        //    if (versionLo == 491 && versionHi == 1008)
        //        //    {
        //        //        raw.Seek(4, SeekOrigin.Current);
        //        //    }
        //        //    UnrealPackageFile.CompressionType compressionType = (UnrealPackageFile.CompressionType)raw.ReadUInt32();


        //        //    int pos = 4;
        //        //    int NumChunks = raw.ReadInt32();
        //        //    var Chunks = new List<Chunk>();

        //        //    //DebugOutput.PrintLn("Reading chunk headers...");
        //        //    for (int i = 0; i < NumChunks; i++)
        //        //    {
        //        //        Chunk c = new Chunk
        //        //        {
        //        //            uncompressedOffset = raw.ReadInt32(),
        //        //            uncompressedSize = raw.ReadInt32(),
        //        //            compressedOffset = raw.ReadInt32(),
        //        //            compressedSize = raw.ReadInt32()
        //        //        };
        //        //        c.Compressed = new byte[c.compressedSize];
        //        //        c.Uncompressed = new byte[c.uncompressedSize];
        //        //        //DebugOutput.PrintLn("Chunk " + i + ", compressed size = " + c.compressedSize + ", uncompressed size = " + c.uncompressedSize);
        //        //        //DebugOutput.PrintLn("Compressed offset = " + c.compressedOffset + ", uncompressed offset = " + c.uncompressedOffset);
        //        //        Chunks.Add(c);
        //        //    }

        //        //    //DebugOutput.PrintLn("\tRead Chunks...");
        //        //    int count = 0;
        //        //    for (int i = 0; i < Chunks.Count; i++)
        //        //    {
        //        //        Chunk c = Chunks[i];
        //        //        raw.Seek(c.compressedOffset, SeekOrigin.Begin);
        //        //        c.Compressed = raw.ReadToBuffer(c.compressedSize);

        //        //        ChunkHeader h = new ChunkHeader
        //        //        {
        //        //            magic = BitConverter.ToInt32(c.Compressed, 0),
        //        //            blocksize = BitConverter.ToInt32(c.Compressed, 4),
        //        //            compressedsize = BitConverter.ToInt32(c.Compressed, 8),
        //        //            uncompressedsize = BitConverter.ToInt32(c.Compressed, 12)
        //        //        };
        //        //        if (h.magic != -1641380927)
        //        //            throw new FormatException("Chunk magic number incorrect");
        //        //        //DebugOutput.PrintLn("Chunkheader read: Magic = " + h.magic + ", Blocksize = " + h.blocksize + ", Compressed Size = " + h.compressedsize + ", Uncompressed size = " + h.uncompressedsize);
        //        //        pos = 16;
        //        //        int blockCount = (h.uncompressedsize % h.blocksize == 0)
        //        //            ?
        //        //            h.uncompressedsize / h.blocksize
        //        //            :
        //        //            h.uncompressedsize / h.blocksize + 1;
        //        //        var BlockList = new List<Block>();
        //        //        //DebugOutput.PrintLn("\t\t" + count + " Read Blockheaders...");
        //        //        for (int j = 0; j < blockCount; j++)
        //        //        {
        //        //            Block b = new Block
        //        //            {
        //        //                compressedsize = BitConverter.ToInt32(c.Compressed, pos),
        //        //                uncompressedsize = BitConverter.ToInt32(c.Compressed, pos + 4)
        //        //            };
        //        //            //DebugOutput.PrintLn("Block " + j + ", compressed size = " + b.compressedsize + ", uncompressed size = " + b.uncompressedsize);
        //        //            pos += 8;
        //        //            BlockList.Add(b);
        //        //        }
        //        //        int outpos = 0;
        //        //        //DebugOutput.PrintLn("\t\t" + count + " Read and decompress Blocks...");
        //        //        foreach (Block b in BlockList)
        //        //        {
        //        //            var datain = new byte[b.compressedsize];
        //        //            var dataout = new byte[b.uncompressedsize];
        //        //            for (int j = 0; j < b.compressedsize; j++)
        //        //                datain[j] = c.Compressed[pos + j];
        //        //            pos += b.compressedsize;

        //        //            switch (compressionType)
        //        //            {
        //        //                case UnrealPackageFile.CompressionType.LZO:
        //        //                    {
        //        //                        if (
        //        //                                LZO2.Decompress(datain, (uint)datain.Length, dataout) != b.uncompressedsize)
        //        //                            throw new Exception("LZO decompression failed!");
        //        //                        break;
        //        //                    }
        //        //                case UnrealPackageFile.CompressionType.Zlib:
        //        //                    {
        //        //                        if (ZlibHelper.Zlib.Decompress(datain, (uint)datain.Length, dataout) != b.uncompressedsize)
        //        //                            throw new Exception("Zlib decompression failed!");
        //        //                        break;
        //        //                    }
        //        //                default:
        //        //                    throw new Exception("Unknown compression type for this package.");
        //        //            }
        //        //            for (int j = 0; j < b.uncompressedsize; j++)
        //        //                c.Uncompressed[outpos + j] = dataout[j];
        //        //            outpos += b.uncompressedsize;
        //        //        }
        //        //        c.header = h;
        //        //        c.blocks = BlockList;
        //        //        count++;
        //        //        Chunks[i] = c;
        //        //    }

        //        //    MemoryStream result = new MemoryStream();
        //        //    foreach (Chunk c in Chunks)
        //        //    {
        //        //        result.Seek(c.uncompressedOffset, SeekOrigin.Begin);
        //        //        result.WriteFromBuffer(c.Uncompressed);
        //        //    }

        //        //    return result;
        //        //}

        //        /// <summary>
        //        /// Reads
        //        /// </summary>
        //        /// <param name="input"></param>
        //        /// <param name="compressionType"></param>
        //        /// <returns></returns>
        //        public static byte[] DecompressChunks(EndianReader input, List<Chunk> chunkTable, UnrealPackageFile.CompressionType compressionType)
        //        {
        //            var fullUncompressedSize = chunkTable.Sum(x => x.uncompressedSize);
        //            byte[] decompressedBuffer = new byte[fullUncompressedSize];
        //            foreach (var chunk in chunkTable)
        //            {
        //                //Header of individual chunk
        //                input.Seek(chunk.compressedOffset, SeekOrigin.Begin);
        //                var uncompressedOffset = input.ReadUInt32(); //where to write to into the decompressed buffer
        //                var uncompressedSize = input.ReadUInt32(); //how much to write
        //                var compressedOffset = input.ReadUInt32(); //where to read from
        //                var compressedSize = input.ReadUInt32(); //how much to read
        //                var firstBlockInfoOffset = (int)input.Position;

        //                var buff = new byte[compressedSize];
        //                input.Seek(compressedOffset, SeekOrigin.Begin);
        //                input.Read(buff, 0, buff.Length);
        //                if (compressionType == UnrealPackageFile.CompressionType.Zlib)
        //                {

        //                }
        //                else if (compressionType == UnrealPackageFile.CompressionType.LZMA)
        //                {

        //                }
        //                //AmaroK86.MassEffect3.ZlibBlock.ZBlock.Decompress(buff, i);
        //                //tasks[i] = AmaroK86.MassEffect3.ZlibBlock.ZBlock.Decompress(buff, i);
        //            }

        //            return decompressedBuffer;
        //        }

        //        #region Block decompression
        //        public static readonly uint zlibmagic = 0x9E2A83C1;
        //        public static readonly uint zlibmaxsegmentsize = 0x20000;


        //        public static byte[] DecompressZLibBlock(byte[] buffer, int num = 0)
        //        {
        //            if (buffer == null)
        //                throw new ArgumentNullException();
        //            using (MemoryStream buffStream = new MemoryStream(buffer))
        //            {
        //                EndianReader reader = EndianReader.SetupForReading(buffStream, (int)zlibmagic, out int zlibBlockMagic);
        //                if ((uint)zlibBlockMagic != zlibmagic)
        //                {
        //                    throw new InvalidDataException("found an invalid zlib block, wrong magic");
        //                }

        //                uint buffMaxSegmentSize = reader.ReadUInt32();
        //                if (buffMaxSegmentSize != zlibmaxsegmentsize)
        //                {
        //                    throw new FormatException("Wrong segment size for ZLIB!");
        //                }

        //                uint totComprSize = reader.ReadUInt32();
        //                uint totUncomprSize = reader.ReadUInt32();

        //                byte[] outputBuffer = new byte[totUncomprSize];
        //                int numOfSegm = (int)Math.Ceiling(totUncomprSize / (double)zlibmaxsegmentsize);
        //                int headSegm = 16;
        //                int dataSegm = headSegm + (numOfSegm * 8);
        //                int buffOff = 0;

        //                for (int i = 0; i < numOfSegm; i++)
        //                {
        //                    reader.Seek(headSegm, SeekOrigin.Begin);
        //                    int comprSegm = reader.ReadInt32();
        //                    int uncomprSegm = reader.ReadInt32();
        //                    headSegm = (int)reader.Position;

        //                    reader.Seek(dataSegm, SeekOrigin.Begin);
        //                    //Console.WriteLine("compr size: {0}, uncompr size: {1}, data offset: 0x{2:X8}", comprSegm, uncomprSegm, dataSegm);
        //                    byte[] src = reader.ReadBytes(comprSegm);
        //                    byte[] dst = new byte[uncomprSegm];
        //                    if (Zlib.Decompress(src, (uint)src.Length, dst) != uncomprSegm)
        //                        throw new Exception("Zlib decompression failed!");

        //                    Buffer.BlockCopy(dst, 0, outputBuffer, buffOff, uncomprSegm);

        //                    buffOff += uncomprSegm;
        //                    dataSegm += comprSegm;
        //                }

        //                reader.Close();
        //                return outputBuffer;
        //            }
        //        }
        //        #endregion

        //        /// <summary>
        //        ///     decompress an entire ME3 pcc file into a new stream
        //        /// </summary>
        //        /// <param name="input">pcc file passed in stream format (wrapped in endianreader)</param>
        //        /// <returns>a decompressed array of bytes</returns>
        //        //public static MemoryStream DecompressME3(EndianReader input)
        //        //{
        //        //    input.Seek(0, SeekOrigin.Begin);
        //        //    var magic = input.ReadUInt32();
        //        //    if (magic != 0x9E2A83C1)
        //        //    {
        //        //        throw new FormatException("not a pcc file");
        //        //    }

        //        //    var versionLo = input.ReadUInt16();
        //        //    var versionHi = input.ReadUInt16();

        //        //    //if (versionLo != 684 &&
        //        //    //    versionHi != 194)
        //        //    //{
        //        //    //    throw new FormatException("unsupported pcc version");
        //        //    //}

        //        //    long headerSize = 8;

        //        //    input.Seek(4, SeekOrigin.Current);
        //        //    headerSize += 4;

        //        //    var folderNameLength = input.ReadInt32();
        //        //    headerSize += 4;

        //        //    var folderNameByteLength =
        //        //        folderNameLength >= 0 ? folderNameLength : (-folderNameLength * 2);
        //        //    input.Seek(folderNameByteLength, SeekOrigin.Current);
        //        //    headerSize += folderNameByteLength;

        //        //    var packageFlagsOffset = input.Position;
        //        //    var packageFlags = input.ReadUInt32();
        //        //    headerSize += 4;

        //        //    if ((packageFlags & 0x02000000u) == 0)
        //        //    {
        //        //        throw new FormatException("pcc file is already decompressed");
        //        //    }

        //        //    if ((packageFlags & 8) != 0)
        //        //    {
        //        //        input.Seek(4, SeekOrigin.Current);
        //        //        headerSize += 4;
        //        //    }

        //        //    uint nameCount = input.ReadUInt32();
        //        //    uint nameOffset = input.ReadUInt32();

        //        //    input.Seek(52, SeekOrigin.Current);
        //        //    headerSize += 60;

        //        //    var generationsCount = input.ReadUInt32();
        //        //    input.Seek(generationsCount * 12, SeekOrigin.Current);
        //        //    headerSize += generationsCount * 12;

        //        //    input.Seek(20, SeekOrigin.Current);
        //        //    headerSize += 24;

        //        //    var blockCount = input.ReadUInt32();
        //        //    int headBlockOff = (int)input.Position;
        //        //    var afterBlockTableOffset = headBlockOff + (blockCount * 16);
        //        //    var indataOffset = afterBlockTableOffset + 8;

        //        //    input.Seek(0, SeekOrigin.Begin);
        //        //    MemoryStream output = new MemoryStream();
        //        //    output.Seek(0, SeekOrigin.Begin);

        //        //    output.WriteFromStream(input.BaseStream, headerSize);
        //        //    output.WriteUInt32(0);// block count

        //        //    input.Seek(afterBlockTableOffset, SeekOrigin.Begin);
        //        //    output.WriteFromStream(input.BaseStream, 8);

        //        //    //check if has extra name list (don't know it's usage...)
        //        //    if ((packageFlags & 0x10000000) != 0)
        //        //    {
        //        //        long curPos = output.Position;
        //        //        output.WriteFromStream(input.BaseStream, nameOffset - curPos);
        //        //    }

        //        //    //decompress blocks in parallel
        //        //    var tasks = new Task<byte[]>[blockCount];
        //        //    var uncompressedOffsets = new uint[blockCount];
        //        //    for (int i = 0; i < blockCount; i++)
        //        //    {
        //        //        input.Seek(headBlockOff, SeekOrigin.Begin);
        //        //        uncompressedOffsets[i] = input.ReadUInt32();
        //        //        var uncompressedSize = input.ReadUInt32();
        //        //        var compressedOffset = input.ReadUInt32();
        //        //        var compressedSize = input.ReadUInt32();
        //        //        headBlockOff = (int)input.Position;

        //        //        var buff = new byte[compressedSize];
        //        //        input.Seek(compressedOffset, SeekOrigin.Begin);
        //        //        input.Read(buff, 0, buff.Length);
        //        //        AmaroK86.MassEffect3.ZlibBlock.ZBlock.Decompress(buff, i);
        //        //        //tasks[i] = AmaroK86.MassEffect3.ZlibBlock.ZBlock.Decompress(buff, i);
        //        //    }
        //        //    Task.WaitAll(tasks);
        //        //    for (int i = 0; i < blockCount; i++)
        //        //    {
        //        //        output.Seek(uncompressedOffsets[i], SeekOrigin.Begin);
        //        //        output.WriteFromBuffer(tasks[i].Result);
        //        //    }

        //        //    //Do not change the IsCompressed bit as it will not accurately reflect the state of the file on disk.
        //        //    //output.Seek(packageFlagsOffset, SeekOrigin.Begin);
        //        //    //output.WriteUInt32(packageFlags & ~0x02000000u, ); //Mark file as decompressed.
        //        //    return output;
        //        //}


        /// <summary>
        /// Decompresses a fully compressed package file. These only occur on console platforms.
        /// </summary>
        /// <param name="rawInput">Input stream to read from</param>
        /// <param name="compressionType">Known compression type of package. If this is not known, it will attempt to be determined, and this variable will be updated.</param>
        /// <returns></returns>
        public static MemoryStream DecompressFullyCompressedPackage(EndianReader rawInput, ref UnrealPackageFile.CompressionType compressionType)
        {
            rawInput.Position = 0;
            var magic            = rawInput.ReadInt32();
            var blockSize        = rawInput.ReadInt32();
            var compressedSize   = rawInput.ReadInt32();
            var decompressedSize = rawInput.ReadInt32();

            var blockCount = 0;

            if (decompressedSize < blockSize)
            {
                blockCount = 1;
            }
            else
            {
                // Calculate the number of blocks
                blockCount = decompressedSize / blockSize;
                if (decompressedSize % blockSize != 0)
                {
                    blockCount++;                                    //Add one to decompress the final data
                }
            }


            MemoryStream outStream = new MemoryStream();
            List <(int blockCompressedSize, int blockDecompressedSize)> blockTable = new List <(int blockCompressedSize, int blockDecompressedSize)>();

            // Read Block Table
            int i = 0;

            while (i < blockCount)
            {
                blockTable.Add((rawInput.ReadInt32(), rawInput.ReadInt32()));
                i++;
            }

            int index = 0;

            foreach (var btInfo in blockTable)
            {
                // Decompress
                //Debug.WriteLine($"Decompressing data at 0x{raw.Position:X8}");
                var datain = rawInput.ReadToBuffer(btInfo.blockCompressedSize);
                if (compressionType == UnrealPackageFile.CompressionType.None)
                {
                    // We have to determine if it's LZMA or LZX based on first few bytes
                    if (datain[0] == 0x5D && BitConverter.ToInt32(datain, 1) == 0x10000)
                    {
                        // This is LZMA header
                        Debug.WriteLine("Fully compressed package: Detected LZMA compression");
                        compressionType = UnrealPackageFile.CompressionType.LZMA;
                    }
                    else
                    {
                        Debug.WriteLine("Fully compressed package: Detected LZX compression");
                        compressionType = UnrealPackageFile.CompressionType.LZX;
                    }
                }

                var dataout = new byte[btInfo.blockDecompressedSize];
                if (dataout.Length == datain.Length)
                {
                    // WiiU SFXGame has weird case where one single block has same sizes and does not have LZMA compression flag for some reason
                    dataout = datain;
                }
                else
                {
                    switch (compressionType)
                    {
                    case UnrealPackageFile.CompressionType.LZO:
                        if (LZO2.Decompress(datain, (uint)datain.Length, dataout) != btInfo.blockDecompressedSize)
                        {
                            throw new Exception("LZO decompression failed!");
                        }
                        break;

                    case UnrealPackageFile.CompressionType.Zlib:
                        if (Zlib.Decompress(datain, (uint)datain.Length, dataout) != btInfo.blockDecompressedSize)
                        {
                            throw new Exception("Zlib decompression failed!");
                        }
                        break;

                    case UnrealPackageFile.CompressionType.LZMA:
                        dataout = LZMA.Decompress(datain, (uint)btInfo.blockDecompressedSize);
                        if (dataout.Length != btInfo.blockDecompressedSize)
                        {
                            throw new Exception("LZMA decompression failed!");
                        }
                        break;

                    case UnrealPackageFile.CompressionType.LZX:
                        if (LZX.Decompress(datain, (uint)datain.Length, dataout) != 0)
                        {
                            throw new Exception("LZX decompression failed!");
                        }
                        break;

                    default:
                        throw new Exception("Unknown compression type for this package.");
                    }
                }

                index++;
                outStream.WriteFromBuffer(dataout);
            }

            outStream.Position = 0;
            return(outStream);
        }
        /// <summary>
        /// Decompresses a compressed package. Works with ME1/ME2/ME3/UDK
        /// </summary>
        /// <param name="raw"></param>
        /// <param name="compressionInfoOffset"></param>
        /// <param name="compressionType"></param>
        /// <param name="NumChunks"></param>
        /// <param name="game"></param>
        /// <param name="platform"></param>
        /// <returns></returns>
        public static MemoryStream DecompressPackage(EndianReader raw, long compressionInfoOffset, UnrealPackageFile.CompressionType compressionType = UnrealPackageFile.CompressionType.None, int NumChunks = 0, MEGame game = MEGame.Unknown, GamePlatform platform = GamePlatform.PC)
        {
            raw.BaseStream.JumpTo(compressionInfoOffset);
            if (compressionType == UnrealPackageFile.CompressionType.None)
            {
                compressionType = (UnrealPackageFile.CompressionType)raw.ReadUInt32();
            }

            if (NumChunks == 0)
            {
                NumChunks = raw.ReadInt32();
            }
            var Chunks          = new List <Chunk>();
            var chunkTableStart = raw.Position;

            //DebugOutput.PrintLn("Reading chunk headers...");
            for (int i = 0; i < NumChunks; i++)
            {
                Chunk c = new Chunk
                {
                    uncompressedOffset = raw.ReadInt32(),
                    uncompressedSize   = raw.ReadInt32(),
                    compressedOffset   = raw.ReadInt32(),
                    compressedSize     = raw.ReadInt32()
                };
                c.Compressed   = new byte[c.compressedSize];
                c.Uncompressed = new byte[c.uncompressedSize];
                Chunks.Add(c);
            }


            //DebugOutput.PrintLn("\tRead Chunks...");
            int count = 0;

            for (int i = 0; i < Chunks.Count; i++)
            {
                Chunk c = Chunks[i];
                //Debug.WriteLine($"Compressed offset at {c.compressedOffset:X8}");
                raw.Seek(c.compressedOffset, SeekOrigin.Begin);
                raw.Read(c.Compressed, 0, c.compressedSize);

                ChunkHeader h = new ChunkHeader
                {
                    magic = EndianReader.ToInt32(c.Compressed, 0, raw.Endian),
                    // must force block size for ME1 xbox cause in place of block size it seems to list package tag again which breaks loads of things
                    blocksize        = (platform == GamePlatform.Xenon && game == MEGame.ME1) ? 0x20000 : EndianReader.ToInt32(c.Compressed, 4, raw.Endian),
                    compressedsize   = EndianReader.ToInt32(c.Compressed, 8, raw.Endian),
                    uncompressedsize = EndianReader.ToInt32(c.Compressed, 12, raw.Endian)
                };

                if (h.magic != -1641380927)
                {
                    throw new FormatException("Chunk magic number incorrect");
                }
                //DebugOutput.PrintLn("Chunkheader read: Magic = " + h.magic + ", Blocksize = " + h.blocksize + ", Compressed Size = " + h.compressedsize + ", Uncompressed size = " + h.uncompressedsize);
                int pos        = 16;
                int blockCount = h.uncompressedsize / h.blocksize;
                if (h.uncompressedsize % h.blocksize != 0)
                {
                    blockCount++;
                }
                var BlockList = new List <Block>();
                //DebugOutput.PrintLn("\t\t" + count + " Read Blockheaders...");
                for (int j = 0; j < blockCount; j++)
                {
                    Block b = new Block
                    {
                        compressedsize   = EndianReader.ToInt32(c.Compressed, pos, raw.Endian),
                        uncompressedsize = EndianReader.ToInt32(c.Compressed, pos + 4, raw.Endian)
                    };
                    //DebugOutput.PrintLn("Block " + j + ", compressed size = " + b.compressedsize + ", uncompressed size = " + b.uncompressedsize);
                    pos += 8;
                    BlockList.Add(b);
                }
                int outpos   = 0;
                int blocknum = 0;
                //DebugOutput.PrintLn("\t\t" + count + " Read and decompress Blocks...");
                foreach (Block b in BlockList)
                {
                    //Debug.WriteLine("Decompressing block " + blocknum);
                    var datain  = new byte[b.compressedsize];
                    var dataout = new byte[b.uncompressedsize];
                    Buffer.BlockCopy(c.Compressed, pos, datain, 0, b.compressedsize);
                    //for (int j = 0; j < b.compressedsize; j++)
                    //    datain[j] = c.Compressed[pos + j];
                    pos += b.compressedsize;

                    switch (compressionType)
                    {
                    case UnrealPackageFile.CompressionType.LZO:
                    {
                        if (LZO2.Decompress(datain, (uint)datain.Length, dataout) != b.uncompressedsize)
                        {
                            throw new Exception("LZO decompression failed!");
                        }
                        break;
                    }

                    case UnrealPackageFile.CompressionType.Zlib:
                    {
                        if (Zlib.Decompress(datain, (uint)datain.Length, dataout) != b.uncompressedsize)
                        {
                            throw new Exception("Zlib decompression failed!");
                        }
                        break;
                    }

                    case UnrealPackageFile.CompressionType.LZMA:
                        dataout = LZMA.Decompress(datain, (uint)b.uncompressedsize);
                        if (dataout.Length != b.uncompressedsize)
                        {
                            throw new Exception("LZMA decompression failed!");
                        }
                        break;

                    case UnrealPackageFile.CompressionType.LZX:
                        if (LZX.Decompress(datain, (uint)datain.Length, dataout) != 0)
                        {
                            throw new Exception("LZX decompression failed!");
                        }
                        break;

                    default:
                        throw new Exception("Unknown compression type for this package.");
                    }
                    for (int j = 0; j < b.uncompressedsize; j++)
                    {
                        c.Uncompressed[outpos + j] = dataout[j];
                    }
                    outpos += b.uncompressedsize;
                    blocknum++;
                }
                c.header = h;
                c.blocks = BlockList;
                count++;
                Chunks[i] = c;
            }

            MemoryStream result = new MemoryStream();

            foreach (Chunk c in Chunks)
            {
                result.Seek(c.uncompressedOffset, SeekOrigin.Begin);
                result.WriteFromBuffer(c.Uncompressed);
            }

            // Reattach the original header
            result.Position = 0;
            raw.Position    = 0;
            raw.BaseStream.CopyToEx(result, Chunks.MinBy(x => x.uncompressedOffset).uncompressedOffset); // Copy the header in
            // Does header need adjusted here to be accurate?
            // Do we change it to show decompressed, as the actual state, or the state of what it was on disk?

            return(result);
        }
Example #4
0
        /// <summary>
        ///     Extracts data from a Brutal Legend package.
        /// </summary>
        /// <param name="HeaderFile">The *.~h Header file path</param>
        /// <param name="DataFile">The ~.~p Data file path</param>
        /// <param name="OutFolder">The output folder</param>
        /// <returns>False if header file is invalid, true otherwise</returns>
        public static bool Extract(string HeaderFile, string DataFile, string OutFolder)
        {
            FileStream Header = new FileStream(HeaderFile, FileMode.Open);
            FileStream Data   = new FileStream(DataFile, FileMode.Open);

            EndianBinaryReader Reader = new EndianBinaryReader(Header, Endian.Big);

            if (StringUtilities.ReadASCIIString(Header, 4) != "dfpf")
            {
                return(false);
            }

            Header.Seek(0x14, SeekOrigin.Begin);
            uint StringsTableOffset = Reader.ReadUInt32();
            uint StringsTableFlags  = Reader.ReadUInt32();
            uint StringsTableLength = Reader.ReadUInt32();
            uint FilesCount         = Reader.ReadUInt32();

            Header.Seek(0x18, SeekOrigin.Current);
            uint FilesTableOffset = Reader.ReadUInt32();

            for (int Index = 0; Index < FilesCount; Index++)
            {
                Header.Seek(FilesTableOffset + Index * 0x10, SeekOrigin.Begin);

                //Lengths
                uint DecompressedLength = Reader.ReadUInt24();
                uint LengthDifference   = (uint)(Reader.ReadUInt16() << 1) | ((DecompressedLength & 1) << 17); //Probably for "obfuscation"
                uint CompressedLength   = Reader.ReadUInt24();

                LengthDifference  |= CompressedLength >> 23;
                DecompressedLength = (DecompressedLength >> 1) + LengthDifference;
                CompressedLength   = (CompressedLength & 0x7fffff) >> 1;

                //Offsets
                uint DataOffset = Reader.ReadUInt24() << 5;
                byte DataFormat = Reader.ReadByte();
                DataOffset |= (uint)(DataFormat & 0xf8) >> 3;
                DataFormat &= 7;

                uint NameOffset = (Reader.ReadUInt24() >> 3) + StringsTableOffset;
                byte Flags      = Reader.ReadByte();

                Header.Seek(NameOffset, SeekOrigin.Begin);
                string FileName = StringUtilities.ReadASCIIString(Header);

                if (OnStatusReport != null)
                {
                    BrutalStatusReport Report = new BrutalStatusReport();

                    Report.Status         = "Extracting " + FileName + "...";
                    Report.ProcessedFiles = Index;
                    Report.TotalFiles     = (int)FilesCount;

                    OnStatusReport(null, Report);
                }

                Data.Seek(DataOffset, SeekOrigin.Begin);
                byte[] Buffer = new byte[CompressedLength];
                Data.Read(Buffer, 0, Buffer.Length);

                ICompression Decompressor;
                switch ((CompressionType)((Flags >> 1) & 3))
                {
                case CompressionType.None: Decompressor = new NoCompression(); break;

                case CompressionType.ZLib: Decompressor = new ZLib(); break;

                case CompressionType.LZX: Decompressor = new LZX(); break;

                default: throw new Exception("Unknown compression!");
                }

                Buffer = Decompressor.Decompress(Buffer, DecompressedLength);

                string FullName = Path.Combine(OutFolder, FileName);
                string DirName  = Path.GetDirectoryName(FullName);
                if (!Directory.Exists(DirName))
                {
                    Directory.CreateDirectory(DirName);
                }
                File.WriteAllBytes(FullName + ".bin", Buffer);
            }

            Header.Close();
            Data.Close();

            return(true);
        }