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? 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("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(); 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()) }; } 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 openKeyProvider; private readonly GameInputCapture inputCapture; private readonly ModernTextInputBridge textInput; private readonly List entries = new List(); 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? CanAttemptOpen { private get; set; } public Func? CanOpen { private get; set; } public Action? OpenDenied { private get; set; } public Func? Submit { private get; set; } public ChatHud(MonoBehaviour runner, ManualLogSource log, Func 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? 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? 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? 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 : ""); 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((FindObjectsSortMode)0); Dictionary dictionary = new Dictionary(); 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 dictionary2 = new Dictionary(); 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 seen = new HashSet(); 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 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(); component.renderMode = (RenderMode)0; component.sortingOrder = 5000; CanvasScaler component2 = canvasObject.GetComponent(); 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(); 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(); 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(); ((Graphic)historyBackground).color = new Color(0.03f, 0.025f, 0.02f, 0.72f); ((Graphic)historyBackground).raycastTarget = false; historyGroup = val.GetComponent(); 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(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = new Vector2(14f, 10f); component2.offsetMax = new Vector2(-14f, -10f); historyText = val2.GetComponent(); ((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(); 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(); 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(); ((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(); 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(); ((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(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; TextMeshProUGUI component2 = val.GetComponent(); ((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.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.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() : 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)(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 { 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 hostMembers = new Dictionary(StringComparer.Ordinal); private readonly Dictionary rateBuckets = new Dictionary(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)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 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 connectedNames = nameResolver.GetConnectedNames(); Dictionary dictionary = new Dictionary(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 item in dictionary) { if (!hostMembers.ContainsKey(item.Key)) { PublishPresence(joined: true, item.Key, item.Value); } } foreach (KeyValuePair hostMember in hostMembers) { if (!dictionary.ContainsKey(hostMember.Key)) { PublishPresence(joined: false, hostMember.Key, hostMember.Value); rateBuckets.Remove(hostMember.Key); } } hostMembers.Clear(); foreach (KeyValuePair 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 GetConnectedNames() { Dictionary dictionary = new Dictionary(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? 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 handler) { addSystemMessage = handler; } internal static void Unbind() { addSystemMessage = null; } } }