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
}
}