Decompiled source of LongChat v1.0.0

BepInEx/plugins/LongChat.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using LongChat.Config;
using LongChat.Diagnostics;
using LongChat.Models;
using LongChat.Patches;
using LongChat.Protocol;
using LongChat.Services;
using Microsoft.CodeAnalysis;
using ProjectM;
using ProjectM.Network;
using ProjectM.UI;
using TMPro;
using Unity.Entities;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LongChat")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("LongChat")]
[assembly: AssemblyTitle("LongChat")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.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 LongChat
{
	internal static class LongChatRuntime
	{
		private static SendQueueService _sendQueue;

		private static AssemblyService _assemblyService;

		private static AssemblyService _overheadAssemblyService;

		private static LongChatConfig _config;

		private static HUDChatWindow? _chatWindow;

		private static bool _incomingReconstructionEnabled;

		private static bool _overheadReconstructionEnabled;

		private static bool _bypassSubmissionRouting;

		private static bool _renderingReconstructedMessage;

		internal static bool IncomingReconstructionEnabled => _incomingReconstructionEnabled;

		internal static int EffectiveFrameByteLimit => TransportLimits.EffectiveFrameByteLimit(_config.FrameByteLimit.Value);

		private static TimeSpan SendDelay => TimeSpan.FromMilliseconds(Math.Max(0, _config.SendDelayMilliseconds.Value));

		private static TimeSpan AcknowledgementTimeout => TimeSpan.FromMilliseconds(Math.Max(250, _config.AcknowledgementTimeoutMilliseconds.Value));

		public static void Initialize(LongChatConfig config)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			_config = config;
			_sendQueue = new SendQueueService();
			_assemblyService = new AssemblyService(Math.Max(1, config.MaximumChunksPerMessage.Value), Math.Max(1, config.MaximumActiveAssemblies.Value), Math.Max(1024, config.MaximumBufferedBytes.Value));
			_overheadAssemblyService = new AssemblyService(Math.Max(1, config.MaximumChunksPerMessage.Value), Math.Max(1, config.MaximumActiveAssemblies.Value), Math.Max(1024, config.MaximumBufferedBytes.Value));
			int num = TransportLimits.EffectiveFrameByteLimit(config.FrameByteLimit.Value);
			if (num != config.FrameByteLimit.Value)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(72, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Configured FrameByteLimit ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(config.FrameByteLimit.Value);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" is outside the supported range; using ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" bytes.");
				}
				log.LogWarning(val);
			}
		}

		public static void ConfigureIncomingReconstruction(bool enabled)
		{
			_incomingReconstructionEnabled = enabled;
			if (!enabled)
			{
				_assemblyService.Clear();
			}
		}

		public static void ConfigureOverheadReconstruction(bool enabled)
		{
			_overheadReconstructionEnabled = enabled;
			if (!enabled)
			{
				_overheadAssemblyService.Clear();
			}
		}

		public static bool IsChannelEnabled(ChatMessageType messageType)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected I4, but got Unknown
			switch ((int)messageType)
			{
			case 0:
				return _config.EnableGlobal.Value;
			case 2:
				return _config.EnableClan.Value;
			case 1:
			case 4:
				return _config.EnableLocal.Value;
			default:
				return false;
			}
		}

		public static bool IsCommand(string text)
		{
			if (_config.DisableSplittingForCommands.Value && text.Length > 0)
			{
				return _config.CommandPrefixes.Value.IndexOf(text[0]) >= 0;
			}
			return false;
		}

		public static bool HandleInputEndEdit(ClientChatSystem chatSystem, string? submittedText)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected I4, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected I4, but got Unknown
			//IL_0064: 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_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			if (_bypassSubmissionRouting)
			{
				return true;
			}
			string text = submittedText ?? string.Empty;
			ChatMessageType defaultMode = chatSystem._DefaultMode;
			DiagnosticLog.Stage("SUBMISSION_ROUTED", $"length={text.Length} displayMode={(int)chatSystem._ChatMode} sendType={(int)defaultMode}");
			if (!_config.Enabled.Value || string.IsNullOrEmpty(text) || !ChatSendTypeSupport.IsSupported(defaultMode) || !IsChannelEnabled(defaultMode) || !TransportLimits.ExceedsFrameLimit(text, _config.FrameByteLimit.Value))
			{
				return true;
			}
			EntityManager entityManager = ((ComponentSystemBase)chatSystem).EntityManager;
			if (IsCommand(text))
			{
				DisplayLocalWarning(entityManager, "LongChat: Commands cannot be split into multiple chat messages.");
				CompleteVanillaSubmission(chatSystem);
				return false;
			}
			if (!TrySendMultipart(chatSystem, text, defaultMode, out string error))
			{
				CompleteVanillaSubmission(chatSystem);
				DisplayLocalWarning(entityManager, error ?? "LongChat: Unable to send the message.");
				RestoreInputText(text);
			}
			return false;
		}

		private static bool TrySendMultipart(ClientChatSystem chatSystem, string text, ChatMessageType messageType, out string? error)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			error = null;
			try
			{
				IReadOnlyList<string> readOnlyList = FrameEncoder.Encode(text, EffectiveFrameByteLimit, Math.Clamp(_config.MaximumChunksPerMessage.Value, 1, 999));
				if (!FrameParser.TryParse(readOnlyList[0], out var frame))
				{
					error = "LongChat: Unable to parse the encoded first frame.";
					return false;
				}
				OutgoingMessage message = new OutgoingMessage(((ComponentSystemBase)chatSystem).EntityManager, messageType, frame.MessageId, text, readOnlyList, delegate(string frame2)
				{
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					SubmitInternalFrame(chatSystem, messageType, frame2);
				});
				DateTime utcNow = DateTime.UtcNow;
				if (!_sendQueue.TryEnqueue(message, Math.Max(1, _config.MaximumQueuedMessages.Value)))
				{
					error = "LongChat: The outgoing message queue is full.";
					return false;
				}
				TickSendQueue(utcNow);
				DiagnosticLog.Stage("MULTIPART_ACCEPTED", $"length={text.Length} frames={readOnlyList.Count} queue={_sendQueue.Count}");
				return true;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(29, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unable to send long message: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
				_sendQueue.Clear();
				error = "LongChat: " + ex.Message;
				return false;
			}
		}

		public static bool HandleFinalFilteredMessage(__c__DisplayClass55_1 callback, string userNameFiltered)
		{
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: 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)
			//IL_015f: Expected I4, but got Unknown
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			__c__DisplayClass55_0 field_Public___c__DisplayClass55_0_ = callback.field_Public___c__DisplayClass55_0_0;
			string text = callback.filteredText ?? string.Empty;
			string userNameFiltered2 = userNameFiltered ?? string.Empty;
			string timeStamp = ((field_Public___c__DisplayClass55_0_ != null) ? field_Public___c__DisplayClass55_0_.timeStamp : null) ?? string.Empty;
			if (!_config.Enabled.Value || !_incomingReconstructionEnabled || _renderingReconstructedMessage || field_Public___c__DisplayClass55_0_ == null || !FrameParser.TryParse(text, out var frame))
			{
				return true;
			}
			DiagnosticLog.Stage("INCOMING_FRAME", $"index={frame.Index} total={frame.Total} payload={frame.Payload.Length} raw={text.Length}");
			if (field_Public___c__DisplayClass55_0_.isSelf && _sendQueue.Acknowledge(frame.MessageId, frame.Index, DateTime.UtcNow, SendDelay))
			{
				DiagnosticLog.Stage("FRAME_ACKNOWLEDGED", $"index={frame.Index} total={frame.Total} queue={_sendQueue.Count}");
			}
			string senderId = $"{field_Public___c__DisplayClass55_0_.fromUser}:{field_Public___c__DisplayClass55_0_.fromCharacterId}";
			AssemblyKey key = new AssemblyKey(senderId, (int)field_Public___c__DisplayClass55_0_.messageType, frame.MessageId);
			IncomingContext context = new IncomingContext(callback, field_Public___c__DisplayClass55_0_.messageType, userNameFiltered2, timeStamp, field_Public___c__DisplayClass55_0_.showTimeStamp, field_Public___c__DisplayClass55_0_.isSelf, field_Public___c__DisplayClass55_0_.isUserAdmin);
			AssemblyAddResult assemblyAddResult = _assemblyService.Add(key, frame, text, context, DateTime.UtcNow);
			switch (assemblyAddResult.Status)
			{
			case AssemblyAddStatus.Complete:
				callback.filteredText = assemblyAddResult.Text;
				return true;
			case AssemblyAddStatus.Pending:
			case AssemblyAddStatus.Duplicate:
				return false;
			case AssemblyAddStatus.Conflict:
				callback.filteredText = string.Join(" ", assemblyAddResult.RawFrames);
				return true;
			default:
				return true;
			}
		}

		public unsafe static bool HandleOverheadChatMessage(NetworkId networkId, ref string text)
		{
			string text2 = text ?? string.Empty;
			if (!_config.Enabled.Value || !_config.EnableOverheadReconstruction.Value || !_overheadReconstructionEnabled || !FrameParser.TryParse(text2, out var frame))
			{
				return true;
			}
			DiagnosticLog.Stage("OVERHEAD_FRAME", $"index={frame.Index} total={frame.Total} payload={frame.Payload.Length} raw={text2.Length}");
			string senderId = ((object)(*(NetworkId*)(&networkId))/*cast due to .constrained prefix*/).ToString();
			AssemblyKey key = new AssemblyKey(senderId, 0, frame.MessageId);
			AssemblyAddResult assemblyAddResult = _overheadAssemblyService.Add(key, frame, text2, null, DateTime.UtcNow);
			switch (assemblyAddResult.Status)
			{
			case AssemblyAddStatus.Complete:
				text = FormatOverheadText(assemblyAddResult.Text);
				DiagnosticLog.Stage("OVERHEAD_COMPLETE", $"length={assemblyAddResult.Text.Length} rendered={text.Length}");
				return true;
			case AssemblyAddStatus.Pending:
			case AssemblyAddStatus.Duplicate:
				return false;
			case AssemblyAddStatus.Conflict:
				text = FormatOverheadText(string.Join(" ", assemblyAddResult.RawFrames));
				DiagnosticLog.Stage("OVERHEAD_CONFLICT", $"frames={assemblyAddResult.RawFrames.Count} rendered={text.Length}");
				return true;
			default:
				return true;
			}
		}

		public static void ObserveChatWindow(HUDChatWindow? chatWindow)
		{
			if (chatWindow != null)
			{
				_chatWindow = chatWindow;
				ApplyInputLimit(chatWindow);
			}
		}

		public static void ApplyInputLimit(HUDChatWindow? chatWindow)
		{
			if (_config.Enabled.Value && ((chatWindow != null) ? chatWindow.ChatInputField : null) != null)
			{
				int num = Math.Max(1, _config.MaxInputCharacters.Value);
				int characterLimit = chatWindow.ChatInputField.characterLimit;
				chatWindow.ChatInputField.characterLimit = num;
				if (characterLimit != num)
				{
					DiagnosticLog.Stage("INPUT_LIMIT_APPLIED", $"before={characterLimit} after={num}");
				}
			}
		}

		public static void Tick()
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			if (!_config.Enabled.Value)
			{
				return;
			}
			if (_chatWindow != null && (Object)(object)_chatWindow == (Object)null)
			{
				_chatWindow = null;
			}
			else if (_chatWindow != null)
			{
				ApplyInputLimit(_chatWindow);
			}
			try
			{
				TickSendQueue(DateTime.UtcNow);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(34, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Sending a multipart frame failed: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
				OutgoingMessage outgoingMessage = _sendQueue.FailActive();
				if ((object)outgoingMessage != null)
				{
					HandleTransportFailure(outgoingMessage, "LongChat: Multipart transport failed; the original text was restored.");
				}
			}
			if (IncomingReconstructionEnabled)
			{
				foreach (ExpiredAssembly item in _assemblyService.Expire(DateTime.UtcNow, TimeSpan.FromMilliseconds(Math.Max(250, _config.AssemblyTimeoutMilliseconds.Value))))
				{
					if (item.Context is IncomingContext context)
					{
						RenderThroughFinalCallback(context, "[Incomplete multipart message] " + item.PartialText);
					}
				}
			}
			if (!_overheadReconstructionEnabled)
			{
				return;
			}
			foreach (ExpiredAssembly item2 in _overheadAssemblyService.Expire(DateTime.UtcNow, TimeSpan.FromMilliseconds(Math.Max(250, _config.AssemblyTimeoutMilliseconds.Value))))
			{
				DiagnosticLog.Stage("OVERHEAD_EXPIRED", $"partial={item2.PartialText.Length}");
			}
		}

		public static void DisplayLocalWarning(EntityManager entityManager, string warning)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			DisplayLocalMessage(entityManager, warning, (ServerChatMessageType)6);
		}

		private static void DisplayLocalMessage(EntityManager entityManager, string text, ServerChatMessageType messageType)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			try
			{
				ClientSystemChatUtils.AddLocalMessage(entityManager, text, messageType);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(33, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unable to display local warning: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
			}
		}

		public static void ClearSession(string? reason = null)
		{
			if (_sendQueue.Count > 0)
			{
				DiagnosticLog.Stage("QUEUE_CLEARED", string.Format("count={0} reason={1}", _sendQueue.Count, reason ?? "session reset"));
			}
			_sendQueue.Clear();
			_assemblyService.Clear();
			_overheadAssemblyService.Clear();
		}

		private static void SendFrame(OutgoingMessage message, string frame)
		{
			message.SubmitFrame(frame);
		}

		private static void SubmitInternalFrame(ClientChatSystem chatSystem, ChatMessageType messageType, string frame)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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_0017: 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_0061: Expected I4, but got Unknown
			FrameParser.TryParse(frame, out var frame2);
			ChatMessageType defaultMode = chatSystem._DefaultMode;
			try
			{
				_bypassSubmissionRouting = true;
				chatSystem._DefaultMode = messageType;
				DiagnosticLog.Stage("INTERNAL_FRAME_SUBMIT", $"frame={frame2.Index}/{frame2.Total} length={frame.Length} sendType={(int)messageType} queue={_sendQueue.Count}");
				chatSystem._OnInputEndEdit(frame);
			}
			finally
			{
				chatSystem._DefaultMode = defaultMode;
				_bypassSubmissionRouting = false;
			}
		}

		private static void TickSendQueue(DateTime now)
		{
			_sendQueue.Tick(now, AcknowledgementTimeout, SendFrame, delegate(OutgoingMessage message)
			{
				HandleTransportFailure(message, "LongChat: The server did not acknowledge the multipart message; the original text was restored.");
			});
		}

		private static void HandleTransportFailure(OutgoingMessage message, string warning)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			DisplayLocalWarning(message.EntityManager, warning);
			RestoreInputText(message.OriginalText);
		}

		private static void RestoreInputText(string text)
		{
			HUDChatWindow? chatWindow = _chatWindow;
			TMP_InputField val = ((chatWindow != null) ? chatWindow.ChatInputField : null);
			if (val == null)
			{
				Plugin.Log.LogWarning((object)"Unable to restore long chat text because the chat input is unavailable.");
				return;
			}
			val.text = text;
			val.MoveTextEnd(false);
			DiagnosticLog.Stage("INPUT_RESTORED", $"length={text.Length}");
		}

		private static void CompleteVanillaSubmission(ClientChatSystem chatSystem)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			try
			{
				_bypassSubmissionRouting = true;
				chatSystem._OnInputEndEdit(string.Empty);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unable to complete vanilla chat cleanup: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
				HUDChatWindow? chatWindow = _chatWindow;
				if (chatWindow != null)
				{
					chatWindow.ClearInputText();
				}
			}
			finally
			{
				_bypassSubmissionRouting = false;
			}
		}

		private static void RenderThroughFinalCallback(IncomingContext context, string text)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Expected O, but got Unknown
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			string filteredText = context.Callback.filteredText;
			try
			{
				_renderingReconstructedMessage = true;
				context.Callback.filteredText = text;
				context.Callback._FilterChatMessages_b__1(context.UserNameFiltered);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(37, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Unable to render multipart fallback: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetBaseException().Message);
				}
				log.LogWarning(val);
				DisplayLocalMessage(((ComponentSystemBase)context.Callback.field_Public___c__DisplayClass55_0_0.__4__this).EntityManager, text, context.MessageType);
			}
			finally
			{
				context.Callback.filteredText = filteredText;
				_renderingReconstructedMessage = false;
			}
		}

		private static string FormatOverheadText(string text)
		{
			return OverheadTextFormatter.Wrap(OverheadTextFormatter.Limit(text, _config.MaxOverheadCharacters.Value), _config.MaxOverheadLineCharacters.Value, _config.EnableOverheadWrapping.Value);
		}
	}
	[BepInPlugin("com.yssena.longchat", "LongChat", "1.0.0")]
	public sealed class Plugin : BasePlugin
	{
		private Harmony? _harmony;

		internal static ManualLogSource Log { get; private set; }

		internal static LongChatConfig Settings { get; private set; }

		public override void Load()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Expected O, but got Unknown
			Log = ((BasePlugin)this).Log;
			Settings = new LongChatConfig(((BasePlugin)this).Config);
			LongChatRuntime.Initialize(Settings);
			_harmony = new Harmony("com.yssena.longchat");
			PatchFeatures(_harmony);
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val;
			if (Settings.LogPatchDiscovery.Value)
			{
				foreach (MethodBase patchedMethod in _harmony.GetPatchedMethods())
				{
					ManualLogSource log = Log;
					val = new BepInExInfoLogInterpolatedStringHandler(9, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Patched ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(patchedMethod.DeclaringType?.FullName);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(patchedMethod.Name);
					}
					log.LogInfo(val);
				}
			}
			ManualLogSource log2 = Log;
			val = new BepInExInfoLogInterpolatedStringHandler(9, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("LongChat");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("1.0.0");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded.");
			}
			log2.LogInfo(val);
			DiagnosticLog.Stage("STARTUP", "version=1.0.0");
		}

		private static void PatchFeatures(Harmony harmony)
		{
			TryPatch(harmony, "chat input expansion", ResolveMethod(typeof(ClientChatSystem), "InitOrUpdateChatWindow", typeof(bool)), null, typeof(ChatInputPatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic));
			TryPatch(harmony, "long chat submission routing", ResolveMethod(typeof(ClientChatSystem), "_OnInputEndEdit", typeof(string)), typeof(ChatEndEditPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic));
			TryPatch(harmony, "chat update scheduler", ResolveMethod(typeof(ClientChatSystem), "OnUpdate"), null, typeof(ChatUpdatePatch).GetMethod("Postfix", BindingFlags.Static | BindingFlags.NonPublic));
			TryPatch(harmony, "client session cleanup", ResolveMethod(typeof(ClientBootstrapSystem), "OnDestroy"), typeof(ClientSessionPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic));
			LongChatRuntime.ConfigureIncomingReconstruction(TryPatch(harmony, "final multipart reconstruction", ResolveMethod(typeof(__c__DisplayClass55_1), "_FilterChatMessages_b__1", typeof(string)), typeof(FinalChatFilterPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)));
			LongChatRuntime.ConfigureOverheadReconstruction(TryPatch(harmony, "overhead multipart reconstruction", ResolveMethod(typeof(ClientChatSystem), "CreateLocalChatMessageSCT", typeof(NetworkIdLookupMap).MakeByRefType(), typeof(NetworkId), typeof(string)), typeof(OverheadChatPatch).GetMethod("Prefix", BindingFlags.Static | BindingFlags.NonPublic)));
		}

		private static MethodInfo? ResolveMethod(Type type, string name, params Type[] parameterTypes)
		{
			return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null);
		}

		private static bool TryPatch(Harmony harmony, string feature, MethodInfo? target, MethodInfo? prefix = null, MethodInfo? postfix = null)
		{
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: 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_0071: Expected O, but got Unknown
			bool flag = default(bool);
			if ((object)target == null)
			{
				ManualLogSource log = Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(57, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("LongChat disabled ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(feature);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": compatible game method was not found.");
				}
				log.LogWarning(val);
				return false;
			}
			try
			{
				harmony.Patch((MethodBase)target, ((object)prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), ((object)postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				ManualLogSource log2 = Log;
				BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(18, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("LongChat enabled ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(feature);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(".");
				}
				log2.LogInfo(val2);
				return true;
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Log;
				bool flag2 = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(20, 2, ref flag2);
				if (flag2)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("LongChat disabled ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(feature);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetBaseException().Message);
				}
				log3.LogWarning(val);
				return false;
			}
		}

		public override bool Unload()
		{
			LongChatRuntime.ClearSession("plugin unloaded");
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			return true;
		}
	}
	internal static class PluginInfo
	{
		public const string Guid = "com.yssena.longchat";

		public const string Name = "LongChat";

		public const string Version = "1.0.0";
	}
}
namespace LongChat.Services
{
	public enum AssemblyAddStatus
	{
		Pending,
		Complete,
		Duplicate,
		Conflict,
		Rejected
	}
	public sealed record AssemblyAddResult(AssemblyAddStatus Status, string? Text = null, IReadOnlyList<string>? RawFrames = null);
	public sealed record ExpiredAssembly(AssemblyKey Key, string PartialText, IReadOnlyList<string> RawFrames, object? Context);
	public sealed class AssemblyService
	{
		private sealed class State
		{
			public int ExpectedChunks { get; }

			public object? Context { get; }

			public Dictionary<int, string> Payloads { get; } = new Dictionary<int, string>();

			public List<string> RawFrames { get; } = new List<string>();

			public int Bytes { get; set; }

			public DateTime LastReceived { get; set; }

			public State(int expectedChunks, object? context, DateTime now)
			{
				ExpectedChunks = expectedChunks;
				Context = context;
				LastReceived = now;
			}
		}

		private readonly Dictionary<AssemblyKey, State> _assemblies = new Dictionary<AssemblyKey, State>();

		private readonly int _maximumChunks;

		private readonly int _maximumActiveAssemblies;

		private readonly int _maximumBufferedBytes;

		private int _bufferedBytes;

		public AssemblyService(int maximumChunks, int maximumActiveAssemblies, int maximumBufferedBytes)
		{
			_maximumChunks = maximumChunks;
			_maximumActiveAssemblies = maximumActiveAssemblies;
			_maximumBufferedBytes = maximumBufferedBytes;
		}

		public AssemblyAddResult Add(AssemblyKey key, ParsedFrame frame, string rawFrame, object? context, DateTime now)
		{
			if (frame.Total > _maximumChunks)
			{
				return new AssemblyAddResult(AssemblyAddStatus.Rejected);
			}
			if (!_assemblies.TryGetValue(key, out State state))
			{
				if (_assemblies.Count >= _maximumActiveAssemblies)
				{
					RemoveOldest();
				}
				state = new State(frame.Total, context, now);
				_assemblies.Add(key, state);
			}
			else if (state.ExpectedChunks != frame.Total)
			{
				string[] rawFrames = state.RawFrames.Append(rawFrame).ToArray();
				Remove(key, state);
				return new AssemblyAddResult(AssemblyAddStatus.Conflict, null, rawFrames);
			}
			if (state.Payloads.TryGetValue(frame.Index, out string value))
			{
				if (value == frame.Payload)
				{
					state.LastReceived = now;
					return new AssemblyAddResult(AssemblyAddStatus.Duplicate);
				}
				string[] rawFrames2 = state.RawFrames.Append(rawFrame).ToArray();
				Remove(key, state);
				return new AssemblyAddResult(AssemblyAddStatus.Conflict, null, rawFrames2);
			}
			int byteCount = Encoding.UTF8.GetByteCount(frame.Payload);
			while (_assemblies.Count > 0 && _bufferedBytes + byteCount > _maximumBufferedBytes)
			{
				KeyValuePair<AssemblyKey, State>? keyValuePair = ((IEnumerable<KeyValuePair<AssemblyKey, State>>)(from pair in _assemblies
					where !pair.Key.Equals(key)
					orderby pair.Value.LastReceived
					select pair)).Select((Func<KeyValuePair<AssemblyKey, State>, KeyValuePair<AssemblyKey, State>?>)((KeyValuePair<AssemblyKey, State> pair) => pair)).FirstOrDefault();
				if (!keyValuePair.HasValue)
				{
					Remove(key, state);
					return new AssemblyAddResult(AssemblyAddStatus.Rejected);
				}
				Remove(keyValuePair.Value.Key, keyValuePair.Value.Value);
			}
			if (byteCount > _maximumBufferedBytes)
			{
				return new AssemblyAddResult(AssemblyAddStatus.Rejected);
			}
			state.Payloads.Add(frame.Index, frame.Payload);
			state.RawFrames.Add(rawFrame);
			state.Bytes += byteCount;
			state.LastReceived = now;
			_bufferedBytes += byteCount;
			if (state.Payloads.Count != state.ExpectedChunks)
			{
				return new AssemblyAddResult(AssemblyAddStatus.Pending);
			}
			string text = string.Concat(from index in Enumerable.Range(1, state.ExpectedChunks)
				select state.Payloads[index]);
			Remove(key, state);
			return new AssemblyAddResult(AssemblyAddStatus.Complete, text);
		}

		public IReadOnlyList<ExpiredAssembly> Expire(DateTime now, TimeSpan timeout)
		{
			ExpiredAssembly[] array = (from pair in _assemblies
				where now - pair.Value.LastReceived >= timeout
				select new ExpiredAssembly(pair.Key, string.Concat(from chunk in pair.Value.Payloads
					orderby chunk.Key
					select chunk.Value), pair.Value.RawFrames.ToArray(), pair.Value.Context)).ToArray();
			ExpiredAssembly[] array2 = array;
			foreach (ExpiredAssembly expiredAssembly in array2)
			{
				if (_assemblies.TryGetValue(expiredAssembly.Key, out State value))
				{
					Remove(expiredAssembly.Key, value);
				}
			}
			return array;
		}

		public void Clear()
		{
			_assemblies.Clear();
			_bufferedBytes = 0;
		}

		private void RemoveOldest()
		{
			KeyValuePair<AssemblyKey, State> keyValuePair = _assemblies.OrderBy<KeyValuePair<AssemblyKey, State>, DateTime>((KeyValuePair<AssemblyKey, State> pair) => pair.Value.LastReceived).First();
			Remove(keyValuePair.Key, keyValuePair.Value);
		}

		private void Remove(AssemblyKey key, State state)
		{
			_assemblies.Remove(key);
			_bufferedBytes -= state.Bytes;
		}
	}
	internal static class ChatSendTypeSupport
	{
		public static bool IsSupported(ChatMessageType messageType)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			if ((int)messageType <= 2 || (int)messageType == 4)
			{
				return true;
			}
			return false;
		}
	}
	internal sealed class SendQueueService
	{
		private sealed class QueuedMessage
		{
			public OutgoingMessage Message { get; }

			public int NextFrame { get; set; }

			public int AwaitingIndex { get; set; }

			public DateTime SentAt { get; set; }

			public DateTime NextSendAt { get; set; }

			public QueuedMessage(OutgoingMessage message)
			{
				Message = message;
			}
		}

		private readonly Queue<QueuedMessage> _messages = new Queue<QueuedMessage>();

		private QueuedMessage? _active;

		public int Count => _messages.Count + ((_active != null) ? 1 : 0);

		public bool TryEnqueue(OutgoingMessage message, int maximumQueuedMessages)
		{
			if (Count >= maximumQueuedMessages)
			{
				return false;
			}
			_messages.Enqueue(new QueuedMessage(message));
			return true;
		}

		public void Tick(DateTime now, TimeSpan acknowledgementTimeout, Action<OutgoingMessage, string> send, Action<OutgoingMessage> timeout)
		{
			if (_active == null && _messages.Count > 0)
			{
				_active = _messages.Dequeue();
			}
			if (_active == null)
			{
				return;
			}
			if (_active.AwaitingIndex > 0)
			{
				if (now - _active.SentAt >= acknowledgementTimeout)
				{
					OutgoingMessage message = _active.Message;
					_active = null;
					timeout(message);
				}
			}
			else if (!(now < _active.NextSendAt))
			{
				string arg = _active.Message.Frames[_active.NextFrame];
				_active.AwaitingIndex = _active.NextFrame + 1;
				_active.SentAt = now;
				send(_active.Message, arg);
			}
		}

		public bool Acknowledge(string messageId, int frameIndex, DateTime now, TimeSpan delay)
		{
			if (_active == null || _active.Message.MessageId != messageId || _active.AwaitingIndex != frameIndex)
			{
				return false;
			}
			_active.NextFrame++;
			_active.AwaitingIndex = 0;
			if (_active.NextFrame >= _active.Message.Frames.Count)
			{
				_active = null;
			}
			else
			{
				_active.NextSendAt = now + delay;
			}
			return true;
		}

		public OutgoingMessage? FailActive()
		{
			OutgoingMessage result = _active?.Message;
			_active = null;
			return result;
		}

		public void Clear()
		{
			_messages.Clear();
			_active = null;
		}
	}
}
namespace LongChat.Protocol
{
	public static class FrameEncoder
	{
		public static IReadOnlyList<string> Encode(string text, int frameByteLimit, int maximumChunks, string? messageId = null)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			if (maximumChunks < 1)
			{
				throw new ArgumentOutOfRangeException("maximumChunks");
			}
			if (messageId == null)
			{
				messageId = MessageIdGenerator.Create();
			}
			if (messageId.Length != 6 || messageId.Any(delegate(char character)
			{
				bool flag;
				switch (character)
				{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				return !flag;
			}))
			{
				throw new ArgumentException("Message IDs must contain exactly six uppercase hexadecimal characters.", "messageId");
			}
			string s = FrameParser.Header(messageId, maximumChunks, maximumChunks);
			int maximumPayloadBytes = frameByteLimit - Encoding.UTF8.GetByteCount(s);
			IReadOnlyList<string> payloads = Utf8Chunker.Split(text, maximumPayloadBytes);
			if (payloads.Count > maximumChunks)
			{
				throw new InvalidOperationException($"Message requires {payloads.Count} chunks; the configured maximum is {maximumChunks}.");
			}
			string[] array = payloads.Select((string payload, int index) => FrameParser.Header(messageId, index + 1, payloads.Count) + payload).ToArray();
			if (array.Any((string frame) => Encoding.UTF8.GetByteCount(frame) > frameByteLimit))
			{
				throw new InvalidOperationException("An encoded frame exceeded the configured byte limit.");
			}
			return array;
		}
	}
	public static class FrameParser
	{
		private const string Prefix = "[[LC1|";

		public static bool TryParse(string text, out ParsedFrame frame)
		{
			frame = default(ParsedFrame);
			if (string.IsNullOrEmpty(text) || !text.StartsWith("[[LC1|", StringComparison.Ordinal))
			{
				return false;
			}
			int num = text.IndexOf("]]", "[[LC1|".Length, StringComparison.Ordinal);
			if (num < 0)
			{
				return false;
			}
			string[] array = text.AsSpan("[[LC1|".Length, num - "[[LC1|".Length).ToString().Split('|');
			if (array.Length != 3 || array[0].Length != 6 || array[0].Any((char character) => !IsUpperHex(character)) || !int.TryParse(array[1], out var result) || !int.TryParse(array[2], out var result2) || result < 1 || result2 < 1 || result > result2)
			{
				return false;
			}
			string messageId = array[0];
			int index = result;
			int total = result2;
			int num2 = num + 2;
			frame = new ParsedFrame(messageId, index, total, text.Substring(num2, text.Length - num2));
			return true;
		}

		public static string Header(string messageId, int index, int total)
		{
			return $"[[LC1|{messageId}|{index}|{total}]]";
		}

		private static bool IsUpperHex(char character)
		{
			switch (character)
			{
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case 'A':
			case 'B':
			case 'C':
			case 'D':
			case 'E':
			case 'F':
				return true;
			default:
				return false;
			}
		}
	}
	public static class MessageIdGenerator
	{
		public static string Create()
		{
			Span<byte> data = stackalloc byte[3];
			RandomNumberGenerator.Fill(data);
			return string.Concat(from value in data.ToArray()
				select value.ToString("X2"));
		}
	}
	internal static class OverheadTextFormatter
	{
		public static string Limit(string text, int maximumCharacters)
		{
			string text2 = text ?? string.Empty;
			int num = Math.Max(1, maximumCharacters);
			if (text2.Length <= num)
			{
				return text2;
			}
			if (num != 1)
			{
				return text2.Substring(0, num - 1) + "…";
			}
			return "…";
		}

		public static string Wrap(string text, int maximumLineCharacters, bool enabled)
		{
			string text2 = text ?? string.Empty;
			if (!enabled)
			{
				return text2;
			}
			int limit = Math.Max(1, maximumLineCharacters);
			string[] array = text2.Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n').Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = WrapParagraph(array[i], limit);
			}
			return string.Join("\n", array);
		}

		private static string WrapParagraph(string paragraph, int limit)
		{
			if (paragraph.Length <= limit)
			{
				return paragraph;
			}
			string[] array = paragraph.Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
			if (array.Length == 0)
			{
				return paragraph;
			}
			StringBuilder stringBuilder = new StringBuilder(paragraph.Length + paragraph.Length / limit + 1);
			StringBuilder stringBuilder2 = new StringBuilder(limit);
			string[] array2 = array;
			foreach (string word in array2)
			{
				AppendWord(stringBuilder, stringBuilder2, word, limit);
			}
			if (stringBuilder2.Length > 0)
			{
				AppendLine(stringBuilder, stringBuilder2.ToString());
			}
			return stringBuilder.ToString();
		}

		private static void AppendWord(StringBuilder output, StringBuilder currentLine, string word, int limit)
		{
			if (word.Length > limit)
			{
				if (currentLine.Length > 0)
				{
					AppendLine(output, currentLine.ToString());
					currentLine.Clear();
				}
				int i;
				for (i = 0; word.Length - i > limit; i += limit)
				{
					AppendLine(output, word.Substring(i, limit));
				}
				if (i < word.Length)
				{
					int num = i;
					currentLine.Append(word.Substring(num, word.Length - num));
				}
			}
			else if (currentLine.Length == 0)
			{
				currentLine.Append(word);
			}
			else if (currentLine.Length + 1 + word.Length <= limit)
			{
				currentLine.Append(' ').Append(word);
			}
			else
			{
				AppendLine(output, currentLine.ToString());
				currentLine.Clear();
				currentLine.Append(word);
			}
		}

		private static void AppendLine(StringBuilder output, string line)
		{
			if (output.Length > 0)
			{
				output.Append('\n');
			}
			output.Append(line);
		}
	}
	public static class TransportLimits
	{
		public const int MinimumFrameByteLimit = 64;

		public const int MaximumFrameByteLimit = 120;

		public static int EffectiveFrameByteLimit(int configuredLimit)
		{
			return Math.Clamp(configuredLimit, 64, 120);
		}

		public static bool ExceedsFrameLimit(string? text, int configuredLimit)
		{
			return Encoding.UTF8.GetByteCount(text ?? string.Empty) > EffectiveFrameByteLimit(configuredLimit);
		}
	}
	public static class Utf8Chunker
	{
		public static IReadOnlyList<string> Split(string text, int maximumPayloadBytes)
		{
			if (text == null)
			{
				throw new ArgumentNullException("text");
			}
			if (maximumPayloadBytes < 4)
			{
				throw new ArgumentOutOfRangeException("maximumPayloadBytes");
			}
			List<string> list = new List<string>();
			int num = 0;
			while (num < text.Length)
			{
				int num2 = FindEnd(text, num, maximumPayloadBytes);
				if (num2 <= num)
				{
					throw new InvalidOperationException("The payload budget cannot contain the next Unicode scalar.");
				}
				int num3 = num;
				list.Add(text.Substring(num3, num2 - num3));
				num = num2;
			}
			return list;
		}

		private static int FindEnd(string text, int start, int maximumBytes)
		{
			int num = 0;
			int num2 = start;
			int num3 = start;
			int num4 = -1;
			int num5 = -1;
			int num6 = -1;
			int num7 = -1;
			while (num2 < text.Length)
			{
				char c = text[num2];
				int num8 = ((!char.IsHighSurrogate(c) || num2 + 1 >= text.Length || !char.IsLowSurrogate(text[num2 + 1])) ? 1 : 2);
				int num9 = ((num8 == 2) ? char.ConvertToUtf32(c, text[num2 + 1]) : c);
				int num10 = ((num9 <= 2047) ? ((num9 <= 127) ? 1 : 2) : ((num9 > 65535) ? 4 : 3));
				int num11 = num10;
				if (num + num11 > maximumBytes)
				{
					break;
				}
				num += num11;
				num2 += num8;
				num3 = num2;
				bool flag;
				switch (num9)
				{
				case 10:
					num4 = num2 - num8;
					continue;
				case 33:
				case 46:
				case 63:
				case 12290:
				case 65281:
				case 65311:
					flag = true;
					break;
				default:
					flag = false;
					break;
				}
				if (flag)
				{
					num5 = num2;
					continue;
				}
				UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(text, num2 - num8);
				if ((uint)(unicodeCategory - 18) <= 6u)
				{
					num6 = num2;
				}
				else if (num8 == 1 && char.IsWhiteSpace(c))
				{
					num7 = num2 - num8;
				}
			}
			if (num3 == text.Length)
			{
				return num3;
			}
			int num12 = start + Math.Max(1, (num3 - start) / 2);
			int[] array = new int[4] { num4, num5, num6, num7 };
			foreach (int num13 in array)
			{
				if (num13 >= num12 && num13 > start)
				{
					return num13;
				}
			}
			int result = num3;
			while (num3 > start && char.IsWhiteSpace(text[num3 - 1]))
			{
				num3--;
			}
			if (num3 <= start)
			{
				return result;
			}
			return num3;
		}
	}
}
namespace LongChat.Patches
{
	internal static class ChatEndEditPatch
	{
		internal static bool Prefix(ClientChatSystem __instance, string text)
		{
			return LongChatRuntime.HandleInputEndEdit(__instance, text);
		}
	}
	internal static class ChatInputPatch
	{
		internal static void Postfix(ClientChatSystem __instance)
		{
			LongChatRuntime.ObserveChatWindow(__instance._ChatWindow);
		}
	}
	internal static class ChatUpdatePatch
	{
		internal static void Postfix()
		{
			LongChatRuntime.Tick();
		}
	}
	internal static class ClientSessionPatch
	{
		internal static void Prefix()
		{
			LongChatRuntime.ClearSession("client session ended");
		}
	}
	internal static class FinalChatFilterPatch
	{
		internal static bool Prefix(__c__DisplayClass55_1 __instance, string userNameFiltered)
		{
			DiagnosticLog.IncomingFiltered(__instance.filteredText, __instance.field_Public___c__DisplayClass55_0_0, userNameFiltered);
			return LongChatRuntime.HandleFinalFilteredMessage(__instance, userNameFiltered);
		}
	}
	internal static class OverheadChatPatch
	{
		internal static bool Prefix(ClientChatSystem __instance, ref NetworkIdLookupMap networkIdMap, NetworkId fromCharacterId, ref string filteredText)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			DiagnosticLog.OverheadRaw(filteredText, fromCharacterId);
			return LongChatRuntime.HandleOverheadChatMessage(fromCharacterId, ref filteredText);
		}
	}
}
namespace LongChat.Models
{
	public readonly record struct AssemblyKey(string SenderId, int Channel, string MessageId);
	internal sealed record IncomingContext(__c__DisplayClass55_1 Callback, ServerChatMessageType MessageType, string UserNameFiltered, string TimeStamp, bool ShowTimeStamp, bool IsLocalUser, bool IsUserAdmin)
	{
		[CompilerGenerated]
		public void Deconstruct(out __c__DisplayClass55_1 Callback, out ServerChatMessageType MessageType, out string UserNameFiltered, out string TimeStamp, out bool ShowTimeStamp, out bool IsLocalUser, out bool IsUserAdmin)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected I4, but got Unknown
			Callback = this.Callback;
			MessageType = (ServerChatMessageType)(int)this.MessageType;
			UserNameFiltered = this.UserNameFiltered;
			TimeStamp = this.TimeStamp;
			ShowTimeStamp = this.ShowTimeStamp;
			IsLocalUser = this.IsLocalUser;
			IsUserAdmin = this.IsUserAdmin;
		}
	}
	internal sealed record OutgoingMessage(EntityManager EntityManager, ChatMessageType MessageType, string MessageId, string OriginalText, IReadOnlyList<string> Frames, Action<string> SubmitFrame)
	{
		[CompilerGenerated]
		public void Deconstruct(out EntityManager EntityManager, out ChatMessageType MessageType, out string MessageId, out string OriginalText, out IReadOnlyList<string> Frames, out Action<string> SubmitFrame)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected I4, but got Unknown
			EntityManager = this.EntityManager;
			MessageType = (ChatMessageType)(int)this.MessageType;
			MessageId = this.MessageId;
			OriginalText = this.OriginalText;
			Frames = this.Frames;
			SubmitFrame = this.SubmitFrame;
		}
	}
	public readonly record struct ParsedFrame(string MessageId, int Index, int Total, string Payload);
}
namespace LongChat.Diagnostics
{
	internal static class DiagnosticLog
	{
		private const string Prefix = "[LC-DIAG]";

