Beispiel #1
0
        static void Pack(string inPath, string outFile, bool eofEntry)
        {
            List <SubFile?> entries = new List <SubFile?>();

            // Parse every file.
            foreach (string file in Directory.GetFiles(inPath))
            {
                // Parse filename.
                string fileName = Path.GetFileName(file);

                int dotPos = fileName.IndexOf('.');
                if (dotPos < 0)
                {
                    dotPos = fileName.Length;
                }
                int uscPos = fileName.LastIndexOf('_', dotPos);

                string numString = fileName.Substring(uscPos + 1, dotPos - uscPos - 1);
                if (!Int32.TryParse(numString, out int fileNum))
                {
                    // No number string found; skip this file.
                    continue;
                }

                // Expand file entries if necessary.
                while (entries.Count <= fileNum)
                {
                    entries.Add(null);
                }

                // Read the file.
                byte[] buffer;
                long   size;
                bool   compressed;
                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                    // Check if the file is compressed.
                    using (MemoryStream ms = new MemoryStream()) {
                        fs.Position = 0;
                        ms.Position = 0;
                        if (LZ77.Decompress(fs, ms) && ms.Position > 8)
                        {
                            compressed = true;
                            size       = ms.Position;
                        }
                        else
                        {
                            compressed = false;
                            size       = fs.Length;
                        }
                    }

                    if (size > int.MaxValue)
                    {
                        if (compressed)
                        {
                            throw new IOException("Uncompressed size of file " + fileName + " exceeds " + int.MaxValue + " bytes.");
                        }
                        else
                        {
                            throw new IOException("Size of file " + fileName + " exceeds " + int.MaxValue + " bytes.");
                        }
                    }

                    buffer      = new byte[fs.Length];
                    fs.Position = 0;
                    if (fs.Read(buffer, 0, buffer.Length) != fs.Length)
                    {
                        throw new IOException("Could not read entirety of input file " + fileName + ".");
                    }
                }

                SubFile entry = new SubFile()
                {
                    Size       = (int)size,
                    Compressed = compressed,
                    Data       = buffer
                };
                entries[fileNum] = entry;
            }
            if (eofEntry)
            {
                // Append EOF entry.
                entries.Add(null);
            }

            // Create directory for the output file.
            string dir = Path.GetDirectoryName(Path.GetFullPath(outFile));

            if (dir.Length > 0)
            {
                Directory.CreateDirectory(dir);
            }

            // Create the output file.
            using (FileStream fs = new FileStream(outFile, FileMode.Create, FileAccess.Write, FileShare.Write)) {
                BinaryWriter bw = new BinaryWriter(fs);

                // Write the header. (Account for terminator entry.)
                long filePos = (entries.Count + 1) * 8;
                for (int i = 0; i < entries.Count; i++)
                {
                    if (entries[i] == null)
                    {
                        // Write empty entry.
                        bw.Write((uint)filePos);
                        bw.Write((uint)0);
                        continue;
                    }

                    // Update entry with offset.
                    SubFile entry = (SubFile)entries[i];
                    entry.Offset = (uint)filePos;
                    entries[i]   = entry;

                    // Write entry.
                    bw.Write((uint)entry.Offset);
                    bw.Write((uint)((uint)entry.Size | (entry.Compressed ? 0x80000000 : 0)));

                    // If file was compressed, round up to multiple of 4.
                    long size = entry.Data.Length;
                    if (entry.Compressed && size % 4 != 0)
                    {
                        size += 4 - (size % 4);
                    }

                    filePos += entry.Data.LongLength;
                    // Round next file offset up to multiple of 4. (Optional?)
                    if (true && filePos % 4 != 0)
                    {
                        filePos += 4 - (filePos % 4);
                    }

                    if (filePos > uint.MaxValue)
                    {
                        throw new IOException("Maximum file size for archive exceeded.");
                    }
                }
                // Write terminator entry.
                bw.Write((uint)filePos);
                bw.Write((uint)0xFFFF);

                // Write the files.
                for (int i = 0; i < entries.Count; i++)
                {
                    if (entries[i] is null)
                    {
                        continue;
                    }
                    SubFile entry = (SubFile)entries[i];

                    // Advance to actual offset of file.
                    uint offset = entry.Offset;
                    while (fs.Position < offset)
                    {
                        fs.WriteByte(0);
                    }

                    // Write the file data.
                    fs.Write(entry.Data, 0, entry.Data.Length);
                }
                // Advance to end of file.
                while (fs.Position < filePos)
                {
                    fs.WriteByte(0);
                }
            }

            Console.WriteLine("Created archive " + Path.GetFileName(outFile) + " with " + entries.Count + " subfiles.");
        }
