Decompiled source of BetterCommunication v0.2.2

BetterCommunication.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace SuperBattleGolf.BetterCommunication;

[BepInPlugin("local.marki.superbattlegolf.bettercommunication", "BetterCommunication", "0.2.2")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "local.marki.superbattlegolf.bettercommunication";

	public const string PluginName = "BetterCommunication";

	public const string PluginVersion = "0.2.2";

	private ConfigEntry<bool> _enableSteamProfileLinks;

	private ConfigEntry<bool> _useSteamProtocol;

	private ConfigEntry<bool> _enableWorldNameTags;

	private ConfigEntry<bool> _enableAvatarLinks;

	private ConfigEntry<bool> _enableChatLinks;

	private ConfigEntry<bool> _enableChatHistory;

	private ConfigEntry<int> _maxChatHistoryMessages;

	private ConfigEntry<bool> _persistChatHistoryAcrossLaunches;

	private SteamProfileLinkInjector _steamProfileLinkInjector;

	private ChatHistoryManager _chatHistoryManager;

	private void Awake()
	{
		_enableSteamProfileLinks = ((BaseUnityPlugin)this).Config.Bind<bool>("Steam Profiles", "EnableSteamProfileLinks", true, "Make player names in supported UI screens clickable to open the player's Steam profile.");
		_useSteamProtocol = ((BaseUnityPlugin)this).Config.Bind<bool>("Steam Profiles", "UseSteamProtocol", true, "Open profiles through steam://openurl/... instead of a plain https URL.");
		_enableWorldNameTags = ((BaseUnityPlugin)this).Config.Bind<bool>("Steam Profiles", "EnableWorldNameTags", false, "Also make in-world floating name tags clickable.");
		_enableAvatarLinks = ((BaseUnityPlugin)this).Config.Bind<bool>("Steam Profiles", "EnableAvatarLinks", true, "Also make supported player avatar images clickable.");
		_enableChatLinks = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "EnableChatLinks", true, "Make URLs in text chat clickable.");
		_enableChatHistory = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "EnableChatHistory", true, "Keep recent chat messages in memory and restore them when the chat UI is recreated.");
		_maxChatHistoryMessages = ((BaseUnityPlugin)this).Config.Bind<int>("Chat", "MaxChatHistoryMessages", 60, "Maximum number of chat messages kept in local history.");
		_persistChatHistoryAcrossLaunches = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "PersistChatHistoryAcrossLaunches", false, "Also save chat history to disk so it survives a full game restart.");
		_steamProfileLinkInjector = new SteamProfileLinkInjector(((BaseUnityPlugin)this).Logger, _enableSteamProfileLinks, _useSteamProtocol, _enableWorldNameTags, _enableAvatarLinks, _enableChatLinks);
		_chatHistoryManager = new ChatHistoryManager(((BaseUnityPlugin)this).Logger, _enableChatHistory, _maxChatHistoryMessages, _persistChatHistoryAcrossLaunches);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin loaded.");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"BetterCommunication adds UI-only Steam profile links and chat improvements without changing gameplay state.");
	}

	private void Update()
	{
		if (_steamProfileLinkInjector != null)
		{
			_steamProfileLinkInjector.Update();
		}
		if (_chatHistoryManager != null)
		{
			_chatHistoryManager.Update();
		}
	}
}
internal sealed class ChatHistoryManager
{
	private const BindingFlags AllMembers = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private static readonly Regex LegacyLinkTagRegex = new Regex("</?(?:u|link)(?:=[^>]+)?>", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private static readonly Regex LegacyColorTagRegex = new Regex("</?color(?:=[^>]+)?>", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private static readonly Regex NoParseTagRegex = new Regex("</?noparse>", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private readonly ManualLogSource _logger;

	private readonly ConfigEntry<bool> _enabled;

	private readonly ConfigEntry<int> _maxMessages;

	private readonly ConfigEntry<bool> _persistAcrossLaunches;

	private readonly Dictionary<string, int> _pendingRestoreIgnores = new Dictionary<string, int>(StringComparer.Ordinal);

	private readonly List<string> _messages = new List<string>();

	private readonly string _historyFilePath;

	private Type _textChatUiType;

	private Type _textChatMessageUiType;

	private int _lastRestoredUiId = -1;

	private float _nextScanTime;

	public ChatHistoryManager(ManualLogSource logger, ConfigEntry<bool> enabled, ConfigEntry<int> maxMessages, ConfigEntry<bool> persistAcrossLaunches)
	{
		_logger = logger;
		_enabled = enabled;
		_maxMessages = maxMessages;
		_persistAcrossLaunches = persistAcrossLaunches;
		_historyFilePath = Path.Combine(Paths.ConfigPath, "local.marki.superbattlegolf.chat_history.txt");
		LoadHistory();
	}

	public void Update()
	{
		if (_enabled.Value && !(Time.unscaledTime < _nextScanTime))
		{
			_nextScanTime = Time.unscaledTime + 0.5f;
			EnsureTypes();
			AttachTrackers();
			RestoreToLatestChatUi();
		}
	}

	public void TryCaptureMessage(string rawMessage)
	{
		if (!_enabled.Value)
		{
			return;
		}
		string text = NormalizeMessage(rawMessage);
		if (string.IsNullOrWhiteSpace(text))
		{
			return;
		}
		if (_pendingRestoreIgnores.TryGetValue(text, out var value) && value > 0)
		{
			value--;
			if (value == 0)
			{
				_pendingRestoreIgnores.Remove(text);
			}
			else
			{
				_pendingRestoreIgnores[text] = value;
			}
		}
		else
		{
			_messages.Add(text);
			TrimMessages();
			SaveHistory();
		}
	}

	private void RestoreToLatestChatUi()
	{
		if (_textChatUiType == null || _messages.Count == 0)
		{
			return;
		}
		Object[] array = Resources.FindObjectsOfTypeAll(_textChatUiType);
		if (array == null || array.Length == 0)
		{
			return;
		}
		Object obj = array[^1];
		Component val = (Component)(object)((obj is Component) ? obj : null);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		int instanceID = ((Object)val).GetInstanceID();
		if (_lastRestoredUiId == instanceID)
		{
			return;
		}
		if (GetVisibleMessageCount() > 0)
		{
			_lastRestoredUiId = instanceID;
			return;
		}
		MethodInfo method = _textChatUiType.GetMethod("ShowMessageInternal", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (!(method == null))
		{
			for (int i = 0; i < _messages.Count; i++)
			{
				string text = _messages[i];
				IncrementPendingIgnore(text);
				method.Invoke(val, new object[1] { text });
			}
			_lastRestoredUiId = instanceID;
			_logger.LogInfo((object)$"Restored {_messages.Count} chat message(s) into a new TextChatUi instance.");
		}
	}

	private void AttachTrackers()
	{
		if (_textChatMessageUiType == null)
		{
			return;
		}
		Object[] array = Resources.FindObjectsOfTypeAll(_textChatMessageUiType);
		foreach (Object obj in array)
		{
			Component val = (Component)(object)((obj is Component) ? obj : null);
			if (!((Object)(object)val == (Object)null))
			{
				ChatHistoryMessageTracker chatHistoryMessageTracker = val.GetComponent<ChatHistoryMessageTracker>();
				if ((Object)(object)chatHistoryMessageTracker == (Object)null)
				{
					chatHistoryMessageTracker = val.gameObject.AddComponent<ChatHistoryMessageTracker>();
				}
				object fieldValue = GetFieldValue(val, "messageText");
				TMP_Text messageText = (TMP_Text)((fieldValue is TMP_Text) ? fieldValue : null);
				chatHistoryMessageTracker.Initialize(this, messageText);
			}
		}
	}

	private int GetVisibleMessageCount()
	{
		if (_textChatMessageUiType == null)
		{
			return 0;
		}
		Object[] array = Resources.FindObjectsOfTypeAll(_textChatMessageUiType);
		int num = 0;
		foreach (Object obj in array)
		{
			Component val = (Component)(object)((obj is Component) ? obj : null);
			if ((Object)(object)val != (Object)null && val.gameObject.activeInHierarchy)
			{
				num++;
			}
		}
		return num;
	}

	private void EnsureTypes()
	{
		if (!(_textChatUiType != null) || !(_textChatMessageUiType != null))
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				CacheType(assembly, "TextChatUi", ref _textChatUiType);
				CacheType(assembly, "TextChatMessageUi", ref _textChatMessageUiType);
			}
		}
	}

	private void LoadHistory()
	{
		_messages.Clear();
		if (!_persistAcrossLaunches.Value || !File.Exists(_historyFilePath))
		{
			return;
		}
		try
		{
			string[] array = File.ReadAllLines(_historyFilePath);
			foreach (string text in array)
			{
				if (!string.IsNullOrWhiteSpace(text))
				{
					byte[] bytes = Convert.FromBase64String(text);
					string text2 = NormalizeMessage(Encoding.UTF8.GetString(bytes));
					if (!string.IsNullOrWhiteSpace(text2))
					{
						_messages.Add(text2);
					}
				}
			}
			TrimMessages();
			SaveHistory();
		}
		catch (Exception ex)
		{
			_logger.LogWarning((object)("Failed to load chat history: " + ex.Message));
		}
	}

	private void SaveHistory()
	{
		if (!_persistAcrossLaunches.Value)
		{
			return;
		}
		try
		{
			string[] contents = _messages.Select((string message) => Convert.ToBase64String(Encoding.UTF8.GetBytes(message))).ToArray();
			File.WriteAllLines(_historyFilePath, contents);
		}
		catch (Exception ex)
		{
			_logger.LogWarning((object)("Failed to save chat history: " + ex.Message));
		}
	}

	private void TrimMessages()
	{
		int num = Math.Max(1, _maxMessages.Value);
		while (_messages.Count > num)
		{
			_messages.RemoveAt(0);
		}
	}

	private void IncrementPendingIgnore(string message)
	{
		if (_pendingRestoreIgnores.TryGetValue(message, out var value))
		{
			_pendingRestoreIgnores[message] = value + 1;
		}
		else
		{
			_pendingRestoreIgnores[message] = 1;
		}
	}

	private static string NormalizeMessage(string message)
	{
		if (string.IsNullOrWhiteSpace(message))
		{
			return string.Empty;
		}
		string text = message.Replace("\r", string.Empty).Replace("\n", " ").Trim();
		if (text.IndexOf("<link", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("<u>", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("<noparse>", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("#79C7FF", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			text = LegacyLinkTagRegex.Replace(text, string.Empty);
			text = NoParseTagRegex.Replace(text, string.Empty);
			text = LegacyColorTagRegex.Replace(text, string.Empty);
		}
		return text.Trim();
	}

	private static void CacheType(Assembly assembly, string typeName, ref Type cache)
	{
		if (!(cache != null))
		{
			cache = assembly.GetType(typeName, throwOnError: false);
		}
	}

	private static object GetFieldValue(object instance, string fieldName)
	{
		if (instance == null)
		{
			return null;
		}
		FieldInfo field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		return (field != null) ? field.GetValue(instance) : null;
	}
}
internal sealed class ChatHistoryMessageTracker : MonoBehaviour
{
	private ChatHistoryManager _manager;

	private TMP_Text _messageText;

	private bool _captured;

	public void Initialize(ChatHistoryManager manager, TMP_Text messageText)
	{
		_manager = manager;
		_messageText = messageText;
	}

	private void Update()
	{
		if (!_captured && _manager != null && !((Object)(object)_messageText == (Object)null))
		{
			string text = _messageText.text;
			if (!string.IsNullOrWhiteSpace(text))
			{
				_manager.TryCaptureMessage(text);
				_captured = true;
			}
		}
	}
}
internal sealed class ChatLinkTextHandler : MonoBehaviour
{
	private const string LinkId = "bc-url";

	private static readonly Regex UrlRegex = new Regex("((?:https?://)?(?:www\\.)?[A-Za-z0-9\\-]+\\.[A-Za-z]{2,}(?:/[^\\s<]*)?)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private static readonly Regex InjectedLinkRegex = new Regex("<link=\"(?<url>[^\"]*)\"><color=#79C7FF>(?<text>.*?)</color></link>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline);

	private static readonly Regex NoParseRegex = new Regex("<noparse>(?<text>.*?)</noparse>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline);

	private static bool _inputSystemReflectionInitialized;

	private static PropertyInfo _mouseCurrentProperty;

	private static PropertyInfo _mousePositionProperty;

	private static PropertyInfo _mouseLeftButtonProperty;

	private static PropertyInfo _buttonWasPressedProperty;

	private static MethodInfo _readValueMethod;

	private TMP_Text _text;

	private string _lastAppliedText;

	private Canvas _canvas;

	private bool _legacyInputUnavailable;

	public void Initialize(TMP_Text text)
	{
		_text = text;
		if ((Object)(object)_text != (Object)null)
		{
			_text.richText = true;
		}
		_canvas = ((Component)this).GetComponentInParent<Canvas>();
	}

	public void Refresh()
	{
		if ((Object)(object)_text == (Object)null)
		{
			return;
		}
		_text.richText = true;
		((Graphic)_text).raycastTarget = true;
		if (!string.Equals(_text.text, _lastAppliedText, StringComparison.Ordinal))
		{
			string text = StripInjectedLinks(_text.text);
			string text2 = InjectLinksPreservingGameMarkup(text);
			if (!string.Equals(_text.text, text2, StringComparison.Ordinal))
			{
				_text.text = text2;
			}
			_lastAppliedText = _text.text;
		}
	}

	private void Update()
	{
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: 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)
		if ((Object)(object)_text == (Object)null || !((Behaviour)this).isActiveAndEnabled || !((Component)this).gameObject.activeInHierarchy || !TryGetMouseClickPosition(out var mousePosition))
		{
			return;
		}
		Camera eventCamera = GetEventCamera();
		int num = TMP_TextUtilities.FindIntersectingLink(_text, Vector2.op_Implicit(mousePosition), eventCamera);
		if (num >= 0 && _text.textInfo != null && _text.textInfo.linkCount > num)
		{
			TMP_LinkInfo val = _text.textInfo.linkInfo[num];
			string text = NormalizeUrl(((TMP_LinkInfo)(ref val)).GetLinkText());
			if (!string.IsNullOrWhiteSpace(text))
			{
				Application.OpenURL(text);
			}
		}
	}

	private bool TryGetMouseClickPosition(out Vector2 mousePosition)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		if (!_legacyInputUnavailable)
		{
			try
			{
				if (Input.GetMouseButtonDown(0))
				{
					mousePosition = Vector2.op_Implicit(Input.mousePosition);
					return true;
				}
			}
			catch (InvalidOperationException)
			{
				_legacyInputUnavailable = true;
			}
		}
		return TryGetInputSystemMouseClickPosition(out mousePosition);
	}

	private static bool TryGetInputSystemMouseClickPosition(out Vector2 mousePosition)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_011d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		mousePosition = Vector2.zero;
		EnsureInputSystemReflection();
		if (_mouseCurrentProperty == null || _mousePositionProperty == null || _mouseLeftButtonProperty == null || _buttonWasPressedProperty == null || _readValueMethod == null)
		{
			return false;
		}
		object value = _mouseCurrentProperty.GetValue(null, null);
		if (value == null)
		{
			return false;
		}
		object value2 = _mouseLeftButtonProperty.GetValue(value, null);
		if (value2 == null)
		{
			return false;
		}
		if (!(_buttonWasPressedProperty.GetValue(value2, null) is int num) || num == 0)
		{
			return false;
		}
		object value3 = _mousePositionProperty.GetValue(value, null);
		if (value3 == null)
		{
			return false;
		}
		object obj = _readValueMethod.Invoke(value3, null);
		if (obj is Vector2)
		{
			mousePosition = (Vector2)obj;
			return true;
		}
		return false;
	}

	private Camera GetEventCamera()
	{
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Invalid comparison between Unknown and I4
		if ((Object)(object)_canvas == (Object)null)
		{
			_canvas = ((Component)this).GetComponentInParent<Canvas>();
		}
		if ((Object)(object)_canvas == (Object)null || (int)_canvas.renderMode == 0)
		{
			return null;
		}
		return _canvas.worldCamera;
	}

	private static string NormalizeUrl(string rawUrl)
	{
		if (string.IsNullOrWhiteSpace(rawUrl))
		{
			return string.Empty;
		}
		if (rawUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase) >= 0)
		{
			return rawUrl;
		}
		return "https://" + rawUrl;
	}

	private static string StripInjectedLinks(string text)
	{
		if (string.IsNullOrWhiteSpace(text))
		{
			return string.Empty;
		}
		return InjectedLinkRegex.Replace(text, "${text}");
	}

	private static string InjectLinks(string text)
	{
		if (string.IsNullOrWhiteSpace(text))
		{
			return string.Empty;
		}
		return UrlRegex.Replace(text, (Match match) => string.Format("<link=\"{0}\"><color=#79C7FF>{1}</color></link>", "bc-url", match.Value));
	}

	private static string InjectLinksPreservingGameMarkup(string text)
	{
		if (string.IsNullOrWhiteSpace(text))
		{
			return string.Empty;
		}
		string text2 = NoParseRegex.Replace(text, delegate(Match match)
		{
			string value = match.Groups["text"].Value;
			string text3 = InjectLinks(value);
			return string.Equals(value, text3, StringComparison.Ordinal) ? match.Value : text3;
		});
		return InjectLinks(text2);
	}

	private static void EnsureInputSystemReflection()
	{
		if (_inputSystemReflectionInitialized)
		{
			return;
		}
		_inputSystemReflectionInitialized = true;
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		foreach (Assembly assembly in assemblies)
		{
			Type type = assembly.GetType("UnityEngine.InputSystem.Mouse", throwOnError: false);
			if (!(type == null))
			{
				_mouseCurrentProperty = type.GetProperty("current", BindingFlags.Static | BindingFlags.Public);
				_mousePositionProperty = type.GetProperty("position", BindingFlags.Instance | BindingFlags.Public);
				_mouseLeftButtonProperty = type.GetProperty("leftButton", BindingFlags.Instance | BindingFlags.Public);
				Type type2 = assembly.GetType("UnityEngine.InputSystem.Controls.ButtonControl", throwOnError: false);
				if (type2 != null)
				{
					_buttonWasPressedProperty = type2.GetProperty("wasPressedThisFrame", BindingFlags.Instance | BindingFlags.Public);
				}
				Type type3 = assembly.GetType("UnityEngine.InputSystem.Controls.Vector2Control", throwOnError: false);
				if (type3 != null)
				{
					_readValueMethod = type3.GetMethod("ReadValue", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
				}
				break;
			}
		}
	}
}
internal sealed class SteamProfileClickTarget : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler, IEventSystemHandler
{
	private const string SteamCommunityPrefix = "https://steamcommunity.com/profiles/";

	private const string SteamProtocolPrefix = "steam://openurl/";

	private const BindingFlags AllMembers = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private Component _sourceComponent;

	private string _steamIdPath;

	private bool _useSteamProtocol;

	private Graphic _graphic;

	private Color _baseColor;

	private bool _hasBaseColor;

	private string _contextLabel;

	public void Initialize(Component sourceComponent, string steamIdPath, bool useSteamProtocol, string contextLabel)
	{
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		_sourceComponent = sourceComponent;
		_steamIdPath = steamIdPath;
		_useSteamProtocol = useSteamProtocol;
		_contextLabel = contextLabel;
		_graphic = ((Component)this).GetComponent<Graphic>();
		if ((Object)(object)_graphic != (Object)null)
		{
			_graphic.raycastTarget = true;
			_baseColor = _graphic.color;
			_hasBaseColor = true;
		}
	}

	public void OnPointerClick(PointerEventData eventData)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Invalid comparison between Unknown and I4
		if (eventData != null && (int)eventData.button != 0)
		{
			return;
		}
		ulong num = ResolveSteamId();
		if (num != 0)
		{
			string text = "https://steamcommunity.com/profiles/" + num + "/";
			if (_useSteamProtocol)
			{
				text = "steam://openurl/" + text;
			}
			Application.OpenURL(text);
		}
	}

	public void OnPointerEnter(PointerEventData eventData)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_graphic != (Object)null && _hasBaseColor)
		{
			_graphic.color = Tint(_baseColor, 1.15f);
		}
	}

	public void OnPointerExit(PointerEventData eventData)
	{
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)_graphic != (Object)null && _hasBaseColor)
		{
			_graphic.color = _baseColor;
		}
	}

