#region References using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; #endregion namespace Ultima { public sealed class UOSound { public string Name; public int ID; public byte[] buffer; public UOSound(string name, int id, byte[] buff) { Name = name; ID = id; buffer = buff; } }; public static class Sounds { private static Dictionary m_Translations; private static FileIndex m_FileIndex; private static UOSound[] m_Cache; private static bool[] m_Removed; static Sounds() { Initialize(); } /// /// Reads Sounds and def /// public static void Initialize() { m_Cache = new UOSound[0xFFF]; m_Removed = new bool[0xFFF]; m_FileIndex = new FileIndex("soundidx.mul", "sound.mul", "soundLegacyMUL.uop", 0xFFF, 8, ".dat", -1, false); var reg = new Regex(@"(\d{1,3}) \x7B(\d{1,3})\x7D (\d{1,3})", RegexOptions.Compiled); m_Translations = new Dictionary(); string line; string path = Files.GetFilePath("Sound.def"); if (path == null) { return; } using (var reader = new StreamReader(path)) { while ((line = reader.ReadLine()) != null) { if (((line = line.Trim()).Length != 0) && !line.StartsWith("#")) { Match match = reg.Match(line); if (match.Success) { m_Translations.Add(int.Parse(match.Groups[1].Value), int.Parse(match.Groups[2].Value)); } } } } } /// /// Returns of ID /// /// /// public static UOSound GetSound(int soundID) { bool translated; return GetSound(soundID, out translated); } /// /// Returns of ID with bool translated in .def /// /// /// /// public static UOSound GetSound(int soundID, out bool translated) { translated = false; if (soundID < 0) { return null; } if (m_Cache[soundID] != null) { return m_Cache[soundID]; } int length, extra; bool patched; Stream stream = m_FileIndex.Seek(soundID, out length, out extra, out patched); if ((m_FileIndex.Index[soundID].lookup < 0) || (length <= 0)) { if (!m_Translations.TryGetValue(soundID, out soundID)) { return null; } translated = true; stream = m_FileIndex.Seek(soundID, out length, out extra, out patched); } if (stream == null) { return null; } length -= 32; int[] waveHeader = WaveHeader(length); var stringBuffer = new byte[32]; var buffer = new byte[length]; stream.Read(stringBuffer, 0, 32); stream.Read(buffer, 0, length); stream.Close(); var resultBuffer = new byte[buffer.Length + (waveHeader.Length << 2)]; Buffer.BlockCopy(waveHeader, 0, resultBuffer, 0, (waveHeader.Length << 2)); Buffer.BlockCopy(buffer, 0, resultBuffer, (waveHeader.Length << 2), buffer.Length); string str = Encoding.ASCII.GetString(stringBuffer); // seems that the null terminator's not being properly recognized :/ if (str.IndexOf('\0') > 0) { str = str.Substring(0, str.IndexOf('\0')); } var sound = new UOSound(str, soundID, resultBuffer); if (Files.CacheData) { if (!translated) // no .def definition { m_Cache[soundID] = sound; } } return sound; } private static int[] WaveHeader(int length) { /* ==================== * = WAVE File layout = * ==================== * char[4] = 'RIFF' \ * int - chunk size |- Riff Header * char[4] = 'WAVE' / * char[4] = 'fmt ' \ * int - chunk size | * short - format | * short - channels | * int - samples p/s|- Format header * int - avg bytes | * short - align | * short - bits p/s / * char[4] - data \ * int - chunk size | - Data header * short[..] - data / * ==================== * */ return new[] {0x46464952, (length + 36), 0x45564157, 0x20746D66, 0x10, 0x010001, 0x5622, 0xAC44, 0x100002, 0x61746164, length}; } /// /// Returns Soundname and tests if valid /// /// /// public static bool IsValidSound(int soundID, out string name) { name = ""; if (soundID < 0) { return false; } int length, extra; bool patched; Stream stream = m_FileIndex.Seek(soundID, out length, out extra, out patched); if ((m_FileIndex.Index[soundID].lookup < 0) || (length <= 0)) { if (!m_Translations.TryGetValue(soundID, out soundID)) { return false; } stream = m_FileIndex.Seek(soundID, out length, out extra, out patched); } if (stream == null) { return false; } var stringBuffer = new byte[32]; stream.Read(stringBuffer, 0, 32); stream.Close(); name = Encoding.ASCII.GetString(stringBuffer); // seems that the null terminator's not being properly recognized :/ if (name.IndexOf('\0') > 0) { name = name.Substring(0, name.IndexOf('\0')); } return true; } /// /// Returns length of SoundID /// /// /// public static double GetSoundLength(int soundID) { if (soundID < 0) { return 0; } double len; if (m_Cache[soundID] != null) { len = m_Cache[soundID].buffer.Length; len -= 44; //wavheaderlength } else { int length, extra; bool patched; Stream stream = m_FileIndex.Seek(soundID, out length, out extra, out patched); if ((m_FileIndex.Index[soundID].lookup < 0) || (length <= 0)) { if (!m_Translations.TryGetValue(soundID, out soundID)) { return 0; } stream = m_FileIndex.Seek(soundID, out length, out extra, out patched); } if (stream == null) { return 0; } stream.Close(); length -= 32; //mulheaderlength len = length; } len /= 0x5622; // Sample Rate len /= 2; return len; } public static void Add(int id, string name, string file) { using (var wav = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) { var resultBuffer = new byte[wav.Length]; wav.Seek(0, SeekOrigin.Begin); wav.Read(resultBuffer, 0, (int)wav.Length); m_Cache[id] = new UOSound(name, id, resultBuffer); m_Removed[id] = false; } } public static void Remove(int id) { m_Removed[id] = true; m_Cache[id] = null; } public static void Save(string path) { string idx = Path.Combine(path, "soundidx.mul"); string mul = Path.Combine(path, "sound.mul"); int Headerlength = 44; using ( FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write), fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write)) { using (BinaryWriter binidx = new BinaryWriter(fsidx), binmul = new BinaryWriter(fsmul)) { for (int i = 0; i < m_Cache.Length; ++i) { UOSound sound = m_Cache[i]; if ((sound == null) && (!m_Removed[i])) { bool trans; sound = GetSound(i, out trans); if (!trans) { m_Cache[i] = sound; } else { sound = null; } } if ((sound == null) || (m_Removed[i])) { binidx.Write(-1); // lookup binidx.Write(-1); // length binidx.Write(-1); // extra } else { binidx.Write((int)fsmul.Position); //lookup var length = (int)fsmul.Position; var b = new byte[32]; if (sound.Name != null) { byte[] bb = Encoding.Default.GetBytes(sound.Name); if (bb.Length > 32) { Array.Resize(ref bb, 32); } bb.CopyTo(b, 0); } binmul.Write(b); using (var m = new MemoryStream(sound.buffer)) { m.Seek(Headerlength, SeekOrigin.Begin); var resultBuffer = new byte[m.Length - Headerlength]; m.Read(resultBuffer, 0, (int)m.Length - Headerlength); binmul.Write(resultBuffer); } length = (int)fsmul.Position - length; binidx.Write(length); binidx.Write(i + 1); } } } } } public static void SaveSoundListToCSV(string FileName) { using ( var Tex = new StreamWriter( new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite), Encoding.GetEncoding(1252))) { Tex.WriteLine("ID;Name;Length"); string name = ""; for (int i = 1; i <= 0xFFF; ++i) { if (IsValidSound(i - 1, out name)) { Tex.Write(String.Format("0x{0:X3}", i)); Tex.Write(String.Format(";{0}", name)); Tex.WriteLine(String.Format(";{0:f}", GetSoundLength(i - 1))); } } } } } }