Decompiled source of BetterChat v1.4.9

BetterChat.dll

Decompiled 5 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BetterChat")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Crystal")]
[assembly: AssemblyProduct("BetterChat")]
[assembly: AssemblyCopyright("Copyright © 2023 Crystal")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("a81c5907-42c8-48a8-a758-24fcacbfd3aa")]
[assembly: AssemblyFileVersion("1.4.9.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.4.9.0")]
namespace BetterChat;

[BepInPlugin("dev.crystal.betterchat", "Better Chat", "1.4.9.0")]
[BepInProcess("valheim.exe")]
[BepInProcess("valheim_server.exe")]
public class BetterChatPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(Chat))]
	private static class Chat_Patches
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Awake_Postfix(Chat __instance)
		{
			__instance.m_hideDelay = HideDelay.Value;
			sChat = __instance;
			Graphic[] componentsInChildren = ((Component)((Terminal)__instance).m_chatWindow).GetComponentsInChildren<Graphic>();
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				componentsInChildren[i].raycastTarget = false;
			}
		}
	}

	[HarmonyPatch(typeof(Player))]
	private static class Player_Patches
	{
		[HarmonyPatch("Awake")]
		[HarmonyPostfix]
		private static void Awake_Postfix(Player __instance)
		{
			Talker component = ((Component)__instance).GetComponent<Talker>();
			ApplyChatDistances(component);
			sTalkers.Add(component);
		}

		[HarmonyPatch("OnDestroy")]
		[HarmonyPrefix]
		private static void OnDestroy_Prefix(Player __instance)
		{
			sTalkers.Remove(((Component)__instance).GetComponent<Talker>());
		}
	}

	[HarmonyPatch(typeof(Chat))]
	private static class Chat_AlwaysShow_Patch
	{
		[HarmonyPatch("Update")]
		[HarmonyPrefix]
		private static bool Update_Prefix(Chat __instance)
		{
			sHideTimerField.SetValue(__instance, 0f);
			return true;
		}
	}

	[HarmonyPatch(typeof(Chat))]
	private static class Chat_Slash_Patches
	{
		private enum TranspilerState
		{
			Searching,
			Inserting,
			Searching2,
			Labeling,
			Finishing
		}

		[HarmonyPatch("Update")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Update_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
		{
			LocalBuilder isSlashPressed = generator.DeclareLocal(typeof(bool));
			isSlashPressed.SetLocalSymInfo("isSlashPressed");
			Label label = generator.DefineLabel();
			yield return new CodeInstruction(OpCodes.Ldc_I4_0, (object)null);
			yield return new CodeInstruction(OpCodes.Stloc, (object)isSlashPressed.LocalIndex);
			TranspilerState state = TranspilerState.Searching;
			foreach (CodeInstruction instruction in instructions)
			{
				switch (state)
				{
				case TranspilerState.Inserting:
					if (instruction.opcode != OpCodes.Brtrue)
					{
						throw new InvalidOperationException(string.Format("[BetterChat] {0} encountered unexpected IL code. Unable to patch. This is most likely due to a game update changing the target code. Disable {1} in the config as a workaround until the mod can be fixed. Details: Search failed, opcode={2}", "Chat_Slash_Patches", "SlashOpensChat", instruction.opcode));
					}
					yield return instruction.Clone();
					yield return new CodeInstruction(OpCodes.Ldc_I4, (object)47);
					yield return new CodeInstruction(OpCodes.Call, (object)typeof(Input).GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) }));
					yield return new CodeInstruction(OpCodes.Stloc, (object)isSlashPressed.LocalIndex);
					yield return new CodeInstruction(OpCodes.Ldloc, (object)isSlashPressed.LocalIndex);
					state = TranspilerState.Searching2;
					break;
				case TranspilerState.Labeling:
					instruction.labels.Add(label);
					state = TranspilerState.Finishing;
					break;
				}
				yield return instruction;
				if (state == TranspilerState.Searching && instruction.opcode == OpCodes.Call)
				{
					if (((MethodBase)instruction.operand).Name == "GetKeyDown")
					{
						state = TranspilerState.Inserting;
					}
				}
				else if (state == TranspilerState.Searching2 && instruction.opcode == OpCodes.Callvirt && ((MethodBase)instruction.operand).Name == "ActivateInputField")
				{
					yield return new CodeInstruction(OpCodes.Ldloc, (object)isSlashPressed.LocalIndex);
					yield return new CodeInstruction(OpCodes.Brfalse, (object)label);
					yield return new CodeInstruction(OpCodes.Ldarg_0, (object)null);
					yield return new CodeInstruction(OpCodes.Ldfld, (object)typeof(Terminal).GetField("m_input"));
					yield return new CodeInstruction(OpCodes.Ldstr, (object)"/");
					yield return new CodeInstruction(OpCodes.Call, (object)typeof(TMP_InputField).GetMethod("set_text"));
					yield return new CodeInstruction(OpCodes.Ldc_I4_1, (object)null);
					yield return new CodeInstruction(OpCodes.Stsfld, (object)typeof(BetterChatPlugin).GetField("sMoveCaretToEnd", BindingFlags.Static | BindingFlags.NonPublic));
					state = TranspilerState.Labeling;
				}
			}
			if (state != TranspilerState.Finishing)
			{
				throw new InvalidOperationException("[BetterChat] Chat_Slash_Patches encountered unexpected IL code. Unable to patch. This is most likely due to a game update changing the target code. Disable SlashOpensChat in the config as a workaround until the mod can be fixed. Details: Never reached finishing state");
			}
		}

		[HarmonyPatch("LateUpdate")]
		[HarmonyPostfix]
		private static void LateUpdate_Postfix(Chat __instance)
		{
			if (sMoveCaretToEnd)
			{
				((TMP_InputField)((Terminal)__instance).m_input).MoveTextEnd(false);
				sMoveCaretToEnd = false;
			}
		}
	}

	[HarmonyPatch(typeof(Chat))]
	private static class Chat_Show_Patch
	{
		[HarmonyPatch("OnNewChatMessage")]
		[HarmonyPostfix]
		private static void OnNewChatMessage_Postfix(Chat __instance, GameObject go, long senderID, Vector3 pos, Type type, string user, string text)
		{
			sHideTimerField.SetValue(__instance, 0f);
		}
	}

	[HarmonyPatch(typeof(Terminal))]
	private static class Chat_MixedCase_Terminal_Patch
	{
		[HarmonyPatch("AddString", new Type[]
		{
			typeof(string),
			typeof(string),
			typeof(Type),
			typeof(bool)
		})]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> AddString_Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return StripForcedCase(instructions);
		}
	}

	[HarmonyPatch(typeof(Chat))]
	private static class Chat_MixedCase_Chat_Patch
	{
		[HarmonyPatch("AddInworldText")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> AddInworldText_Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			return StripForcedCase(instructions);
		}
	}

	[HarmonyPatch(typeof(Chat))]
	private static class Chat_Shout_Patch
	{
		[HarmonyPatch("InputText")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> InputText_Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			foreach (CodeInstruction instruction in instructions)
			{
				if (instruction.opcode == OpCodes.Ldstr && instruction.operand.Equals("say "))
				{
					instruction.operand = "s ";
				}
				yield return instruction;
			}
		}
	}

	[HarmonyPatch(typeof(Minimap))]
	private static class Minimap_Patches
	{
		[HarmonyPatch("UpdateDynamicPins")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			for (int i = 0; i < list.Count - 1; i++)
			{
				if (list[i + 1].opcode == OpCodes.Call && ((MethodBase)list[i + 1].operand).Name == "UpdateShoutPins")
				{
					list.RemoveRange(i, 2);
					break;
				}
			}
			return list;
		}
	}

	public const string ModId = "dev.crystal.betterchat";

	public static ConfigEntry<bool> AlwaysVisible;

	public static ConfigEntry<float> HideDelay;

	public static ConfigEntry<bool> ForceCase;

	public static ConfigEntry<bool> SlashOpensChat;

	public static ConfigEntry<bool> DefaultShout;

	public static ConfigEntry<bool> ShowShoutPings;

	public static ConfigEntry<float> TalkDistance;

	public static ConfigEntry<float> WhisperDistance;

	private static Harmony sChatAwakeHarmony;

	private static Harmony sPlayerHarmony;

	private static Harmony sChatShowHarmony;

	private static Harmony sChatAlwaysShowHarmony;

	private static Harmony sChatMixedCaseHarmony;

	private static Harmony sChatShoutHarmony;

	private static Harmony sMinimapHarmony;

	private static Harmony sChatSlashHarmony;

	private static Chat sChat;

	private static List<Talker> sTalkers;

	private static readonly FieldInfo sHideTimerField;

	private static bool sMoveCaretToEnd;

	static BetterChatPlugin()
	{
		sTalkers = new List<Talker>();
		sHideTimerField = typeof(Chat).GetField("m_hideTimer", BindingFlags.Instance | BindingFlags.NonPublic);
	}

	private void Awake()
	{
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d0: Expected O, but got Unknown
		//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01df: Expected O, but got Unknown
		//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ee: Expected O, but got Unknown
		//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fd: Expected O, but got Unknown
		//IL_0202: Unknown result type (might be due to invalid IL or missing references)
		//IL_020c: Expected O, but got Unknown
		//IL_0211: Unknown result type (might be due to invalid IL or missing references)
		//IL_021b: Expected O, but got Unknown
		//IL_0220: Unknown result type (might be due to invalid IL or missing references)
		//IL_022a: Expected O, but got Unknown
		//IL_022f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0239: Expected O, but got Unknown
		AlwaysVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "AlwaysVisible", false, "If True, the chat window will remain visible at all times. If False, the chat window will appear when new messages are received.");
		AlwaysVisible.SettingChanged += AlwaysVisible_SettingChanged;
		HideDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "HideDelay", 10f, "The time, in seconds, to keep the chat window visible after sending or receiving a message. Minimum is 0.5. Has no effect if AlwaysVisible=true.");
		HideDelay.SettingChanged += HideDelay_SettingChanged;
		ForceCase = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "ForceCase", false, "If True, shout will be in all caps and whisper will be in all lowercase (game default). If False, messages will appear as they were originally entered.");
		ForceCase.SettingChanged += ForceCase_SettingChanged;
		SlashOpensChat = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "SlashOpensChat", true, "If True, pressing the slash key (/) will open the chat window and start a message.");
		SlashOpensChat.SettingChanged += SlashOpensChat_SettingChanged;
		DefaultShout = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "DefaultShout", false, "If True, text entered will shout by default - type /say for talk. If False, chat will be talk by default - type /s for shout.");
		DefaultShout.SettingChanged += DefaultShout_SettingChanged;
		ShowShoutPings = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "ShowShoutPings", true, "If True, pings will show on your map when players shout (game default). If False, the pings will not show. (Other players can still see your shout pings.)");
		ShowShoutPings.SettingChanged += ShowShoutPings_SettingChanged;
		TalkDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "TalkDistance", 15f, "The maximum distance from a player at which you will receive their normal chat messages (not whisper or shout). Game default is 15. Acceptable range is 1-100.");
		TalkDistance.SettingChanged += Distance_SettingChanged;
		WhisperDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Chat", "WhisperDistance", 4f, "The maximum distance from a player at which you will receive their whispered chat messages. Game default is 4. Acceptable range is 1-20");
		WhisperDistance.SettingChanged += Distance_SettingChanged;
		ClampConfig();
		sChatAwakeHarmony = new Harmony("dev.crystal.betterchat_ChatAwake");
		sPlayerHarmony = new Harmony("dev.crystal.betterchat_Player");
		sChatShowHarmony = new Harmony("dev.crystal.betterchat_ChatShow");
		sChatAlwaysShowHarmony = new Harmony("dev.crystal.betterchat_ChatAlwaysShow");
		sChatMixedCaseHarmony = new Harmony("dev.crystal.betterchat_ChatMixedCase");
		sChatShoutHarmony = new Harmony("dev.crystal.betterchat_ChatShout");
		sMinimapHarmony = new Harmony("dev.crystal.betterchat_Minimap");
		sChatSlashHarmony = new Harmony("dev.crystal.betterchat_ChatSlash");
		sChatAwakeHarmony.PatchAll(typeof(Chat_Patches));
		sPlayerHarmony.PatchAll(typeof(Player_Patches));
		if (AlwaysVisible.Value)
		{
			sChatAlwaysShowHarmony.PatchAll(typeof(Chat_AlwaysShow_Patch));
		}
		else
		{
			sChatShowHarmony.PatchAll(typeof(Chat_Show_Patch));
		}
		if (!ForceCase.Value)
		{
			sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Terminal_Patch));
			sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Chat_Patch));
		}
		if (DefaultShout.Value)
		{
			sChatShoutHarmony.PatchAll(typeof(Chat_Shout_Patch));
		}
		if (!ShowShoutPings.Value)
		{
			sMinimapHarmony.PatchAll(typeof(Minimap_Patches));
		}
		if (SlashOpensChat.Value)
		{
			sChatSlashHarmony.PatchAll(typeof(Chat_Slash_Patches));
		}
	}

	private void OnDestroy()
	{
		sChatAwakeHarmony.UnpatchSelf();
		sPlayerHarmony.UnpatchSelf();
		sChatShowHarmony.UnpatchSelf();
		sChatAlwaysShowHarmony.UnpatchSelf();
		sChatMixedCaseHarmony.UnpatchSelf();
		sChatShoutHarmony.UnpatchSelf();
		sMinimapHarmony.UnpatchSelf();
		sChatSlashHarmony.UnpatchSelf();
	}

	private static void ClampConfig()
	{
		if (HideDelay.Value < 0.5f)
		{
			HideDelay.Value = 0.5f;
		}
		if (HideDelay.Value > 3600f)
		{
			HideDelay.Value = 3600f;
		}
		if (TalkDistance.Value < 1f)
		{
			TalkDistance.Value = 1f;
		}
		if (TalkDistance.Value > 100f)
		{
			TalkDistance.Value = 100f;
		}
		if (WhisperDistance.Value < 1f)
		{
			WhisperDistance.Value = 1f;
		}
		if (WhisperDistance.Value > 20f)
		{
			WhisperDistance.Value = 20f;
		}
	}

	private void AlwaysVisible_SettingChanged(object sender, EventArgs e)
	{
		if (AlwaysVisible.Value)
		{
			sChatShowHarmony.UnpatchSelf();
			sChatAlwaysShowHarmony.PatchAll(typeof(Chat_AlwaysShow_Patch));
		}
		else
		{
			sChatAlwaysShowHarmony.UnpatchSelf();
			sChatShowHarmony.PatchAll(typeof(Chat_Show_Patch));
		}
	}

	private void HideDelay_SettingChanged(object sender, EventArgs e)
	{
		ClampConfig();
		if ((Object)(object)sChat != (Object)null)
		{
			sChat.m_hideDelay = HideDelay.Value;
		}
	}

	private void ForceCase_SettingChanged(object sender, EventArgs e)
	{
		if (ForceCase.Value)
		{
			sChatMixedCaseHarmony.UnpatchSelf();
			return;
		}
		sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Terminal_Patch));
		sChatMixedCaseHarmony.PatchAll(typeof(Chat_MixedCase_Chat_Patch));
	}

	private void SlashOpensChat_SettingChanged(object sender, EventArgs e)
	{
		if (SlashOpensChat.Value)
		{
			sChatSlashHarmony.PatchAll(typeof(Chat_Slash_Patches));
		}
		else
		{
			sChatSlashHarmony.UnpatchSelf();
		}
	}

	private void DefaultShout_SettingChanged(object sender, EventArgs e)
	{
		if (DefaultShout.Value)
		{
			sChatShoutHarmony.PatchAll(typeof(Chat_Shout_Patch));
		}
		else
		{
			sChatShoutHarmony.UnpatchSelf();
		}
	}

	private void ShowShoutPings_SettingChanged(object sender, EventArgs e)
	{
		if (ShowShoutPings.Value)
		{
			sMinimapHarmony.UnpatchSelf();
		}
		else
		{
			sMinimapHarmony.PatchAll(typeof(Minimap_Patches));
		}
	}

	private void Distance_SettingChanged(object sender, EventArgs e)
	{
		ClampConfig();
		foreach (Talker sTalker in sTalkers)
		{
			ApplyChatDistances(sTalker);
		}
	}

	private static void ApplyChatDistances(Talker talker)
	{
		talker.m_visperDistance = WhisperDistance.Value;
		talker.m_normalDistance = TalkDistance.Value;
	}

	private static IEnumerable<CodeInstruction> StripForcedCase(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		for (int i = 0; i < list.Count; i++)
		{
			if (list[i].opcode == OpCodes.Callvirt)
			{
				MethodBase methodBase = list[i].operand as MethodBase;
				if (methodBase != null && (methodBase.Name == "ToLowerInvariant" || methodBase.Name == "ToUpper"))
				{
					list.RemoveRange(i - 1, 3);
					i -= 2;
				}
			}
		}
		return list;
	}
}