Decompiled source of SimpleChat v0.1.7

SULFUR SimpleChat.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using PerfectRandom.Sulfur.Core;
using PerfectRandom.Sulfur.Core.Input;
using PerfectRandom.Sulfur.Core.UI;
using PerfectRandom.Sulfur.Gameplay.Input;
using Ryuka.Sulfur.NativeUI;
using SULFURTogether.Api;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.UI;
using de.nemophila.sulfur.simplechat.Api;
using de.nemophila.sulfur.simplechat.Localization;
using de.nemophila.sulfur.simplechat.Networking;
using de.nemophila.sulfur.simplechat.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("SULFUR SimpleChat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SULFUR SimpleChat")]
[assembly: AssemblyTitle("SULFUR SimpleChat")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 de.nemophila.sulfur.simplechat
{
	internal static class ModInfo
	{
		public const string Guid = "de.nemophila.sulfur.simplechat";

		public const string Name = "SULFUR SimpleChat";

		public const string Version = "0.1.7";

		public const string TogetherGuid = "com.ryuka.sulfur.together";

		public const string TogetherMinimumVersion = "1.2.3";

		public const string NativeUiGuid = "ryuka.sulfur.nativeui";

		public const string NativeUiMinimumVersion = "0.10.1";

		public const string ChannelId = "de.nemophila.sulfur.simplechat.v1";
	}
	[BepInPlugin("de.nemophila.sulfur.simplechat", "SULFUR SimpleChat", "0.1.7")]
	[BepInDependency("com.ryuka.sulfur.together", "1.2.3")]
	[BepInDependency("ryuka.sulfur.nativeui", "0.10.1")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private ChatHud? hud;

		private ChatController? controller;

		private ConfigEntry<Key>? openChatKey;

		private void Awake()
		{
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			Loc.Initialize(((BaseUnityPlugin)this).Info.Location);
			openChatKey = ((BaseUnityPlugin)this).Config.Bind<Key>("Input", "OpenChatKey", (Key)35, "Keyboard key used to open SimpleChat during normal gameplay.");
			hud = new ChatHud((MonoBehaviour)(object)this, ((BaseUnityPlugin)this).Logger, () => openChatKey.Value);
			SimpleChatApi.Bind(delegate(string message)
			{
				if (hud == null)
				{
					return false;
				}
				hud.AddSystemText(message);
				return true;
			});
			controller = new ChatController(((BaseUnityPlugin)this).Logger, hud);
			hud.CanAttemptOpen = controller.CanAttemptOpen;
			hud.CanOpen = controller.CanOpen;
			hud.OpenDenied = controller.NotifyOpenDenied;
			hud.Submit = controller.SubmitLocal;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("SULFUR SimpleChat v0.1.7 loaded. OpenChatKey=" + ((object)openChatKey.Value/*cast due to .constrained prefix*/).ToString()));
		}

		private void Update()
		{
			float unscaledTime = Time.unscaledTime;
			controller?.Tick(unscaledTime);
			hud?.Tick(unscaledTime);
		}

		private void LateUpdate()
		{
			hud?.LateTick(Time.unscaledTime);
		}

		private void OnDestroy()
		{
			SimpleChatApi.Unbind();
			controller?.Dispose();
			controller = null;
			hud?.Dispose();
			hud = null;
		}
	}
}
namespace de.nemophila.sulfur.simplechat.UI
{
	internal sealed class ChatHud
	{
		private sealed class DisplayEntry
		{
			private bool isChat;

			private bool isRawSystem;

			private string key = "";

			private string fallback = "";

			private object[] args = Array.Empty<object>();

			private string name = "";

			private string text = "";

			public float AddedAt { get; set; }

			public static DisplayEntry Chat(string name, string text)
			{
				return new DisplayEntry
				{
					isChat = true,
					name = (name ?? ""),
					text = (text ?? "")
				};
			}

			public static DisplayEntry System(string key, string fallback, object[] args)
			{
				return new DisplayEntry
				{
					key = (key ?? ""),
					fallback = (fallback ?? ""),
					args = (args ?? Array.Empty<object>())
				};
			}

			public static DisplayEntry SystemText(string text)
			{
				return new DisplayEntry
				{
					isRawSystem = true,
					text = (text ?? "")
				};
			}

			public string Render()
			{
				if (isChat)
				{
					return Loc.Format("chat.line", "{0}: {1}", name, text);
				}
				if (isRawSystem)
				{
					return text;
				}
				return Loc.Format(key, fallback, args);
			}
		}

		private const int HistoryLimit = 50;

		private const int PassiveLineCount = 6;

		private const int FocusedLineCount = 12;

		private const float PassiveHoldSeconds = 8f;

		private const float PassiveFadeSeconds = 2f;

		private const int FocusActivationAttempts = 3;

		private readonly MonoBehaviour runner;

		private readonly ManualLogSource log;

		private readonly Func<Key> openKeyProvider;

		private readonly GameInputCapture inputCapture;

		private readonly ModernTextInputBridge textInput;

		private readonly List<DisplayEntry> entries = new List<DisplayEntry>();

		private GameObject canvasObject;

		private RectTransform overlayRoot;

		private CanvasGroup historyGroup;

		private Image historyBackground;

		private TextMeshProUGUI historyText;

		private GameObject inputRoot;

		private TMP_InputField inputField;

		private TextMeshProUGUI inputText;

		private TextMeshProUGUI placeholderText;

		private Coroutine? activationCoroutine;

		private bool focused;

		private float newestEntryTime = float.NegativeInfinity;

		private float nextFontRefresh;

		private int lastLanguageVersion = -1;

		private Key? warnedInvalidOpenKey;

		private Vector2 lastHistorySize = new Vector2(-1f, -1f);

		private bool clearOnActivation;

		public Func<bool>? CanAttemptOpen { private get; set; }

		public Func<bool>? CanOpen { private get; set; }

		public Action? OpenDenied { private get; set; }

		public Func<string, bool>? Submit { private get; set; }

		public ChatHud(MonoBehaviour runner, ManualLogSource log, Func<Key> openKeyProvider)
		{
			//IL_0028: 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)
			this.runner = runner;
			this.log = log;
			this.openKeyProvider = openKeyProvider;
			inputCapture = new GameInputCapture(log);
			textInput = new ModernTextInputBridge(log);
			Build();
		}

		public void Tick(float now)
		{
			//IL_01cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)canvasObject == (Object)null)
			{
				return;
			}
			if (focused && activationCoroutine == null)
			{
				if (!inputCapture.IsOwnershipValid)
				{
					log.LogDebug((object)"Closing chat because game input ownership changed.");
					EndFocus();
				}
				else if ((Object)(object)EventSystem.current == (Object)null || !inputField.isFocused || (Object)(object)EventSystem.current.currentSelectedGameObject != (Object)(object)((Component)inputField).gameObject)
				{
					log.LogDebug((object)"Chat input focus was lost; scheduling recovery.");
					ScheduleActivation(clearText: false);
				}
			}
			Keyboard current = Keyboard.current;
			if (current != null)
			{
				if (!focused && IsOpenKeyPressed(current))
				{
					Func<bool>? canAttemptOpen = CanAttemptOpen;
					if (canAttemptOpen == null || canAttemptOpen())
					{
						TryBeginFocus();
					}
				}
				else if (focused && ((ButtonControl)current.escapeKey).wasPressedThisFrame)
				{
					if (!textInput.ShouldConsumeCommandThisFrame)
					{
						EndFocus();
					}
				}
				else if (focused && (((ButtonControl)current.enterKey).wasPressedThisFrame || ((ButtonControl)current.numpadEnterKey).wasPressedThisFrame) && !textInput.ShouldConsumeCommandThisFrame)
				{
					string text = inputField.text;
					Func<string, bool>? submit = Submit;
					if (submit != null && submit(text))
					{
						EndFocus();
					}
					else
					{
						ScheduleActivation(clearText: false);
					}
				}
			}
			if (Loc.LanguageVersion != lastLanguageVersion)
			{
				lastLanguageVersion = Loc.LanguageVersion;
				((TMP_Text)placeholderText).text = Loc.Get("input.placeholder", "Type a message...");
				RebuildHistory();
				RefreshFont();
				nextFontRefresh = now + 1f;
			}
			if (now >= nextFontRefresh)
			{
				nextFontRefresh = now + 1f;
				RefreshFont();
			}
			Rect rect = ((TMP_Text)historyText).rectTransform.rect;
			Vector2 val = ((Rect)(ref rect)).size - lastHistorySize;
			if (((Vector2)(ref val)).sqrMagnitude > 0.25f)
			{
				RebuildHistory();
			}
			UpdateVisibility(now);
		}

		public void LateTick(float now)
		{
			textInput.LateTick(now);
		}

		public void AddChat(string displayName, string text)
		{
			AddEntry(DisplayEntry.Chat(displayName, text));
		}

		public void AddSystem(string key, string fallback, params object[] args)
		{
			AddEntry(DisplayEntry.System(key, fallback, args));
		}

		public void AddSystemText(string text)
		{
			AddEntry(DisplayEntry.SystemText(text));
		}

		public void Clear()
		{
			entries.Clear();
			newestEntryTime = float.NegativeInfinity;
			if (focused)
			{
				EndFocus();
			}
			RebuildHistory();
		}

		public void Dispose()
		{
			if (activationCoroutine != null)
			{
				runner.StopCoroutine(activationCoroutine);
				activationCoroutine = null;
			}
			inputCapture.Release();
			textInput.Dispose();
			focused = false;
			if ((Object)(object)canvasObject != (Object)null)
			{
				Object.Destroy((Object)(object)canvasObject);
			}
		}

		private void AddEntry(DisplayEntry entry)
		{
			entry.AddedAt = Time.unscaledTime;
			entries.Add(entry);
			if (entries.Count > 50)
			{
				entries.RemoveAt(0);
			}
			newestEntryTime = entry.AddedAt;
			RebuildHistory();
		}