	public void Refresh(Component sourceComponent, string steamIdPath, bool useSteamProtocol, string contextLabel)
	{
		_sourceComponent = sourceComponent;
		_steamIdPath = steamIdPath;
		_useSteamProtocol = useSteamProtocol;
		_contextLabel = contextLabel;
	}

	private ulong ResolveSteamId()
	{
		object obj = _sourceComponent;
		if (obj == null || string.IsNullOrWhiteSpace(_steamIdPath))
		{
			return 0uL;
		}
		string[] array = _steamIdPath.Split(new char[1] { '.' });
		foreach (string name in array)
		{
			if (obj == null)
			{
				return 0uL;
			}
			Type type = obj.GetType();
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null)
			{
				obj = property.GetValue(obj, null);
				continue;
			}
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				obj = field.GetValue(obj);
				continue;
			}
			return 0uL;
		}
		if (!(obj is ulong result))
		{
			if (!(obj is ulong result2))
			{
				if (obj is uint)
				{
					return (uint)obj;
				}
				return 0uL;
			}
			return result2;
		}
		return result;
	}

	private static Color Tint(Color color, float factor)
	{
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		return new Color(Mathf.Clamp01(color.r * factor), Mathf.Clamp01(color.g * factor), Mathf.Clamp01(color.b * factor), color.a);
	}
}
internal sealed class SteamProfileLinkInjector
{
	private const BindingFlags AllMembers = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private readonly ManualLogSource _logger;

