mysimulation/server/tso.files/Formats/DBPF/DBPFFile.cs
Tony Bark 8fec258215 Added FSO.Files for use with the API server
Don't ask me. FreeSO is the prime example of dependency hell.
2024-05-01 04:38:12 -04:00

184 lines
5.7 KiB
C#
Executable file

using System;
using System.Collections.Generic;
using System.IO;
using FSO.Files.Utils;
namespace FSO.Files.Formats.DBPF
{
/// <summary>
/// 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.
/// </summary>
public class DBPFFile : IDisposable
{
public int DateCreated;
public int DateModified;
private uint IndexMajorVersion;
private uint NumEntries;
private IoBuffer m_Reader;
private List<DBPFEntry> m_EntriesList = new List<DBPFEntry>();
private Dictionary<ulong, DBPFEntry> m_EntryByID = new Dictionary<ulong, DBPFEntry>();
private Dictionary<DBPFTypeID, List<DBPFEntry>> m_EntriesByType = new Dictionary<DBPFTypeID, List<DBPFEntry>>();
private IoBuffer Io;
/// <summary>
/// Constructs a new DBPF instance.
/// </summary>
public DBPFFile()
{
}
/// <summary>
/// Creates a DBPF instance from a path.
/// </summary>
/// <param name="file">The path to an DBPF archive.</param>
public DBPFFile(string file)
{
var stream = File.OpenRead(file);
Read(stream);
}
/// <summary>
/// Reads a DBPF archive from a stream.
/// </summary>
/// <param name="stream">The stream to read from.</param>
public void Read(Stream stream)
{
m_EntryByID = new Dictionary<ulong,DBPFEntry>();
m_EntriesList = new List<DBPFEntry>();
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<DBPFEntry>());
m_EntriesByType[entry.TypeID].Add(entry);
}
}
/// <summary>
/// Gets a DBPFEntry's data from this DBPF instance.
/// </summary>
/// <param name="entry">Entry to retrieve data for.</param>
/// <returns>Data for entry.</returns>
public byte[] GetEntry(DBPFEntry entry)
{
m_Reader.Seek(SeekOrigin.Begin, entry.FileOffset);
return m_Reader.ReadBytes((int)entry.FileSize);
}
/// <summary>
/// Gets an entry from its ID (TypeID + FileID).
/// </summary>
/// <param name="ID">The ID of the entry.</param>
/// <returns>The entry's data.</returns>
public byte[] GetItemByID(ulong ID)
{
if (m_EntryByID.ContainsKey(ID))
return GetEntry(m_EntryByID[ID]);
else
return null;
}
/// <summary>
/// Gets all entries of a specific type.
/// </summary>
/// <param name="Type">The Type of the entry.</param>
/// <returns>The entry data, paired with its instance id.</returns>
public List<KeyValuePair<uint, byte[]>> GetItemsByType(DBPFTypeID Type)
{
var result = new List<KeyValuePair<uint, byte[]>>();
var entries = m_EntriesByType[Type];
for (int i = 0; i < entries.Count; i++)
{
result.Add(new KeyValuePair<uint, byte[]>(entries[i].InstanceID, GetEntry(entries[i])));
}
return result;
}
#region IDisposable Members
/// <summary>
/// Disposes this DBPF instance.
/// </summary>
public void Dispose()
{
Io.Dispose();
}
#endregion
}
}