Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LyricDisplay v1.0.0
LyricDisplay.dll
Decompiled 3 hours agousing 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<string> _multiCacheTexts = new List<string>(); 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<LyricRenderTrack> 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<LyricRenderTrack> 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<LyricRenderTrack> 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<LyricRenderTrack> 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<string> list = new List<string>(); 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<string, List<LyricLine>> _index = new Dictionary<string, List<LyricLine>>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, List<LyricLine>> _externalIndex = new Dictionary<string, List<LyricLine>>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _scannedExternalFolders = new HashSet<string>(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<string> 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<string, List<LyricLine>> 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<string, List<LyricLine>> target, string folder) { if (!Directory.Exists(folder)) { return; } string[] files = Directory.GetFiles(folder, "*.lrc", SearchOption.AllDirectories); foreach (string text in files) { try { List<LyricLine> 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<LyricLine> Parse(string text) { List<LyricLine> list = new List<LyricLine>(); 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<double> list2 = new List<double>(); 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<LyricLine> MergeTranslations(List<LyricLine> lines) { List<LyricLine> list = new List<LyricLine>(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<LyricLine> 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<LyricLine> _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<LyricLine> lines) { _lines = lines ?? new List<LyricLine>(); } 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<bool> _enabled; private ConfigEntry<string> _toggleKey; private ConfigEntry<float> _verticalOffset; private ConfigEntry<float> _horizontalOffset; private ConfigEntry<int> _fontSize; private ConfigEntry<bool> _showNextLine; private ConfigEntry<float> _backgroundAlpha; private ConfigEntry<float> _holdAfterEnd; private ConfigEntry<LyricAlign> _textAlign; private ConfigEntry<bool> _showHeader; private ConfigEntry<bool> _showInSpectate; private ConfigEntry<float> _maxAudioDistance; private ConfigEntry<int> _maxSimultaneousSources; private LyricLibrary _library; private SongTracker _tracker; private LyricHudRenderer _renderer; private readonly Dictionary<int, LyricTimeline> _timelineBySource = new Dictionary<int, LyricTimeline>(); private readonly Dictionary<int, int> _timelineVersionBySource = new Dictionary<int, int>(); private readonly HashSet<int> _activeSourceIds = new HashSet<int>(); private readonly List<int> _staleSourceIds = new List<int>(); private IReadOnlyList<ActiveTrackState> _activeTracks; private readonly List<LyricRenderTrack> _renderTracks = new List<LyricRenderTrack>(); 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<bool>("General", "Enabled", true, "Enable the lyric HUD."); _toggleKey = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ToggleDisplayKey", "End", "Key to show/hide the HUD. Unity KeyCode name, e.g. F9 or End."); _verticalOffset = ((BaseUnityPlugin)this).Config.Bind<float>("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<float>("Display", "HorizontalOffset", 0f, "Shift the panel left/right as a fraction of screen width. Negative = left, positive = right."); _fontSize = ((BaseUnityPlugin)this).Config.Bind<int>("Display", "FontSize", 22, "Font size of the lyrics."); _showNextLine = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowNextLine", true, "Show the next lyric line below the current one."); _backgroundAlpha = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "BackgroundAlpha", 0.55f, "Background darkness (0 = transparent, 1 = solid)."); _holdAfterEnd = ((BaseUnityPlugin)this).Config.Bind<float>("Display", "HoldAfterEnd", 4f, "How long the last line stays after the music stops (seconds)."); _textAlign = ((BaseUnityPlugin)this).Config.Bind<LyricAlign>("Display", "TextAlign", LyricAlign.Center, "Align text inside the panel: Left, Center or Right."); _showHeader = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowHeader", true, "Show 'Title - Artist - Album' above the lyrics (unknown when missing)."); _showInSpectate = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "ShowInSpectate", true, "Show lyrics while spectating after death. Set false to hide them."); _maxAudioDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Distance", "MaxAudioDistance", 35f, "Max distance to show a boombox's lyrics. Farther boomboxes are ignored."); _maxSimultaneousSources = ((BaseUnityPlugin)this).Config.Bind<int>("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<KeyCode>(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<LyricRenderTrack> 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<AudioClip, string> _clipToName = new Dictionary<AudioClip, string>(); private readonly Dictionary<AudioClip, string> _clipToPath = new Dictionary<AudioClip, string>(); private readonly HashSet<string> _externalFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<AudioSource, ActiveTrackState> _tracks = new Dictionary<AudioSource, ActiveTrackState>(); private readonly List<ActiveTrackState> _result = new List<ActiveTrackState>(); private int _versionCounter; public IReadOnlyCollection<string> 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<ActiveTrackState> 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<AudioSource> list = null; foreach (KeyValuePair<AudioSource, ActiveTrackState> 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<AudioSource>(); } 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; } }