	private readonly ConfigEntry<bool> _enabled;

	private readonly ConfigEntry<bool> _useSteamProtocol;

	private readonly ConfigEntry<bool> _enableWorldNameTags;

	private readonly ConfigEntry<bool> _enableAvatarLinks;

	private readonly ConfigEntry<bool> _enableChatLinks;

	private readonly Dictionary<string, int> _loggedInjectionCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

	private Type _matchSetupPlayerType;

	private Type _pauseMenuPlayerEntryType;

	private Type _nameTagUiType;

	private Type _textChatMessageUiType;

	private float _nextScanTime;

	public SteamProfileLinkInjector(ManualLogSource logger, ConfigEntry<bool> enabled, ConfigEntry<bool> useSteamProtocol, ConfigEntry<bool> enableWorldNameTags, ConfigEntry<bool> enableAvatarLinks, ConfigEntry<bool> enableChatLinks)
	{
		_logger = logger;
		_enabled = enabled;
		_useSteamProtocol = useSteamProtocol;
		_enableWorldNameTags = enableWorldNameTags;
		_enableAvatarLinks = enableAvatarLinks;
		_enableChatLinks = enableChatLinks;
	}

	public void Update()
	{
		if (_enabled.Value && !(Time.unscaledTime < _nextScanTime))
		{
			_nextScanTime = Time.unscaledTime + 1f;
			EnsureTypes();
			InjectLinks();
		}
	}