		private const string RawPrefix = "[LC-RAW]";

		public static void Stage(string stage, string details)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			if (Plugin.Settings.LogTransportDiagnostics.Value)
			{
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(2, 3, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("[LC-DIAG]");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(stage);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(details);
				}
				log.LogInfo(val);
			}
		}

		public static void IncomingFiltered(string? filteredText, __c__DisplayClass55_0? context, string? userNameFiltered)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Settings.LogIncomingRawMessages.Value)
			{
				string text = filteredText ?? string.Empty;
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(123, 12, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("[LC-RAW]");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" chars=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(text.Length);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" bytes=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(Encoding.UTF8.GetByteCount(text));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("type=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>((context != null) ? ((int)context.messageType) : 0);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" fromUser=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape((context != null) ? ((object)context.fromUser/*cast due to .constrained prefix*/).ToString() : null));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\" ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("fromCharacter=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape((context != null) ? ((object)context.fromCharacterId/*cast due to .constrained prefix*/).ToString() : null));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\" ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("isLocalUser=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(context != null && context.isSelf);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" userName=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape(userNameFiltered));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\" ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("timeStamp=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape((context != null) ? context.timeStamp : null));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\" showTimeStamp=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(context != null && context.showTimeStamp);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("isUserAdmin=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(context != null && context.isUserAdmin);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" text=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape(text));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\"");
				}
				log.LogInfo(val);
			}
		}

		public unsafe static void OverheadRaw(string? rawText, NetworkId networkId)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			if (Plugin.Settings.LogIncomingRawMessages.Value)
			{
				string text = rawText ?? string.Empty;
				ManualLogSource log = Plugin.Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(49, 5, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("[LC-RAW]");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" overhead=true chars=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(text.Length);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" bytes=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(Encoding.UTF8.GetByteCount(text));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("networkId=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape(((object)(*(NetworkId*)(&networkId))/*cast due to .constrained prefix*/).ToString()));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\" text=\"");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(RawLogEscaper.Escape(text));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("\"");
				}
				log.LogInfo(val);
			}
		}
	}
	public static class RawLogEscaper
	{
		public static string Escape(string? value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				switch (c)
				{
				case '\\':
					stringBuilder.Append("\\\\");
					continue;
				case '"':
					stringBuilder.Append("\\\"");
					continue;
				case '\r':
					stringBuilder.Append("\\r");
					continue;
				case '\n':
					stringBuilder.Append("\\n");
					continue;
				case '\t':
					stringBuilder.Append("\\t");
					continue;
				}
				if (char.IsControl(c))
				{
					stringBuilder.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("X4"));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.ToString();
		}
	}
}
namespace LongChat.Config
{
	internal sealed class LongChatConfig
	{
		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<int> MaxInputCharacters { get; }

