public Result Add(EQArchiveFile File, bool ReplaceSimilarImage) { if (File == null) { return(Result.InvalidArgument); } EQArchiveFile _archiveFile; if (ReplaceSimilarImage) { _archiveFile = this.FindFileOrSimilarImage(Filename); } else { _archiveFile = this.FindFile(Filename); } if (_archiveFile != null) { // We already have a file that has a similar name. Import the new file AS that file, replacing it. this.Files.RemoveAt(this.Files.IndexOfValue(_archiveFile)); File.Filename = _archiveFile.Filename; } this.Files[File.Filename.ToLower()] = File; this.IsDirty = true; return(File.Status); }
public Result Remove(EQArchiveFile File) { if (File == null) { return(Result.FileNotFound); } this.Files.RemoveAt(this.Files.IndexOfValue(File)); this.IsDirty = true; return(Result.OK); }
public EQArchiveFile AsFormat(string NewFormat, bool ChangeExtension) { if ((NewFormat == null) || (NewFormat == "") || (this.GetImage() == null)) { return(this); } NewFormat = NewFormat.ToLower(); if (NewFormat == "auto") { switch (GetAlphaBits()) { case 0: case 1: NewFormat = "16-bit"; break; case 8: NewFormat = "32-bit"; break; default: // ? NewFormat = "32-bit"; break; } } EQArchiveFile _newFile = null; if (NewFormat != this.ImageFormat) { _newFile = new EQArchiveFile(); _newFile.Filename = this.Filename; _newFile.SetImage(this.GetImage(), NewFormat); } if (ChangeExtension && !System.IO.Path.GetExtension(this.Filename).Equals((NewFormat[0] == '.' ? NewFormat : ".dds"), StringComparison.CurrentCultureIgnoreCase)) { // Gotta change the extension if (_newFile == null) { _newFile = new EQArchiveFile(); _newFile.SetContents(this.GetContents()); } _newFile.Filename = System.IO.Path.GetFileNameWithoutExtension(this.Filename) + (NewFormat[0] == '.' ? NewFormat : ".dds"); } else { if (_newFile == null) { _newFile = this; // Unchanged from our current contents } else { _newFile.Filename = this.Filename; } } return(_newFile); }
public Result Save() { if ((this.FilePath == "") || (this.Filename == "(Untitled)") || (this.Filename == "")) { return(Result.InvalidArgument); } Result _result = Result.OK; try { using (BinaryWriter _file = new BinaryWriter(File.Create(this.FilePath + @"\" + this.Filename))) { // // Step 1 - Get an order of files by filename CRC, per standard PFS archive practice. // SortedList <UInt32, EQArchiveFile> _filesByCRC = new SortedList <UInt32, EQArchiveFile>(); foreach (EQArchiveFile _entry in this.Files.Values) { _filesByCRC.Add(GetFilenameCRC(_entry.Filename), _entry); } // // Step 2 - Build the directory of filenames and compress it for adding at the end of the archive // EQArchiveFile _directory = new EQArchiveFile(); byte[] _directoryBytes = new byte[64 * 1024]; // global_chr.s3d = ~29,000 bytes of filenames! using (BinaryWriter _stream = new BinaryWriter(new MemoryStream(_directoryBytes))) { UInt32 _directorySize = 0; _stream.Write((UInt32)this.Files.Count); foreach (EQArchiveFile _entry in _filesByCRC.Values) { _stream.Write((UInt32)_entry.Filename.Length + 1); foreach (char _c in _entry.Filename) { _stream.Write(_c); } _stream.Write('\0'); } _directorySize = (UInt32)_stream.BaseStream.Position; Array.Resize <byte>(ref _directoryBytes, (int)_directorySize); _directory.SetContents(_directoryBytes); } // // Step 3 - Build the file header // Header _header = new Header(); _header.MagicNumber = _MagicNumber; _header.VersionNumber = 0x00020000; // a. Index Pointer must be determined. Start with the size after the header itself _header.IndexPointer = 4 + 4 + 4; // b. Add in the size of all of the compressed chunks and their two size values foreach (EQArchiveFile _entry in this.Files.Values) { _header.IndexPointer += (4 + 4) * (_entry.CompressedChunks == null ? 1 : (UInt32)_entry.CompressedChunks.Count); _header.IndexPointer += _entry.Size.Compressed; } // c. Add in the size of the compressed filename directory and its size values _header.IndexPointer += (4 + 4) * (UInt32)_directory.CompressedChunks.Count + _directory.Size.Compressed; // // Step 4 - Write the file Header // _file.Write(_header.IndexPointer); _file.Write(_header.MagicNumber); _file.Write(_header.VersionNumber); // // Step 5 - Compressed File Chunks // foreach (EQArchiveFile _entry in _filesByCRC.Values) { _entry.FilePointer = (UInt32)_file.BaseStream.Position; foreach (EQArchiveFile.Chunk _chunk in _entry.CompressedChunks) { _file.Write(_chunk.Size.Compressed); _file.Write(_chunk.Size.Uncompressed); _file.Write(_chunk.CompressedData, 0, (int)_chunk.Size.Compressed); } } // // Step 6 - Filename Directory compressed chunks at the end // _directory.FilePointer = (UInt32)_file.BaseStream.Position; foreach (EQArchiveFile.Chunk _chunk in _directory.CompressedChunks) { _file.Write(_chunk.Size.Compressed); _file.Write(_chunk.Size.Uncompressed); _file.Write(_chunk.CompressedData, 0, (int)_chunk.Size.Compressed); } // // Step 7 - Index of File Entries // _file.Write((UInt32)(this.Files.Count + 1)); foreach (KeyValuePair <UInt32, EQArchiveFile> _kvp in _filesByCRC) { _file.Write(_kvp.Key); _file.Write(_kvp.Value.FilePointer); _file.Write(_kvp.Value.Size.Uncompressed); } // // Step 8 - Add filename directory to end of index // _file.Write(0xFFFFFFFFU); _file.Write(_directory.FilePointer); _file.Write(_directory.Size.Uncompressed); // // Step 9 - PFS Footer // foreach (char _letter in _FooterToken) { _file.Write(_letter); } _file.Write(_header.DateStamp); _file.Close(); } } catch { return(Result.FileWriteError); } if (_result == Result.OK) { this.IsDirty = false; } return(_result); }
public static EQArchive Load(string Filename, byte[] Contents) { if (Filename.Length == 0 || Contents == null) { // We got a bad filename, or the file is zero length, and thus not a PFS archive. return(null); } Header _header = new Header(); using (BinaryReader _input = new BinaryReader(new MemoryStream(Contents))) { try { // // 1. Read the file header // _header.IndexPointer = _input.ReadUInt32(); _header.MagicNumber = _input.ReadUInt32(); _header.VersionNumber = _input.ReadUInt32(); } catch { // Too small to be a PFS archive return(null); } if (_header.MagicNumber != _MagicNumber) { // Not a PFS archive return(null); } EQArchive _archive = new EQArchive(); _archive.Filename = Filename; _archive.SizeOnDisk = (UInt32)Contents.Length; try { // // 2. Read Index of File Pointers and Sizes in Archive // _input.BaseStream.Seek(_header.IndexPointer, SeekOrigin.Begin); _header.EntryCount = _input.ReadUInt32(); if (_header.EntryCount == 0) { // Empty archive...? _archive.Files = new SortedList <string, EQArchiveFile>(); } else { _archive.Files = new SortedList <string, EQArchiveFile>((int)_header.EntryCount); } // Filename directory is the "file" at the end of the archive (with the highest FilePointer) EQArchiveFile _directory = null; // For verification later, which is optional, but will catch a malformed/corrupted archive. Dictionary <UInt32, UInt32> _filenameCRCs = new Dictionary <UInt32, UInt32>(); // Files in a PFS archive tend to be stored by ascending order of FilenameCRC. // However, the filename directory is sorted by FilePointer SortedList <UInt32, EQArchiveFile> _files = new SortedList <UInt32, EQArchiveFile>(); for (UInt32 _index = 0; _index < _header.EntryCount; _index++) { EQArchiveFile _file = new EQArchiveFile(); UInt32 _filenameCRC = _input.ReadUInt32(); _file.FilePointer = _input.ReadUInt32(); _file.Size.Uncompressed = _input.ReadUInt32(); _filenameCRCs.Add(_file.FilePointer, _filenameCRC); if ((_directory == null) || (_file.FilePointer > _directory.FilePointer)) { _directory = _file; } _files.Add(_file.FilePointer, _file); } if ((_input.BaseStream.Length - _input.BaseStream.Position) >= 9) { // PFS Footer char[] _token = _input.ReadChars(5); if (new String(_token).Equals("STEVE")) { // Valid Footer Token _header.DateStamp = _input.ReadUInt32(); } } // // 3. Read the compressed file entries (each split into compressed chunks) // foreach (EQArchiveFile _file in _files.Values) { // Seek to entry position in stream _input.BaseStream.Seek(_file.FilePointer, SeekOrigin.Begin); UInt32 _totalUncompressedBytes = _file.Size.Uncompressed; _file.Size.Uncompressed = 0; while ((_file.Size.Uncompressed < _totalUncompressedBytes) && (_input.BaseStream.Position < _input.BaseStream.Length)) { UInt32 _blockSizeCmp = _input.ReadUInt32(); UInt32 _blockSizeUnc = _input.ReadUInt32(); // Sanity Check 1: Uncompressed data larger than what we were told? if ((_blockSizeUnc + _file.Size.Uncompressed) > _totalUncompressedBytes) { throw new Exception(); } // Sanity Check 2: Compressed data goes past the end of the file? if ((_input.BaseStream.Position + _blockSizeCmp) > _input.BaseStream.Length) { throw new Exception(); } _file.AddChunk(new EQArchiveFile.Chunk(Contents, (UInt32)_input.BaseStream.Position, _blockSizeCmp, _blockSizeUnc, true)); _input.BaseStream.Position += _blockSizeCmp; } } // // 4. Unpack and parse the directory of filenames from the "file" at the end of the archive (highest FilePointer) // // Remove directory from file entries in archive. We'll have to rebuild it when saving the archive anyway. _files.Remove(_directory.FilePointer); _header.EntryCount--; // Load filenames from directory BinaryReader _dirStream = new BinaryReader(new MemoryStream(_directory.GetContents())); UInt32 _filenameCount = _dirStream.ReadUInt32(); if (_filenameCount > _header.EntryCount) { // If we somehow have more filenames than entries in the archive, ignore the glitched extras _filenameCount = _header.EntryCount; } _archive.Files = new SortedList <string, EQArchiveFile>(); foreach (EQArchiveFile _file in _files.Values) { Int32 _len = _dirStream.ReadInt32(); char[] _inputname = _dirStream.ReadChars(_len); UInt32 _crc = GetFilenameCRC(_inputname); if (_crc != _filenameCRCs[_file.FilePointer]) { // Filename doesn't match with what we were given in Step 2 // This happens in gequip.s3d. We are ignoring it. //throw new Exception(); } _file.Filename = new string(_inputname, 0, _len - 1); _archive.Files.Add(_file.Filename.ToLower(), _file); } // All entries loaded and filenames read from directory. _archive.Status = Result.OK; } catch { _archive.Status = Result.MalformedFile; } return(_archive); } }
public Result Add(EQArchiveFile File) { return(this.Add(File, false)); }