	private void InjectLinks()
	{
		InjectForType(_matchSetupPlayerType, "playerNickname", "Guid", "MatchSetupPlayer");
		InjectForType(_pauseMenuPlayerEntryType, "playerName", "CurrentPlayerGuid", "PauseMenuPlayerEntry");
		if (_enableAvatarLinks.Value)
		{
			InjectForType(_matchSetupPlayerType, "playerIcon", "Guid", "MatchSetupPlayer Icon");
			InjectForType(_pauseMenuPlayerEntryType, "playerIcon", "CurrentPlayerGuid", "PauseMenuPlayerEntry Icon");
		}
		if (_enableWorldNameTags.Value)
		{
			InjectForType(_nameTagUiType, "tag", "playerInfo.PlayerId.Guid", "NameTagUi");
		}
		if (_enableChatLinks.Value)
		{
			InjectChatLinks();
		}
	}

	private void InjectForType(Type sourceType, string targetFieldName, string steamIdPath, string contextLabel)
	{
		if (sourceType == null)
		{
			return;
		}
		Object[] array = Resources.FindObjectsOfTypeAll(sourceType);
		int num = 0;
		foreach (Object obj in array)
		{
			Component val = (Component)(object)((obj is Component) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			object fieldValue = GetFieldValue(val, targetFieldName);
			Component val2 = (Component)((fieldValue is Component) ? fieldValue : null);
			if (!((Object)(object)val2 == (Object)null))
			{
				SteamProfileClickTarget steamProfileClickTarget = val2.GetComponent<SteamProfileClickTarget>();
				if ((Object)(object)steamProfileClickTarget == (Object)null)
				{
					steamProfileClickTarget = val2.gameObject.AddComponent<SteamProfileClickTarget>();
					num++;
				}
				steamProfileClickTarget.Refresh(val, steamIdPath, _useSteamProtocol.Value, contextLabel);
				EnsureGraphicTargeting(val2);
			}
		}
		if (num > 0)
		{
			LogInjectionCount(contextLabel, num);
		}
	}

	private void EnsureTypes()
	{
		if (!(_matchSetupPlayerType != null) || !(_pauseMenuPlayerEntryType != null) || !(_nameTagUiType != null) || !(_textChatMessageUiType != null))
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				CacheType(assembly, "MatchSetupPlayer", ref _matchSetupPlayerType);
				CacheType(assembly, "PauseMenuPlayerEntry", ref _pauseMenuPlayerEntryType);
				CacheType(assembly, "NameTagUi", ref _nameTagUiType);
				CacheType(assembly, "TextChatMessageUi", ref _textChatMessageUiType);
			}
		}
	}

	private void InjectChatLinks()
	{
		if (_textChatMessageUiType == null)
		{
			return;
		}
		Object[] array = Resources.FindObjectsOfTypeAll(_textChatMessageUiType);
		int num = 0;
		foreach (Object obj in array)
		{
			Component val = (Component)(object)((obj is Component) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			object fieldValue = GetFieldValue(val, "messageText");
			TMP_Text val2 = (TMP_Text)((fieldValue is TMP_Text) ? fieldValue : null);
			if ((Object)(object)val2 == (Object)null)
			{
				continue;
			}
			GameObject val3 = EnsureChatLinkOverlay(val.gameObject);
			if (!((Object)(object)val3 == (Object)null))
			{
				ChatLinkTextHandler chatLinkTextHandler = val3.GetComponent<ChatLinkTextHandler>();
				if ((Object)(object)chatLinkTextHandler == (Object)null)
				{
					chatLinkTextHandler = val3.AddComponent<ChatLinkTextHandler>();
					num++;
				}
				chatLinkTextHandler.Initialize(val2);
				chatLinkTextHandler.Refresh();
			}
		}
		if (num > 0)
		{
			LogInjectionCount("TextChatMessageUi Links", num);
		}
	}

	private void LogInjectionCount(string contextLabel, int injectedCount)
	{
		if (!_loggedInjectionCounts.TryGetValue(contextLabel, out var value) || value != injectedCount)
		{
			_loggedInjectionCounts[contextLabel] = injectedCount;
			_logger.LogInfo((object)$"Injected {injectedCount} Steam profile link target(s) for {contextLabel}.");
		}
	}

	private static void CacheType(Assembly assembly, string typeName, ref Type cache)
	{
		if (!(cache != null))
		{
			cache = assembly.GetType(typeName, throwOnError: false);
		}
	}

	private static object GetFieldValue(object instance, string fieldName)
	{
		if (instance == null)
		{
			return null;
		}
		FieldInfo field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		return (field != null) ? field.GetValue(instance) : null;
	}

	private static void EnsureGraphicTargeting(Component component)
	{
		PropertyInfo property = ((object)component).GetType().GetProperty("raycastTarget", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		if (property != null && property.PropertyType == typeof(bool) && property.CanWrite)
		{
			property.SetValue(component, true, null);
		}
	}

	private static GameObject EnsureChatLinkOverlay(GameObject messageRoot)
	{
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Expected O, but got Unknown
		//IL_0108: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Unknown result type (might be due to invalid IL or missing references)
		//IL_012f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0161: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: 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_007e: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)messageRoot == (Object)null)
		{
			return null;
		}
		Transform val = messageRoot.transform.Find("BetterCommunicationChatOverlay");
		if ((Object)(object)val != (Object)null)
		{
			Image component = ((Component)val).GetComponent<Image>();
			if ((Object)(object)component != (Object)null)
			{
				((Graphic)component).raycastTarget = true;
				Color color = ((Graphic)component).color;
				((Graphic)component).color = new Color(color.r, color.g, color.b, 0f);
			}
			return ((Component)val).gameObject;
		}
		RectTransform component2 = messageRoot.GetComponent<RectTransform>();
		if ((Object)(object)component2 == (Object)null)
		{
			return null;
		}
		GameObject val2 = new GameObject("BetterCommunicationChatOverlay", new Type[2]
		{
			typeof(RectTransform),
			typeof(Image)
		});
		val2.transform.SetParent(messageRoot.transform, false);
		RectTransform component3 = val2.GetComponent<RectTransform>();
		component3.anchorMin = Vector2.zero;
		component3.anchorMax = Vector2.one;
		component3.offsetMin = Vector2.zero;
		component3.offsetMax = Vector2.zero;
		((Transform)component3).SetAsLastSibling();
		Image component4 = val2.GetComponent<Image>();
		((Graphic)component4).color = new Color(0f, 0f, 0f, 0f);
		((Graphic)component4).raycastTarget = true;
		return val2;
	}
}