		public ConfigEntry<int> FrameByteLimit { get; }

		public ConfigEntry<int> SendDelayMilliseconds { get; }

		public ConfigEntry<int> AcknowledgementTimeoutMilliseconds { get; }

		public ConfigEntry<int> MaximumChunksPerMessage { get; }

		public ConfigEntry<int> MaximumQueuedMessages { get; }

		public ConfigEntry<int> AssemblyTimeoutMilliseconds { get; }

		public ConfigEntry<int> MaximumActiveAssemblies { get; }

		public ConfigEntry<int> MaximumBufferedBytes { get; }

		public ConfigEntry<bool> EnableOverheadReconstruction { get; }

		public ConfigEntry<int> MaxOverheadCharacters { get; }

		public ConfigEntry<bool> EnableOverheadWrapping { get; }

		public ConfigEntry<int> MaxOverheadLineCharacters { get; }

		public ConfigEntry<bool> EnableLocal { get; }

		public ConfigEntry<bool> EnableGlobal { get; }

		public ConfigEntry<bool> EnableClan { get; }

		public ConfigEntry<bool> DisableSplittingForCommands { get; }

		public ConfigEntry<string> CommandPrefixes { get; }

		public ConfigEntry<bool> LogTransportDiagnostics { get; }

		public ConfigEntry<bool> LogIncomingRawMessages { get; }

