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;
}
}
}