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)
{
}
}
}