Beispiel #2
0
        static void Extract(string inFile, string outPath, bool decompress, bool eofEntry)
        {
            List <SubFile> entries = new List <SubFile>();

            using (FileStream arcFile = new FileStream(inFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (MemoryStream uncompressed = new MemoryStream()) {
                    BinaryReader br                  = new BinaryReader(arcFile);
                    long         headerEnd           = arcFile.Length;
                    long         maxUncompressedSize = 0;

                    // Load header.
                    SubFile entry = default;
                    while (arcFile.Position < headerEnd)
                    {
                        uint offset = br.ReadUInt32();
                        uint size   = br.ReadUInt32();

                        entry = new SubFile()
                        {
                            Offset     = offset,
                            Size       = (int)(size & 0x7FFFFFFF),
                            Compressed = (size & 0x80000000) != 0
                        };
                        entries.Add(entry);
                        if (entry.Compressed)
                        {
                            maxUncompressedSize = Math.Max(entry.Size, maxUncompressedSize);
                        }

                        headerEnd = Math.Min(entry.Offset, headerEnd);
                    }
                    if (arcFile.Position != headerEnd || (eofEntry && (entry.Size > 0 || entry.Compressed)))
                    {
                        throw new InvalidDataException("Invalid archive file header.");
                    }
                    if (eofEntry)
                    {
                        // Remove EOF entry.
                        entries.RemoveAt(entries.Count - 1);
                    }

                    // Create directory to hold files.
                    if (!outPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
                    {
                        outPath += Path.DirectorySeparatorChar;
                    }
                    Directory.CreateDirectory(outPath);

                    int digits = (entries.Count - 1).ToString().Length;
                    // Pre-allocate decompression buffer.
                    uncompressed.SetLength(maxUncompressedSize);

                    // Extract the files.
                    for (int i = 0; i < entries.Count; i++)
                    {
                        entry = entries[i];
                        long start = entry.Offset;

                        // Skip last size 0xFFFF entry.
                        if (i == entries.Count - 1 && start == arcFile.Length && entry.Size == 0xFFFF && !entry.Compressed)
                        {
                            entries.RemoveAt(i);
                            continue;
                        }

                        string outFilePath = outPath + Path.GetFileNameWithoutExtension(inFile) + "_" + i.ToString().PadLeft(digits, '0') + ".bin";

                        // Get size of the file.
                        long size;
                        if (entry.Compressed)
                        {
                            // Get compressed size by decompressing, and check if decompressed size matches size in file entry.
                            arcFile.Position      = start;
                            uncompressed.Position = 0;
                            if (!LZ77.Decompress(arcFile, uncompressed) || uncompressed.Position != entry.Size)
                            {
                                throw new InvalidDataException("Could not read subfile " + i + ": invalid LZ77 compressed data.");
                            }
                            size = decompress ? uncompressed.Position : (arcFile.Position - start);
                            uncompressed.Position = 0;
                        }
                        else
                        {
                            size = entry.Size;
                        }

                        // Read the file.
                        arcFile.Position = start;
                        entry.Data       = new byte[size];
                        if ((decompress && entry.Compressed ? (Stream)uncompressed : (Stream)arcFile).Read(entry.Data, 0, (int)size) < size)
                        {
                            throw new InvalidDataException("Could not read subfile " + i + ": invalid size.");
                        }

                        // Write the file.
                        using (FileStream subFile = new FileStream(outFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) {
                            subFile.Write(entry.Data, 0, entry.Data.Length);
                        }
                    }
                }

            Console.WriteLine("Extracted " + entries.Count + " subfiles from archive " + Path.GetFileName(inFile) + ".");
        }