using System; using System.Collections.Generic; using System.IO; namespace FSO.Files.HIT { /// /// Enumeration constants. /// public struct EventMappingEquate { public string Label; public int Value; } /// /// This section assigns a sound ID to a number of subroutines /// (exported or not) in the corresponding HIT file. /// 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; } /// /// HOT (short for HIT Options Table) is an ini format that defines /// enumeration constants and track data for HIT binary files. /// public class Hot { private int m_Version; private int m_LoadPriority; private List m_EventMappingEquations = new List(); private List m_TrackDataList = new List(); public Dictionary Tracks = new Dictionary(); public Dictionary Patches = new Dictionary(); public Dictionary Hitlists = new Dictionary(); public Dictionary TrackData = new Dictionary(); public Dictionary Events = new Dictionary(); private Dictionary EventMappingEquate = new Dictionary(); public HSM AsmNames; /// /// Gets this Hot instance's list of EventMappingEquate instances. /// public List EventMappingEquations { get { return m_EventMappingEquations; } } /// /// Gets this Hot instance's list of TrackData instances. /// public List 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 } }