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;
}
}