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

96
server/tso.files/HIT/EVT.cs Executable file
View file

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.HIT
{
/// <summary>
/// EVT is a CSV format that defines a list of events for HIT to listen for.
/// </summary>
public class EVT
{
public List<EVTEntry> Entries;
/// <summary>
/// Creates a new evt file.
/// </summary>
/// <param name="Filedata">The data to create the evt from.</param>
public EVT(byte[] Filedata)
{
ReadFile(new MemoryStream(Filedata));
}
/// <summary>
/// Creates a new evt file.
/// </summary>
/// <param name="Filedata">The path to the data to create the evt from.</param>
public EVT(string Filepath)
{
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
private void ReadFile(Stream data)
{
Entries = new List<EVTEntry>();
BinaryReader Reader = new BinaryReader(data);
string CommaSeparatedValues = new string(Reader.ReadChars((int)data.Length));
string[] Lines = CommaSeparatedValues.Split(new string[] { "\r\n" }, StringSplitOptions.None);
for (int i = 0; i < Lines.Length; i++)
{
if (Lines[i] == "") continue;
string[] Values = Lines[i].Split(',');
var Entry = new EVTEntry();
Entry.Name = Values[0].ToLowerInvariant();
Entry.EventType = ParseHexString(Values[1]);
Entry.TrackID = ParseHexString(Values[2]);
Entry.Unknown = ParseHexString(Values[3]);
Entry.Unknown2 = ParseHexString(Values[4]);
Entry.Unknown3 = ParseHexString(Values[5]);
Entry.Unknown4 = ParseHexString(Values[6]);
Entries.Add(Entry);
}
Reader.Close();
}
private uint ParseHexString(string input)
{
bool IsHex = false;
input = input.ToLowerInvariant();
if (input == "") return 0;
if (input.StartsWith("0x"))
{
input = input.Substring(2);
IsHex = true;
}
else if (input.Contains("a") || input.Contains("b") || input.Contains("c") || input.Contains("d") || input.Contains("e") || input.Contains("f"))
{
IsHex = true;
}
if (IsHex)
{
return Convert.ToUInt32(input, 16);
}
else
{
return Convert.ToUInt32(input);
}
}
}
public class EVTEntry
{
public string Name;
public uint EventType;
public uint TrackID;
public uint Unknown;
public uint Unknown2;
public uint Unknown3;
public uint Unknown4;
}
}

153
server/tso.files/HIT/FSC.cs Executable file
View file

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.HIT
{
public class FSC
{
/// <summary>
/// FSC is a tabulated plaintext format that describes a sequence of notes to be played. In this game it is used to sequence the ambient sounds.
/// The conditions in which the sequence is randomized are not entirely apparent, and have been mostly guessed.
/// </summary>
///
public List<FSCNote> Notes;
public string VersionCode;
public ushort MasterVolume;
public ushort Priority;
public ushort Min;
public ushort Max;
public ushort Rows; //these seem to be outright lies, but let's leave them in
public ushort Columns;
public ushort Tempo;
public ushort BPB; //beats per bar
public ushort SelX;
public ushort SelY;
public ushort QuanX;
public ushort QuanY;
public ushort DiffX;
public ushort DiffY;
public List<int> RandomJumpPoints;
/// <summary>
/// Creates a new hsm file.
/// </summary>
/// <param name="Filedata">The data to create the hsm from.</param>
public FSC(byte[] Filedata)
{
ReadFile(new MemoryStream(Filedata));
}
/// <summary>
/// Creates a new hsm file.
/// </summary>
/// <param name="Filedata">The path to the data to create the hsm from.</param>
public FSC(string Filepath)
{
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
private void ReadFile(Stream stream)
{
var io = new StreamReader(stream);
Notes = new List<FSCNote>();
RandomJumpPoints = new List<int>();
VersionCode = io.ReadLine();
var line = io.ReadLine();
while (line.StartsWith("#"))
line = io.ReadLine();
//Header
string[] Head = line.Split('\t');
MasterVolume = Convert.ToUInt16(Head[1]);
Priority = Convert.ToUInt16(Head[2]);
Min = Convert.ToUInt16(Head[3]);
Max = Convert.ToUInt16(Head[4]);
Rows = Convert.ToUInt16(Head[5]);
Columns = Convert.ToUInt16(Head[6]);
Tempo = Convert.ToUInt16(Head[7]);
BPB = Convert.ToUInt16(Head[8]);
SelX = Convert.ToUInt16(Head[9]);
SelY = Convert.ToUInt16(Head[10]);
if(Head[11][0] != '-') QuanX = Convert.ToUInt16(Head[11]);
if (Head[12][0] != '-') QuanY = Convert.ToUInt16(Head[12]);
DiffX = Convert.ToUInt16(Head[13]);
DiffY = Convert.ToUInt16(Head[14]);
line = io.ReadLine();
while (line.StartsWith("#") || line.StartsWith("cells"))
line = io.ReadLine();
while (!io.EndOfStream) //read notes
{
string line2 = io.ReadLine();
string[] Values = line2.Split('\t');
if (!line.StartsWith("#") && Values.Length == 20)
{
var note = new FSCNote()
{
Volume = Convert.ToUInt16(Values[1]),
Rand = Values[2] != "0",
LRPan = Convert.ToUInt16(Values[3]),
FBPan = Convert.ToUInt16(Values[4]),
Rand2 = Values[5] != "0",
Fin = Convert.ToUInt16(Values[6]),
FOut = Convert.ToUInt16(Values[7]),
dly = Convert.ToUInt16(Values[8]),
Rand3 = Values[9] != "0",
Loop = Convert.ToUInt16(Values[10]),
Loop2 = Values[11] != "0",
Quant = Convert.ToUInt16(Values[12]),
Prob = Convert.ToUInt16(Values[13]),
pitchL = Convert.ToInt16(Values[14]),
pitchR = Convert.ToInt16(Values[15]),
Fast = Values[16] != "0",
GroupID = Convert.ToUInt16(Values[17]),
Stereo = Values[18] != "0",
Filename = Values[19]
};
if (note.Rand) RandomJumpPoints.Add(Notes.Count);
Notes.Add(note);
}
}
io.Close();
}
}
public struct FSCNote
{
public ushort Volume; //0-1024
public bool Rand;
public ushort LRPan; //0-1024
public ushort FBPan; //0-1024, front back
public bool Rand2;
public ushort Fin;
public ushort FOut;
public ushort dly;
public bool Rand3; //what
public ushort Loop;
public bool Loop2; //might be count then decider here
public ushort Quant; //but then what is this?
public ushort Prob; //probably random probability, not sure of range (0-16?)
public short pitchL; //pitch offsets
public short pitchR;
public bool Fast;
public ushort GroupID;
public bool Stereo;
public string Filename;
}
}

View file

@ -0,0 +1,72 @@
namespace FSO.Files.HIT
{
public enum HITArgs
{
kArgsNormal = 0,
kArgsVolPan = 1,
kArgsIdVolPan = 2,
kArgsXYZ = 3
}
public enum HITControlGroups
{
kGroupSFX = 1,
kGroupMusic = 2,
kGroupVox = 3
}
public enum HITDuckingPriorities
{
duckpri_unknown1 = 32,
duckpri_unknown2 = 5000,
duckpri_always = 0x0,
duckpri_low = 0x1,
duckpri_normal = 0x14,
duckpri_high = 0x1e,
duckpri_higher = 0x28,
duckpri_evenhigher = 0x32,
duckpri_never = 0x64
}
public enum HITEvents
{
kSoundobPlay = 1,
kSoundobStop = 2,
kSoundobKill = 3,
kSoundobUpdate = 4,
kSoundobSetVolume = 5,
kSoundobSetPitch = 6,
kSoundobSetPan = 7,
kSoundobSetPosition = 8,
kSoundobSetFxType = 9,
kSoundobSetFxLevel = 10,
kSoundobPause = 11,
kSoundobUnpause = 12,
kSoundobLoad = 13,
kSoundobUnload = 14,
kSoundobCache = 15,
kSoundobUncache = 16,
kSoundobCancelNote = 19,
kKillAll = 20,
kPause = 21,
kUnpause = 22,
kKillInstance = 23,
kTurnOnTV = 30,
kTurnOffTV = 31,
kUpdateSourceVolPan = 32,
kSetMusicMode = 36,
kPlayPiano = 43,
debugeventson = 44,
debugeventsoff = 45,
debugsampleson = 46,
debugsamplesoff = 47,
debugtrackson = 48,
debugtracksoff = 49
}
public enum HITPerson
{
Instance = 0x0,
Gender = 0x1
}
}

101
server/tso.files/HIT/HITFile.cs Executable file
View file

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace FSO.Files.HIT
{
/// <summary>
/// HIT files contain the bytecode to be executed when plaing track events.
/// </summary>
public class HITFile
{
public string MagicNumber;
public uint MajorVersion;
public uint MinorVersion;
public byte[] Data;
public Dictionary<uint, uint> EntryPointByTrackID;
/// <summary>
/// Creates a new track.
/// </summary>
/// <param name="Filedata">The data to create the track from.</param>
public HITFile(byte[] Filedata)
{
ReadFile(new MemoryStream(Filedata));
}
/// <summary>
/// Creates a new track.
/// </summary>
/// <param name="Filedata">The path to the data to create the track from.</param>
public HITFile(string Filepath)
{
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
private void ReadFile(Stream data)
{
BinaryReader Reader = new BinaryReader(data);
MagicNumber = new string(Reader.ReadChars(4));
MajorVersion = Reader.ReadUInt32();
MinorVersion = Reader.ReadUInt32();
var signature = new string(Reader.ReadChars(4));
var tableLoc = FindBytePattern(Reader.BaseStream, new byte[] { (byte)'E', (byte)'N', (byte)'T', (byte)'P' });
if (tableLoc != -1)
{
Reader.BaseStream.Seek(tableLoc, SeekOrigin.Begin);
EntryPointByTrackID = new Dictionary<uint, uint>();
while (true)
{
var EndTest = ASCIIEncoding.ASCII.GetString(Reader.ReadBytes(4)); //can be invalid chars
if (EndTest.Equals("EENT", StringComparison.InvariantCultureIgnoreCase))
{
break;
}
else
{
Reader.BaseStream.Position -= 4; //go back to read it as a table entry
var track = Reader.ReadUInt32();
var address = Reader.ReadUInt32();
EntryPointByTrackID.Add(track, address);
}
}
}
Reader.BaseStream.Seek(0, SeekOrigin.Begin);
this.Data = Reader.ReadBytes((int)Reader.BaseStream.Length);
Reader.Close();
}
public int FindBytePattern(Stream stream, byte[] pattern)
{ //a simple pattern matcher
stream.Seek(0, SeekOrigin.Begin);
for (int i = 0; i < stream.Length; i++)
{
var b = stream.ReadByte();
if (b == pattern[0])
{
bool match = true;
for (int j = 1; j < pattern.Length; j++)
{
var b2 = stream.ReadByte();
if (b2 != pattern[j])
{
match = false;
break;
}
}
if (match) return (int)stream.Position;
else stream.Seek(i+1, SeekOrigin.Begin);
}
}
return -1; //no match
}
}
}

50
server/tso.files/HIT/HSM.cs Executable file
View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.HIT
{
public class HSM
{
/// <summary>
/// HSM is a plaintext format that names various HIT constants including subroutine locations.
/// </summary>
///
public Dictionary<string, int> Constants;
/// <summary>
/// Creates a new hsm file.
/// </summary>
/// <param name="Filedata">The data to create the hsm from.</param>
public HSM(byte[] Filedata)
{
ReadFile(new MemoryStream(Filedata));
}
/// <summary>
/// Creates a new hsm file.
/// </summary>
/// <param name="Filedata">The path to the data to create the hsm from.</param>
public HSM(string Filepath)
{
ReadFile(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
private void ReadFile(Stream stream)
{
var io = new StreamReader(stream);
Constants = new Dictionary<string, int>();
while (!io.EndOfStream)
{
string line = io.ReadLine();
string[] Values = line.Split(' ');
var name = Values[0].ToLowerInvariant();
if (!Constants.ContainsKey(name)) Constants.Add(name, Convert.ToInt32(Values[1])); //the repeats are just labels for locations (usually called gotit)
}
io.Close();
}
}
}

103
server/tso.files/HIT/Hitlist.cs Executable file
View file

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.HIT
{
/// <summary>
/// HLS refers to two binary formats that both define a list of IDs, known as a hitlist.
/// One format is a Pascal string with a 4-byte, little-endian length, representing a
/// comma-seperated list of decimal values, or decimal ranges (e.g. "1025-1035"), succeeded
/// by a single LF newline.
/// </summary>
public class Hitlist
{
private uint m_IDCount;
public List<uint> IDs; //variable length so it's easier to fill with ranges
/// <summary>
/// Creates a new hitlist.
/// </summary>
/// <param name="Filedata">The data to create the hitlist from.</param>
public Hitlist(byte[] Filedata)
{
Read(new MemoryStream(Filedata));
}
private void Read(Stream data)
{
BinaryReader Reader = new BinaryReader(data);
IDs = new List<uint>();
var VerOrCount = Reader.ReadUInt32();
try
{
if (VerOrCount == 1) //binary format, no hitlist is ever going to have length 1... (i hope)
{
m_IDCount = Reader.ReadUInt32();
for (int i = 0; i < m_IDCount; i++)
IDs.Add(Reader.ReadUInt32());
Reader.Close();
}
else
{
var str = new string(Reader.ReadChars((int)VerOrCount));
Populate(str);
}
}
catch
{
Reader.BaseStream.Seek(4, SeekOrigin.Begin); //attempt 3rd mystery format, count+int32
for (int i = 0; i < VerOrCount; i++)
IDs.Add(Reader.ReadUInt32());
}
}
public Hitlist()
{
IDs = new List<uint>();
}
public void Populate(string str)
{
var commaSplit = str.Split(',');
for (int i = 0; i < commaSplit.Length; i++)
{
var dashSplit = commaSplit[i].Split('-');
if (dashSplit.Length > 1)
{ //range, parse two values and fill in the gap
var min = Convert.ToUInt32(dashSplit[0]);
var max = Convert.ToUInt32(dashSplit[1]);
for (uint j = min; j <= max; j++)
{
IDs.Add(j);
}
}
else
{ //literal entry, add to list
IDs.Add(Convert.ToUInt32(commaSplit[i]));
}
}
}
public static Hitlist HitlistFromString(string str)
{
var result = new Hitlist();
result.Populate(str);
return result;
}
/// <summary>
/// Creates a new hitlist.
/// </summary>
/// <param name="Filepath">The path to the hitlist to read.</param>
public Hitlist(string Filepath)
{
Read(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
}
}
}

236
server/tso.files/HIT/Hot.cs Executable file
View file

@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FSO.Files.HIT
{
/// <summary>
/// Enumeration constants.
/// </summary>
public struct EventMappingEquate
{
public string Label;
public int Value;
}
/// <summary>
/// This section assigns a sound ID to a number of subroutines
/// (exported or not) in the corresponding HIT file.
/// </summary>
public struct TrackData
{
//The syntax for TrackData is A = B, where A is the sound's File ID
//and B is the offset to the subroutine in the accompanying HIT file.
public long FileID;
public int SubRoutineOffset;
}
/// <summary>
/// HOT (short for HIT Options Table) is an ini format that defines
/// enumeration constants and track data for HIT binary files.
/// </summary>
public class Hot
{
private int m_Version;
private int m_LoadPriority;
private List<EventMappingEquate> m_EventMappingEquations = new List<EventMappingEquate>();
private List<TrackData> m_TrackDataList = new List<TrackData>();
public Dictionary<uint, Track> Tracks = new Dictionary<uint, Track>();
public Dictionary<uint, Patch> Patches = new Dictionary<uint, Patch>();
public Dictionary<uint, Hitlist> Hitlists = new Dictionary<uint, Hitlist>();
public Dictionary<uint, uint> TrackData = new Dictionary<uint, uint>();
public Dictionary<string, EVTEntry> Events = new Dictionary<string, EVTEntry>();
private Dictionary<string, int> EventMappingEquate = new Dictionary<string, int>();
public HSM AsmNames;
/// <summary>
/// Gets this Hot instance's list of EventMappingEquate instances.
/// </summary>
public List<EventMappingEquate> EventMappingEquations
{
get { return m_EventMappingEquations; }
}
/// <summary>
/// Gets this Hot instance's list of TrackData instances.
/// </summary>
public List<TrackData> TrackDataList
{
get { return m_TrackDataList; }
}
public Hot(byte[] FileData)
{
LoadFrom(FileData);
}
public void LoadFrom(byte[] FileData)
{
StreamReader Reader = new StreamReader(new MemoryStream(FileData));
HotReadMode ActiveState = HotReadMode.None;
while (!Reader.EndOfStream)
{
string CurrentLine = Reader.ReadLine().Trim().Replace("\r\n", "");
switch (CurrentLine)
{
case "[EventMappingEquate]":
ActiveState = HotReadMode.EventMappingEquate;
break;
case "[Options]":
ActiveState = HotReadMode.Options;
break;
case "[TrackData]":
ActiveState = HotReadMode.TrackData;
break;
case "[EventMapping]":
//equivalent to .evt file
//(name)=(eventMappingEquate as event type),(trackid),0,0,0,0
ActiveState = HotReadMode.EventMapping;
break;
case "[Track]":
//equivalent to a lot of track files
//(trackid)=0,(subroutine),(volume),(arguments),(duckingPriority),(controlGroup),(soundPressureLevel),@(hitlistID),(patchID)
//patch id is usually 0, in favor of single item hitlists
ActiveState = HotReadMode.Track;
break;
case "[Patch]":
//(patchid)=(name),(filenameInQuotes),(looped),(piano),0,0,0
ActiveState = HotReadMode.Patch;
break;
case "[GlobalHitList]":
//(hitlistid)=(hitlistString)
//note: many hitlists contain just one patch
ActiveState = HotReadMode.GlobalHitList;
break;
}
if (!CurrentLine.Contains("["))
{
if (!CurrentLine.Contains("]"))
{
if (CurrentLine != "")
{
string[] Params = CurrentLine.Split("=".ToCharArray());
//EventMappingEquate fields look like: kSndobPlay=1
switch (ActiveState)
{
case HotReadMode.EventMappingEquate:
EventMappingEquate EMappingEquate = new EventMappingEquate();
EMappingEquate.Label = Params[0];
EMappingEquate.Value = int.Parse(Params[1]);
EventMappingEquate[EMappingEquate.Label] = EMappingEquate.Value;
break;
//Options fields look like: Version=1
case HotReadMode.Options:
switch (Params[0])
{
case "Version":
m_Version = int.Parse(Params[1]);
break;
case "LoadPriority":
m_LoadPriority = int.Parse(Params[1]);
break;
}
break;
//TrackData fields look like: 0xb0f4=0x10
case HotReadMode.TrackData:
TrackData.Add(Convert.ToUInt32(Params[0], 16), Convert.ToUInt32(Params[1], 16));
break;
case HotReadMode.EventMapping:
var commaSplit = Params[1].Split(',');
Events[Params[0].ToLowerInvariant()] = new EVTEntry
{
Name = Params[0].ToLowerInvariant(),
EventType = (uint)ParseEME(commaSplit[0]),
TrackID = (commaSplit.Length>1)?(uint)ParseEME(commaSplit[1]):0
};
break;
case HotReadMode.Track:
var tid = uint.Parse(Params[0]);
var tcSplit = Params[1].Split(',');
var trk = new Track()
{
SubroutineID = 0,//(uint)HSMConst(tcSplit[1]),
Volume = (uint)ParseEME(tcSplit[2]),
ArgType = (HITArgs)ParseEME(tcSplit[3]),
DuckingPriority = (HITDuckingPriorities)ParseEME(tcSplit[4]),
ControlGroup = (HITControlGroups)ParseEME(tcSplit[5]),
HitlistID = (uint)HSMConst(tcSplit[7].Substring(1)), //cut out @
SoundID = (uint)ParseEME(tcSplit[8])
};
if (trk.HitlistID != 0 && TrackData.ContainsKey(trk.HitlistID)) trk.SubroutineID = TrackData[trk.HitlistID];
if (trk.SoundID != 0 && TrackData.ContainsKey(trk.SoundID)) trk.SubroutineID = TrackData[trk.SoundID];
Tracks[tid] = trk;
break;
case HotReadMode.Patch:
var pid = uint.Parse(Params[0]);
var patch = new Patch(Params[1]);
Patches[pid] = patch;
break;
case HotReadMode.GlobalHitList:
var hid = uint.Parse(Params[0]);
try
{
var hitlist = Hitlist.HitlistFromString(Params[1]);
Hitlists[hid] = hitlist;
} catch (Exception)
{
/*
* todo: saxophone seems to reference an hsm.
* 20016=sulsaxj_way_aa
* 20017=sulsaxk_way_solo
* 20018=sulsaxl_way_fin
* these labels are in the hsm but they have a different case...
*/
}
break;
}
}
}
}
}
}
private int HSMConst(string input)
{
int result = 0;
AsmNames?.Constants?.TryGetValue(input, out result);
return result;
}
private int ParseEME(string eme)
{
int result = 0;
if (int.TryParse(eme, out result)) return result;
EventMappingEquate.TryGetValue(eme, out result);
return result;
}
public Hot(string Filepath) : this(File.ReadAllBytes(Filepath))
{
}
public Hot(string Filepath, HSM myAsm)
{
AsmNames = myAsm;
LoadFrom(File.ReadAllBytes(Filepath));
}
}
public enum HotReadMode
{
None,
EventMappingEquate,
Options,
TrackData,
EventMapping,
Track,
Patch,
GlobalHitList
}
}

28
server/tso.files/HIT/Patch.cs Executable file
View file

@ -0,0 +1,28 @@
namespace FSO.Files.HIT
{
public class Patch
{
public string Name;
public string Filename;
public bool Looped;
public bool Piano;
public uint FileID; //patches are stubbed out in TSO.
public bool TSO;
public Patch(uint id)
{
FileID = id;
TSO = true;
}
public Patch(string patchString)
{
var elems = patchString.Split(',');
if (elems.Length > 1) Name = elems[1];
if (elems.Length > 2) Filename = elems[2].Substring(1, elems[2].Length-2).Replace('\\', '/');
if (elems.Length > 3) Looped = elems[3] != "0";
if (elems.Length > 4) Piano = elems[4] != "0";
}
}
}

91
server/tso.files/HIT/TLO.cs Executable file
View file

@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.IO;
//ok, why do we have support for this? it's only used by hitlab and only the hitlab test files have them...
namespace FSO.Files.HIT
{
/// <summary>
/// Represents a TLO file.
/// TLO (short for Track Logic according to hitlab.ini) is a format
/// used solely for Hitlab. Integers are little-endian.
/// </summary>
public class TLO
{
private uint m_Count;
public List<TLOSection> Sections = new List<TLOSection>();
/// <summary>
/// Creates a new tracklogic instance.
/// </summary>
/// <param name="Filedata">The data to create the tracklogic instance from.</param>
public TLO(byte[] Filedata)
{
BinaryReader Reader = new BinaryReader(new MemoryStream(Filedata));
Reader.ReadBytes(4); //Reserved.
m_Count = Reader.ReadUInt32();
for (int i = 0; i < m_Count; i++)
{
TLOSection Section = new TLOSection();
Section.Name = new string(Reader.ReadChars(Reader.ReadInt32()));
Section.GroupID1 = Reader.ReadUInt32();
Section.FileID1 = Reader.ReadUInt32();
Section.GroupID2 = Reader.ReadUInt32();
Section.FileID1 = Reader.ReadUInt32();
Section.TypeID = Reader.ReadUInt32();
Section.GroupID3 = Reader.ReadUInt32();
Section.FileID3 = Reader.ReadUInt32();
Sections.Add(Section);
}
Reader.Close();
}
/// <summary>
/// Creates a new tracklogic instance.
/// </summary>
/// <param name="Filepath">The path to the tracklogic file to read.</param>
public TLO(string Filepath)
{
BinaryReader Reader = new BinaryReader(File.Open(Filepath, FileMode.Open, FileAccess.Read, FileShare.Read));
Reader.ReadBytes(4); //Reserved.
m_Count = Reader.ReadUInt32();
for (int i = 0; i < m_Count; i++)
{
TLOSection Section = new TLOSection();
Section.Name = new string(Reader.ReadChars(Reader.ReadInt32()));
Section.GroupID1 = Reader.ReadUInt32();
Section.FileID1 = Reader.ReadUInt32();
Section.GroupID2 = Reader.ReadUInt32();
Section.FileID1 = Reader.ReadUInt32();
Section.TypeID = Reader.ReadUInt32();
Section.GroupID3 = Reader.ReadUInt32();
Section.FileID3 = Reader.ReadUInt32();
Sections.Add(Section);
}
Reader.Close();
}
}
/// <summary>
/// Represents a section in a tracklogic file.
/// </summary>
public class TLOSection
{
public string Name;
public uint GroupID1;
public uint FileID1;
public uint GroupID2;
public uint FileID2;
public uint TypeID;
public uint GroupID3;
public uint FileID3;
}
}

111
server/tso.files/HIT/Track.cs Executable file
View file

@ -0,0 +1,111 @@
using System;
using System.IO;
namespace FSO.Files.HIT
{
/// <summary>
/// TRK is a CSV format that defines a HIT track.
/// </summary>
public class Track
{
private bool TWODKT = false; //Optional encoding as Pascal string, typical Maxis...
public string MagicNumber;
public uint Version;
public string TrackName;
public uint SoundID;
public uint TrackID;
public HITArgs ArgType;
public HITControlGroups ControlGroup;
public HITDuckingPriorities DuckingPriority;
public uint Looped;
public uint Volume;
public bool LoopDefined = false;
//ts1
public uint SubroutineID;
public uint HitlistID;
/// <summary>
/// Creates a new track.
/// </summary>
/// <param name="Filedata">The data to create the track from.</param>
public Track(byte[] Filedata)
{
BinaryReader Reader = new BinaryReader(new MemoryStream(Filedata));
MagicNumber = new string(Reader.ReadChars(4));
if(MagicNumber == "2DKT")
TWODKT = true;
int CurrentVal = 8;
string data;
if(!TWODKT)
data = new string(Reader.ReadChars(Filedata.Length));
else
data = new string(Reader.ReadChars(Reader.ReadInt32()));
string[] Values = data.Split(',');
//MagicNumber = Values[0];
Version = ParseHexString(Values[1]);
TrackName = Values[2];
SoundID = ParseHexString(Values[3]);
TrackID = ParseHexString(Values[4]);
if (Values[5] != "\r\n" && Values[5] != "ETKD" && Values[5] != "") //some tracks terminate here...
{
ArgType = (HITArgs)ParseHexString(Values[5]);
ControlGroup = (HITControlGroups)ParseHexString(Values[7]);
if (Version == 2)
CurrentVal++;
CurrentVal += 3; //skip two unknowns and clsid
DuckingPriority = (HITDuckingPriorities)ParseHexString(Values[CurrentVal]);
CurrentVal++;
Looped = ParseHexString(Values[CurrentVal]);
LoopDefined = true;
CurrentVal++;
Volume = ParseHexString(Values[CurrentVal]);
}
Reader.Close();
}
public Track() { }
private uint ParseHexString(string input)
{
bool IsHex = false;
if (input == "") return 0;
if (input.StartsWith("0x"))
{
input = input.Substring(2);
IsHex = true;
}
else if (input.Contains("a") || input.Contains("b") || input.Contains("c") || input.Contains("d") || input.Contains("e") || input.Contains("f"))
{
IsHex = true;
}
if (IsHex)
{
return Convert.ToUInt32(input, 16);
}
else
{
try
{
return Convert.ToUInt32(input);
}
catch (Exception)
{
return 0;
}
}
}
}
}