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