using System; using System.Collections.Generic; using System.Text; using System.IO; namespace FSO.Files.FAR3 { /// /// Represents a single FAR3 archive. /// public class FAR3Archive : IDisposable { private BinaryReader m_Reader; public static bool isReadingSomething = false; private string m_ArchivePath; private Dictionary m_Entries = new Dictionary(); private List m_EntriesList = new List(); private Dictionary m_EntryByID = new Dictionary(); private uint m_ManifestOffset; /// /// Creates a new FAR3Archive instance from a path. /// /// The path to the archive. public FAR3Archive(string Path) { m_ArchivePath = Path; if (isReadingSomething == false) { isReadingSomething = true; try { m_Reader = new BinaryReader(File.Open(Path, FileMode.Open, FileAccess.Read, FileShare.Read)); } catch (Exception ex) { throw new FAR3Exception("Could not open the specified archive - " + Path + "! (FAR3Archive())"); } string Header = Encoding.ASCII.GetString(m_Reader.ReadBytes(8)); uint Version = m_Reader.ReadUInt32(); if ((Header != "FAR!byAZ") || (Version != 3)) { throw new FAR3Exception("Archive wasn't a valid FAR V.3 archive! (FAR3Archive())"); } uint ManifestOffset = m_Reader.ReadUInt32(); m_ManifestOffset = ManifestOffset; m_Reader.BaseStream.Seek(ManifestOffset, SeekOrigin.Begin); uint NumFiles = m_Reader.ReadUInt32(); for (int i = 0; i < NumFiles; i++) { Far3Entry Entry = new Far3Entry(); Entry.DecompressedFileSize = m_Reader.ReadUInt32(); byte dummy0 = m_Reader.ReadByte(); byte dummy1 = m_Reader.ReadByte(); byte dummy2 = m_Reader.ReadByte(); Entry.CompressedFileSize = (uint)((dummy0 << 0) | (dummy1 << 8) | (dummy2) << 16); Entry.DataType = m_Reader.ReadByte(); Entry.DataOffset = m_Reader.ReadUInt32(); //Entry.HasFilename = m_Reader.ReadUInt16(); Entry.IsCompressed = m_Reader.ReadByte(); Entry.AccessNumber = m_Reader.ReadByte(); Entry.FilenameLength = m_Reader.ReadUInt16(); Entry.TypeID = m_Reader.ReadUInt32(); Entry.FileID = m_Reader.ReadUInt32(); Entry.Filename = Encoding.ASCII.GetString(m_Reader.ReadBytes(Entry.FilenameLength)); if (!m_Entries.ContainsKey(Entry.Filename)) m_Entries.Add(Entry.Filename, Entry); m_EntriesList.Add(Entry); m_EntryByID.Add(Entry.FileID, Entry); //isn't this a bad idea? i have a feeling this is a bad idea... } //Keep the stream open, it helps peformance. //m_Reader.Close(); isReadingSomething = false; } } /// /// Gets an entry's data from a Far3Entry instance. /// /// The Far3Entry instance. /// The entry's data. public byte[] GetEntry(Far3Entry Entry) { lock (m_Reader) { m_Reader.BaseStream.Seek((long)Entry.DataOffset, SeekOrigin.Begin); isReadingSomething = true; if (Entry.IsCompressed == 0x01) { m_Reader.BaseStream.Seek(9, SeekOrigin.Current); uint Filesize = m_Reader.ReadUInt32(); ushort CompressionID = m_Reader.ReadUInt16(); if (CompressionID == 0xFB10) { byte dummy0 = m_Reader.ReadByte(); byte dummy1 = m_Reader.ReadByte(); byte dummy2 = m_Reader.ReadByte(); uint DecompressedSize = (uint)((dummy0 << 0x10) | (dummy1 << 0x08) | +dummy2); Decompresser Dec = new Decompresser(); Dec.CompressedSize = Filesize; Dec.DecompressedSize = DecompressedSize; byte[] DecompressedData = Dec.Decompress(m_Reader.ReadBytes((int)Filesize)); //m_Reader.Close(); isReadingSomething = false; return DecompressedData; } else { m_Reader.BaseStream.Seek((m_Reader.BaseStream.Position - 15), SeekOrigin.Begin); byte[] Data = m_Reader.ReadBytes((int)Entry.DecompressedFileSize); //m_Reader.Close(); isReadingSomething = false; return Data; } } else { byte[] Data = m_Reader.ReadBytes((int)Entry.DecompressedFileSize); //m_Reader.Close(); isReadingSomething = false; return Data; } } throw new FAR3Exception("FAR3Entry didn't exist in archive - FAR3Archive.GetEntry()"); } /// /// Returns the entries of this FAR3Archive as byte arrays together with their corresponding FileIDs. /// /// A List of KeyValuePair instances. public List> GetAllEntries() { List> toReturn = new List>(); foreach (Far3Entry Entry in m_EntriesList) { toReturn.Add(new KeyValuePair(Entry.FileID, GetEntry(Entry))); } return toReturn; } /// /// Returns the entries of this FAR3Archive as FAR3Entry instances in a List. /// /// Returns the entries of this FAR3Archive as FAR3Entry instances in a List. public List GetAllFAR3Entries() { List Entries = new List(); foreach (KeyValuePair KVP in m_Entries) Entries.Add(KVP.Value); return Entries; } /// /// Gets an entry from a FileID. /// /// The entry's FileID. /// The entry's data. public byte[] GetItemByID(uint FileID) { var item = m_EntryByID[FileID]; if (item == null) { throw new FAR3Exception("Didn't find entry!"); } return GetEntry(item); } /// /// Gets an entry from its ID (TypeID + FileID). /// /// The ID of the entry. /// The entry's data. public byte[] GetItemByID(ulong ID) { byte[] Bytes = BitConverter.GetBytes(ID); uint FileID = BitConverter.ToUInt32(Bytes, 4); uint TypeID = BitConverter.ToUInt32(Bytes, 0); var item = m_EntryByID[FileID]; if (item == null || item.TypeID != TypeID) { throw new FAR3Exception("Didn't find entry!"); } return GetEntry(item); } /// /// Gets an entry's data from a filename. /// /// The filename of the entry. /// The entry's data. public byte[] this[string Filename] { get { return GetEntry(m_Entries[Filename]); } } #region IDisposable Members /// /// Disposes this FAR3Archive instance. /// public void Dispose() { if (m_Reader != null) { m_Reader.Close(); } } #endregion } }