		private void TryBeginFocus()
		{
			if (!focused && inputCapture.TryCapture(out var _))
			{
				Func<bool>? canOpen = CanOpen;
				if (canOpen == null || !canOpen())
				{
					inputCapture.Release();
					OpenDenied?.Invoke();
					return;
				}
				if ((Object)(object)EventSystem.current == (Object)null)
				{
					inputCapture.Release();
					AddSystem("system.input_unavailable", "The game UI input system is not ready.");
					return;
				}
				string text = (((Object)(object)EventSystem.current.currentInputModule != (Object)null) ? ((object)EventSystem.current.currentInputModule).GetType().FullName : "<none>");
				log.LogDebug((object)("Opening chat input. EventSystem=" + ((object)EventSystem.current).GetType().FullName + " InputModule=" + text + "."));
				focused = true;
				inputRoot.SetActive(true);
				((Selectable)inputField).interactable = true;
				inputField.SetTextWithoutNotify("");
				inputField.ForceLabelUpdate();
				RebuildHistory();
				ScheduleActivation(clearText: true);
			}
		}

		private void ScheduleActivation(bool clearText)
		{
			clearOnActivation = clearText;
			if (activationCoroutine == null)
			{
				activationCoroutine = runner.StartCoroutine(ActivateOnNextFrame());
			}
		}

		private IEnumerator ActivateOnNextFrame()
		{
			yield return null;
			if (!focused || (Object)(object)inputField == (Object)null)
			{
				activationCoroutine = null;
				yield break;
			}
			if (clearOnActivation)
			{
				inputField.SetTextWithoutNotify("");
			}
			inputField.ForceLabelUpdate();
			for (int attempt = 1; attempt <= 3; attempt++)
			{
				EventSystem current = EventSystem.current;
				if ((Object)(object)current == (Object)null)
				{
					break;
				}
				current.SetSelectedGameObject(((Component)inputField).gameObject);
				((Selectable)inputField).Select();
				inputField.ActivateInputField();
				inputField.ForceLabelUpdate();
				yield return null;
				if (!focused)
				{
					activationCoroutine = null;
					yield break;
				}
				if (inputField.isFocused && (Object)(object)EventSystem.current != (Object)null && (Object)(object)EventSystem.current.currentSelectedGameObject == (Object)(object)((Component)inputField).gameObject)
				{
					activationCoroutine = null;
					textInput.Begin(inputField);
					log.LogDebug((object)("Chat input focus activated on attempt " + attempt + "."));
					yield break;
				}
			}
			activationCoroutine = null;
			log.LogWarning((object)("Chat input focus could not be activated after " + 3 + " attempts."));
			FailFocusActivation();
		}

		private void EndFocus()
		{
			if (focused)
			{
				if (activationCoroutine != null)
				{
					runner.StopCoroutine(activationCoroutine);
					activationCoroutine = null;
				}
				focused = false;
				textInput.End();
				inputField.DeactivateInputField(false);
				if ((Object)(object)EventSystem.current != (Object)null && (Object)(object)EventSystem.current.currentSelectedGameObject == (Object)(object)((Component)inputField).gameObject)
				{
					EventSystem.current.SetSelectedGameObject((GameObject)null);
				}
				inputField.SetTextWithoutNotify("");
				inputRoot.SetActive(false);
				inputCapture.Release();
				RebuildHistory();
			}
		}

		private void UpdateVisibility(float now)
		{
			if (focused)
			{
				historyGroup.alpha = 1f;
				((Behaviour)historyBackground).enabled = true;
				return;
			}
			float num = now - newestEntryTime;
			float num2 = ((num <= 8f) ? 1f : (1f - (num - 8f) / 2f));
			historyGroup.alpha = Mathf.Clamp01(num2);
			((Behaviour)historyBackground).enabled = false;
		}

