Decompiled source of LyricDisplay v1.0.0

LyricDisplay.dll

Decompiled 3 hours ago
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<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;
	}
}