using System;
using System.Collections.Generic;
using System.IO;
using FSO.Files.Utils;
namespace FSO.Files.Formats.DBPF
{
///
/// The database-packed file (DBPF) is a format used to store data for pretty much all Maxis games after The Sims,
/// including The Sims Online (the first appearance of this format), SimCity 4, The Sims 2, Spore, The Sims 3, and
/// SimCity 2013.
///
public class DBPFFile : IDisposable
{
public int DateCreated;
public int DateModified;
private uint IndexMajorVersion;
private uint NumEntries;
private IoBuffer m_Reader;
private List m_EntriesList = new List();
private Dictionary m_EntryByID = new Dictionary();
private Dictionary> m_EntriesByType = new Dictionary>();
private IoBuffer Io;
///
/// Constructs a new DBPF instance.
///
public DBPFFile()
{
}
///
/// Creates a DBPF instance from a path.
///
/// The path to an DBPF archive.
public DBPFFile(string file)
{
var stream = File.OpenRead(file);
Read(stream);
}
///
/// Reads a DBPF archive from a stream.
///
/// The stream to read from.
public void Read(Stream stream)
{
m_EntryByID = new Dictionary();
m_EntriesList = new List();
var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN);
m_Reader = io;
this.Io = io;
var magic = io.ReadCString(4);
if (magic != "DBPF")
{
throw new Exception("Not a DBPF file");
}
var majorVersion = io.ReadUInt32();
var minorVersion = io.ReadUInt32();
var version = majorVersion + (((double)minorVersion)/10.0);
/** Unknown, set to 0 **/
io.Skip(12);
if (version == 1.0)
{
this.DateCreated = io.ReadInt32();
this.DateModified = io.ReadInt32();
}
if (version < 2.0)
{
IndexMajorVersion = io.ReadUInt32();
}
NumEntries = io.ReadUInt32();
uint indexOffset = 0;
if (version < 2.0)
{
indexOffset = io.ReadUInt32();
}
var indexSize = io.ReadUInt32();
if (version < 2.0)
{
var trashEntryCount = io.ReadUInt32();
var trashIndexOffset = io.ReadUInt32();
var trashIndexSize = io.ReadUInt32();
var indexMinor = io.ReadUInt32();
}
else if (version == 2.0)
{
var indexMinor = io.ReadUInt32();
indexOffset = io.ReadUInt32();
io.Skip(4);
}
/** Padding **/
io.Skip(32);
io.Seek(SeekOrigin.Begin, indexOffset);
for (int i = 0; i < NumEntries; i++)
{
var entry = new DBPFEntry();
entry.TypeID = (DBPFTypeID)io.ReadUInt32();
entry.GroupID = (DBPFGroupID)io.ReadUInt32();
entry.InstanceID = io.ReadUInt32();
entry.FileOffset = io.ReadUInt32();
entry.FileSize = io.ReadUInt32();
m_EntriesList.Add(entry);
ulong id = (((ulong)entry.InstanceID) << 32) + (ulong)entry.TypeID;
if (!m_EntryByID.ContainsKey(id))
m_EntryByID.Add(id, entry);
if (!m_EntriesByType.ContainsKey(entry.TypeID))
m_EntriesByType.Add(entry.TypeID, new List());
m_EntriesByType[entry.TypeID].Add(entry);
}
}
///
/// Gets a DBPFEntry's data from this DBPF instance.
///
/// Entry to retrieve data for.
/// Data for entry.
public byte[] GetEntry(DBPFEntry entry)
{
m_Reader.Seek(SeekOrigin.Begin, entry.FileOffset);
return m_Reader.ReadBytes((int)entry.FileSize);
}
///
/// Gets an entry from its ID (TypeID + FileID).
///
/// The ID of the entry.
/// The entry's data.
public byte[] GetItemByID(ulong ID)
{
if (m_EntryByID.ContainsKey(ID))
return GetEntry(m_EntryByID[ID]);
else
return null;
}
///
/// Gets all entries of a specific type.
///
/// The Type of the entry.
/// The entry data, paired with its instance id.
public List> GetItemsByType(DBPFTypeID Type)
{
var result = new List>();
var entries = m_EntriesByType[Type];
for (int i = 0; i < entries.Count; i++)
{
result.Add(new KeyValuePair(entries[i].InstanceID, GetEntry(entries[i])));
}
return result;
}
#region IDisposable Members
///
/// Disposes this DBPF instance.
///
public void Dispose()
{
Io.Dispose();
}
#endregion
}
}