Added FSO.Files for use with the API server

Don't ask me. FreeSO is the prime example of dependency hell.
This commit is contained in:
Tony Bark 2024-05-01 04:38:12 -04:00
parent 4b5e584eeb
commit 8fec258215
104 changed files with 14653 additions and 163 deletions

View file

@ -0,0 +1,60 @@
namespace FSO.Files.Formats.DBPF
{
/// <summary>
/// GroupIDs for DBPF archives, defined in sys\\tsosounddata.ini
/// </summary>
public enum DBPFGroupID : uint
{
Multiplayer = 0x29dd0888,
Custom = 0x29daa4a6,
CustomTrks = 0x29d9359d,
Tracks = 0xa9c6c89a,
TrackDefs = 0xfdbdbf87,
tsov2 = 0x69c6c943,
Samples = 0x9dbdbf89,
HitLists = 0x9dbdbf74,
HitListsTemp = 0xc9c6c9b3,
Stings = 0xddbdbf8c,
HitLabUI = 0x1d6962cf,
HitLabTestSamples = 0x1d8a8b4f,
HitLabTest = 0xbd6e5937,
EP2 = 0xdde8f5c6,
EP5Samps = 0x8a6fcc30
}
/// <summary>
/// TypeIDs for DBPF archives, defined in sys\\tsoaudio.ini
/// </summary>
public enum DBPFTypeID : uint
{
XA = 0x1d07eb4b,
UTK = 0x1b6b9806,
WAV = 0xbb7051f5,
MP3 = 0x3cec2b47,
TRK = 0x5D73A611,
HIT = 0x7b1acfcd,
SoundFX = 0x2026960b,
}
/// <summary>
/// Represents an entry in a DBPF archive.
/// </summary>
public class DBPFEntry
{
//A 4-byte integer describing what type of file is held
public DBPFTypeID TypeID;
//A 4-byte integer identifying what resource group the file belongs to
public DBPFGroupID GroupID;
//A 4-byte ID assigned to the file which, together with the Type ID and the second instance ID (if applicable), is assumed to be unique all throughout the game
public uint InstanceID;
//too bad we're not using a version with a second instance id!!
//A 4-byte unsigned integer specifying the offset to the entry's data from the beginning of the archive
public uint FileOffset;
//A 4-byte unsigned integer specifying the size of the entry's data
public uint FileSize;
}
}

View file

@ -0,0 +1,184 @@
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
}
}