		private void RebuildHistory()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)historyText == (Object)null)
			{
				return;
			}
			Rect rect = ((TMP_Text)historyText).rectTransform.rect;
			Vector2 val = (lastHistorySize = ((Rect)(ref rect)).size);
			int num = (focused ? 12 : 6);
			string text = "";
			int num2 = 0;
			int num3 = entries.Count - 1;
			while (num3 >= 0 && num2 < num)
			{
				string text2 = entries[num3].Render();
				string text3 = ((text.Length == 0) ? text2 : (text2 + "\n" + text));
				if (val.x > 1f && val.y > 1f && ((TMP_Text)historyText).GetPreferredValues(text3, val.x, float.PositiveInfinity).y > val.y - 1f && num2 > 0)
				{
					break;
				}
				text = text3;
				num2++;
				num3--;
			}
			((TMP_Text)historyText).text = text;
		}

		private void RefreshFont()
		{
			TextMeshProUGUI[] array = Object.FindObjectsByType<TextMeshProUGUI>((FindObjectsSortMode)0);
			Dictionary<TMP_FontAsset, int> dictionary = new Dictionary<TMP_FontAsset, int>();
			foreach (TextMeshProUGUI val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)((TMP_Text)val).font == (Object)null) && !((TMP_Text)val).transform.IsChildOf((Transform)(object)overlayRoot))
				{
					dictionary.TryGetValue(((TMP_Text)val).font, out var value);
					dictionary[((TMP_Text)val).font] = value + 1;
				}
			}
			string probe = BuildFontProbe();
			Dictionary<TMP_FontAsset, int> dictionary2 = new Dictionary<TMP_FontAsset, int>();
			TextMeshProUGUI val2 = null;
			int num = int.MaxValue;
			int num2 = -1;
			int num3 = int.MaxValue;
			foreach (TextMeshProUGUI val3 in array)
			{
				if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((TMP_Text)val3).font == (Object)null) && !((TMP_Text)val3).transform.IsChildOf((Transform)(object)overlayRoot))
				{
					TMP_FontAsset font = ((TMP_Text)val3).font;
					if (!dictionary2.TryGetValue(font, out var value2))
					{
						value2 = (dictionary2[font] = CountMissingGlyphs(font, probe));
					}
					int num5 = dictionary[font];
					int instanceID = ((Object)font).GetInstanceID();
					if (value2 <= num && (value2 != num || num5 >= num2) && (value2 != num || num5 != num2 || instanceID < num3))
					{
						val2 = val3;
						num = value2;
						num2 = num5;
						num3 = instanceID;
					}
				}
			}
			TMP_FontAsset val4 = (((Object)(object)val2 != (Object)null) ? ((TMP_Text)val2).font : TMP_Settings.defaultFontAsset);
			if (!((Object)(object)val4 == (Object)null))
			{
				Material material = (((Object)(object)val2 != (Object)null) ? ((TMP_Text)val2).fontSharedMaterial : null);
				if (ApplyFont(historyText, val4, material) | ApplyFont(inputText, val4, material) | ApplyFont(placeholderText, val4, material))
				{
					RebuildHistory();
				}
			}
		}

		private string BuildFontProbe()
		{
			StringBuilder stringBuilder = new StringBuilder();
			HashSet<int> seen = new HashSet<int>();
			AppendFontProbe(stringBuilder, seen, ((Object)(object)placeholderText != (Object)null) ? ((TMP_Text)placeholderText).text : "");
			AppendFontProbe(stringBuilder, seen, ((Object)(object)historyText != (Object)null) ? ((TMP_Text)historyText).text : "");
			AppendFontProbe(stringBuilder, seen, ((Object)(object)inputField != (Object)null) ? inputField.text : "");
			return stringBuilder.ToString();
		}

		private static void AppendFontProbe(StringBuilder probe, HashSet<int> seen, string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return;
			}
			for (int i = 0; i < value.Length; i++)
			{
				char c = value[i];
				int num;
				if (char.IsHighSurrogate(c) && i + 1 < value.Length && char.IsLowSurrogate(value[i + 1]))
				{
					num = char.ConvertToUtf32(c, value[++i]);
				}
				else
				{
					if (char.IsSurrogate(c) || char.IsWhiteSpace(c) || char.IsControl(c))
					{
						continue;
					}
					num = c;
				}
				if (seen.Add(num))
				{
					probe.Append(char.ConvertFromUtf32(num));
				}
			}
		}

		private static int CountMissingGlyphs(TMP_FontAsset font, string probe)
		{
			if (probe.Length == 0)
			{
				return 0;
			}
			try
			{
				uint[] array = default(uint[]);
				font.HasCharacters(probe, ref array, true, false);
				return (array != null) ? array.Length : 0;
			}
			catch (Exception)
			{
				return int.MaxValue;
			}
		}

		private void Build()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Expected O, but got Unknown
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			canvasObject = new GameObject("SULFUR SimpleChat Canvas", new Type[4]
			{
				typeof(RectTransform),
				typeof(Canvas),
				typeof(CanvasScaler),
				typeof(GraphicRaycaster)
			});
			Object.DontDestroyOnLoad((Object)(object)canvasObject);
			Canvas component = canvasObject.GetComponent<Canvas>();
			component.renderMode = (RenderMode)0;
			component.sortingOrder = 5000;
			CanvasScaler component2 = canvasObject.GetComponent<CanvasScaler>();
			component2.uiScaleMode = (ScaleMode)1;
			component2.referenceResolution = new Vector2(1920f, 1080f);
			component2.matchWidthOrHeight = 0.5f;
			GameObject val = new GameObject("Chat Overlay", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(canvasObject.transform, false);
			overlayRoot = val.GetComponent<RectTransform>();
			overlayRoot.anchorMin = Vector2.zero;
			overlayRoot.anchorMax = Vector2.zero;
			overlayRoot.pivot = Vector2.zero;
			overlayRoot.anchoredPosition = new Vector2(32f, 96f);
			overlayRoot.sizeDelta = new Vector2(800f, 390f);
			BuildHistory();
			BuildInput();
			((TMP_Text)placeholderText).text = Loc.Get("input.placeholder", "Type a message...");
			RefreshFont();
			lastLanguageVersion = Loc.LanguageVersion;
			UpdateVisibility(Time.unscaledTime);
		}

		private void BuildHistory()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Expected O, but got Unknown
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Expected O, but got Unknown
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_021e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("History", new Type[4]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image),
				typeof(CanvasGroup)
			});
			val.transform.SetParent((Transform)(object)overlayRoot, false);
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0f, 0f);
			component.anchorMax = new Vector2(1f, 1f);
			component.offsetMin = new Vector2(0f, 50f);
			component.offsetMax = Vector2.zero;
			historyBackground = val.GetComponent<Image>();
			((Graphic)historyBackground).color = new Color(0.03f, 0.025f, 0.02f, 0.72f);
			((Graphic)historyBackground).raycastTarget = false;
			historyGroup = val.GetComponent<CanvasGroup>();
			historyGroup.blocksRaycasts = false;
			historyGroup.interactable = false;
			GameObject val2 = new GameObject("Messages", new Type[4]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(TextMeshProUGUI),
				typeof(Shadow)
			});
			val2.transform.SetParent(val.transform, false);
			RectTransform component2 = val2.GetComponent<RectTransform>();
			component2.anchorMin = Vector2.zero;
			component2.anchorMax = Vector2.one;
			component2.offsetMin = new Vector2(14f, 10f);
			component2.offsetMax = new Vector2(-14f, -10f);
			historyText = val2.GetComponent<TextMeshProUGUI>();
			((TMP_Text)historyText).fontSize = 21f;
			((Graphic)historyText).color = Color.white;
			((TMP_Text)historyText).alignment = (TextAlignmentOptions)1025;
			((TMP_Text)historyText).richText = false;
			((Graphic)historyText).raycastTarget = false;
			((TMP_Text)historyText).textWrappingMode = (TextWrappingModes)1;
			((TMP_Text)historyText).overflowMode = (TextOverflowModes)3;
			Shadow component3 = val2.GetComponent<Shadow>();
			component3.effectColor = Color32.op_Implicit(new Color32((byte)10, (byte)8, (byte)6, (byte)220));
			component3.effectDistance = new Vector2(1.5f, -1.5f);
			component3.useGraphicAlpha = true;
		}

		private void BuildInput()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			inputRoot = new GameObject("Input", new Type[4]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image),
				typeof(TMP_InputField)
			});
			inputRoot.transform.SetParent((Transform)(object)overlayRoot, false);
			RectTransform component = inputRoot.GetComponent<RectTransform>();
			component.anchorMin = new Vector2(0f, 0f);
			component.anchorMax = new Vector2(1f, 0f);
			component.pivot = new Vector2(0.5f, 0f);
			component.sizeDelta = new Vector2(0f, 42f);
			Image component2 = inputRoot.GetComponent<Image>();
			((Graphic)component2).color = new Color(0.07f, 0.055f, 0.035f, 0.96f);
			GameObject val = new GameObject("Text Area", new Type[2]
			{
				typeof(RectTransform),
				typeof(RectMask2D)
			});
			val.transform.SetParent(inputRoot.transform, false);
			RectTransform component3 = val.GetComponent<RectTransform>();
			component3.anchorMin = Vector2.zero;
			component3.anchorMax = Vector2.one;
			component3.offsetMin = new Vector2(13f, 5f);
			component3.offsetMax = new Vector2(-13f, -5f);
			inputText = CreateInputText("Text", component3, Color.white, (FontStyles)0);
			placeholderText = CreateInputText("Placeholder", component3, new Color(1f, 1f, 1f, 0.45f), (FontStyles)2);
			inputField = inputRoot.GetComponent<TMP_InputField>();
			((Selectable)inputField).targetGraphic = (Graphic)(object)component2;
			inputField.textViewport = component3;
			inputField.textComponent = (TMP_Text)(object)inputText;
			inputField.placeholder = (Graphic)(object)placeholderText;
			inputField.contentType = (ContentType)0;
			inputField.lineType = (LineType)0;
			inputField.characterLimit = 256;
			inputField.richText = false;
			inputField.shouldHideMobileInput = true;
			inputField.shouldHideSoftKeyboard = true;
			inputField.shouldActivateOnSelect = true;
			inputField.onFocusSelectAll = false;
			inputField.resetOnDeActivation = false;
			inputField.restoreOriginalTextOnEscape = false;
			inputField.customCaretColor = true;
			inputField.caretColor = new Color(1f, 0.72f, 0.28f, 1f);
			inputField.selectionColor = new Color(1f, 0.55f, 0.1f, 0.35f);
			TMP_InputField obj = inputField;
			Navigation navigation = default(Navigation);
			((Navigation)(ref navigation)).mode = (Mode)0;
			((Selectable)obj).navigation = navigation;
			((TMP_Text)inputText).text = "";
			inputField.SetTextWithoutNotify("");
			inputField.ForceLabelUpdate();
			inputRoot.SetActive(false);
		}

		private unsafe bool IsOpenKeyPressed(Keyboard keyboard)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: 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)
			//IL_003f: 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_0013: Unknown result type (might be due to invalid IL or missing references)
			Key val = openKeyProvider();
			if ((int)val == 0)
			{
				return false;
			}
			try
			{
				bool wasPressedThisFrame = ((ButtonControl)keyboard[val]).wasPressedThisFrame;
				warnedInvalidOpenKey = null;
				return wasPressedThisFrame;
			}
			catch (Exception ex)
			{
				if (warnedInvalidOpenKey != (Key?)val)
				{
					warnedInvalidOpenKey = val;
					log.LogWarning((object)("Configured OpenChatKey '" + ((object)(*(Key*)(&val))/*cast due to .constrained prefix*/).ToString() + "' is unavailable: " + ex.Message));
				}
				return false;
			}
		}

		private void FailFocusActivation()
		{
			if (focused)
			{
				focused = false;
				textInput.End();
				inputField.DeactivateInputField(false);
				if ((Object)(object)EventSystem.current != (Object)null && (Object)(object)EventSystem.current.currentSelectedGameObject == (Object)(object)((Component)inputField).gameObject)
				{
					EventSystem.current.SetSelectedGameObject((GameObject)null);
				}
				inputField.SetTextWithoutNotify("");
				inputRoot.SetActive(false);
				inputCapture.Release();
				RebuildHistory();
				AddSystem("system.focus_failed", "The chat input field could not receive keyboard focus.");
			}
		}

		private static bool ApplyFont(TextMeshProUGUI text, TMP_FontAsset font, Material? material)
		{
			bool result = false;
			if ((Object)(object)((TMP_Text)text).font != (Object)(object)font)
			{
				((TMP_Text)text).font = font;
				result = true;
			}
			if ((Object)(object)material != (Object)null && (Object)(object)((TMP_Text)text).fontSharedMaterial != (Object)(object)material)
			{
				((TMP_Text)text).fontSharedMaterial = material;
				result = true;
			}
			return result;
		}

		private static TextMeshProUGUI CreateInputText(string name, RectTransform parent, Color color, FontStyles style)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(TextMeshProUGUI)
			});
			val.transform.SetParent((Transform)(object)parent, false);
			RectTransform component = val.GetComponent<RectTransform>();
			component.anchorMin = Vector2.zero;
			component.anchorMax = Vector2.one;
			component.offsetMin = Vector2.zero;
			component.offsetMax = Vector2.zero;
			TextMeshProUGUI component2 = val.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component2).fontSize = 21f;
			((Graphic)component2).color = color;
			((TMP_Text)component2).fontStyle = style;
			((TMP_Text)component2).alignment = (TextAlignmentOptions)4097;
			((TMP_Text)component2).richText = false;
			((Graphic)component2).raycastTarget = false;
			((TMP_Text)component2).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)component2).overflowMode = (TextOverflowModes)0;
			return component2;
		}
	}
	internal enum InputCaptureFailure
	{
		None,
		NoActiveInputReader,
		GameplayUnavailable
	}
	internal sealed class GameInputCapture
	{
		private struct MapState
		{
			private bool ui;

			private bool inventory;

			private bool cinematic;

			private bool loading;

			private bool devTools;

			public static MapState Capture(PlayerInputActions source)
			{
				//IL_000b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: 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)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_004f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0060: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				MapState result = default(MapState);
				UIActions uI = source.UI;
				result.ui = ((UIActions)(ref uI)).enabled;
				InventoryActions val = source.Inventory;
				result.inventory = ((InventoryActions)(ref val)).enabled;
				CinematicActions val2 = source.Cinematic;
				result.cinematic = ((CinematicActions)(ref val2)).enabled;
				LoadingActions val3 = source.Loading;
				result.loading = ((LoadingActions)(ref val3)).enabled;
				DevToolsActions val4 = source.DevTools;
				result.devTools = ((DevToolsActions)(ref val4)).enabled;
				return result;
			}

			public override bool Equals(object obj)
			{
				if (!(obj is MapState mapState))
				{
					return false;
				}
				if (ui == mapState.ui && inventory == mapState.inventory && cinematic == mapState.cinematic && loading == mapState.loading)
				{
					return devTools == mapState.devTools;
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (int)(((((((uint)((ui ? 1 : 0) * 397) ^ (inventory ? 1u : 0u)) * 397) ^ (cinematic ? 1u : 0u)) * 397) ^ (loading ? 1u : 0u)) * 397) ^ (devTools ? 1 : 0);
			}
		}

		private readonly ManualLogSource log;

		private InputReader? reader;

		private PlayerInputActions? actions;

		private bool onFootWasEnabled;

		private bool fKeysWereEnabled;

		private MapState otherMaps;

		private float nextUnavailableLogTime;

		private float nextRecoveryLogTime;

		public bool IsCaptured => actions != null;

		public bool IsOwnershipValid
		{
			get
			{
				if (actions != null && (Object)(object)reader != (Object)null && reader.inputActions == actions)
				{
					return otherMaps.Equals(MapState.Capture(actions));
				}
				return false;
			}
		}

		public GameInputCapture(ManualLogSource log)
		{
			this.log = log;
		}

		public bool TryCapture(out InputCaptureFailure failure)
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			if (IsCaptured)
			{
				failure = InputCaptureFailure.None;
				return true;
			}
			GameManager instance = StaticInstance<GameManager>.Instance;
			InputReader val = (((Object)(object)instance != (Object)null) ? instance.InputReader : null);
			PlayerInputActions val2 = (((Object)(object)val != (Object)null) ? val.inputActions : null);
			if ((Object)(object)instance == (Object)null || (Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy || val2 == null)
			{
				failure = InputCaptureFailure.NoActiveInputReader;
				LogUnavailable("Chat input capture ignored: no active player InputReader.");
				return false;
			}
			if (!TryPrepareSafeGameplay(instance, val2))
			{
				failure = InputCaptureFailure.GameplayUnavailable;
				LogUnavailableState(instance, val2);
				return false;
			}
			reader = val;
			actions = val2;
			OnFootActions onFoot = val2.OnFoot;
			onFootWasEnabled = ((OnFootActions)(ref onFoot)).enabled;
			FKeysActions fKeys = val2.FKeys;
			fKeysWereEnabled = ((FKeysActions)(ref fKeys)).enabled;
			otherMaps = MapState.Capture(val2);
			onFoot = val2.OnFoot;
			((OnFootActions)(ref onFoot)).Disable();
			fKeys = val2.FKeys;
			((FKeysActions)(ref fKeys)).Disable();
			failure = InputCaptureFailure.None;
			log.LogDebug((object)"Chat input captured: OnFoot and FKeys disabled.");
			return true;
		}

		public void Release()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			PlayerInputActions val = actions;
			InputReader val2 = reader;
			actions = null;
			reader = null;
			if (val != null && !((Object)(object)val2 == (Object)null))
			{
				if (val2.inputActions != val || !otherMaps.Equals(MapState.Capture(val)))
				{
					log.LogDebug((object)"Chat input release delegated to the game because input ownership changed.");
					return;
				}
				OnFootActions onFoot = val.OnFoot;
				SetMapEnabled(((OnFootActions)(ref onFoot)).Get(), onFootWasEnabled);
				FKeysActions fKeys = val.FKeys;
				SetMapEnabled(((FKeysActions)(ref fKeys)).Get(), fKeysWereEnabled);
				log.LogDebug((object)("Chat input released: OnFoot=" + onFootWasEnabled + " FKeys=" + fKeysWereEnabled + "."));
			}
		}

		private static void SetMapEnabled(InputActionMap map, bool enabled)
		{
			if (enabled)
			{
				map.Enable();
			}
			else
			{
				map.Disable();
			}
		}

		private bool TryPrepareSafeGameplay(GameManager manager, PlayerInputActions source)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			if ((int)manager.gameState == 3 && (int)manager.currentLocks == 0)
			{
				InventoryActions inventory = source.Inventory;
				if (!((InventoryActions)(ref inventory)).enabled)
				{
					CinematicActions cinematic = source.Cinematic;
					if (!((CinematicActions)(ref cinematic)).enabled)
					{
						LoadingActions loading = source.Loading;
						if (!((LoadingActions)(ref loading)).enabled)
						{
							DevToolsActions devTools = source.DevTools;
							if (!((DevToolsActions)(ref devTools)).enabled && !IsGameUiOpen(manager) && !IsTextInputFocused())
							{
								UIActions uI = source.UI;
								bool enabled = ((UIActions)(ref uI)).enabled;
								OnFootActions onFoot = source.OnFoot;
								bool flag = !((OnFootActions)(ref onFoot)).enabled;
								FKeysActions fKeys = source.FKeys;
								bool flag2 = !((FKeysActions)(ref fKeys)).enabled;
								if (enabled)
								{
									uI = source.UI;
									((UIActions)(ref uI)).Disable();
								}
								if (flag || flag2)
								{
									onFoot = source.OnFoot;
									((OnFootActions)(ref onFoot)).Enable();
									fKeys = source.FKeys;
									((FKeysActions)(ref fKeys)).Enable();
								}
								if ((enabled || flag || flag2) && Time.unscaledTime >= nextRecoveryLogTime)
								{
									nextRecoveryLogTime = Time.unscaledTime + 2f;
									log.LogWarning((object)("Recovered stale gameplay input maps before opening chat: UI=" + enabled + " OnFootDisabled=" + flag + " FKeysDisabled=" + flag2 + "."));
								}
								onFoot = source.OnFoot;
								if (((OnFootActions)(ref onFoot)).enabled)
								{
									uI = source.UI;
									return !((UIActions)(ref uI)).enabled;
								}
								return false;
							}
						}
					}
				}
			}
			return false;
		}

		private static bool IsGameUiOpen(GameManager manager)
		{
			CanvasManager canvasManager = manager.canvasManager;
			if ((Object)(object)canvasManager != (Object)null && (((Object)(object)canvasManager.pauseMenu != (Object)null && ((Component)canvasManager.pauseMenu).gameObject.activeInHierarchy) || ((Object)(object)canvasManager.optionsScreen != (Object)null && canvasManager.optionsScreen.IsShown)))
			{
				return true;
			}
			UIManager instance = StaticInstance<UIManager>.Instance;
			if ((Object)(object)instance != (Object)null && (Object)(object)instance.InventoryUI != (Object)null && instance.InventoryUI.IsOpened)
			{
				return true;
			}
			if ((Object)(object)manager.PlayerScript != (Object)null && (Object)(object)manager.PlayerScript.dialogController != (Object)null)
			{
				return (Object)(object)manager.PlayerScript.dialogController.CurrentSpeakable != (Object)null;
			}
			return false;
		}

		private static bool IsTextInputFocused()
		{
			GameObject val = (((Object)(object)EventSystem.current != (Object)null) ? EventSystem.current.currentSelectedGameObject : null);
			TMP_InputField val2 = (((Object)(object)val != (Object)null) ? val.GetComponentInParent<TMP_InputField>() : null);
			if ((Object)(object)val2 != (Object)null)
			{
				return val2.isFocused;
			}
			return false;
		}

		private void LogUnavailableState(GameManager manager, PlayerInputActions actions)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			if (!(Time.unscaledTime < nextUnavailableLogTime))
			{
				nextUnavailableLogTime = Time.unscaledTime + 2f;
				ManualLogSource obj = log;
				string[] obj2 = new string[19]
				{
					"Chat input capture ignored: GameState=",
					((object)manager.gameState/*cast due to .constrained prefix*/).ToString(),
					" Locks=",
					((object)manager.currentLocks/*cast due to .constrained prefix*/).ToString(),
					" OnFoot=",
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null,
					null
				};
				OnFootActions onFoot = actions.OnFoot;
				obj2[5] = ((OnFootActions)(ref onFoot)).enabled.ToString();
				obj2[6] = " FKeys=";
				FKeysActions fKeys = actions.FKeys;
				obj2[7] = ((FKeysActions)(ref fKeys)).enabled.ToString();
				obj2[8] = " UI=";
				UIActions uI = actions.UI;
				obj2[9] = ((UIActions)(ref uI)).enabled.ToString();
				obj2[10] = " Inventory=";
				InventoryActions inventory = actions.Inventory;
				obj2[11] = ((InventoryActions)(ref inventory)).enabled.ToString();
				obj2[12] = " Cinematic=";
				CinematicActions cinematic = actions.Cinematic;
				obj2[13] = ((CinematicActions)(ref cinematic)).enabled.ToString();
				obj2[14] = " Loading=";
				LoadingActions loading = actions.Loading;
				obj2[15] = ((LoadingActions)(ref loading)).enabled.ToString();
				obj2[16] = " DevTools=";
				DevToolsActions devTools = actions.DevTools;
				obj2[17] = ((DevToolsActions)(ref devTools)).enabled.ToString();
				obj2[18] = ".";
				obj.LogDebug((object)string.Concat(obj2));
			}
		}

		private void LogUnavailable(string message)
		{
			if (!(Time.unscaledTime < nextUnavailableLogTime))
			{
				nextUnavailableLogTime = Time.unscaledTime + 2f;
				log.LogDebug((object)message);
			}
		}
	}
	internal sealed class ModernTextInputBridge : IDisposable
	{
		private const float BackspaceInitialDelay = 0.45f;

		private const float BackspaceRepeatInterval = 0.05f;

		private readonly ManualLogSource log;

		private readonly StringBuilder pendingCharacters = new StringBuilder();

		private TMP_InputField? input;

		private Keyboard? keyboard;

		private bool active;

		private bool composing;

		private int compositionInteractionFrame = -1;

		private bool pendingText;

		private TextEditState pendingTextBaseline;

		private bool pendingSpace;

		private TextEditState pendingSpaceBaseline;

		private bool pendingBackspace;

		private bool pendingBackspaceIsInitial;

		private TextEditState pendingBackspaceBaseline;

		private bool backspaceHeld;

		private bool backspaceRepeatStarted;

		private float nextBackspaceRepeat;

		private TextEditState lastLateState;

		private bool hasLastLateState;

		private int textFallbackCount;

		private int spaceFallbackCount;

		private int backspaceFallbackCount;

		private int lastSpacePressFrame = -1;

		private int lastBackspacePressFrame = -1;

		private bool candidatePositionFailureLogged;

		public bool ShouldConsumeCommandThisFrame
		{
			get
			{
				if (active)
				{
					if (!composing)
					{
						return compositionInteractionFrame == Time.frameCount;
					}
					return true;
				}
				return false;
			}
		}

		public ModernTextInputBridge(ManualLogSource log)
		{
			this.log = log;
		}

		public void Begin(TMP_InputField field)
		{
			input = field;
			active = true;
			composing = false;
			compositionInteractionFrame = -1;
			candidatePositionFailureLogged = false;
			lastSpacePressFrame = -1;
			ClearPendingEdits();
			BindKeyboard(Keyboard.current);
			RefreshFocus();
			InputSystem.onAfterUpdate -= OnAfterInputUpdate;
			InputSystem.onAfterUpdate += OnAfterInputUpdate;
			lastLateState = Snapshot();
			hasLastLateState = true;
		}

		public void RefreshFocus()
		{
			if (!active)
			{
				return;
			}
			BindKeyboard(Keyboard.current);
			try
			{
				Keyboard? obj = keyboard;
				if (obj != null)
				{
					obj.SetIMEEnabled(true);
				}
			}
			catch (Exception ex)
			{
				log.LogDebug((object)("Could not refresh IME state: " + ex.Message));
			}
		}

		public void LateTick(float now)
		{
			if (active && !((Object)(object)input == (Object)null))
			{
				if (keyboard != Keyboard.current)
				{
					BindKeyboard(Keyboard.current);
				}
				ResolvePendingText();
				ResolvePendingSpace();
				ResolvePendingBackspace();
				TextEditState textEditState = Snapshot();
				if (backspaceHeld && backspaceRepeatStarted && hasLastLateState && textEditState != lastLateState)
				{
					nextBackspaceRepeat = now + 0.05f;
				}
				lastLateState = textEditState;
				hasLastLateState = true;
				UpdateCandidatePosition();
			}
		}

		public void End()
		{
			if (active || keyboard != null)
			{
				active = false;
				InputSystem.onAfterUpdate -= OnAfterInputUpdate;
				UnbindKeyboard();
				input = null;
				composing = false;
				compositionInteractionFrame = -1;
				backspaceHeld = false;
				backspaceRepeatStarted = false;
				hasLastLateState = false;
				ClearPendingEdits();
			}
		}

		public void Dispose()
		{
			End();
		}

		private void BindKeyboard(Keyboard? next)
		{
			if (keyboard == next)
			{
				return;
			}
			UnbindKeyboard();
			keyboard = next;
			if (keyboard == null || !active)
			{
				return;
			}
			keyboard.onTextInput += OnTextInput;
			keyboard.onIMECompositionChange += OnImeCompositionChange;
			try
			{
				keyboard.SetIMEEnabled(true);
				log.LogDebug((object)("Modern text input attached. Layout=" + keyboard.keyboardLayout + " IMESelected=" + keyboard.imeSelected.isPressed + "."));
			}
			catch (Exception ex)
			{
				log.LogDebug((object)("Could not enable modern IME input: " + ex.Message));
			}
		}

		private void UnbindKeyboard()
		{
			Keyboard val = keyboard;
			keyboard = null;
			if (val == null)
			{
				return;
			}
			val.onTextInput -= OnTextInput;
			val.onIMECompositionChange -= OnImeCompositionChange;
			try
			{
				val.SetIMEEnabled(false);
			}
			catch (Exception ex)
			{
				log.LogDebug((object)("Could not disable modern IME input: " + ex.Message));
			}
		}

		private void OnTextInput(char character)
		{
			if (CanEdit() && !char.IsControl(character))
			{
				if (!pendingText)
				{
					pendingTextBaseline = Snapshot();
					pendingText = true;
				}
				pendingCharacters.Append(character);
			}
		}

		private unsafe void OnImeCompositionChange(IMECompositionString composition)
		{
			if (active)
			{
				bool num = composing;
				composing = ((object)(*(IMECompositionString*)(&composition))/*cast due to .constrained prefix*/).ToString().Length != 0;
				if (num || composing)
				{
					compositionInteractionFrame = Time.frameCount;
				}
			}
		}

		private void OnAfterInputUpdate()
		{
			if (!CanEdit())
			{
				return;
			}
			Keyboard val = keyboard;
			if (val == null)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			bool wasPressedThisFrame = ((ButtonControl)val.backspaceKey).wasPressedThisFrame;
			bool wasReleasedThisFrame = ((ButtonControl)val.backspaceKey).wasReleasedThisFrame;
			if (composing || compositionInteractionFrame == Time.frameCount)
			{
				pendingSpace = false;
				if (wasReleasedThisFrame)
				{
					backspaceHeld = false;
					backspaceRepeatStarted = false;
					pendingBackspace = false;
				}
				return;
			}
			if (((ButtonControl)val.spaceKey).wasPressedThisFrame && lastSpacePressFrame != Time.frameCount)
			{
				lastSpacePressFrame = Time.frameCount;
				pendingSpaceBaseline = Snapshot();
				pendingSpace = true;
			}
			if (wasPressedThisFrame)
			{
				if (lastBackspacePressFrame != Time.frameCount)
				{
					lastBackspacePressFrame = Time.frameCount;
					pendingBackspaceBaseline = Snapshot();
					pendingBackspace = true;
					pendingBackspaceIsInitial = true;
					backspaceHeld = !wasReleasedThisFrame;
					backspaceRepeatStarted = false;
					nextBackspaceRepeat = unscaledTime + 0.45f;
				}
				else if (wasReleasedThisFrame)
				{
					backspaceHeld = false;
				}
			}
			else if (wasReleasedThisFrame)
			{
				backspaceHeld = false;
				backspaceRepeatStarted = false;
				pendingBackspace = false;
			}
			else if (backspaceHeld && ((ButtonControl)val.backspaceKey).isPressed && !pendingBackspace && unscaledTime >= nextBackspaceRepeat)
			{
				pendingBackspaceBaseline = Snapshot();
				pendingBackspace = true;
				pendingBackspaceIsInitial = false;
				backspaceRepeatStarted = true;
				nextBackspaceRepeat = unscaledTime + 0.05f;
			}
		}

		private void ResolvePendingText()
		{
			if (!pendingText || (Object)(object)input == (Object)null)
			{
				return;
			}
			string value = pendingCharacters.ToString();
			TextEditState textEditState = Snapshot();
			TextEditState textEditState2 = TextEdit.InsertIfUnchanged(pendingTextBaseline, textEditState, value, input.characterLimit);
			if (textEditState2 != textEditState)
			{
				Apply(textEditState2);
				textFallbackCount++;
				if (textFallbackCount == 1)
				{
					log.LogDebug((object)"Modern text-input fallback activated.");
				}
			}
			pendingText = false;
			pendingCharacters.Clear();
		}

		private void ResolvePendingSpace()
		{
			if (!pendingSpace || (Object)(object)input == (Object)null)
			{
				return;
			}
			if (composing || compositionInteractionFrame == Time.frameCount)
			{
				pendingSpace = false;
				return;
			}
			TextEditState textEditState = Snapshot();
			TextEditState textEditState2 = TextEdit.InsertIfUnchanged(pendingSpaceBaseline, textEditState, " ", input.characterLimit);
			if (textEditState2 != textEditState)
			{
				Apply(textEditState2);
				spaceFallbackCount++;
				if (spaceFallbackCount == 1)
				{
					log.LogDebug((object)"Modern Space fallback activated.");
				}
			}
			pendingSpace = false;
		}

		private void ResolvePendingBackspace()
		{
			if (!pendingBackspace)
			{
				return;
			}
			TextEditState textEditState = TextEdit.DeleteBackward(pendingBackspaceBaseline);
			if (Snapshot() == pendingBackspaceBaseline && textEditState != pendingBackspaceBaseline)
			{
				Apply(textEditState);
				backspaceFallbackCount++;
				if (backspaceFallbackCount == 1)
				{
					log.LogDebug((object)"Modern Backspace fallback activated.");
				}
			}
			if (!pendingBackspaceIsInitial)
			{
				backspaceRepeatStarted = true;
			}
			pendingBackspace = false;
		}

		private bool CanEdit()
		{
			if (active && (Object)(object)input != (Object)null)
			{
				return input.isFocused;
			}
			return false;
		}

		private TextEditState Snapshot()
		{
			TMP_InputField val = input;
			if ((Object)(object)val == (Object)null)
			{
				return new TextEditState("", 0, 0);
			}
			return new TextEditState(val.text, val.selectionStringAnchorPosition, val.selectionStringFocusPosition);
		}

		private void Apply(TextEditState state)
		{
			TMP_InputField val = input;
			if (!((Object)(object)val == (Object)null))
			{
				val.SetTextWithoutNotify(state.Text);
				val.selectionStringAnchorPosition = state.Anchor;
				val.selectionStringFocusPosition = state.Focus;
				val.ForceLabelUpdate();
				((UnityEvent<string>)(object)val.onValueChanged).Invoke(state.Text);
			}
		}

		private void UpdateCandidatePosition()
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			TMP_InputField val = input;
			Keyboard val2 = keyboard;
			if ((Object)(object)val == (Object)null || val2 == null || !val.isFocused)
			{
				return;
			}
			try
			{
				TMP_Text textComponent = val.textComponent;
				TextMeshProUGUI val3 = (TextMeshProUGUI)(object)((textComponent is TextMeshProUGUI) ? textComponent : null);
				Vector2 val4;
				if ((Object)(object)val3 != (Object)null && TryGetCaretScreenPosition(val, val3, out var screenPosition))
				{
					val4 = screenPosition;
				}
				else
				{
					Vector3[] array = (Vector3[])(object)new Vector3[4];
					val.textViewport.GetWorldCorners(array);
					val4 = RectTransformUtility.WorldToScreenPoint((Camera)null, array[0]);
				}
				val2.SetIMECursorPosition(new Vector2(val4.x, (float)Screen.height - val4.y));
			}
			catch (Exception ex)
			{
				if (!candidatePositionFailureLogged)
				{
					candidatePositionFailureLogged = true;
					log.LogDebug((object)("Could not update IME candidate position: " + ex.Message));
				}
			}
		}

		private static bool TryGetCaretScreenPosition(TMP_InputField field, TextMeshProUGUI text, out Vector2 screenPosition)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: 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_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			field.ForceLabelUpdate();
			((TMP_Text)text).ForceMeshUpdate(false, false);
			TMP_TextInfo textInfo = ((TMP_Text)text).textInfo;
			int characterCount = textInfo.characterCount;
			if (characterCount == 0)
			{
				Vector3[] array = (Vector3[])(object)new Vector3[4];
				field.textViewport.GetWorldCorners(array);
				screenPosition = RectTransformUtility.WorldToScreenPoint((Camera)null, array[0]);
				return true;
			}
			int selectionStringFocusPosition = field.selectionStringFocusPosition;
			float num = textInfo.characterInfo[0].origin;
			float baseLine = textInfo.characterInfo[0].baseLine;
			for (int i = 0; i < characterCount; i++)
			{
				TMP_CharacterInfo val = textInfo.characterInfo[i];
				if (val.index >= selectionStringFocusPosition)
				{
					num = val.origin;
					baseLine = val.baseLine;
					break;
				}
				num = val.xAdvance;
				baseLine = val.baseLine;
			}
			Vector3 val2 = ((Transform)((TMP_Text)text).rectTransform).TransformPoint(new Vector3(num, baseLine, 0f));
			screenPosition = RectTransformUtility.WorldToScreenPoint((Camera)null, val2);
			return true;
		}

		private void ClearPendingEdits()
		{
			pendingText = false;
			pendingCharacters.Clear();
			pendingSpace = false;
			pendingBackspace = false;
			pendingBackspaceIsInitial = false;
		}
	}
	internal readonly struct TextEditState : IEquatable<TextEditState>
	{
		public string Text { get; }

		public int Anchor { get; }

		public int Focus { get; }

		public TextEditState(string text, int anchor, int focus)
		{
			Text = text ?? "";
			Anchor = Clamp(anchor, 0, Text.Length);
			Focus = Clamp(focus, 0, Text.Length);
		}

		public bool Equals(TextEditState other)
		{
			if (string.Equals(Text, other.Text, StringComparison.Ordinal) && Anchor == other.Anchor)
			{
				return Focus == other.Focus;
			}
			return false;
		}

		public override bool Equals(object? obj)
		{
			if (obj is TextEditState other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (((StringComparer.Ordinal.GetHashCode(Text ?? "") * 397) ^ Anchor) * 397) ^ Focus;
		}

		public static bool operator ==(TextEditState left, TextEditState right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(TextEditState left, TextEditState right)
		{
			return !left.Equals(right);
		}

		private static int Clamp(int value, int minimum, int maximum)
		{
			if (value < minimum)
			{
				return minimum;
			}
			if (value <= maximum)
			{
				return value;
			}
			return maximum;
		}
	}
	internal static class TextEdit
	{
		public static TextEditState InsertIfUnchanged(TextEditState baseline, TextEditState current, string value, int characterLimit)
		{
			if (!(current == baseline))
			{
				return current;
			}
			return Insert(baseline, value, characterLimit);
		}

		public static TextEditState Insert(TextEditState state, string value, int characterLimit)
		{
			if (string.IsNullOrEmpty(value))
			{
				return state;
			}
			int num = Math.Min(state.Anchor, state.Focus);
			int num2 = Math.Max(state.Anchor, state.Focus);
			int num3 = state.Text.Length - (num2 - num);
			int maximumLength = ((characterLimit <= 0) ? int.MaxValue : Math.Max(0, characterLimit - num3));
			string text = TruncateUtf16(value, maximumLength);
			if (text.Length == 0 && num == num2)
			{
				return state;
			}
			string text2 = state.Text.Remove(num, num2 - num).Insert(num, text);
			int num4 = num + text.Length;
			return new TextEditState(text2, num4, num4);
		}

		public static TextEditState DeleteBackward(TextEditState state)
		{
			int num = Math.Min(state.Anchor, state.Focus);
			int num2 = Math.Max(state.Anchor, state.Focus);
			if (num != num2)
			{
				return new TextEditState(state.Text.Remove(num, num2 - num), num, num);
			}
			int focus = state.Focus;
			if (focus <= 0 || state.Text.Length == 0)
			{
				return state;
			}
			int num3 = PreviousTextElementStart(state.Text, focus);
			return new TextEditState(state.Text.Remove(num3, focus - num3), num3, num3);
		}

		private static int PreviousTextElementStart(string text, int caret)
		{
			int[] array = StringInfo.ParseCombiningCharacters(text);
			int result = 0;
			for (int i = 0; i < array.Length && array[i] < caret; i++)
			{
				result = array[i];
			}
			return result;
		}

		private static string TruncateUtf16(string value, int maximumLength)
		{
			if (maximumLength >= value.Length)
			{
				return value;
			}
			if (maximumLength <= 0)
			{
				return "";
			}
			int num = maximumLength;
			if (num < value.Length && num > 0 && char.IsHighSurrogate(value[num - 1]) && char.IsLowSurrogate(value[num]))
			{
				num--;
			}
			return value.Substring(0, num);
		}
	}
}
namespace de.nemophila.sulfur.simplechat.Networking
{
	internal sealed class ChatController : IDisposable
	{
		private sealed class RateBucket
		{
			public float Tokens;

			public float LastRefill;
		}

		private const float MembershipPollInterval = 0.5f;

		private const int HelloAttemptLimit = 5;

		private const float HelloRetryInterval = 1f;

		private const float RateLimitBurst = 5f;

		private const float RateLimitRefillPerSecond = 1f;

		private readonly ManualLogSource log;

		private readonly ChatHud hud;

		private readonly TogetherNameResolver nameResolver;

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

		private readonly Dictionary<string, RateBucket> rateBuckets = new Dictionary<string, RateBucket>(StringComparer.Ordinal);

		private IExternalChannelRegistration? registration;

		private SessionRole role;

		private bool sessionActive;

		private string localPeerId = "";

		private ulong sequence;

		private ulong lastReceivedSequence;

		private bool welcomed;

		private bool handshakeFailed;

		private int helloAttempts;

		private float nextHelloTime;

		private float nextMembershipPoll;

		private bool transportReady;

		private bool unavailableNoticeShown;

		public ChatController(ManualLogSource log, ChatHud hud)
		{
			this.log = log;
			this.hud = hud;
			nameResolver = new TogetherNameResolver(log);
			int num = ReadExternalChannelApiVersion();
			if (num != 1)
			{
				log.LogError((object)("Unsupported SULFUR Together external-channel API version: " + num));
				return;
			}
			try
			{
				registration = NetExternalChannel.Register("de.nemophila.sulfur.simplechat.v1", (Action<string, byte[]>)OnReceive);
				transportReady = true;
			}
			catch (Exception ex)
			{
				log.LogError((object)("Failed to register the SimpleChat network channel: " + ex));
			}
		}

		public void Tick(float now)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Invalid comparison between Unknown and I4
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Invalid comparison between Unknown and I4
			bool flag = transportReady && NetSessionInfo.IsSessionActive;
			SessionRole val = (SessionRole)(flag ? ((int)NetSessionInfo.Role) : 0);
			string text = (flag ? NetSessionInfo.LocalPeerId : "");
			if (flag != sessionActive || val != role || text != localPeerId)
			{
				BeginSession(flag, val, text, now);
			}
			if (sessionActive)
			{
				if ((int)role == 1 && now >= nextMembershipPoll)
				{
					nextMembershipPoll = now + 0.5f;
					PollHostMembership();
				}
				else if ((int)role == 2)
				{
					TickClientHandshake(now);
				}
			}
		}

		public bool CanOpen()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			if (!transportReady || !sessionActive)
			{
				return false;
			}
			if ((int)role != 1)
			{
				if ((int)role == 2)
				{
					return welcomed;
				}
				return false;
			}
			return true;
		}

		public bool CanAttemptOpen()
		{
			if (handshakeFailed)
			{
				return false;
			}
			if (!transportReady || !sessionActive)
			{
				return !unavailableNoticeShown;
			}
			return true;
		}

		public void NotifyOpenDenied()
		{
			if (!transportReady)
			{
				if (!unavailableNoticeShown)
				{
					unavailableNoticeShown = true;
					hud.AddSystem("system.transport_unavailable", "SimpleChat could not connect to SULFUR Together.");
				}
			}
			else if (!sessionActive)
			{
				if (!unavailableNoticeShown)
				{
					unavailableNoticeShown = true;
					hud.AddSystem("system.offline", "Chat is available in an active SULFUR Together session.");
				}
			}
			else if (!handshakeFailed)
			{
				hud.AddSystem("system.connecting", "SimpleChat is connecting to the host...");
			}
		}

		public bool SubmitLocal(string input)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Invalid comparison between Unknown and I4
			string normalized;
			ChatErrorCode chatErrorCode = ChatTextPolicy.Validate(input, out normalized);
			if (chatErrorCode != ChatErrorCode.None)
			{
				ShowError(chatErrorCode);
				return false;
			}
			if (!CanOpen())
			{
				NotifyOpenDenied();
				return false;
			}
			if ((int)role == 1)
			{
				return AcceptHostSubmission((localPeerId.Length == 0) ? "host" : localPeerId, normalized, sendRemoteError: false);
			}
			if (!Send(ChatPacket.Submit(normalized), (ExternalTarget)0))
			{
				hud.AddSystem("system.send_failed", "The message could not be sent.");
				return false;
			}
			return true;
		}

		public void Dispose()
		{
			((IDisposable)registration)?.Dispose();
			registration = null;
			transportReady = false;
		}

		private void BeginSession(bool active, SessionRole newRole, string newLocalPeerId, float now)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Invalid comparison between Unknown and I4
			sessionActive = active;
			role = newRole;
			localPeerId = newLocalPeerId ?? "";
			sequence = 0uL;
			lastReceivedSequence = 0uL;
			welcomed = false;
			handshakeFailed = false;
			helloAttempts = 0;
			nextHelloTime = now;
			nextMembershipPoll = now;
			unavailableNoticeShown = false;
			hostMembers.Clear();
			rateBuckets.Clear();
			hud.Clear();
			if (sessionActive && (int)role == 1)
			{
				SeedHostMembership();
			}
		}

		private void TickClientHandshake(float now)
		{
			if (!welcomed && !handshakeFailed && !(now < nextHelloTime))
			{
				if (helloAttempts >= 5)
				{
					handshakeFailed = true;
					hud.AddSystem("system.handshake_failed", "No response was received from the host's SimpleChat.");
				}
				else
				{
					Send(ChatPacket.Hello("0.1.7"), (ExternalTarget)0);
					helloAttempts++;
					nextHelloTime = now + 1f;
				}
			}
		}

		private void OnReceive(string senderPeerId, byte[] payload)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Invalid comparison between Unknown and I4
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Invalid comparison between Unknown and I4
			if (!sessionActive)
			{
				return;
			}
			if (!ChatProtocol.TryDecode(payload, out ChatPacket packet, out ChatDecodeError error) || packet == null)
			{
				if (error == ChatDecodeError.UnsupportedVersion)
				{
					if ((int)role == 1 && IsConnectedMember(senderPeerId))
					{
						SendError(senderPeerId, ChatErrorCode.ProtocolMismatch);
					}
					else if ((int)role == 2 && IsTrustedHost(senderPeerId))
					{
						handshakeFailed = true;
						ShowError(ChatErrorCode.ProtocolMismatch);
					}
				}
			}
			else if ((int)role == 1)
			{
				HandleHostPacket(senderPeerId, packet);
			}
			else if ((int)role == 2 && IsTrustedHost(senderPeerId))
			{
				HandleClientPacket(packet);
			}
		}

		private void HandleHostPacket(string senderPeerId, ChatPacket packet)
		{
			if (!IsConnectedMember(senderPeerId))
			{
				SendError(senderPeerId, ChatErrorCode.NotSessionMember);
				return;
			}
			switch (packet.Kind)
			{
			case ChatPacketKind.Hello:
				Send(ChatPacket.Welcome(sequence), (ExternalTarget)2, senderPeerId);
				break;
			case ChatPacketKind.Submit:
				AcceptHostSubmission(senderPeerId, packet.Text, sendRemoteError: true);
				break;
			}
		}

		private void HandleClientPacket(ChatPacket packet)
		{
			switch (packet.Kind)
			{
			case ChatPacketKind.Welcome:
				lastReceivedSequence = packet.Sequence;
				welcomed = true;
				handshakeFailed = false;
				break;
			case ChatPacketKind.Chat:
				if (welcomed && AcceptSequence(packet.Sequence))
				{
					hud.AddChat(ChatTextPolicy.CleanDisplayName(packet.DisplayName, packet.PeerId), packet.Text);
				}
				break;
			case ChatPacketKind.Presence:
				if (welcomed && AcceptSequence(packet.Sequence))
				{
					string text = ChatTextPolicy.CleanDisplayName(packet.DisplayName, packet.PeerId);
					if (packet.Joined)
					{
						hud.AddSystem("system.joined", "{0} joined the session.", text);
					}
					else
					{
						hud.AddSystem("system.left", "{0} left the session.", text);
					}
				}
				break;
			case ChatPacketKind.Error:
				if (packet.ErrorCode == ChatErrorCode.ProtocolMismatch)
				{
					handshakeFailed = true;
				}
				ShowError(packet.ErrorCode);
				break;
			case ChatPacketKind.Submit:
				break;
			}
		}

		private bool AcceptHostSubmission(string senderPeerId, string input, bool sendRemoteError)
		{
			string normalized;
			ChatErrorCode chatErrorCode = ChatTextPolicy.Validate(input, out normalized);
			if (chatErrorCode != ChatErrorCode.None)
			{
				RejectSubmission(senderPeerId, chatErrorCode, sendRemoteError);
				return false;
			}
			if (!TryConsumeRateToken(senderPeerId, Time.unscaledTime))
			{
				RejectSubmission(senderPeerId, ChatErrorCode.RateLimited, sendRemoteError);
				return false;
			}
			string displayName = nameResolver.Resolve(senderPeerId);
			ChatPacket packet = ChatPacket.Chat(NextSequence(), senderPeerId, displayName, normalized);
			Send(packet, (ExternalTarget)1);
			hud.AddChat(displayName, normalized);
			return true;
		}

		private void RejectSubmission(string peerId, ChatErrorCode errorCode, bool remote)
		{
			if (remote)
			{
				SendError(peerId, errorCode);
			}
			else
			{
				ShowError(errorCode);
			}
		}

		private void SeedHostMembership()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, string> connectedNames = nameResolver.GetConnectedNames();
			foreach (ExternalPeer peer in NetSessionInfo.Peers)
			{
				ExternalPeer current = peer;
				string peerId = ((ExternalPeer)(ref current)).PeerId;
				hostMembers[peerId] = (connectedNames.TryGetValue(peerId, out var value) ? value : ChatTextPolicy.CleanDisplayName(null, peerId));
			}
		}

		private void PollHostMembership()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, string> connectedNames = nameResolver.GetConnectedNames();
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			foreach (ExternalPeer peer in NetSessionInfo.Peers)
			{
				ExternalPeer current = peer;
				string peerId = ((ExternalPeer)(ref current)).PeerId;
				dictionary[peerId] = (connectedNames.TryGetValue(peerId, out var value) ? value : ChatTextPolicy.CleanDisplayName(null, peerId));
			}
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (!hostMembers.ContainsKey(item.Key))
				{
					PublishPresence(joined: true, item.Key, item.Value);
				}
			}
			foreach (KeyValuePair<string, string> hostMember in hostMembers)
			{
				if (!dictionary.ContainsKey(hostMember.Key))
				{
					PublishPresence(joined: false, hostMember.Key, hostMember.Value);
					rateBuckets.Remove(hostMember.Key);
				}
			}
			hostMembers.Clear();
			foreach (KeyValuePair<string, string> item2 in dictionary)
			{
				hostMembers[item2.Key] = item2.Value;
			}
		}

		private void PublishPresence(bool joined, string peerId, string displayName)
		{
			ulong num = NextSequence();
			Send(ChatPacket.Presence(num, joined, peerId, displayName), (ExternalTarget)1);
			if (joined)
			{
				hud.AddSystem("system.joined", "{0} joined the session.", displayName);
			}
			else
			{
				hud.AddSystem("system.left", "{0} left the session.", displayName);
			}
		}

		private bool AcceptSequence(ulong candidate)
		{
			if (candidate <= lastReceivedSequence)
			{
				return false;
			}
			lastReceivedSequence = candidate;
			return true;
		}

		private ulong NextSequence()
		{
			sequence = ((sequence == ulong.MaxValue) ? 1 : (sequence + 1));
			return sequence;
		}

		private bool TryConsumeRateToken(string peerId, float now)
		{
			if (!rateBuckets.TryGetValue(peerId, out RateBucket value))
			{
				value = new RateBucket
				{
					Tokens = 5f,
					LastRefill = now
				};
				rateBuckets[peerId] = value;
			}
			float num = Mathf.Max(0f, now - value.LastRefill);
			value.Tokens = Mathf.Min(5f, value.Tokens + num * 1f);
			value.LastRefill = now;
			if (value.Tokens < 1f)
			{
				return false;
			}
			value.Tokens -= 1f;
			return true;
		}

		private bool IsConnectedMember(string peerId)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			foreach (ExternalPeer peer in NetSessionInfo.Peers)
			{
				ExternalPeer current = peer;
				if (string.Equals(((ExternalPeer)(ref current)).PeerId, peerId, StringComparison.Ordinal))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsTrustedHost(string senderPeerId)
		{
			return string.Equals(senderPeerId, "host", StringComparison.Ordinal);
		}

		private void SendError(string peerId, ChatErrorCode errorCode)
		{
			Send(ChatPacket.Error(errorCode), (ExternalTarget)2, peerId);
		}

		private bool Send(ChatPacket packet, ExternalTarget target, string? targetPeerId = null)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				byte[] array = ChatProtocol.Encode(packet);
				return NetExternalChannel.Send("de.nemophila.sulfur.simplechat.v1", array, (ExternalDelivery)0, target, targetPeerId);
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("SimpleChat send failed: " + ex.Message));
				return false;
			}
		}

		private void ShowError(ChatErrorCode errorCode)
		{
			switch (errorCode)
			{
			case ChatErrorCode.Empty:
				hud.AddSystem("error.empty", "Enter a message first.");
				break;
			case ChatErrorCode.TooLong:
				hud.AddSystem("error.too_long", "The message is too long (maximum 256 characters).");
				break;
			case ChatErrorCode.InvalidCharacters:
				hud.AddSystem("error.invalid_characters", "The message contains unsupported control characters.");
				break;
			case ChatErrorCode.RateLimited:
				hud.AddSystem("error.rate_limited", "You are sending messages too quickly.");
				break;
			case ChatErrorCode.NotSessionMember:
				hud.AddSystem("error.not_member", "The host no longer recognizes this session member.");
				break;
			case ChatErrorCode.ProtocolMismatch:
				hud.AddSystem("error.protocol", "The host is running an incompatible SimpleChat protocol.");
				break;
			default:
				hud.AddSystem("error.unknown", "The message was rejected by the host.");
				break;
			}
		}

		private static int ReadExternalChannelApiVersion()
		{
			object obj = typeof(NetExternalChannel).GetField("ApiVersion", BindingFlags.Static | BindingFlags.Public)?.GetRawConstantValue();
			if (obj is int)
			{
				return (int)obj;
			}
			return -1;
		}
	}
	internal enum ChatPacketKind : byte
	{
		Hello = 1,
		Welcome,
		Submit,
		Chat,
		Presence,
		Error
	}
	internal enum ChatErrorCode : byte
	{
		None,
		Empty,
		TooLong,
		InvalidCharacters,
		RateLimited,
		NotSessionMember,
		ProtocolMismatch
	}
	internal enum ChatDecodeError
	{
		None,
		Malformed,
		UnsupportedVersion
	}
	internal sealed class ChatPacket
	{
		public ChatPacketKind Kind { get; private set; }

		public string ModVersion { get; private set; } = "";

		public ulong Sequence { get; private set; }

		public string PeerId { get; private set; } = "";

		public string DisplayName { get; private set; } = "";

		public string Text { get; private set; } = "";

		public bool Joined { get; private set; }

		public ChatErrorCode ErrorCode { get; private set; }

		public static ChatPacket Hello(string modVersion)
		{
			return new ChatPacket
			{
				Kind = ChatPacketKind.Hello,
				ModVersion = (modVersion ?? "")
			};
		}

		public static ChatPacket Welcome(ulong sequence)
		{
			return new ChatPacket
			{
				Kind = ChatPacketKind.Welcome,
				Sequence = sequence
			};
		}

		public static ChatPacket Submit(string text)
		{
			return new ChatPacket
			{
				Kind = ChatPacketKind.Submit,
				Text = (text ?? "")
			};
		}

		public static ChatPacket Chat(ulong sequence, string peerId, string displayName, string text)
		{
			return new ChatPacket
			{
				Kind = ChatPacketKind.Chat,
				Sequence = sequence,
				PeerId = (peerId ?? ""),
				DisplayName = (displayName ?? ""),
				Text = (text ?? "")
			};
		}

		public static ChatPacket Presence(ulong sequence, bool joined, string peerId, string displayName)
		{
			return new ChatPacket
			{
				Kind = ChatPacketKind.Presence,
				Sequence = sequence,
				Joined = joined,
				PeerId = (peerId ?? ""),
				DisplayName = (displayName ?? "")
			};
		}

		public static ChatPacket Error(ChatErrorCode code)
		{
			return new ChatPacket
			{
				Kind = ChatPacketKind.Error,
				ErrorCode = code
			};
		}

		internal static ChatPacket FromWire(ChatPacketKind kind, string modVersion, ulong sequence, string peerId, string displayName, string text, bool joined, ChatErrorCode errorCode)
		{
			return new ChatPacket
			{
				Kind = kind,
				ModVersion = modVersion,
				Sequence = sequence,
				PeerId = peerId,
				DisplayName = displayName,
				Text = text,
				Joined = joined,
				ErrorCode = errorCode
			};
		}
	}
	internal static class ChatProtocol
	{
		private const uint Magic = 1396918356u;

		private const ushort ProtocolVersion = 1;

		public const int MaxPacketBytes = 4096;

		private static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		public static byte[] Encode(ChatPacket packet)
		{
			if (packet == null)
			{
				throw new ArgumentNullException("packet");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Utf8);
			binaryWriter.Write(1396918356u);
			binaryWriter.Write((ushort)1);
			binaryWriter.Write((byte)packet.Kind);
			switch (packet.Kind)
			{
			case ChatPacketKind.Hello:
				binaryWriter.Write(packet.ModVersion);
				break;
			case ChatPacketKind.Welcome:
				binaryWriter.Write(packet.Sequence);
				break;
			case ChatPacketKind.Submit:
				binaryWriter.Write(packet.Text);
				break;
			case ChatPacketKind.Chat:
				binaryWriter.Write(packet.Sequence);
				binaryWriter.Write(packet.PeerId);
				binaryWriter.Write(packet.DisplayName);
				binaryWriter.Write(packet.Text);
				break;
			case ChatPacketKind.Presence:
				binaryWriter.Write(packet.Sequence);
				binaryWriter.Write(packet.Joined);
				binaryWriter.Write(packet.PeerId);
				binaryWriter.Write(packet.DisplayName);
				break;
			case ChatPacketKind.Error:
				binaryWriter.Write((byte)packet.ErrorCode);
				break;
			default:
				throw new InvalidDataException("Unknown chat packet kind: " + packet.Kind);
			}
			binaryWriter.Flush();
			byte[] array = memoryStream.ToArray();
			if (array.Length > 4096)
			{
				throw new InvalidDataException("Chat packet exceeds the size limit.");
			}
			return array;
		}

		public static bool TryDecode(byte[] payload, out ChatPacket? packet, out ChatDecodeError error)
		{
			packet = null;
			error = ChatDecodeError.Malformed;
			if (payload == null || payload.Length < 7 || payload.Length > 4096)
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(payload, writable: false);
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Utf8);
				if (binaryReader.ReadUInt32() != 1396918356)
				{
					return false;
				}
				if (binaryReader.ReadUInt16() != 1)
				{
					error = ChatDecodeError.UnsupportedVersion;
					return false;
				}
				ChatPacketKind chatPacketKind = (ChatPacketKind)binaryReader.ReadByte();
				string modVersion = "";
				ulong sequence = 0uL;
				string peerId = "";
				string displayName = "";
				string text = "";
				bool joined = false;
				ChatErrorCode errorCode = ChatErrorCode.None;
				switch (chatPacketKind)
				{
				case ChatPacketKind.Hello:
					modVersion = ReadLimitedString(binaryReader, 64);
					break;
				case ChatPacketKind.Welcome:
					sequence = binaryReader.ReadUInt64();
					break;
				case ChatPacketKind.Submit:
					text = ReadLimitedString(binaryReader, 2048);
					break;
				case ChatPacketKind.Chat:
					sequence = binaryReader.ReadUInt64();
					peerId = ReadLimitedString(binaryReader, 128);
					displayName = ReadLimitedString(binaryReader, 256);
					text = ReadLimitedString(binaryReader, 2048);
					break;
				case ChatPacketKind.Presence:
					sequence = binaryReader.ReadUInt64();
					joined = binaryReader.ReadBoolean();
					peerId = ReadLimitedString(binaryReader, 128);
					displayName = ReadLimitedString(binaryReader, 256);
					break;
				case ChatPacketKind.Error:
					errorCode = (ChatErrorCode)binaryReader.ReadByte();
					break;
				default:
					return false;
				}
				if (memoryStream.Position != memoryStream.Length)
				{
					return false;
				}
				packet = ChatPacket.FromWire(chatPacketKind, modVersion, sequence, peerId, displayName, text, joined, errorCode);
				error = ChatDecodeError.None;
				return true;
			}
			catch (Exception)
			{
				packet = null;
				error = ChatDecodeError.Malformed;
				return false;
			}
		}

		private static string ReadLimitedString(BinaryReader reader, int maxCharacters)
		{
			string text = reader.ReadString();
			if (text.Length > maxCharacters)
			{
				throw new InvalidDataException("String exceeds the chat protocol limit.");
			}
			return text;
		}
	}
	internal static class ChatTextPolicy
	{
		public const int MaxCharacters = 256;

		public const int MaxUtf8Bytes = 1024;

		private static readonly Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

		public static ChatErrorCode Validate(string? input, out string normalized)
		{
			normalized = (input ?? "").Trim();
			if (normalized.Length == 0)
			{
				return ChatErrorCode.Empty;
			}
			if (normalized.Length > 256)
			{
				return ChatErrorCode.TooLong;
			}
			try
			{
				if (StrictUtf8.GetByteCount(normalized) > 1024)
				{
					return ChatErrorCode.TooLong;
				}
			}
			catch (EncoderFallbackException)
			{
				return ChatErrorCode.InvalidCharacters;
			}
			for (int i = 0; i < normalized.Length; i++)
			{
				char c = normalized[i];
				UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c);
				if (char.IsControl(c) || unicodeCategory == UnicodeCategory.LineSeparator || unicodeCategory == UnicodeCategory.ParagraphSeparator)
				{
					return ChatErrorCode.InvalidCharacters;
				}
			}
			return ChatErrorCode.None;
		}

		public static string CleanDisplayName(string? value, string peerId)
		{
			string text = (string.IsNullOrWhiteSpace(value) ? peerId : value);
			StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 48));
			for (int i = 0; i < text.Length; i++)
			{
				if (stringBuilder.Length >= 48)
				{
					break;
				}
				char c = text[i];
				UnicodeCategory unicodeCategory = char.GetUnicodeCategory(c);
				if (char.IsControl(c) || unicodeCategory == UnicodeCategory.LineSeparator || unicodeCategory == UnicodeCategory.ParagraphSeparator)
				{
					stringBuilder.Append(' ');
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			string text2 = stringBuilder.ToString().Trim();
			if (text2.Length != 0)
			{
				return text2;
			}
			if (!string.IsNullOrWhiteSpace(peerId))
			{
				return peerId;
			}
			return "Unknown";
		}
	}
	internal sealed class TogetherNameResolver
	{
		private readonly ManualLogSource log;

		private readonly PropertyInfo? serviceProperty;

		private Type? cachedServiceType;

		private PropertyInfo? snapshotProperty;

		private Type? cachedPeerType;

		private PropertyInfo? peerIdProperty;

		private PropertyInfo? playerNameProperty;

		private PropertyInfo? isConnectedProperty;

		private bool warned;

		public TogetherNameResolver(ManualLogSource log)
		{
			this.log = log;
			serviceProperty = typeof(NetSessionInfo).Assembly.GetType("SULFURTogether.Networking.CoopConnection", throwOnError: false)?.GetProperty("Service", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
		}

		public Dictionary<string, string> GetConnectedNames()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal);
			try
			{
				object obj = serviceProperty?.GetValue(null, null);
				if (obj == null)
				{
					return dictionary;
				}
				PrepareServiceMetadata(obj.GetType());
				if (!(snapshotProperty?.GetValue(obj, null) is IEnumerable enumerable))
				{
					return dictionary;
				}
				foreach (object item in enumerable)
				{
					if (item == null)
					{
						continue;
					}
					PreparePeerMetadata(item.GetType());
					object obj2 = isConnectedProperty?.GetValue(item, null);
					if (obj2 is bool && (bool)obj2)
					{
						string text = (peerIdProperty?.GetValue(item, null) as string) ?? "";
						if (text.Length != 0)
						{
							string value = playerNameProperty?.GetValue(item, null) as string;
							dictionary[text] = ChatTextPolicy.CleanDisplayName(value, text);
						}
					}
				}
				warned = false;
			}
			catch (Exception ex)
			{
				if (!warned)
				{
					warned = true;
					log.LogWarning((object)("Could not read SULFUR Together player names; peer ids will be shown instead. " + ex.GetType().Name + ": " + ex.Message));
				}
			}
			return dictionary;
		}

		public string Resolve(string peerId)
		{
			if (!GetConnectedNames().TryGetValue(peerId, out string value))
			{
				return ChatTextPolicy.CleanDisplayName(null, peerId);
			}
			return value;
		}

		private void PrepareServiceMetadata(Type serviceType)
		{
			if (!(cachedServiceType == serviceType))
			{
				cachedServiceType = serviceType;
				snapshotProperty = serviceType.GetProperty("SessionSnapshot", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (snapshotProperty == null)
				{
					throw new MissingMemberException(serviceType.FullName, "SessionSnapshot");
				}
			}
		}

		private void PreparePeerMetadata(Type peerType)
		{
			if (!(cachedPeerType == peerType))
			{
				cachedPeerType = peerType;
				peerIdProperty = peerType.GetProperty("PeerId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				playerNameProperty = peerType.GetProperty("PlayerName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				isConnectedProperty = peerType.GetProperty("IsConnected", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (peerIdProperty == null || playerNameProperty == null || isConnectedProperty == null)
				{
					throw new MissingMemberException(peerType.FullName, "PeerId/PlayerName/IsConnected");
				}
			}
		}
	}
}
namespace de.nemophila.sulfur.simplechat.Localization
{
	internal static class Loc
	{
		public static int LanguageVersion => SulfurLocalization.LanguageVersion;

		public static void Initialize(string assemblyLocation)
		{
			SulfurLocalization.LoadPluginLocalization("de.nemophila.sulfur.simplechat", assemblyLocation);
			SulfurLocalization.RefreshCurrentLanguage(true);
		}

		public static string Get(string key, string fallback)
		{
			return SulfurLocalization.Get("de.nemophila.sulfur.simplechat", key, fallback);
		}

		public static string Format(string key, string fallback, params object[] args)
		{
			string format = Get(key, fallback);
			try
			{
				return string.Format(format, args);
			}
			catch (FormatException)
			{
				return string.Format(fallback, args);
			}
		}
	}
}
namespace de.nemophila.sulfur.simplechat.Api
{
	public static class SimpleChatApi
	{
		public const int ApiVersion = 1;

		private static Func<string, bool>? addSystemMessage;

		public static bool TryAddSystemMessage(string message)
		{
			if (ChatTextPolicy.Validate(message, out string normalized) != ChatErrorCode.None)
			{
				return false;
			}
			return addSystemMessage?.Invoke(normalized) ?? false;
		}

		internal static void Bind(Func<string, bool> handler)
		{
			addSystemMessage = handler;
		}

		internal static void Unbind()
		{
			addSystemMessage = null;
		}
	}
}