using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LyricDisplay")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LyricDisplay")] [assembly: AssemblyTitle("LyricDisplay")] [assembly: AssemblyVersion("1.0.0.0")] namespace Moxia.LyricDisplay; public enum LyricAlign { Left, Center, Right } public sealed class LyricRenderTrack { public string HeaderText; public string CurrentText; public string SecondText; } public sealed class LyricHudRenderer { private const float PanelMinWidth = 320f; private const float PanelPaddingX = 24f; private const float PanelPaddingY = 20f; private const float LineGap = 6f; private const float HeaderGap = 4f; private const float SourceGap = 10f; private readonly string _fontsFolder; private GUIStyle _currentStyle; private GUIStyle _nextStyle; private GUIStyle _headerStyle; private Texture2D _panelTexture; private Font _dynamicFont; private bool _fontReady; private LyricAlign _currentAlign = LyricAlign.Center; private string _bundledFontPath; private string _cachedCurrentText; private string _cachedSecondLine; private string _cachedHeaderText; private int _cachedFontSize = -1; private LyricAlign _cachedAlign = LyricAlign.Center; private float _layoutPanelWidth; private float _layoutContentWidth; private float _layoutHeaderHeight; private float _layoutCurrentHeight; private float _layoutNextHeight; private readonly List _multiCacheTexts = new List(); private int _multiCacheCount = -1; private float _multiPanelWidth; private float _multiContentWidth; private float _multiTotalHeight; private float[] _multiHeaderHeights; private float[] _multiCurrentHeights; public LyricHudRenderer(string fontsFolder) { _fontsFolder = fontsFolder; } public void DrawTracks(IReadOnlyList tracks, float verticalOffset, float horizontalOffset, int fontSize, float backgroundAlpha, LyricAlign align, bool showHeader) { if (tracks != null && tracks.Count != 0) { EnsureStyles(fontSize, align); if (tracks.Count == 1) { DrawSingle(tracks[0], verticalOffset, horizontalOffset, backgroundAlpha, showHeader); } else { DrawMulti(tracks, verticalOffset, horizontalOffset, backgroundAlpha, showHeader); } } } private void DrawSingle(LyricRenderTrack track, float verticalOffset, float horizontalOffset, float backgroundAlpha, bool showHeader) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) string currentText = track.CurrentText; string secondText = track.SecondText; string text = ((showHeader && !string.IsNullOrEmpty(track.HeaderText)) ? track.HeaderText : null); float num = (float)Screen.height * (1f - verticalOffset); if (LayoutChanged(currentText, secondText, text, _currentStyle.fontSize, _currentAlign)) { ComputeLayout(currentText, secondText, text, _currentStyle.fontSize, _currentAlign); } float layoutPanelWidth = _layoutPanelWidth; float layoutContentWidth = _layoutContentWidth; float layoutCurrentHeight = _layoutCurrentHeight; float layoutNextHeight = _layoutNextHeight; float layoutHeaderHeight = _layoutHeaderHeight; float num2 = layoutHeaderHeight + ((layoutHeaderHeight > 0f) ? 4f : 0f) + layoutCurrentHeight + ((layoutNextHeight > 0f) ? (layoutNextHeight + 6f) : 0f) + 40f; float num3 = (float)Screen.width * horizontalOffset; float num4 = (float)Screen.width * 0.5f - layoutPanelWidth * 0.5f + num3; Rect rect = default(Rect); ((Rect)(ref rect))..ctor(num4, num - num2 * 0.5f, layoutPanelWidth, num2); DrawPanel(rect, backgroundAlpha); float num5 = ((Rect)(ref rect)).y + 20f; if (text != null) { Rect rect2 = default(Rect); ((Rect)(ref rect2))..ctor(((Rect)(ref rect)).x + 24f, num5, layoutContentWidth, layoutHeaderHeight); DrawOutlined(rect2, text, _headerStyle); num5 += layoutHeaderHeight + 4f; } Rect rect3 = default(Rect); ((Rect)(ref rect3))..ctor(((Rect)(ref rect)).x + 24f, num5, layoutContentWidth, layoutCurrentHeight); DrawOutlined(rect3, currentText, _currentStyle); if (!string.IsNullOrEmpty(secondText)) { Rect rect4 = default(Rect); ((Rect)(ref rect4))..ctor(((Rect)(ref rect)).x + 24f, ((Rect)(ref rect3)).yMax + 6f, layoutContentWidth, layoutNextHeight); DrawOutlined(rect4, secondText, _nextStyle); } } private void DrawMulti(IReadOnlyList tracks, float verticalOffset, float horizontalOffset, float backgroundAlpha, bool showHeader) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) float num = (float)Screen.height * (1f - verticalOffset); if (MultiLayoutChanged(tracks, showHeader)) { ComputeMultiLayout(tracks, showHeader); } float multiPanelWidth = _multiPanelWidth; float multiContentWidth = _multiContentWidth; float multiTotalHeight = _multiTotalHeight; float[] multiHeaderHeights = _multiHeaderHeights; float[] multiCurrentHeights = _multiCurrentHeights; float num2 = (float)Screen.width * horizontalOffset; float num3 = (float)Screen.width * 0.5f - multiPanelWidth * 0.5f + num2; Rect rect = default(Rect); ((Rect)(ref rect))..ctor(num3, num - multiTotalHeight * 0.5f, multiPanelWidth, multiTotalHeight); DrawPanel(rect, backgroundAlpha); float num4 = ((Rect)(ref rect)).y + 20f; Rect rect2 = default(Rect); Rect rect3 = default(Rect); for (int i = 0; i < tracks.Count; i++) { string text = ((showHeader && !string.IsNullOrEmpty(tracks[i].HeaderText)) ? tracks[i].HeaderText : null); if (text != null) { ((Rect)(ref rect2))..ctor(((Rect)(ref rect)).x + 24f, num4, multiContentWidth, multiHeaderHeights[i]); DrawOutlined(rect2, text, _headerStyle); num4 += multiHeaderHeights[i] + 4f; } ((Rect)(ref rect3))..ctor(((Rect)(ref rect)).x + 24f, num4, multiContentWidth, multiCurrentHeights[i]); DrawOutlined(rect3, tracks[i].CurrentText, _currentStyle); num4 += multiCurrentHeights[i] + ((i < tracks.Count - 1) ? 10f : 0f); } } private bool MultiLayoutChanged(IReadOnlyList tracks, bool showHeader) { int num = 0; for (int i = 0; i < tracks.Count; i++) { num += ((showHeader && !string.IsNullOrEmpty(tracks[i].HeaderText)) ? 1 : 0) + 1; } if (_multiCacheCount != num) { return true; } int num2 = 0; for (int j = 0; j < tracks.Count; j++) { string text = ((showHeader && !string.IsNullOrEmpty(tracks[j].HeaderText)) ? tracks[j].HeaderText : null); if (text != null && !string.Equals(_multiCacheTexts[num2++], text, StringComparison.Ordinal)) { return true; } if (!string.Equals(_multiCacheTexts[num2++], tracks[j].CurrentText, StringComparison.Ordinal)) { return true; } } return false; } private void ComputeMultiLayout(IReadOnlyList tracks, bool showHeader) { //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Expected O, but got Unknown _multiCacheTexts.Clear(); _multiCacheCount = 0; _multiPanelWidth = 320f; for (int i = 0; i < tracks.Count; i++) { string text = ((showHeader && !string.IsNullOrEmpty(tracks[i].HeaderText)) ? tracks[i].HeaderText : null); if (text != null) { _multiCacheTexts.Add(text); _multiCacheCount++; float x = _headerStyle.CalcSize(new GUIContent(text)).x; _multiPanelWidth = Mathf.Max(_multiPanelWidth, x + 48f); } _multiCacheTexts.Add(tracks[i].CurrentText); _multiCacheCount++; float x2 = _currentStyle.CalcSize(new GUIContent(tracks[i].CurrentText)).x; _multiPanelWidth = Mathf.Max(_multiPanelWidth, x2 + 48f); } _multiContentWidth = _multiPanelWidth - 48f; if (_multiHeaderHeights == null || _multiHeaderHeights.Length != tracks.Count) { _multiHeaderHeights = new float[tracks.Count]; _multiCurrentHeights = new float[tracks.Count]; } _multiTotalHeight = 40f; for (int j = 0; j < tracks.Count; j++) { string text2 = ((showHeader && !string.IsNullOrEmpty(tracks[j].HeaderText)) ? tracks[j].HeaderText : null); float num = ((text2 != null) ? _headerStyle.CalcHeight(new GUIContent(text2), _multiContentWidth) : 0f); float num2 = _currentStyle.CalcHeight(new GUIContent(tracks[j].CurrentText), _multiContentWidth); _multiHeaderHeights[j] = num; _multiCurrentHeights[j] = num2; _multiTotalHeight += num + ((num > 0f) ? 4f : 0f) + num2; if (j < tracks.Count - 1) { _multiTotalHeight += 10f; } } } public static string ResolveSecondLine(LyricLine current, LyricLine next) { if (!string.IsNullOrWhiteSpace(current.Translation)) { return current.Translation; } if (next != null && !string.IsNullOrWhiteSpace(next.Text)) { return next.Text; } return null; } private bool LayoutChanged(string currentText, string secondLine, string headerText, int fontSize, LyricAlign align) { if (_cachedFontSize == fontSize && _cachedAlign == align && string.Equals(_cachedCurrentText, currentText, StringComparison.Ordinal) && string.Equals(_cachedSecondLine, secondLine, StringComparison.Ordinal)) { return !string.Equals(_cachedHeaderText, headerText, StringComparison.Ordinal); } return true; } private void ComputeLayout(string currentText, string secondLine, string headerText, int fontSize, LyricAlign align) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown _cachedCurrentText = currentText; _cachedSecondLine = secondLine; _cachedHeaderText = headerText; _cachedFontSize = fontSize; _cachedAlign = align; float x = _currentStyle.CalcSize(new GUIContent(currentText)).x; float num = ((!string.IsNullOrEmpty(secondLine)) ? _nextStyle.CalcSize(new GUIContent(secondLine)).x : 0f); float num2 = ((headerText != null) ? _headerStyle.CalcSize(new GUIContent(headerText)).x : 0f); _layoutPanelWidth = Mathf.Max(320f, Mathf.Max(x, Mathf.Max(num, num2)) + 48f); _layoutContentWidth = _layoutPanelWidth - 48f; _layoutCurrentHeight = _currentStyle.CalcHeight(new GUIContent(currentText), _layoutContentWidth); _layoutNextHeight = ((!string.IsNullOrEmpty(secondLine)) ? _nextStyle.CalcHeight(new GUIContent(secondLine), _layoutContentWidth) : 0f); _layoutHeaderHeight = ((headerText != null) ? _headerStyle.CalcHeight(new GUIContent(headerText), _layoutContentWidth) : 0f); } private void EnsureStyles(int fontSize, LyricAlign align) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown //IL_017d: Unknown result type (might be due to invalid IL or missing references) if (_currentStyle == null || _currentStyle.fontSize != fontSize || _currentAlign != align) { EnsureFont(); _currentAlign = align; TextAnchor alignment = (TextAnchor)(align switch { LyricAlign.Right => 5, LyricAlign.Left => 3, _ => 4, }); _currentStyle = new GUIStyle(GUI.skin.label) { fontSize = fontSize, fontStyle = (FontStyle)1, alignment = alignment, wordWrap = true }; _currentStyle.normal.textColor = Color.white; if ((Object)(object)_dynamicFont != (Object)null) { _currentStyle.font = _dynamicFont; } _nextStyle = new GUIStyle(GUI.skin.label) { fontSize = Mathf.Max(10, fontSize - 6), fontStyle = (FontStyle)0, alignment = alignment, wordWrap = true }; _nextStyle.normal.textColor = new Color(1f, 1f, 1f, 0.65f); if ((Object)(object)_dynamicFont != (Object)null) { _nextStyle.font = _dynamicFont; } _headerStyle = new GUIStyle(GUI.skin.label) { fontSize = Mathf.Max(9, fontSize - 10), fontStyle = (FontStyle)0, alignment = alignment, wordWrap = true }; _headerStyle.normal.textColor = new Color(1f, 1f, 1f, 0.45f); if ((Object)(object)_dynamicFont != (Object)null) { _headerStyle.font = _dynamicFont; } } } private void EnsureFont() { if (_fontReady) { return; } _fontReady = true; _dynamicFont = LoadBundledFont(); if ((Object)(object)_dynamicFont != (Object)null) { ManualLogSource log = LyricDisplayPlugin.Log; if (log != null) { log.LogInfo((object)("[HUD] using bundled font file: " + _bundledFontPath)); } return; } string[] array = new string[5] { "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", "Noto Sans CJK SC", "Noto Sans SC" }; foreach (string text in array) { try { Font val = Font.CreateDynamicFontFromOSFont(text, 24); if ((Object)(object)val != (Object)null) { _dynamicFont = val; return; } } catch (Exception) { } } array = new string[5] { "Microsoft YaHei", "Microsoft YaHei UI", "SimHei", "SimSun", "Arial" }; foreach (string text2 in array) { try { Font val2 = Font.CreateDynamicFontFromOSFont(text2, 24); if ((Object)(object)val2 != (Object)null) { _dynamicFont = val2; break; } } catch (Exception) { } } } private Font LoadBundledFont() { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown if (string.IsNullOrEmpty(_fontsFolder) || !Directory.Exists(_fontsFolder)) { return null; } try { List list = new List(); list.AddRange(Directory.GetFiles(_fontsFolder, "*.otf")); list.AddRange(Directory.GetFiles(_fontsFolder, "*.ttf")); if (list.Count == 0) { return null; } foreach (string item in list) { try { Font val = new Font(item); if ((Object)(object)val != (Object)null && val.dynamic) { _bundledFontPath = item; return val; } } catch (Exception) { } } } catch (Exception) { } return null; } private void DrawOutlined(Rect rect, string text, GUIStyle style) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) Color color = GUI.color; GUI.color = Color.black; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (i != 0 || j != 0) { GUI.Label(new Rect(((Rect)(ref rect)).x + (float)i, ((Rect)(ref rect)).y + (float)j, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height), text, style); } } } GUI.color = color; GUI.Label(rect, text, style); } private void DrawPanel(Rect rect, float alpha) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (!(alpha <= 0f)) { if ((Object)(object)_panelTexture == (Object)null) { _panelTexture = new Texture2D(1, 1); _panelTexture.SetPixel(0, 0, Color.white); _panelTexture.Apply(); } Color color = GUI.color; GUI.color = new Color(0f, 0f, 0f, Mathf.Clamp01(alpha)); GUI.DrawTexture(rect, (Texture)(object)_panelTexture); GUI.color = color; } } } public sealed class LyricLibrary { private readonly string _folder; private readonly Dictionary> _index = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _externalIndex = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly HashSet _scannedExternalFolders = new HashSet(StringComparer.OrdinalIgnoreCase); public string Folder => _folder; public int Count => _index.Count; public int ExternalCount => _externalIndex.Count; public LyricLibrary(string folder) { _folder = folder; Reload(); } public void Reload() { _index.Clear(); ScanFolderInto(_index, _folder); } public void SetExternalFolders(IEnumerable folders) { _externalIndex.Clear(); _scannedExternalFolders.Clear(); if (folders == null) { return; } foreach (string folder in folders) { if (!string.IsNullOrEmpty(folder)) { string item; try { item = Path.GetFullPath(folder); } catch (Exception) { item = folder; } if (_scannedExternalFolders.Add(item)) { ScanFolderInto(_externalIndex, folder); } } } } public void AddPluginLyricFolders(string pluginsRoot, params string[] folderTokens) { if (string.IsNullOrEmpty(pluginsRoot) || !Directory.Exists(pluginsRoot) || folderTokens == null) { return; } string text = null; try { text = Path.GetFullPath(_folder); } catch (Exception) { } try { string[] directories = Directory.GetDirectories(pluginsRoot, "*", SearchOption.AllDirectories); foreach (string text2 in directories) { string fileName = Path.GetFileName(text2); if (string.IsNullOrEmpty(fileName)) { continue; } bool flag = false; for (int j = 0; j < folderTokens.Length; j++) { if (fileName.IndexOf(folderTokens[j], StringComparison.OrdinalIgnoreCase) >= 0) { flag = true; break; } } if (flag) { string text3; try { text3 = Path.GetFullPath(text2); } catch (Exception) { text3 = text2; } if ((text == null || !string.Equals(text3, text, StringComparison.OrdinalIgnoreCase)) && _scannedExternalFolders.Add(text3)) { ScanFolderInto(_externalIndex, text2); } } } } catch (Exception ex3) { ManualLogSource log = LyricDisplayPlugin.Log; if (log != null) { log.LogWarning((object)("Failed to scan plugins folder for lyric folders: " + ex3.Message)); } } } public LyricTimeline Load(string songName, string fileBaseName = null) { LyricTimeline lyricTimeline = TryLoadFrom(_externalIndex, songName); if (lyricTimeline != null) { return lyricTimeline; } lyricTimeline = TryLoadFrom(_externalIndex, fileBaseName); if (lyricTimeline != null) { return lyricTimeline; } lyricTimeline = TryLoadFrom(_index, songName); if (lyricTimeline != null) { return lyricTimeline; } return TryLoadFrom(_index, fileBaseName); } private static LyricTimeline TryLoadFrom(Dictionary> index, string key) { if (string.IsNullOrEmpty(key)) { return null; } if (index.TryGetValue(key, out var value)) { return new LyricTimeline(value); } return null; } private void ScanFolderInto(Dictionary> target, string folder) { if (!Directory.Exists(folder)) { return; } string[] files = Directory.GetFiles(folder, "*.lrc", SearchOption.AllDirectories); foreach (string text in files) { try { List list = LyricParser.ParseFile(text); if (list.Count != 0) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (!string.IsNullOrEmpty(fileNameWithoutExtension)) { target[fileNameWithoutExtension] = list; } } } catch (Exception ex) { ManualLogSource log = LyricDisplayPlugin.Log; if (log != null) { log.LogWarning((object)("Failed to parse lyric file " + text + ": " + ex.Message)); } } } } } public sealed class LyricLine { public double TimeSeconds { get; } public string Text { get; } public string Translation { get; } public LyricLine(double timeSeconds, string text, string translation = null) { TimeSeconds = timeSeconds; Text = text ?? string.Empty; Translation = translation; } } public static class LyricParser { private static readonly Regex TimestampRegex = new Regex("\\[(\\d{1,3}):(\\d{1,2})(?:[.:](\\d{1,3}))?\\]", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex OffsetRegex = new Regex("^\\[offset:\\s*([+-]?\\d+)\\]$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex WordTagRegex = new Regex("<\\d+,\\d+>", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex WhitespaceRegex = new Regex("\\s+", RegexOptions.Compiled | RegexOptions.CultureInvariant); private static readonly Regex SpaceBeforePunctuationRegex = new Regex("\\s+([,.;:!?])", RegexOptions.Compiled | RegexOptions.CultureInvariant); public static List Parse(string text) { List list = new List(); if (string.IsNullOrEmpty(text)) { return list; } double num = 0.0; string[] array = text.Split('\n'); for (int i = 0; i < array.Length; i++) { string text2 = array[i].TrimEnd('\r'); List list2 = new List(); int num2 = 0; bool flag = true; Match match = TimestampRegex.Match(text2); while (match.Success) { if (match.Index != num2) { flag = false; break; } double num3 = double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); double num4 = double.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture); double num5 = 0.0; if (match.Groups[3].Success) { string value = match.Groups[3].Value; num5 = double.Parse(value, CultureInfo.InvariantCulture) / Math.Pow(10.0, value.Length); } list2.Add(num3 * 60.0 + num4 + num5); num2 = match.Index + match.Length; match = match.NextMatch(); } if (!flag || list2.Count == 0) { Match match2 = OffsetRegex.Match(text2); if (match2.Success) { num = double.Parse(match2.Groups[1].Value, CultureInfo.InvariantCulture) / 1000.0; } continue; } string input = WordTagRegex.Replace(text2.Substring(num2).Trim(), " "); input = WhitespaceRegex.Replace(input, " ").Trim(); input = SpaceBeforePunctuationRegex.Replace(input, "$1"); foreach (double item in list2) { list.Add(new LyricLine(item + num, input)); } } list = list.OrderBy((LyricLine l) => l.TimeSeconds).ToList(); return MergeTranslations(list); } private static List MergeTranslations(List lines) { List list = new List(lines.Count); int num = 0; while (num < lines.Count) { LyricLine lyricLine = lines[num]; string text = null; int i; for (i = num + 1; i < lines.Count && Math.Abs(lines[i].TimeSeconds - lyricLine.TimeSeconds) < 0.001; i++) { text = lines[i].Text; } if (text != null && string.Equals(text.Trim(), lyricLine.Text.Trim(), StringComparison.Ordinal)) { text = null; } list.Add(new LyricLine(lyricLine.TimeSeconds, lyricLine.Text, text)); num = i; } return list; } public static List ParseFile(string path) { return Parse(ReadFileSmart(path)); } private static string ReadFileSmart(string path) { try { return File.ReadAllText(path, Encoding.UTF8); } catch (Exception) { } try { return File.ReadAllText(path, Encoding.Default); } catch (Exception) { return string.Empty; } } } public sealed class LyricTimeline { private readonly List _lines; private int _index = -1; public bool IsLoaded => _lines.Count > 0; public int Count => _lines.Count; public double Duration { get { if (_lines.Count <= 0) { return 0.0; } return _lines[_lines.Count - 1].TimeSeconds; } } public LyricLine Current { get { if (!IsValidIndex(_index)) { return null; } return _lines[_index]; } } public LyricLine Next { get { if (!IsValidIndex(_index + 1)) { return null; } return _lines[_index + 1]; } } public LyricTimeline(List lines) { _lines = lines ?? new List(); } public void Reset() { _index = -1; } public bool Advance(double timeSeconds) { int num = LowerBound(timeSeconds); if (num == _index) { return false; } _index = num; return true; } private bool IsValidIndex(int index) { if (index >= 0) { return index < _lines.Count; } return false; } private int LowerBound(double t) { int num = 0; int num2 = _lines.Count - 1; int result = -1; while (num <= num2) { int num3 = (num + num2) / 2; if (_lines[num3].TimeSeconds <= t) { result = num3; num = num3 + 1; } else { num2 = num3 - 1; } } return result; } } internal static class PluginInfo { public const string PLUGIN_GUID = "LyricDisplay"; public const string PLUGIN_NAME = "LyricDisplay"; public const string PLUGIN_VERSION = "1.0.0"; } [BepInPlugin("LyricDisplay", "LyricDisplay", "1.0.0")] public sealed class LyricDisplayPlugin : BaseUnityPlugin { internal static ManualLogSource Log; internal static LyricDisplayPlugin Instance; private ConfigEntry _enabled; private ConfigEntry _toggleKey; private ConfigEntry _verticalOffset; private ConfigEntry _horizontalOffset; private ConfigEntry _fontSize; private ConfigEntry _showNextLine; private ConfigEntry _backgroundAlpha; private ConfigEntry _holdAfterEnd; private ConfigEntry _textAlign; private ConfigEntry _showHeader; private ConfigEntry _showInSpectate; private ConfigEntry _maxAudioDistance; private ConfigEntry _maxSimultaneousSources; private LyricLibrary _library; private SongTracker _tracker; private LyricHudRenderer _renderer; private readonly Dictionary _timelineBySource = new Dictionary(); private readonly Dictionary _timelineVersionBySource = new Dictionary(); private readonly HashSet _activeSourceIds = new HashSet(); private readonly List _staleSourceIds = new List(); private IReadOnlyList _activeTracks; private readonly List _renderTracks = new List(); private string _cachedSkipReason; private bool _displayToggled = true; private bool _spectating; private Vector3 _playerPosition; private static Camera _mainCamera; private bool _cfgEnabled = true; private bool _cfgShowHeader = true; private bool _cfgShowNextLine = true; private float _cfgVerticalOffset = 0.26f; private float _cfgHorizontalOffset; private int _cfgFontSize = 22; private float _cfgBackgroundAlpha = 0.55f; private LyricAlign _cfgTextAlign = LyricAlign.Center; private KeyCode _toggleKeyCode = (KeyCode)279; private bool _toggleKeyParsed; private static Type _startOfRoundType; private static PropertyInfo _instanceProperty; private static PropertyInfo _localPlayerProperty; private static PropertyInfo _isDeadProperty; private static FieldInfo _spectateUiField; private static bool _spectateReflectFailed; private bool _diagPlaying; private bool _diagInitialized; private float _lastDiagTime = -100f; private string _lastDiagReason = string.Empty; private void Awake() { //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Expected O, but got Unknown //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable the lyric HUD."); _toggleKey = ((BaseUnityPlugin)this).Config.Bind("General", "ToggleDisplayKey", "End", "Key to show/hide the HUD. Unity KeyCode name, e.g. F9 or End."); _verticalOffset = ((BaseUnityPlugin)this).Config.Bind("Display", "VerticalOffset", 0.26f, "Height of the lyrics from the bottom of the screen (0-1). 0.13 sits right above the inventory bar."); _horizontalOffset = ((BaseUnityPlugin)this).Config.Bind("Display", "HorizontalOffset", 0f, "Shift the panel left/right as a fraction of screen width. Negative = left, positive = right."); _fontSize = ((BaseUnityPlugin)this).Config.Bind("Display", "FontSize", 22, "Font size of the lyrics."); _showNextLine = ((BaseUnityPlugin)this).Config.Bind("Display", "ShowNextLine", true, "Show the next lyric line below the current one."); _backgroundAlpha = ((BaseUnityPlugin)this).Config.Bind("Display", "BackgroundAlpha", 0.55f, "Background darkness (0 = transparent, 1 = solid)."); _holdAfterEnd = ((BaseUnityPlugin)this).Config.Bind("Display", "HoldAfterEnd", 4f, "How long the last line stays after the music stops (seconds)."); _textAlign = ((BaseUnityPlugin)this).Config.Bind("Display", "TextAlign", LyricAlign.Center, "Align text inside the panel: Left, Center or Right."); _showHeader = ((BaseUnityPlugin)this).Config.Bind("Display", "ShowHeader", true, "Show 'Title - Artist - Album' above the lyrics (unknown when missing)."); _showInSpectate = ((BaseUnityPlugin)this).Config.Bind("Display", "ShowInSpectate", true, "Show lyrics while spectating after death. Set false to hide them."); _maxAudioDistance = ((BaseUnityPlugin)this).Config.Bind("Distance", "MaxAudioDistance", 35f, "Max distance to show a boombox's lyrics. Farther boomboxes are ignored."); _maxSimultaneousSources = ((BaseUnityPlugin)this).Config.Bind("Distance", "MaxSimultaneousSources", 3, "Max number of boomboxes shown at once (closest first)."); string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "."; _library = new LyricLibrary(Path.Combine(path, "Lyrics")); _tracker = new SongTracker(); _renderer = new LyricHudRenderer(Path.Combine(path, "Fonts")); _library.AddPluginLyricFolders(Paths.PluginPath, "CustomBoomboxMusic", "Lyrics"); Harmony val = new Harmony("LyricDisplay"); Type type = AccessTools.TypeByName("BoomboxItem"); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "StartMusic", (Type[])null, (Type[])null) : null); if (methodInfo != null) { val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SongTracker), "StartMusicPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Hooked BoomboxItem.StartMusic for lyric tracking"); } else { Log.LogWarning((object)"BoomboxItem.StartMusic not found - lyric tracking unavailable"); } Log.LogInfo((object)string.Format("LyricDisplay v{0} loaded; {1} lyric file(s) indexed from {2}", "1.0.0", _library.Count, _library.Folder)); _toggleKey.SettingChanged += delegate { ParseToggleKey(); }; ParseToggleKey(); } private void Update() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) _cfgEnabled = _enabled.Value; if (!_cfgEnabled) { return; } _cfgShowHeader = _showHeader.Value; _cfgShowNextLine = _showNextLine.Value; _cfgVerticalOffset = _verticalOffset.Value; _cfgHorizontalOffset = _horizontalOffset.Value; _cfgFontSize = _fontSize.Value; _cfgBackgroundAlpha = _backgroundAlpha.Value; _cfgTextAlign = _textAlign.Value; _playerPosition = GetLocalPlayerPosition(); _activeTracks = _tracker.Poll(_playerPosition, _maxAudioDistance.Value, _maxSimultaneousSources.Value, _holdAfterEnd.Value); if (_tracker.ConsumeExternalFoldersChanged()) { _library.SetExternalFolders(_tracker.ExternalFolders); } _spectating = !_showInSpectate.Value && IsSpectating(); _activeSourceIds.Clear(); for (int i = 0; i < _activeTracks.Count; i++) { ActiveTrackState activeTrackState = _activeTracks[i]; if ((Object)(object)activeTrackState.Source == (Object)null) { continue; } int instanceID = ((Object)activeTrackState.Source).GetInstanceID(); _activeSourceIds.Add(instanceID); if (!_timelineBySource.TryGetValue(instanceID, out var _) || !_timelineVersionBySource.TryGetValue(instanceID, out var value2) || value2 != activeTrackState.Version) { LyricTimeline lyricTimeline = _library.Load(activeTrackState.SongName, activeTrackState.FileBaseName); _timelineBySource[instanceID] = lyricTimeline; _timelineVersionBySource[instanceID] = activeTrackState.Version; if (lyricTimeline != null) { Log.LogInfo((object)$"Lyrics loaded for '{activeTrackState.SongName}' ({lyricTimeline.Count} lines; external index {_library.ExternalCount})"); } else if (!string.IsNullOrEmpty(activeTrackState.SongName) || !string.IsNullOrEmpty(activeTrackState.FileBaseName)) { Log.LogInfo((object)("No lyrics found for '" + activeTrackState.SongName + "'")); } } } if (_timelineBySource.Count != _activeSourceIds.Count) { _staleSourceIds.Clear(); foreach (int key in _timelineBySource.Keys) { if (!_activeSourceIds.Contains(key)) { _staleSourceIds.Add(key); } } for (int j = 0; j < _staleSourceIds.Count; j++) { _timelineBySource.Remove(_staleSourceIds[j]); _timelineVersionBySource.Remove(_staleSourceIds[j]); } } for (int k = 0; k < _activeTracks.Count; k++) { ActiveTrackState activeTrackState2 = _activeTracks[k]; if (!((Object)(object)activeTrackState2.Source == (Object)null)) { LyricTimeline timelineFor = GetTimelineFor(activeTrackState2); if (timelineFor != null && activeTrackState2.IsPlaying && activeTrackState2.TimeSeconds >= 0.0) { timelineFor.Advance(activeTrackState2.TimeSeconds); } } } if (!_diagInitialized || _diagPlaying != _activeTracks.Count > 0) { _diagInitialized = true; _diagPlaying = _activeTracks.Count > 0; string text = "null"; if (_activeTracks.Count > 0) { LyricTimeline timelineFor2 = GetTimelineFor(_activeTracks[0]); text = ((timelineFor2 != null && timelineFor2.Current != null) ? timelineFor2.Current.Text : "null"); } Log.LogInfo((object)$"[HUD] active sources={_activeTracks.Count} song='{((_activeTracks.Count > 0) ? _activeTracks[0].SongName : string.Empty)}' time={((_activeTracks.Count > 0) ? _activeTracks[0].TimeSeconds : 0.0):0.000}s current='{text}'"); } _cachedSkipReason = ComputeSkipReason(); BuildRenderTracks(_renderTracks); } private LyricTimeline GetTimelineFor(ActiveTrackState track) { if (track == null || (Object)(object)track.Source == (Object)null) { return null; } _timelineBySource.TryGetValue(((Object)track.Source).GetInstanceID(), out var value); return value; } private void HandleToggleKey() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (_toggleKeyParsed) { Event current = Event.current; if (current != null && (int)current.type == 4 && current.keyCode == _toggleKeyCode) { _displayToggled = !_displayToggled; } } } private void ParseToggleKey() { string value = _toggleKey.Value; _toggleKeyParsed = !string.IsNullOrEmpty(value) && Enum.TryParse(value, ignoreCase: true, out _toggleKeyCode); } private void OnGUI() { if (!_cfgEnabled) { return; } HandleToggleKey(); string cachedSkipReason = _cachedSkipReason; if (cachedSkipReason != null) { if (cachedSkipReason != _lastDiagReason || Time.unscaledTime - _lastDiagTime > 5f) { _lastDiagReason = cachedSkipReason; _lastDiagTime = Time.unscaledTime; Log.LogInfo((object)("[HUD] skip: " + cachedSkipReason)); } return; } try { _renderer.DrawTracks(_renderTracks, _cfgVerticalOffset, _cfgHorizontalOffset, _cfgFontSize, _cfgBackgroundAlpha, _cfgTextAlign, _cfgShowHeader); } catch (Exception arg) { Log.LogError((object)$"[HUD] draw error: {arg}"); } } private void BuildRenderTracks(List tracks) { tracks.Clear(); for (int i = 0; i < _activeTracks.Count; i++) { ActiveTrackState activeTrackState = _activeTracks[i]; LyricTimeline timelineFor = GetTimelineFor(activeTrackState); if (timelineFor == null) { continue; } LyricLine current = timelineFor.Current; if (current != null && !string.IsNullOrEmpty(current.Text)) { LyricRenderTrack lyricRenderTrack = new LyricRenderTrack { HeaderText = null, CurrentText = current.Text, SecondText = LyricHudRenderer.ResolveSecondLine(current, _cfgShowNextLine ? timelineFor.Next : null) }; if (_cfgShowHeader) { TrackMetadata metadata = activeTrackState.Metadata; lyricRenderTrack.HeaderText = ((metadata != null) ? metadata.ToDisplayString() : "unknown - unknown - unknown"); } tracks.Add(lyricRenderTrack); } } } private string ComputeSkipReason() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (!_displayToggled) { return "display toggled off"; } if (!IsInGameScene()) { Scene activeScene = SceneManager.GetActiveScene(); return "not in game scene (scene='" + ((Scene)(ref activeScene)).name + "')"; } if (_spectating) { return "spectating (player dead)"; } if (_activeTracks == null || _activeTracks.Count == 0) { return "no audio source within range"; } bool flag = false; for (int i = 0; i < _activeTracks.Count; i++) { LyricTimeline timelineFor = GetTimelineFor(_activeTracks[i]); if (timelineFor != null && timelineFor.Current != null && !string.IsNullOrEmpty(timelineFor.Current.Text)) { flag = true; break; } } if (!flag) { return "no renderable lyrics for nearby sources"; } return null; } private static bool IsInGameScene() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (string.IsNullOrEmpty(name)) { return false; } if (!name.Equals("MainMenu", StringComparison.Ordinal)) { return !name.StartsWith("Init", StringComparison.Ordinal); } return false; } private static bool IsSpectating() { if (_spectateReflectFailed) { return false; } try { if (_startOfRoundType == null) { _startOfRoundType = AccessTools.TypeByName("StartOfRound"); if (_startOfRoundType == null) { _spectateReflectFailed = true; return false; } _instanceProperty = _startOfRoundType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); _localPlayerProperty = _startOfRoundType.GetProperty("localPlayerController", BindingFlags.Instance | BindingFlags.Public); _spectateUiField = _startOfRoundType.GetField("spectateUI", BindingFlags.Instance | BindingFlags.Public); if (_instanceProperty == null || _localPlayerProperty == null) { _spectateReflectFailed = true; return false; } } object value = _instanceProperty.GetValue(null, null); if (value == null) { return false; } if (_spectateUiField != null) { object? value2 = _spectateUiField.GetValue(value); Behaviour val = (Behaviour)((value2 is Behaviour) ? value2 : null); if (val != null && val.isActiveAndEnabled) { return true; } } object value3 = _localPlayerProperty.GetValue(value, null); if (value3 == null) { return false; } if (_isDeadProperty == null) { _isDeadProperty = value3.GetType().GetProperty("isPlayerDead", BindingFlags.Instance | BindingFlags.Public); if (_isDeadProperty == null) { _spectateReflectFailed = true; return false; } } object value4 = _isDeadProperty.GetValue(value3, null); bool flag = default(bool); int num; if (value4 is bool) { flag = (bool)value4; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } catch { _spectateReflectFailed = true; return false; } } private static Vector3 GetLocalPlayerPosition() { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_mainCamera == (Object)null) { _mainCamera = Camera.main; } if ((Object)(object)_mainCamera != (Object)null) { return ((Component)_mainCamera).transform.position; } } catch (Exception) { } try { if (_startOfRoundType == null) { _startOfRoundType = AccessTools.TypeByName("StartOfRound"); } if (_startOfRoundType == null || _instanceProperty == null || _localPlayerProperty == null) { return Vector3.zero; } object value = _instanceProperty.GetValue(null, null); if (value == null) { return Vector3.zero; } object value2 = _localPlayerProperty.GetValue(value, null); if (value2 == null) { return Vector3.zero; } object? obj = value2.GetType().GetProperty("transform", BindingFlags.Instance | BindingFlags.Public)?.GetValue(value2, null); Transform val = (Transform)((obj is Transform) ? obj : null); return (val != null) ? val.position : Vector3.zero; } catch (Exception) { return Vector3.zero; } } } public sealed class ActiveTrackState { public AudioSource Source; public string SongName; public string FileBaseName; public TrackMetadata Metadata; public bool IsPlaying; public bool WasPlaying; public double TimeSeconds; public float LastStopTime = float.MinValue; public float Distance = -1f; public int Version; public AudioClip LastClip; } public sealed class SongTracker { internal static SongTracker Instance; private const string CbmAssemblyName = "baer1.CustomBoomboxMusic"; private const string CbmAudioManagerType = "CustomBoomboxMusic.AudioManager"; private const string CbmAudioClipsProperty = "AudioClips"; private const string CbmAudioFileType = "CustomBoomboxMusic.AudioFile"; private const string CbmAudioClipProperty = "AudioClip"; private const string CbmNameProperty = "Name"; private const string CbmFilePathProperty = "FilePath"; private readonly Dictionary _clipToName = new Dictionary(); private readonly Dictionary _clipToPath = new Dictionary(); private readonly HashSet _externalFolders = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _tracks = new Dictionary(); private readonly List _result = new List(); private int _versionCounter; public IReadOnlyCollection ExternalFolders => _externalFolders; public bool ExternalFoldersChanged { get; private set; } public SongTracker() { Instance = this; } public bool ConsumeExternalFoldersChanged() { bool externalFoldersChanged = ExternalFoldersChanged; ExternalFoldersChanged = false; return externalFoldersChanged; } public static void StartMusicPostfix(object __instance) { try { Instance?.OnStartMusic(__instance); } catch (Exception) { } } public IReadOnlyList Poll(Vector3? playerPosition, float maxDistance, int maxSources, float holdSeconds) { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) _result.Clear(); List list = null; foreach (KeyValuePair track in _tracks) { ActiveTrackState value = track.Value; AudioSource source = value.Source; bool flag = (Object)(object)source != (Object)null && source.isPlaying; if (value.WasPlaying && !flag) { value.LastStopTime = Time.unscaledTime; } value.WasPlaying = flag; value.IsPlaying = flag; value.TimeSeconds = (flag ? ((double)source.time) : (-1.0)); float num = Time.unscaledTime - value.LastStopTime; if (!flag && num > holdSeconds + 8f) { if (list == null) { list = new List(); } list.Add(track.Key); } else { value.Distance = ((playerPosition.HasValue && (Object)(object)source != (Object)null) ? Vector3.Distance(playerPosition.Value, ((Component)source).transform.position) : (-1f)); if (flag || !(num > holdSeconds)) { _result.Add(value); } } } if (list != null) { foreach (AudioSource item in list) { _tracks.Remove(item); } } if (maxDistance > 0f) { _result.RemoveAll((ActiveTrackState t) => t.Distance > maxDistance); } _result.Sort(CompareTracks); if (maxSources > 0 && _result.Count > maxSources) { _result.RemoveRange(maxSources, _result.Count - maxSources); } return _result; } private static int CompareTracks(ActiveTrackState a, ActiveTrackState b) { if (a.IsPlaying != b.IsPlaying) { if (!a.IsPlaying) { return 1; } return -1; } if (a.Distance < 0f && b.Distance < 0f) { return 0; } if (a.Distance < 0f) { return 1; } if (b.Distance < 0f) { return -1; } return a.Distance.CompareTo(b.Distance); } private void OnStartMusic(object boombox) { if (boombox == null) { return; } AudioSource val = ReadBoomboxAudio(boombox); if ((Object)(object)val == (Object)null) { return; } if (!_tracks.TryGetValue(val, out var value)) { value = new ActiveTrackState { Source = val }; _tracks[val] = value; } string text = ResolveSongName(val.clip); if (text != value.SongName || (Object)(object)val.clip != (Object)(object)value.LastClip) { value.SongName = text; value.LastClip = val.clip; value.Version = ++_versionCounter; string text2 = null; if ((Object)(object)val.clip != (Object)null && _clipToPath.TryGetValue(val.clip, out var value2)) { text2 = value2; } value.FileBaseName = ((text2 != null) ? Path.GetFileNameWithoutExtension(text2) : TrimExtension(text)); value.Metadata = TrackMetadataReader.Read(text2, text); } } private static AudioSource ReadBoomboxAudio(object boombox) { object? obj = boombox.GetType().GetField("boomboxAudio", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(boombox); return (AudioSource)((obj is AudioSource) ? obj : null); } private string ResolveSongName(AudioClip clip) { if ((Object)(object)clip == (Object)null) { return null; } RefreshCatalog(); if (_clipToName.TryGetValue(clip, out var value) && !string.IsNullOrEmpty(value)) { return value; } return TrimExtension(((Object)clip).name); } private static string TrimExtension(string name) { if (string.IsNullOrEmpty(name)) { return name; } int num = name.LastIndexOf('.'); if (num <= 0) { return name; } return name.Substring(0, num); } private void RefreshCatalog() { try { Assembly assembly = null; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly2 in assemblies) { if (assembly2.GetName().Name == "baer1.CustomBoomboxMusic") { assembly = assembly2; break; } } if (assembly == null) { return; } Type type = assembly.GetType("CustomBoomboxMusic.AudioManager"); Type type2 = assembly.GetType("CustomBoomboxMusic.AudioFile"); if (type == null || type2 == null) { return; } PropertyInfo property = type.GetProperty("AudioClips", BindingFlags.Static | BindingFlags.Public); PropertyInfo property2 = type2.GetProperty("AudioClip", BindingFlags.Instance | BindingFlags.Public); PropertyInfo property3 = type2.GetProperty("Name", BindingFlags.Instance | BindingFlags.Public); PropertyInfo property4 = type2.GetProperty("FilePath", BindingFlags.Instance | BindingFlags.Public); if (property == null || property2 == null || property3 == null || property4 == null || !(property.GetValue(null) is IEnumerable enumerable)) { return; } bool flag = false; _clipToName.Clear(); _clipToPath.Clear(); _externalFolders.Clear(); foreach (object item in enumerable) { object? value = property2.GetValue(item); AudioClip val = (AudioClip)((value is AudioClip) ? value : null); string value2 = property3.GetValue(item) as string; string text = property4.GetValue(item) as string; if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(value2)) { _clipToName[val] = value2; } if (!string.IsNullOrEmpty(text)) { if ((Object)(object)val != (Object)null) { _clipToPath[val] = text; } string directoryName = Path.GetDirectoryName(text); if (!string.IsNullOrEmpty(directoryName) && _externalFolders.Add(directoryName)) { flag = true; } } } if (flag) { ExternalFoldersChanged = true; } } catch (Exception) { } } } public sealed class TrackMetadata { public string Title { get; } public string Artist { get; } public string Album { get; } public TrackMetadata(string title, string artist, string album) { Title = Clean(title); Artist = Clean(artist); Album = Clean(album); } public string ToDisplayString() { return (Title ?? "unknown") + " - " + (Artist ?? "unknown") + " - " + (Album ?? "unknown"); } private static string Clean(string value) { if (string.IsNullOrWhiteSpace(value)) { return null; } string text = value.Trim(); if (text.Length != 0) { return text; } return null; } } public static class TrackMetadataReader { private const long MaxId3Read = 8388608L; private const long MaxTagSize = 16777216L; public static TrackMetadata Read(string audioPath, string fallbackName) { TrackMetadata trackMetadata = ((!string.IsNullOrEmpty(audioPath) && File.Exists(audioPath)) ? ReadId3(audioPath) : null); TrackMetadata trackMetadata2 = ((!string.IsNullOrEmpty(fallbackName)) ? FromFileName(fallbackName) : null); if (trackMetadata == null && trackMetadata2 == null) { return new TrackMetadata(null, null, null); } return new TrackMetadata(trackMetadata?.Title ?? trackMetadata2?.Title, trackMetadata?.Artist ?? trackMetadata2?.Artist, trackMetadata?.Album ?? trackMetadata2?.Album); } public static TrackMetadata FromFileName(string baseName) { string text = baseName?.Trim(); if (string.IsNullOrEmpty(text)) { return new TrackMetadata(null, null, null); } int num = text.IndexOf(" - ", StringComparison.Ordinal); if (num <= 0) { return new TrackMetadata(text, null, null); } string artist = text.Substring(0, num).Trim(); string text2 = text.Substring(num + 3).Trim(); string album = null; int num2 = text2.LastIndexOf(" (", StringComparison.Ordinal); if (num2 > 0 && text2.EndsWith(")", StringComparison.Ordinal)) { string text3 = text2.Substring(num2 + 2, text2.Length - num2 - 3).Trim(); if (text3.Length > 0) { album = text3; text2 = text2.Substring(0, num2).Trim(); } } return new TrackMetadata(text2, artist, album); } private static TrackMetadata ReadId3(string path) { try { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); if (fileStream.Length < 10) { return null; } byte[] array = new byte[10]; if (ReadFully(fileStream, array) < 10) { return null; } if (array[0] != 73 || array[1] != 68 || array[2] != 51) { return null; } byte b = array[3]; long num = SyncSafeToInt(array, 6); if (num <= 0 || num > 16777216) { return null; } byte[] array2 = new byte[Math.Min(num, 8388608L)]; int num2 = ReadFully(fileStream, array2); if (num2 < 10) { return null; } string title = null; string artist = null; string album = null; int num3 = 0; while (num3 + 10 <= num2 && array2[num3] != 0) { string text = Encoding.ASCII.GetString(array2, num3, 4); long num4 = ((b == 4) ? SyncSafeToInt(array2, num3 + 4) : BigEndianToInt(array2, num3 + 4)); num3 += 10; if (num4 <= 0 || num3 + num4 > num2) { break; } switch (text) { case "TIT2": title = DecodeTextFrame(array2, num3, (int)num4); break; case "TPE1": artist = DecodeTextFrame(array2, num3, (int)num4); break; case "TALB": album = DecodeTextFrame(array2, num3, (int)num4); break; } num3 += (int)num4; } return new TrackMetadata(title, artist, album); } catch (Exception) { return null; } } private static string DecodeTextFrame(byte[] data, int offset, int length) { if (length <= 1) { return null; } byte b = data[offset]; int num = offset + 1; int num2 = length - 1; string text; switch (b) { case 0: text = Latin1ToString(data, num, num2); break; case 1: text = DecodeUtf16(data, num, num2, withBom: true); break; case 2: text = DecodeUtf16(data, num, num2, withBom: false); break; case 3: text = Encoding.UTF8.GetString(data, num, num2); break; default: return null; } int num3 = text.IndexOf('\0'); if (num3 >= 0) { text = text.Substring(0, num3); } if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return null; } private static string Latin1ToString(byte[] data, int offset, int length) { char[] array = new char[length]; for (int i = 0; i < length; i++) { array[i] = (char)data[offset + i]; } return new string(array); } private static string DecodeUtf16(byte[] data, int offset, int length, bool withBom) { if (length <= 0) { return string.Empty; } int num = offset; int num2 = length; bool flag = !withBom; if (withBom && num2 >= 2) { if (data[num] == 254 && data[num + 1] == byte.MaxValue) { flag = true; num += 2; num2 -= 2; } else if (data[num] == byte.MaxValue && data[num + 1] == 254) { flag = false; num += 2; num2 -= 2; } } if (num2 <= 0) { return string.Empty; } if (!flag) { return Encoding.Unicode.GetString(data, num, num2); } return Encoding.BigEndianUnicode.GetString(data, num, num2); } private static long SyncSafeToInt(byte[] data, int offset) { return ((long)(data[offset] & 0x7F) << 21) | ((long)(data[offset + 1] & 0x7F) << 14) | ((long)(data[offset + 2] & 0x7F) << 7) | (data[offset + 3] & 0x7F); } private static long BigEndianToInt(byte[] data, int offset) { return (long)(((ulong)data[offset] << 24) | ((ulong)data[offset + 1] << 16) | ((ulong)data[offset + 2] << 8) | data[offset + 3]); } private static int ReadFully(Stream stream, byte[] buffer) { int i; int num; for (i = 0; i < buffer.Length; i += num) { num = stream.Read(buffer, i, buffer.Length - i); if (num <= 0) { break; } } return i; } }