Decompiled source of ChatRichText v1.0.0

BepInEx/plugins/ChatRichText/ChatRichText.dll

Decompiled 3 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ChatRichText")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ChatRichText")]
[assembly: AssemblyTitle("ChatRichText")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 ChatRichText
{
	internal static class ChatColors
	{
		private static readonly Dictionary<string, string> Named = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			{ "red", "FF4040" },
			{ "orange", "FF8C1A" },
			{ "yellow", "FFD43B" },
			{ "green", "4CD964" },
			{ "lime", "A8E063" },
			{ "teal", "1ABC9C" },
			{ "cyan", "3BD6E6" },
			{ "blue", "4D9DE0" },
			{ "navy", "3B5BA5" },
			{ "purple", "A46BE0" },
			{ "magenta", "E668C8" },
			{ "pink", "FF8FC4" },
			{ "brown", "A9744F" },
			{ "white", "FFFFFF" },
			{ "grey", "9AA0A6" },
			{ "gray", "9AA0A6" },
			{ "black", "202124" }
		};

		public const int MaxValueLength = 16;

		public static bool TryResolve(string value, out string tag)
		{
			tag = null;
			if (string.IsNullOrEmpty(value) || value.Length > 16)
			{
				return false;
			}
			if (value[0] == '#')
			{
				int num = value.Length - 1;
				if (num != 6 && num != 8)
				{
					return false;
				}
				for (int i = 1; i < value.Length; i++)
				{
					if (!IsHex(value[i]))
					{
						return false;
					}
				}
				tag = "<color=" + value + ">";
				return true;
			}
			if (Named.TryGetValue(value, out var value2))
			{
				tag = "<color=#" + value2 + ">";
				return true;
			}
			return false;
		}

		public static string NameList()
		{
			List<string> list = new List<string>(Named.Keys);
			list.Sort(StringComparer.OrdinalIgnoreCase);
			return string.Join(", ", list.ToArray());
		}

		private static bool IsHex(char c)
		{
			if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
			{
				if (c >= 'A')
				{
					return c <= 'F';
				}
				return false;
			}
			return true;
		}
	}
	internal static class ChatPatches
	{
		private static bool _loggedFailure;

		public static int Apply(Harmony harmony)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			return 0 + Patch(harmony, AccessTools.Method(typeof(ChatManager), "ChatMessage", new Type[2]
			{
				typeof(CSteamID),
				typeof(string)
			}, (Type[])null), "ChatManager.ChatMessage(CSteamID, string)", new HarmonyMethod(AccessTools.Method(typeof(ChatPatches), "FormatIncomingMessage", (Type[])null, (Type[])null))) + Patch(harmony, AccessTools.Method(typeof(ChatManager), "Awake", (Type[])null, (Type[])null), "ChatManager.Awake", null, new HarmonyMethod(AccessTools.Method(typeof(ChatPatches), "EnableRichText", (Type[])null, (Type[])null)));
		}

		private static int Patch(Harmony harmony, MethodBase target, string label, HarmonyMethod prefix = null, HarmonyMethod postfix = null)
		{
			if (target == null)
			{
				Plugin.Log.LogError((object)("Harmony patch failed: could not find " + label + ". The game may have been updated; chat will be left as the game draws it."));
				return 0;
			}
			try
			{
				harmony.Patch(target, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.Log.LogInfo((object)("  patched: " + label));
				return 1;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Harmony patch failed on {label}: {arg}");
				return 0;
			}
		}

		private static bool FormatIncomingMessage(CSteamID from, string message)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Settings.Enabled.Value)
			{
				return true;
			}
			try
			{
				TmpLineBuilder tmpLineBuilder = new TmpLineBuilder();
				if (from == SteamManager.CurrentLobbyID)
				{
					tmpLineBuilder.GameMarkup("[<color=#" + ColorUtility.ToHtmlStringRGB(GameInfo.MiniBossColor) + ">Server</color>] ");
				}
				else
				{
					tmpLineBuilder.GameMarkup("[<color=#" + ColorUtility.ToHtmlStringRGB(GameInfo.RedColor) + ">");
					tmpLineBuilder.Literal(SteamFriends.GetFriendPersonaName(from));
					tmpLineBuilder.GameMarkup("</color>] ");
				}
				RichTextParser.ParseInto(message, tmpLineBuilder);
				ChatManager.ChatMessage(tmpLineBuilder.Finish());
				return false;
			}
			catch (Exception arg)
			{
				if (!_loggedFailure)
				{
					_loggedFailure = true;
					Plugin.Log.LogError((object)$"Formatting failed, falling back to vanilla chat (further occurrences suppressed): {arg}");
				}
				return true;
			}
		}

		private static void EnableRichText(ChatManager __instance)
		{
			try
			{
				TextMeshProUGUI messageText = __instance._messageText;
				if ((Object)(object)messageText != (Object)null && !((TMP_Text)messageText).richText)
				{
					((TMP_Text)messageText).richText = true;
					Plugin.Log.LogInfo((object)"Enabled richText on the chat log component.");
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Could not set richText on the chat log: {arg}");
			}
		}
	}
	internal sealed class ChatRichTextConfig
	{
		public ConfigEntry<bool> Enabled { get; }

		public ChatRichTextConfig(ConfigFile file)
		{
			Enabled = file.Bind<bool>("General", "ChatRichTextEnabled", true, "Formats chat messages with Steam-style markdown: *bold*, **bold**, _italic_, ~~strikethrough~~, and [color=NAME]text[/color] or [color=#RRGGBB]text[/color]. Accepted colour names: " + ChatColors.NameList() + ". Set to false to leave chat exactly as the game draws it.");
		}
	}
	[BepInPlugin("com.flabagizza.howtofish.chatrichtext", "ChatRichText", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.flabagizza.howtofish.chatrichtext";

		public const string PluginName = "ChatRichText";

		public const string PluginVersion = "1.0.0";

		private Harmony _harmony;

		internal static ManualLogSource Log { get; private set; }

		internal static ChatRichTextConfig Settings { get; private set; }

		private void Awake()
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new ChatRichTextConfig(((BaseUnityPlugin)this).Config);
			Log.LogInfo((object)"ChatRichText 1.0.0 loading...");
			_harmony = new Harmony("com.flabagizza.howtofish.chatrichtext");
			int num = ChatPatches.Apply(_harmony);
			if (num == 2)
			{
				Log.LogInfo((object)"Harmony patches applied.");
			}
			else
			{
				Log.LogError((object)$"Harmony patches incomplete: {num} of 2 applied. See errors above.");
			}
			if (!Settings.Enabled.Value)
			{
				Log.LogInfo((object)"ChatRichTextEnabled is false - chat will be left as the game draws it.");
			}
			Log.LogInfo((object)"ChatRichText 1.0.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class RichTextParser
	{
		private const int MaxDepth = 6;

		private const int MaxTags = 48;

		private const string ColorOpen = "[color=";

		private const string ColorClose = "[/color]";

		public static void ParseInto(string raw, TmpLineBuilder builder)
		{
			if (!string.IsNullOrEmpty(raw))
			{
				int tagBudget = 48;
				Parse(raw, 0, raw.Length, builder, 0, ref tagBudget);
			}
		}

		private static void Parse(string s, int start, int end, TmpLineBuilder b, int depth, ref int tagBudget)
		{
			int num = start;
			while (num < end)
			{
				if (depth < 6 && tagBudget > 0 && TryMarkup(s, num, end, b, depth, ref tagBudget, out var next))
				{
					num = next;
					continue;
				}
				b.Literal(s[num]);
				num++;
			}
		}

		private static bool TryMarkup(string s, int i, int end, TmpLineBuilder b, int depth, ref int tagBudget, out int next)
		{
			next = i;
			switch (s[i])
			{
			case '[':
				return TryColor(s, i, end, b, depth, ref tagBudget, out next);
			case '~':
				return TryDelimited(s, i, end, "~~", "<s>", "</s>", b, depth, ref tagBudget, out next);
			case '*':
				if (TryDelimited(s, i, end, "**", "<b>", "</b>", b, depth, ref tagBudget, out next))
				{
					return true;
				}
				return TryDelimited(s, i, end, "*", "<b>", "</b>", b, depth, ref tagBudget, out next);
			case '_':
				return TryItalic(s, i, end, b, depth, ref tagBudget, out next);
			default:
				return false;
			}
		}

		private static bool TryDelimited(string s, int i, int end, string delim, string open, string close, TmpLineBuilder b, int depth, ref int tagBudget, out int next)
		{
			next = i;
			if (!Matches(s, i, end, delim))
			{
				return false;
			}
			int num = i + delim.Length;
			if (num >= end || char.IsWhiteSpace(s[num]))
			{
				return false;
			}
			for (int j = num + 1; j + delim.Length <= end; j++)
			{
				if (Matches(s, j, end, delim) && !char.IsWhiteSpace(s[j - 1]))
				{
					Emit(s, num, j, open, close, b, depth, ref tagBudget);
					next = j + delim.Length;
					return true;
				}
			}
			return false;
		}

		private static bool TryItalic(string s, int i, int end, TmpLineBuilder b, int depth, ref int tagBudget, out int next)
		{
			next = i;
			if (s[i] != '_' || !IsBoundary(s, i - 1, end))
			{
				return false;
			}
			int num = i + 1;
			if (num >= end || char.IsWhiteSpace(s[num]))
			{
				return false;
			}
			for (int j = num + 1; j < end; j++)
			{
				if (s[j] == '_' && !char.IsWhiteSpace(s[j - 1]) && IsBoundary(s, j + 1, end))
				{
					Emit(s, num, j, "<i>", "</i>", b, depth, ref tagBudget);
					next = j + 1;
					return true;
				}
			}
			return false;
		}

		private static bool TryColor(string s, int i, int end, TmpLineBuilder b, int depth, ref int tagBudget, out int next)
		{
			next = i;
			if (!Matches(s, i, end, "[color="))
			{
				return false;
			}
			int num = i + "[color=".Length;
			int num2 = -1;
			int num3 = num + 16;
			if (num3 > end)
			{
				num3 = end;
			}
			for (int j = num; j < num3; j++)
			{
				if (s[j] == ']')
				{
					num2 = j;
					break;
				}
			}
			if (num2 < 0)
			{
				return false;
			}
			if (!ChatColors.TryResolve(s.Substring(num, num2 - num), out var tag))
			{
				return false;
			}
			int num4 = num2 + 1;
			int num5 = FindColorClose(s, num4, end);
			if (num5 < 0 || num5 == num4)
			{
				return false;
			}
			Emit(s, num4, num5, tag, "</color>", b, depth, ref tagBudget);
			next = num5 + "[/color]".Length;
			return true;
		}

		private static int FindColorClose(string s, int from, int end)
		{
			int num = 0;
			for (int i = from; i < end; i++)
			{
				if (s[i] != '[')
				{
					continue;
				}
				if (Matches(s, i, end, "[/color]"))
				{
					if (num == 0)
					{
						return i;
					}
					num--;
					i += "[/color]".Length - 1;
				}
				else if (Matches(s, i, end, "[color="))
				{
					num++;
					i += "[color=".Length - 1;
				}
			}
			return -1;
		}

		private static void Emit(string s, int contentStart, int contentEnd, string open, string close, TmpLineBuilder b, int depth, ref int tagBudget)
		{
			tagBudget--;
			b.Tag(open);
			Parse(s, contentStart, contentEnd, b, depth + 1, ref tagBudget);
			b.Tag(close);
		}

		private static bool Matches(string s, int i, int end, string token)
		{
			if (i + token.Length > end)
			{
				return false;
			}
			for (int j = 0; j < token.Length; j++)
			{
				if (s[i + j] != token[j])
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsBoundary(string s, int index, int end)
		{
			if (index < 0 || index >= end)
			{
				return true;
			}
			char c = s[index];
			if (!char.IsLetterOrDigit(c))
			{
				return c != '_';
			}
			return false;
		}
	}
	internal sealed class TmpLineBuilder
	{
		private readonly StringBuilder _sb = new StringBuilder(160);

		private bool _inNoParse;

		public void Literal(string text)
		{
			if (!string.IsNullOrEmpty(text))
			{
				for (int i = 0; i < text.Length; i++)
				{
					Literal(text[i]);
				}
			}
		}

		public void Literal(char c)
		{
			OpenNoParse();
			_sb.Append(c);
			if (c == '<')
			{
				_sb.Append("</noparse>");
				_inNoParse = false;
			}
		}

		public void Tag(string tag)
		{
			CloseNoParse();
			_sb.Append(tag);
		}

		public void GameMarkup(string markup)
		{
			CloseNoParse();
			_sb.Append(markup);
		}

		public string Finish()
		{
			CloseNoParse();
			return _sb.ToString();
		}

		private void OpenNoParse()
		{
			if (!_inNoParse)
			{
				_sb.Append("<noparse>");
				_inNoParse = true;
			}
		}

		private void CloseNoParse()
		{
			if (_inNoParse)
			{
				_sb.Append("</noparse>");
				_inNoParse = false;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}