		public ConfigEntry<bool> LogPatchDiscovery { get; }

		public LongChatConfig(ConfigFile config)
		{
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable LongChat.");
			MaxInputCharacters = config.Bind<int>("General", "MaxInputCharacters", 4096, "Maximum characters accepted by the chat input.");
			FrameByteLimit = config.Bind<int>("Transport", "FrameByteLimit", 120, "Maximum UTF-8 bytes in each transmitted frame. Values are clamped to 64-120.");
			SendDelayMilliseconds = config.Bind<int>("Transport", "SendDelayMilliseconds", 1, "Delay between multipart frames.");
			AcknowledgementTimeoutMilliseconds = config.Bind<int>("Transport", "AcknowledgementTimeoutMilliseconds", 3000, "Time to wait for the server to echo each multipart frame.");
			MaximumChunksPerMessage = config.Bind<int>("Transport", "MaximumChunksPerMessage", 32, "Maximum frames in one long message.");
			MaximumQueuedMessages = config.Bind<int>("Transport", "MaximumQueuedMessages", 8, "Maximum long messages waiting to send.");
			AssemblyTimeoutMilliseconds = config.Bind<int>("Receiving", "AssemblyTimeoutMilliseconds", 3000, "Time before an incomplete message is displayed partially.");
			MaximumActiveAssemblies = config.Bind<int>("Receiving", "MaximumActiveAssemblies", 32, "Maximum simultaneous incoming multipart messages.");
			MaximumBufferedBytes = config.Bind<int>("Receiving", "MaximumBufferedBytes", 262144, "Maximum incoming multipart payload bytes.");
			EnableOverheadReconstruction = config.Bind<bool>("Receiving", "EnableOverheadReconstruction", true, "Suppress multipart transport frames in overhead chat bubbles and show one reconstructed bubble.");
			MaxOverheadCharacters = config.Bind<int>("Receiving", "MaxOverheadCharacters", 500, "Maximum characters shown in a reconstructed overhead chat bubble. Main chat still shows the full message.");
			EnableOverheadWrapping = config.Bind<bool>("Receiving", "EnableOverheadWrapping", true, "Insert line breaks into reconstructed overhead chat bubbles.");
			MaxOverheadLineCharacters = config.Bind<int>("Receiving", "MaxOverheadLineCharacters", 80, "Maximum characters per reconstructed overhead chat bubble line.");
			EnableLocal = config.Bind<bool>("Channels", "EnableLocal", true, "Enable Region and Local chat splitting.");
			EnableGlobal = config.Bind<bool>("Channels", "EnableGlobal", true, "Enable Global chat splitting.");
			EnableClan = config.Bind<bool>("Channels", "EnableClan", true, "Enable Clan chat splitting.");
			DisableSplittingForCommands = config.Bind<bool>("Commands", "DisableSplittingForCommands", true, "Reject oversized command messages instead of splitting them.");
			CommandPrefixes = config.Bind<string>("Commands", "CommandPrefixes", ".,/!", "Characters that identify chat commands.");
			LogTransportDiagnostics = config.Bind<bool>("Debug", "LogTransportDiagnostics", false, "Log content-free multipart routing, acknowledgement, and recovery diagnostics.");
			LogIncomingRawMessages = config.Bind<bool>("Debug", "LogIncomingRawMessages", false, "Log complete formatted incoming chat text and metadata for temporary diagnostics. This may record private chat.");
			LogPatchDiscovery = config.Bind<bool>("Debug", "LogPatchDiscovery", false, "Log resolved Harmony patch targets.");
		}
	}
}
namespace System.Runtime.CompilerServices
{
	internal static class IsExternalInit
	{
	}
}