Decompiled source of Captionman v1.0.0

plugins/BatteryDie-Captionman/Captionman.dll

Decompiled 3 hours ago
using System;
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.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("BatteryDie")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+ae3e2b15ff7bb4dcb16b001e239f837ad14ed2bd")]
[assembly: AssemblyProduct("Captionman")]
[assembly: AssemblyTitle("Captionman")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Captionman
{
	[BepInPlugin("BatteryDie.Captionman", "Captionman", "1.0.0")]
	public class Captionman : BaseUnityPlugin
	{
		private GameAudioCaptionService? _gameAudioService;

		internal static Captionman Instance { get; private set; }

		internal static ManualLogSource Logger => Instance._logger;

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal Harmony? Harmony { get; set; }

		internal ConfigEntry<bool> EnableCaptionsUI { get; private set; }

		internal ConfigEntry<bool> GameAudioCaptions { get; private set; }

		internal ConfigEntry<float> GameAudioRepeatCooldownSeconds { get; private set; }

		internal ConfigEntry<string> GameAudioCaptionFile { get; private set; }

		internal ConfigEntry<float> BackgroundOpacity { get; private set; }

		internal ConfigEntry<float> TextSize { get; private set; }

		internal ConfigEntry<bool> DisableTextColour { get; private set; }

		internal ConfigEntry<bool> TextLeftAlign { get; private set; }

		internal ConfigEntry<float> HorizontalPosition { get; private set; }

		internal ConfigEntry<float> VerticalPosition { get; private set; }

		internal ConfigEntry<bool> EnableDebug { get; private set; }

		internal GameAudioCaptionService? GameAudioService => _gameAudioService;

		private void Awake()
		{
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Expected O, but got Unknown
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Expected O, but got Unknown
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Expected O, but got Unknown
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fb: Expected O, but got Unknown
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
			EnableCaptionsUI = ((BaseUnityPlugin)this).Config.Bind<bool>("Captions", "EnableCaptionsUI", true, "Master toggle for caption rendering across the entire game");
			GameAudioCaptions = ((BaseUnityPlugin)this).Config.Bind<bool>("Captions", "GameAudioCaptions", true, "Enable closed captions for game audio");
			GameAudioRepeatCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Captions", "GameAudioRepeatCooldownSeconds", 4f, new ConfigDescription("Minimum cooldown in seconds before the same game-audio caption text can appear again", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			GameAudioCaptionFile = ((BaseUnityPlugin)this).Config.Bind<string>("Captions", "GameAudioCaptionFile", "captionsEN.csv", "Caption CSV filename to load. If not found, captionsEN.csv is used as fallback.");
			BackgroundOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "BackgroundOpacity", 0.7f, new ConfigDescription("Opacity of caption background from 0.0 (transparent) to 1.0 (opaque)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			if (BackgroundOpacity.Value < 0f || BackgroundOpacity.Value > 1f)
			{
				BackgroundOpacity.Value = Mathf.Clamp01(BackgroundOpacity.Value);
				((BaseUnityPlugin)this).Config.Save();
			}
			TextSize = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "TextSize", 16f, new ConfigDescription("Caption font size from 10.0 to 25.0", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 25f), Array.Empty<object>()));
			if (TextSize.Value < 10f || TextSize.Value > 25f)
			{
				TextSize.Value = Mathf.Clamp(TextSize.Value, 10f, 25f);
				((BaseUnityPlugin)this).Config.Save();
			}
			DisableTextColour = ((BaseUnityPlugin)this).Config.Bind<bool>("Appearance", "DisableTextColour", false, "Disable custom text colour tags (for example <c:red>Alert</c>)");
			TextLeftAlign = ((BaseUnityPlugin)this).Config.Bind<bool>("Appearance", "TextLeftAlign", false, "Align caption text to the left instead of centered");
			HorizontalPosition = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "HorizontalPosition", 0f, new ConfigDescription("Horizontal position offset for captions", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-270f, 260f), Array.Empty<object>()));
			if (HorizontalPosition.Value < -270f || HorizontalPosition.Value > 260f)
			{
				HorizontalPosition.Value = Mathf.Clamp(HorizontalPosition.Value, -270f, 260f);
				((BaseUnityPlugin)this).Config.Save();
			}
			VerticalPosition = ((BaseUnityPlugin)this).Config.Bind<float>("Appearance", "VerticalPosition", 50f, new ConfigDescription("Vertical position offset for captions", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 350f), Array.Empty<object>()));
			if (VerticalPosition.Value < 0f || VerticalPosition.Value > 350f)
			{
				VerticalPosition.Value = Mathf.Clamp(VerticalPosition.Value, 0f, 350f);
				((BaseUnityPlugin)this).Config.Save();
			}
			EnableDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("Developer", "EnableDebug", false, "Enable debug logging for troubleshooting");
			GameAudioCaptionFile.SettingChanged += delegate
			{
				SoundCaptionCatalog.ReloadFromConfig();
			};
			SoundCaptionCatalog.ReloadFromConfig();
			_gameAudioService = new GameAudioCaptionService(this);
			CaptionUI.EnsureInstance();
			Patch();
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} has loaded!");
		}

		internal void Patch()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_0025: Expected O, but got Unknown
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			Harmony.PatchAll();
		}

		internal void Unpatch()
		{
			Harmony? harmony = Harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			CaptionUI.EnsureInstance();
		}

		internal static void LogInfo(string message)
		{
			Logger.LogInfo((object)(message ?? ""));
		}

		internal static void LogWarning(string message)
		{
			Logger.LogWarning((object)(message ?? ""));
		}

		internal static void LogError(string message)
		{
			Logger.LogError((object)(message ?? ""));
		}

		internal static void LogDebug(string message)
		{
			if (Instance.EnableDebug.Value)
			{
				Logger.LogInfo((object)("[Debug] " + message));
			}
		}

		internal static void LogOutput(string playerName, string text)
		{
			Logger.LogInfo((object)(playerName + ": " + text));
		}
	}
	public static class CaptionmanApi
	{
		public static bool SendCaption(string text)
		{
			return CaptionUI.AddSystemCaptionSafe(text);
		}

		public static bool SendCaption(string speaker, string text)
		{
			return CaptionUI.AddSpeakerCaptionSafe(speaker, text);
		}
	}
	public class CaptionUI : MonoBehaviour
	{
		internal enum CaptionKind
		{
			Speaker,
			GameAudio,
			System
		}

		private class CaptionEntry
		{
			public string Speaker { get; set; } = string.Empty;


			public string Text { get; set; } = string.Empty;


			public float Timestamp { get; set; }

			public float DisplayDuration { get; set; }

			public CaptionKind Kind { get; set; }
		}

		private class PendingCaption
		{
			public string Speaker { get; set; } = string.Empty;


			public string Text { get; set; } = string.Empty;


			public CaptionKind Kind { get; set; }
		}

		private static readonly Queue<PendingCaption> PendingCaptions = new Queue<PendingCaption>();

		private const int MaxPendingCaptions = 40;

		private readonly Queue<CaptionEntry> _captionQueue = new Queue<CaptionEntry>();

		private const int MaxCaptions = 6;

		private const float MinDisplayDuration = 3f;

		private const float MaxDisplayDuration = 14f;

		private static readonly Regex WhitespaceRegex = new Regex("\\s+", RegexOptions.Compiled);

		private static readonly Regex ColorOpenTagRegex = new Regex("<c:[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex ColorCloseTagRegex = new Regex("</c>", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Dictionary<string, string> ApprovedTextColors = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["red"] = "#FF5A5A",
			["yellow"] = "#FFD85A",
			["green"] = "#6DDB7B",
			["blue"] = "#73B7FF",
			["cyan"] = "#66E0FF",
			["orange"] = "#FFAD5A",
			["pink"] = "#FF7CC8",
			["white"] = "#FFFFFF",
			["gray"] = "#B8B8B8",
			["grey"] = "#B8B8B8"
		};

		private const float MaxPanelWidth = 500f;

		private const float MinPanelWidth = 180f;

		private const float MinPanelHeight = 38f;

		private const float MaxPanelHeight = 260f;

		private GameObject? _uiContainer;

		private RectTransform? _containerRect;

		private Image? _backgroundPanel;

		private TextMeshProUGUI? _captionText;

		private CanvasGroup? _canvasGroup;

		private readonly Color _backgroundBaseColor = new Color(0f, 0f, 0f, 1f);

		private readonly Color _textColor = new Color(1f, 1f, 1f, 1f);

		private const float DefaultFontSize = 16f;

		private const float PanelPadding = 10f;

		private float _lastAppliedOpacity = -1f;

		private float _lastAppliedFontSize = -1f;

		private bool? _lastAppliedTextLeftAlign;

		private float _lastAppliedHorizontalPosition = float.NaN;

		private float _lastAppliedVerticalPosition = float.NaN;

		public static CaptionUI? Instance { get; private set; }

		internal static void EnsureInstance()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			if ((Object)(object)Instance != (Object)null)
			{
				return;
			}
			try
			{
				CaptionUI captionUI = Object.FindObjectOfType<CaptionUI>();
				if ((Object)(object)captionUI != (Object)null)
				{
					Instance = captionUI;
					return;
				}
				GameObject val = new GameObject("CaptionUI");
				val.AddComponent<CaptionUI>();
			}
			catch (Exception ex)
			{
				Captionman.LogWarning("Failed to create Caption UI: " + ex.Message);
			}
		}

		public static bool AddSpeakerCaptionSafe(string speaker, string text)
		{
			return AddCaptionSafe(speaker, text, CaptionKind.Speaker);
		}

		public static bool AddSystemCaptionSafe(string text)
		{
			return AddCaptionSafe(string.Empty, text, CaptionKind.System);
		}

		internal static bool AddGameAudioCaptionSafe(string text)
		{
			return AddCaptionSafe(string.Empty, text, CaptionKind.GameAudio);
		}

		private static bool AddCaptionSafe(string speaker, string text, CaptionKind kind)
		{
			if (kind == CaptionKind.Speaker && string.IsNullOrWhiteSpace(speaker))
			{
				return false;
			}
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			EnsureInstance();
			if ((Object)(object)Instance != (Object)null)
			{
				Instance.EnqueueCaption(speaker, text, kind);
				return true;
			}
			PendingCaptions.Enqueue(new PendingCaption
			{
				Speaker = speaker,
				Text = text,
				Kind = kind
			});
			while (PendingCaptions.Count > 40)
			{
				PendingCaptions.Dequeue();
			}
			return false;
		}

		private void Awake()
		{
			if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			Instance = this;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
			TryInitializeUI();
			FlushPendingCaptions();
		}

		private void Update()
		{
			if ((Object)(object)_uiContainer == (Object)null || (Object)(object)_captionText == (Object)null)
			{
				TryInitializeUI();
				FlushPendingCaptions();
			}
			if ((Object)(object)_uiContainer == (Object)null)
			{
				return;
			}
			ApplyBackgroundOpacity();
			ApplyFontSize();
			ApplyTextAlignment();
			ApplyContainerPosition();
			if ((Object)(object)Captionman.Instance == (Object)null || !Captionman.Instance.EnableCaptionsUI.Value)
			{
				_uiContainer.SetActive(false);
				return;
			}
			CleanupOldCaptions();
			if (_captionQueue.Count == 0)
			{
				_uiContainer.SetActive(false);
				return;
			}
			_uiContainer.SetActive(true);
			UpdateCaptionDisplay();
		}

		private bool TryInitializeUI()
		{
			if ((Object)(object)_uiContainer != (Object)null)
			{
				return true;
			}
			RectTransform val = ResolveOrCreateParentCanvasRect();
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Initialize(val);
			return (Object)(object)_uiContainer != (Object)null;
		}

		private RectTransform? ResolveOrCreateParentCanvasRect()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			if ((Object)(object)HUDCanvas.instance != (Object)null && (Object)(object)HUDCanvas.instance.rect != (Object)null)
			{
				((Component)this).transform.SetParent(((Component)HUDCanvas.instance).transform, false);
				return HUDCanvas.instance.rect;
			}
			GameObject val = new GameObject("CaptionmanOverlayCanvas");
			val.transform.SetParent(((Component)this).transform, false);
			Canvas val2 = val.AddComponent<Canvas>();
			val2.renderMode = (RenderMode)0;
			val2.sortingOrder = 2000;
			val.AddComponent<CanvasScaler>();
			val.AddComponent<GraphicRaycaster>();
			return val.GetComponent<RectTransform>();
		}

		private void Initialize(RectTransform parentRect)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Expected O, but got Unknown
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			_uiContainer = new GameObject("CaptionUIContainer");
			_containerRect = _uiContainer.AddComponent<RectTransform>();
			((Transform)_containerRect).SetParent((Transform)(object)parentRect, false);
			_containerRect.anchorMin = new Vector2(0.5f, 0f);
			_containerRect.anchorMax = new Vector2(0.5f, 0f);
			_containerRect.pivot = new Vector2(0.5f, 0f);
			_containerRect.anchoredPosition = new Vector2(0f, 50f);
			_containerRect.sizeDelta = new Vector2(500f, 120f);
			_canvasGroup = _uiContainer.AddComponent<CanvasGroup>();
			_canvasGroup.alpha = 1f;
			GameObject val = new GameObject("CaptionPanel");
			RectTransform val2 = val.AddComponent<RectTransform>();
			((Transform)val2).SetParent((Transform)(object)_containerRect, false);
			val2.anchorMin = new Vector2(0f, 0f);
			val2.anchorMax = new Vector2(1f, 1f);
			val2.offsetMin = Vector2.zero;
			val2.offsetMax = Vector2.zero;
			_backgroundPanel = val.AddComponent<Image>();
			ApplyBackgroundOpacity(force: true);
			GameObject val3 = new GameObject("CaptionText");
			RectTransform val4 = val3.AddComponent<RectTransform>();
			((Transform)val4).SetParent((Transform)(object)val2, false);
			val4.anchorMin = new Vector2(0f, 0f);
			val4.anchorMax = new Vector2(1f, 1f);
			val4.offsetMin = new Vector2(10f, 10f);
			val4.offsetMax = new Vector2(-10f, -10f);
			_captionText = val3.AddComponent<TextMeshProUGUI>();
			((TMP_Text)_captionText).alignment = (TextAlignmentOptions)1026;
			((TMP_Text)_captionText).fontStyle = (FontStyles)1;
			((Graphic)_captionText).color = _textColor;
			((TMP_Text)_captionText).enableWordWrapping = true;
			((TMP_Text)_captionText).overflowMode = (TextOverflowModes)0;
			ApplyFontSize(force: true);
			TrySetGameFont();
			_uiContainer.SetActive(false);
			Captionman.LogDebug("Caption UI initialized");
		}

		private void TrySetGameFont()
		{
			if ((Object)(object)_captionText == (Object)null)
			{
				return;
			}
			try
			{
				GameObject val = GameObject.Find("Tax Haul");
				if (!((Object)(object)val == (Object)null))
				{
					TMP_Text component = val.GetComponent<TMP_Text>();
					if ((Object)(object)component != (Object)null && (Object)(object)component.font != (Object)null)
					{
						((TMP_Text)_captionText).font = component.font;
					}
				}
			}
			catch (Exception ex)
			{
				Captionman.LogDebug("Unable to apply game font: " + ex.Message);
			}
		}

		private void EnqueueCaption(string speaker, string text, CaptionKind kind)
		{
			_captionQueue.Enqueue(new CaptionEntry
			{
				Speaker = speaker,
				Text = text,
				Timestamp = Time.time,
				DisplayDuration = ComputeReadDurationSeconds(speaker, text, kind),
				Kind = kind
			});
			while (_captionQueue.Count > 6)
			{
				_captionQueue.Dequeue();
			}
		}

		private void FlushPendingCaptions()
		{
			if (!((Object)(object)Instance != (Object)(object)this))
			{
				while (PendingCaptions.Count > 0)
				{
					PendingCaption pendingCaption = PendingCaptions.Dequeue();
					EnqueueCaption(pendingCaption.Speaker, pendingCaption.Text, pendingCaption.Kind);
				}
			}
		}

		private void CleanupOldCaptions()
		{
			float time = Time.time;
			while (_captionQueue.Count > 0)
			{
				CaptionEntry captionEntry = _captionQueue.Peek();
				if (!(time - captionEntry.Timestamp < captionEntry.DisplayDuration))
				{
					_captionQueue.Dequeue();
					continue;
				}
				break;
			}
		}

		private void UpdateCaptionDisplay()
		{
			if ((Object)(object)_captionText == (Object)null || (Object)(object)_canvasGroup == (Object)null)
			{
				return;
			}
			List<string> list = new List<string>(_captionQueue.Count);
			foreach (CaptionEntry item in _captionQueue)
			{
				switch (item.Kind)
				{
				case CaptionKind.GameAudio:
					list.Add(TransformColorTags(item.Text));
					break;
				case CaptionKind.Speaker:
					list.Add("<b>" + TransformColorTags(item.Speaker) + ":</b> " + TransformColorTags(item.Text));
					break;
				default:
					list.Add(TransformColorTags(item.Text));
					break;
				}
			}
			((TMP_Text)_captionText).text = string.Join("\n", list);
			UpdatePanelSize();
			_canvasGroup.alpha = 1f;
		}

		private static float ComputeReadDurationSeconds(string speaker, string text, CaptionKind kind)
		{
			string text2 = StripCustomColorTags(text);
			string text3 = StripCustomColorTags(speaker);
			string text4 = ((kind == CaptionKind.Speaker && !string.IsNullOrWhiteSpace(speaker)) ? (text3 + ": " + text2) : text2);
			if (string.IsNullOrWhiteSpace(text4))
			{
				return 3f;
			}
			string text5 = WhitespaceRegex.Replace(text4.Trim(), " ");
			int length = text5.Length;
			string[] array = text5.Split(' ', StringSplitOptions.RemoveEmptyEntries);
			int num = array.Length;
			float num2 = (float)num / 3f;
			float num3 = (float)length / 14f;
			float num4 = 1.5f + Mathf.Max(num2, num3);
			return Mathf.Clamp(num4, 3f, 14f);
		}

		private static string StripCustomColorTags(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			string input = ColorOpenTagRegex.Replace(text, string.Empty);
			return ColorCloseTagRegex.Replace(input, string.Empty);
		}

		private static string TransformColorTags(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			bool flag = (Object)(object)Captionman.Instance == (Object)null || !Captionman.Instance.DisableTextColour.Value;
			StringBuilder stringBuilder = new StringBuilder(text.Length + 16);
			Stack<bool> stack = new Stack<bool>();
			int num = 0;
			while (num < text.Length)
			{
				if (num + 3 < text.Length && text[num] == '<' && (text[num + 1] == 'c' || text[num + 1] == 'C') && text[num + 2] == ':')
				{
					int num2 = text.IndexOf('>', num + 3);
					if (num2 > num)
					{
						string colorToken = text.Substring(num + 3, num2 - (num + 3)).Trim();
						if (flag && TryResolveColorToken(colorToken, out string colorHex))
						{
							stringBuilder.Append("<color=").Append(colorHex).Append('>');
							stack.Push(item: true);
						}
						else
						{
							stack.Push(item: false);
						}
						num = num2 + 1;
						continue;
					}
				}
				if (num + 3 < text.Length && text[num] == '<' && text[num + 1] == '/' && (text[num + 2] == 'c' || text[num + 2] == 'C') && text[num + 3] == '>')
				{
					if (stack.Count > 0 && stack.Pop())
					{
						stringBuilder.Append("</color>");
					}
					num += 4;
				}
				else
				{
					stringBuilder.Append(text[num]);
					num++;
				}
			}
			while (stack.Count > 0)
			{
				if (stack.Pop())
				{
					stringBuilder.Append("</color>");
				}
			}
			return stringBuilder.ToString();
		}

		private static bool TryResolveColorToken(string colorToken, out string colorHex)
		{
			colorHex = string.Empty;
			if (ApprovedTextColors.TryGetValue(colorToken, out string value))
			{
				colorHex = value;
				return true;
			}
			if (TryParseRgbColorToken(colorToken, out colorHex))
			{
				return true;
			}
			return false;
		}

		private static bool TryParseRgbColorToken(string colorToken, out string colorHex)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			colorHex = string.Empty;
			string[] array = colorToken.Split(',', StringSplitOptions.RemoveEmptyEntries);
			if (array.Length != 3)
			{
				return false;
			}
			if (!TryParseColorComponent(array[0], out var component) || !TryParseColorComponent(array[1], out var component2) || !TryParseColorComponent(array[2], out var component3))
			{
				return false;
			}
			Color32 val = default(Color32);
			((Color32)(ref val))..ctor((byte)component, (byte)component2, (byte)component3, byte.MaxValue);
			colorHex = "#" + ColorUtility.ToHtmlStringRGB(Color32.op_Implicit(val));
			return true;
		}

		private static bool TryParseColorComponent(string raw, out int component)
		{
			component = 0;
			string text = raw.Trim();
			if (text.Length == 0)
			{
				return false;
			}
			if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
			{
				component = Mathf.Clamp(result, 0, 255);
				return true;
			}
			if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
			{
				return false;
			}
			if (result2 <= 1f)
			{
				component = Mathf.Clamp(Mathf.RoundToInt(result2 * 255f), 0, 255);
				return true;
			}
			component = Mathf.Clamp(Mathf.RoundToInt(result2), 0, 255);
			return true;
		}

		private void ApplyBackgroundOpacity(bool force = false)
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_backgroundPanel == (Object)null) && !((Object)(object)Captionman.Instance == (Object)null))
			{
				float num = Mathf.Clamp01(Captionman.Instance.BackgroundOpacity.Value);
				if (force || !Mathf.Approximately(num, _lastAppliedOpacity))
				{
					((Graphic)_backgroundPanel).color = new Color(_backgroundBaseColor.r, _backgroundBaseColor.g, _backgroundBaseColor.b, num);
					_lastAppliedOpacity = num;
				}
			}
		}

		private void UpdatePanelSize()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_captionText == (Object)null) && !((Object)(object)_containerRect == (Object)null))
			{
				Vector2 preferredValues = ((TMP_Text)_captionText).GetPreferredValues(((TMP_Text)_captionText).text, 480f, 0f);
				float num = Mathf.Clamp(preferredValues.x + 20f, 180f, 500f);
				float num2 = Mathf.Clamp(preferredValues.y + 20f, 38f, 260f);
				_containerRect.sizeDelta = new Vector2(num, num2);
			}
		}

		private void ApplyFontSize(bool force = false)
		{
			if (!((Object)(object)_captionText == (Object)null) && !((Object)(object)Captionman.Instance == (Object)null))
			{
				float num = Mathf.Clamp(Captionman.Instance.TextSize.Value, 10f, 25f);
				if (force || !Mathf.Approximately(num, _lastAppliedFontSize))
				{
					((TMP_Text)_captionText).fontSize = num;
					_lastAppliedFontSize = num;
				}
			}
		}

		private void ApplyTextAlignment(bool force = false)
		{
			if (!((Object)(object)_captionText == (Object)null) && !((Object)(object)Captionman.Instance == (Object)null))
			{
				bool value = Captionman.Instance.TextLeftAlign.Value;
				if (force || !_lastAppliedTextLeftAlign.HasValue || _lastAppliedTextLeftAlign.Value != value)
				{
					((TMP_Text)_captionText).alignment = (TextAlignmentOptions)(value ? 1025 : 1026);
					_lastAppliedTextLeftAlign = value;
				}
			}
		}

		private void ApplyContainerPosition(bool force = false)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_containerRect == (Object)null) && !((Object)(object)Captionman.Instance == (Object)null))
			{
				float num = Mathf.Clamp(Captionman.Instance.HorizontalPosition.Value, -270f, 260f);
				float num2 = Mathf.Clamp(Captionman.Instance.VerticalPosition.Value, 0f, 350f);
				if (!float.IsFinite(num))
				{
					num = 0f;
				}
				if (!float.IsFinite(num2))
				{
					num2 = 50f;
				}
				if (force || !Mathf.Approximately(num, _lastAppliedHorizontalPosition) || !Mathf.Approximately(num2, _lastAppliedVerticalPosition))
				{
					_containerRect.anchoredPosition = new Vector2(num, num2);
					_lastAppliedHorizontalPosition = num;
					_lastAppliedVerticalPosition = num2;
				}
			}
		}

		public void ClearCaptions()
		{
			_captionQueue.Clear();
			if ((Object)(object)_captionText != (Object)null)
			{
				((TMP_Text)_captionText).text = string.Empty;
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	internal class GameAudioCaptionService
	{
		private readonly Captionman _plugin;

		private const float ProximityRadius = 30f;

		private readonly Dictionary<string, float> _cooldowns = new Dictionary<string, float>();

		public GameAudioCaptionService(Captionman plugin)
		{
			_plugin = plugin;
		}

		internal void OnAudioEvent(string captionText, Vector3? emitterPosition, bool isGlobal = false)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (_plugin.EnableCaptionsUI.Value && _plugin.GameAudioCaptions.Value)
			{
				if (!isGlobal && emitterPosition.HasValue && !IsWithinProximity(emitterPosition.Value))
				{
					Captionman.LogDebug("GameAudio suppressed (out of range): " + captionText);
				}
				else if (!IsOnCooldown(captionText))
				{
					SetCooldown(captionText);
					CaptionUI.AddGameAudioCaptionSafe(captionText);
					Captionman.LogDebug("GameAudio caption: " + captionText);
				}
			}
		}

		private bool IsWithinProximity(Vector3 emitterPosition)
		{
			//IL_0015: 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_0042: 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)
			try
			{
				PlayerAvatar instance = PlayerAvatar.instance;
				if ((Object)(object)instance != (Object)null)
				{
					return Vector3.Distance(((Component)instance).transform.position, emitterPosition) <= 30f;
				}
				Camera main = Camera.main;
				if ((Object)(object)main != (Object)null)
				{
					return Vector3.Distance(((Component)main).transform.position, emitterPosition) <= 30f;
				}
			}
			catch (Exception ex)
			{
				Captionman.LogDebug("Proximity check error: " + ex.Message);
			}
			return true;
		}

		private bool IsOnCooldown(string captionText)
		{
			if (!_cooldowns.TryGetValue(captionText, out var value))
			{
				return false;
			}
			float num = Mathf.Max(0f, _plugin.GameAudioRepeatCooldownSeconds.Value);
			return Time.time - value < num;
		}

		private void SetCooldown(string captionText)
		{
			_cooldowns[captionText] = Time.time;
		}
	}
	[HarmonyPatch]
	internal static class GameAudioPatches
	{
		private static void HandlePlayPostfix(Sound __instance, Vector3 position, AudioSource __result)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__result == (Object)null || __instance.Sounds == null || __instance.Sounds.Length == 0)
			{
				return;
			}
			GameAudioCaptionService gameAudioCaptionService = Captionman.Instance?.GameAudioService;
			if (gameAudioCaptionService != null)
			{
				AudioClip clip = __result.clip;
				string text = ((clip != null) ? ((Object)clip).name : null);
				if (string.IsNullOrWhiteSpace(text))
				{
					AudioClip obj = __instance.Sounds[0];
					text = ((obj != null) ? ((Object)obj).name : null);
				}
				if (!string.IsNullOrWhiteSpace(text) && SoundCaptionCatalog.Current.TryResolve(text, __instance, out string caption, out bool isGlobal))
				{
					Captionman.LogDebug($"GameAudio CSV match: '{text}' -> '{caption}' (global={isGlobal})");
					gameAudioCaptionService.OnAudioEvent(caption, position, isGlobal);
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Sound), "Play", new Type[]
		{
			typeof(Vector3),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void Sound_Play_Vector3_Postfix(Sound __instance, Vector3 position, AudioSource __result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			HandlePlayPostfix(__instance, position, __result);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Sound), "Play", new Type[]
		{
			typeof(Transform),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void Sound_Play_Transform_Postfix(Sound __instance, Transform followTarget, AudioSource __result)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			HandlePlayPostfix(__instance, ((Object)(object)followTarget != (Object)null) ? followTarget.position : Vector3.zero, __result);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Sound), "Play", new Type[]
		{
			typeof(Transform),
			typeof(Vector3),
			typeof(float),
			typeof(float),
			typeof(float),
			typeof(float)
		})]
		private static void Sound_Play_TransformContact_Postfix(Sound __instance, Vector3 contactPoint, AudioSource __result)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			HandlePlayPostfix(__instance, contactPoint, __result);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Sound), "PlayLoop")]
		private static void Sound_PlayLoop_Prefix(Sound __instance, bool playing)
		{
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			if (!playing || (Object)(object)__instance.Source == (Object)null || !((Behaviour)__instance.Source).enabled || __instance.Sounds == null || __instance.Sounds.Length == 0)
			{
				return;
			}
			GameAudioCaptionService gameAudioCaptionService = Captionman.Instance?.GameAudioService;
			if (gameAudioCaptionService == null)
			{
				return;
			}
			AudioClip clip = __instance.Source.clip;
			string text = ((clip != null) ? ((Object)clip).name : null);
			if (string.IsNullOrWhiteSpace(text))
			{
				AudioClip obj = __instance.Sounds[0];
				text = ((obj != null) ? ((Object)obj).name : null);
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				if (!SoundCaptionCatalog.Current.TryResolve(text, __instance, out string caption, out bool isGlobal))
				{
					Captionman.LogDebug("No caption for loop \"" + text + "\"");
					return;
				}
				Vector3 position = ((Component)__instance.Source).transform.position;
				Captionman.LogDebug($"GameAudio CSV match (loop): '{text}' -> '{caption}' (global={isGlobal})");
				gameAudioCaptionService.OnAudioEvent(caption, position, isGlobal);
			}
		}
	}
	internal sealed class SoundCaptionCatalog
	{
		internal readonly struct Entry
		{
			internal string Caption { get; }

			internal bool IsGlobal { get; }

			internal Entry(string caption, bool isGlobal)
			{
				Caption = caption;
				IsGlobal = isGlobal;
			}
		}

		private sealed class ResolvedCaptionCatalog
		{
			internal string CsvPath { get; }

			internal string Source { get; }

			internal ResolvedCaptionCatalog(string csvPath, string source)
			{
				CsvPath = csvPath;
				Source = source;
			}
		}

		private static readonly object CatalogLock = new object();

		private const string DefaultCaptionFileName = "captionsEN.csv";

		private readonly Dictionary<string, Entry> _entriesByName;

		private static SoundCaptionCatalog _current = new SoundCaptionCatalog(new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase));

		internal static SoundCaptionCatalog Current
		{
			get
			{
				lock (CatalogLock)
				{
					return _current;
				}
			}
		}

		private SoundCaptionCatalog(Dictionary<string, Entry> entriesByName)
		{
			_entriesByName = entriesByName;
		}

		internal static void ReloadFromConfig()
		{
			string text = NormalizeCaptionSelector(Captionman.Instance?.GameAudioCaptionFile?.Value);
			ResolvedCaptionCatalog resolvedCaptionCatalog = ResolveCsvPath(text);
			string text2 = (string.IsNullOrWhiteSpace(text) ? "captionsEN.csv" : EnsureCsvExtension(text));
			if (resolvedCaptionCatalog != null)
			{
				string fileName = Path.GetFileName(resolvedCaptionCatalog.CsvPath);
				if (!string.Equals(text2, fileName, StringComparison.OrdinalIgnoreCase) && string.Equals(fileName, "captionsEN.csv", StringComparison.OrdinalIgnoreCase))
				{
					Captionman.LogWarning("Failed to load " + text2 + ", falling back to captionsEN.csv");
				}
				else
				{
					Captionman.LogInfo("Successfully loaded " + fileName);
				}
			}
			SoundCaptionCatalog current = Load(resolvedCaptionCatalog);
			lock (CatalogLock)
			{
				_current = current;
			}
		}

		private static SoundCaptionCatalog Load(ResolvedCaptionCatalog? resolvedCatalog)
		{
			if (resolvedCatalog == null)
			{
				Captionman.LogWarning("Caption CSV not found. Configure Captions.GameAudioCaptionFile with a CSV filename like captionsEN.csv. captionsEN.csv is always used as fallback when available.");
				return new SoundCaptionCatalog(new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase));
			}
			try
			{
				string csvPath = resolvedCatalog.CsvPath;
				List<string> list = File.ReadLines(csvPath).ToList();
				if (list.Count == 0)
				{
					Captionman.LogWarning("Caption CSV is empty: " + csvPath);
					return new SoundCaptionCatalog(new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase));
				}
				List<string> header = ParseCsvLine(list[0]);
				int num = FindColumnIndex(header, "name");
				int num2 = FindColumnIndex(header, "caption");
				int num3 = FindColumnIndex(header, "isglobal");
				if (num < 0 || num2 < 0)
				{
					Captionman.LogError("Caption CSV is missing required columns: name, caption");
					return new SoundCaptionCatalog(new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase));
				}
				Dictionary<string, Entry> dictionary = new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase);
				int num4 = 0;
				int num5 = 0;
				for (int i = 1; i < list.Count; i++)
				{
					string text = list[i];
					if (string.IsNullOrWhiteSpace(text))
					{
						continue;
					}
					List<string> list2 = ParseCsvLine(text);
					if (list2.Count <= Math.Max(num, num2))
					{
						continue;
					}
					string text2 = list2[num].Trim();
					string text3 = list2[num2].Trim();
					bool flag = num3 >= 0 && num3 < list2.Count && ParseBool(list2[num3]);
					if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(text3))
					{
						dictionary[text2] = new Entry(text3, flag);
						num4++;
						if (flag)
						{
							num5++;
						}
					}
				}
				Captionman.LogInfo($"Loaded sound caption catalog: {num4} entries ({num5} global) from {Path.GetFileName(csvPath)} ({resolvedCatalog.Source})");
				return new SoundCaptionCatalog(dictionary);
			}
			catch (Exception ex)
			{
				Captionman.LogError("Failed to load sound caption CSV: " + ex.Message);
				return new SoundCaptionCatalog(new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase));
			}
		}

		internal bool TryResolve(string clipName, Sound sound, out string caption, out bool isGlobal)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Invalid comparison between Unknown and I4
			caption = string.Empty;
			isGlobal = false;
			if (string.IsNullOrWhiteSpace(clipName))
			{
				return false;
			}
			if (!_entriesByName.TryGetValue(clipName, out var value))
			{
				Captionman.LogDebug("No caption for \"" + clipName + "\"");
				return false;
			}
			caption = value.Caption;
			isGlobal = value.IsGlobal || clipName.IndexOf(" global", StringComparison.OrdinalIgnoreCase) >= 0 || (int)sound.Type == 7;
			return true;
		}

		private static ResolvedCaptionCatalog? ResolveCsvPath(string selector)
		{
			string text = (string.IsNullOrWhiteSpace(selector) ? "captionsEN.csv" : EnsureCsvExtension(selector));
			List<string> searchDirectories = GetSearchDirectories();
			foreach (string item in searchDirectories)
			{
				string text2 = Path.Combine(item, text);
				if (File.Exists(text2))
				{
					return new ResolvedCaptionCatalog(text2, "requested-root");
				}
				string text3 = Path.Combine(item, "Captions", text);
				if (File.Exists(text3))
				{
					return new ResolvedCaptionCatalog(text3, "requested-captions");
				}
			}
			if (!string.Equals(text, "captionsEN.csv", StringComparison.OrdinalIgnoreCase))
			{
				foreach (string item2 in searchDirectories)
				{
					string text4 = Path.Combine(item2, "captionsEN.csv");
					if (File.Exists(text4))
					{
						return new ResolvedCaptionCatalog(text4, "default-root");
					}
					string text5 = Path.Combine(item2, "Captions", "captionsEN.csv");
					if (File.Exists(text5))
					{
						return new ResolvedCaptionCatalog(text5, "default-captions");
					}
				}
			}
			string[] array = new string[3] { "game_audio_captions.csv", "sound_caption_review.csv", "sound_captions.csv" };
			foreach (string item3 in searchDirectories)
			{
				string[] array2 = array;
				foreach (string text6 in array2)
				{
					string text7 = Path.Combine(item3, "Captions", text6);
					if (File.Exists(text7))
					{
						Captionman.LogDebug("Using legacy caption catalog fallback: " + text7);
						return new ResolvedCaptionCatalog(text7, "legacy-captions");
					}
					string text8 = Path.Combine(item3, text6);
					if (File.Exists(text8))
					{
						Captionman.LogDebug("Using legacy caption catalog fallback: " + text8);
						return new ResolvedCaptionCatalog(text8, "legacy-root");
					}
				}
			}
			return null;
		}

		private static string EnsureCsvExtension(string fileName)
		{
			if (fileName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
			{
				return fileName;
			}
			return fileName + ".csv";
		}

		private static string NormalizeCaptionSelector(string? value)
		{
			return value?.Trim() ?? string.Empty;
		}

		private static List<string> GetSearchDirectories()
		{
			List<string> list = new List<string>
			{
				Paths.PluginPath,
				Path.Combine(Paths.PluginPath, "BatteryDie.Captionman"),
				Path.Combine(Paths.PluginPath, "BatteryDie-Captionman"),
				Paths.GameRootPath,
				Paths.ConfigPath
			};
			string directoryName = Path.GetDirectoryName(typeof(Captionman).Assembly.Location);
			if (!string.IsNullOrWhiteSpace(directoryName))
			{
				list.Insert(0, directoryName);
			}
			return list.Where((string dir) => !string.IsNullOrWhiteSpace(dir)).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
		}

		private static int FindColumnIndex(IReadOnlyList<string> header, string columnName)
		{
			for (int i = 0; i < header.Count; i++)
			{
				if (string.Equals(header[i].Trim(), columnName, StringComparison.OrdinalIgnoreCase))
				{
					return i;
				}
			}
			return -1;
		}

		private static List<string> ParseCsvLine(string line)
		{
			List<string> list = new List<string>();
			StringBuilder stringBuilder = new StringBuilder();
			bool flag = false;
			for (int i = 0; i < line.Length; i++)
			{
				char c = line[i];
				switch (c)
				{
				case '"':
					if (flag && i + 1 < line.Length && line[i + 1] == '"')
					{
						stringBuilder.Append('"');
						i++;
					}
					else
					{
						flag = !flag;
					}
					continue;
				case ',':
					if (!flag)
					{
						list.Add(stringBuilder.ToString());
						stringBuilder.Clear();
						continue;
					}
					break;
				}
				stringBuilder.Append(c);
			}
			list.Add(stringBuilder.ToString());
			return list;
		}

		private static bool ParseBool(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			string text = value.Trim();
			if (bool.TryParse(text, out var result))
			{
				return result;
			}
			if (!string.Equals(text, "1", StringComparison.OrdinalIgnoreCase) && !string.Equals(text, "yes", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(text, "y", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}
	}
}