Decompiled source of WhatsDab v1.1.0

WhatsDab.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppScheduleOne.DevUtilities;
using Il2CppScheduleOne.Networking;
using Il2CppScheduleOne.PlayerScripts;
using Il2CppSteamworks;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using Microsoft.CodeAnalysis;
using Sideload.Api;
using UnityEngine;
using WhatsDab;
using WhatsDab.Chat;
using WhatsDab.Lobby;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "WhatsDab", "1.1.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-WhatsDab")]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonOptionalDependencies(new string[] { "Sideload" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("WhatsDab")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+642e8fea8bea44ef6dd6d70b9ca4c21cbf40e4f2")]
[assembly: AssemblyProduct("WhatsDab")]
[assembly: AssemblyTitle("WhatsDab")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 DooDesch
{
	internal static class ModVersion
	{
		internal const string Current = "1.1.0";
	}
}
namespace Sideload.Api
{
	public static class Apps
	{
		private static bool _bound;

		private static int _probeAttempts;

		private static readonly List<Action> _pending = new List<Action>();

		private static Action<string, string, string, string, Assembly> _registerApp;

		private static Action<string, string, Func<string, string, string>> _handle;

		private static Action<string, string, string> _emit;

		private static Action<string, string> _allowHost;

		private static Action<string, string> _declareOrientations;

		private static Action<string, int> _setBadge;

		private static Action<string, string, string> _notify;

		private static Action<string, string, string, float> _notifyFor;

		private static Func<string, bool> _isOnScreen;

		private static Action<string, string, byte[]> _setImage;

		private static Action<string, bool> _setIconHidden;

		private static Action<string, bool> _setAppOpen;

		private static Func<string, bool> _isAppOpen;

		private static Func<bool, bool> _setPhoneRaised;

		private static Func<bool> _isPhoneRaised;

		private static Action<string, string, Func<string, string, bool>> _claimKeys;

		public static bool Available
		{
			get
			{
				EnsureBound();
				return _bound;
			}
		}

		internal static bool HasKeys
		{
			get
			{
				EnsureBound();
				return _claimKeys != null;
			}
		}

		internal static bool HasPhone
		{
			get
			{
				EnsureBound();
				return _setPhoneRaised != null;
			}
		}

		internal static bool HasOpen
		{
			get
			{
				EnsureBound();
				if (_setAppOpen != null)
				{
					return _setIconHidden != null;
				}
				return false;
			}
		}

		public static AppHandle Register(string id, string bundlePrefix, string title = null, string iconLabel = null, Assembly hostAssembly = null)
		{
			AppHandle result = new AppHandle(id);
			if (string.IsNullOrEmpty(id))
			{
				return result;
			}
			Assembly asm = hostAssembly ?? Assembly.GetCallingAssembly();
			string t = title;
			string il = iconLabel;
			string prefix = bundlePrefix;
			EnsureBound();
			if (_registerApp != null)
			{
				_registerApp(id, t, il, prefix, asm);
			}
			else
			{
				_pending.Add(delegate
				{
					_registerApp?.Invoke(id, t, il, prefix, asm);
				});
			}
			return result;
		}

		internal static void WhenBound(Action work)
		{
			if (work != null)
			{
				EnsureBound();
				if (_bound)
				{
					work();
				}
				else
				{
					_pending.Add(work);
				}
			}
		}

		internal static void HandleCall(string appId, string name, Func<string, string> handler)
		{
			if (_handle != null && handler != null)
			{
				_handle(appId, name, (string app, string argument) => handler(argument));
			}
		}

		internal static void EmitEvent(string appId, string name, string payload)
		{
			_emit?.Invoke(appId, name, payload);
		}

		internal static void AllowNetHost(string appId, string host)
		{
			_allowHost?.Invoke(appId, host);
		}

		internal static void Orient(string appId, string orientations)
		{
			_declareOrientations?.Invoke(appId, orientations);
		}

		internal static void Badge(string appId, int count)
		{
			_setBadge?.Invoke(appId, count);
		}

		internal static void Notify(string appId, string title, string subtitle, float seconds)
		{
			if (_notifyFor != null)
			{
				_notifyFor(appId, title, subtitle, seconds);
			}
			else if (_notify != null)
			{
				_notify(appId, title, subtitle);
			}
		}

		internal static bool OnScreen(string appId)
		{
			if (_isOnScreen != null)
			{
				return _isOnScreen(appId);
			}
			return false;
		}

		internal static void SetImage(string appId, string name, byte[] png)
		{
			_setImage?.Invoke(appId, name, png);
		}

		internal static void HideIcon(string appId, bool hidden)
		{
			_setIconHidden?.Invoke(appId, hidden);
		}

		internal static void OpenApp(string appId, bool open)
		{
			_setAppOpen?.Invoke(appId, open);
		}

		internal static bool AppIsOpen(string appId)
		{
			if (_isAppOpen != null)
			{
				return _isAppOpen(appId);
			}
			return false;
		}

		internal static bool RaisePhone(bool raised)
		{
			if (_setPhoneRaised != null)
			{
				return _setPhoneRaised(raised);
			}
			return false;
		}

		internal static bool PhoneIsRaised()
		{
			if (_isPhoneRaised != null)
			{
				return _isPhoneRaised();
			}
			return false;
		}

		internal static void ClaimKeys(string appId, string keys, Func<string, string, bool> handler)
		{
			_claimKeys?.Invoke(appId, keys, handler);
		}

		private static void EnsureBound()
		{
			if (_bound)
			{
				return;
			}
			try
			{
				Type type = FindBridge(_probeAttempts++ % 30 == 0);
				if (type == null || (type.GetField("AbiVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is int num && num < 1))
				{
					return;
				}
				_registerApp = Get<Action<string, string, string, string, Assembly>>(type, "RegisterApp");
				if (_registerApp == null)
				{
					return;
				}
				_handle = Get<Action<string, string, Func<string, string, string>>>(type, "Handle");
				_emit = Get<Action<string, string, string>>(type, "Emit");
				_allowHost = Get<Action<string, string>>(type, "AllowHost");
				_declareOrientations = Get<Action<string, string>>(type, "DeclareOrientations");
				_setBadge = Get<Action<string, int>>(type, "SetBadge");
				_notify = Get<Action<string, string, string>>(type, "Notify");
				_notifyFor = Get<Action<string, string, string, float>>(type, "NotifyFor");
				_isOnScreen = Get<Func<string, bool>>(type, "IsAppOnScreen");
				_setImage = Get<Action<string, string, byte[]>>(type, "SetImage");
				_setIconHidden = Get<Action<string, bool>>(type, "SetIconHidden");
				_setAppOpen = Get<Action<string, bool>>(type, "SetAppOpen");
				_isAppOpen = Get<Func<string, bool>>(type, "IsAppOpen");
				_setPhoneRaised = Get<Func<bool, bool>>(type, "SetPhoneRaised");
				_isPhoneRaised = Get<Func<bool>>(type, "IsPhoneRaised");
				_claimKeys = Get<Action<string, string, Func<string, string, bool>>>(type, "ClaimKeys");
				_bound = true;
				for (int i = 0; i < _pending.Count; i++)
				{
					try
					{
						_pending[i]();
					}
					catch
					{
					}
				}
				_pending.Clear();
			}
			catch
			{
			}
		}

		private static T Get<T>(Type t, string field) where T : class
		{
			return t.GetField(field, BindingFlags.Static | BindingFlags.Public)?.GetValue(null) as T;
		}

		private static Type FindBridge(bool scan)
		{
			Type type = Type.GetType("Sideload.Bridge.SideloadBridge, Sideload", throwOnError: false);
			if (type != null || !scan)
			{
				return type;
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					type = assembly.GetType("Sideload.Bridge.SideloadBridge", throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				catch
				{
				}
			}
			return null;
		}
	}
	public sealed class AppHandle
	{
		private readonly string _id;

		public string Id => _id;

		public bool IsOnScreen
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.OnScreen(_id);
				}
				return false;
			}
		}

		public bool IsOpen
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.AppIsOpen(_id);
				}
				return false;
			}
		}

		public static bool CanOpenProgrammatically
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasOpen;
				}
				return false;
			}
		}

		public static bool CanClaimKeys
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasKeys;
				}
				return false;
			}
		}

		internal AppHandle(string id)
		{
			_id = id ?? "";
		}

		public AppHandle OnCall(string name, Func<string, string> handler)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HandleCall(id, name, handler);
			});
			return this;
		}

		public void Emit(string name, string payload = "")
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.EmitEvent(id, name, payload);
			});
		}

		public AppHandle AllowHost(string host)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.AllowNetHost(id, host);
			});
			return this;
		}

		public AppHandle Orientation(params string[] supported)
		{
			string id = _id;
			string list = ((supported == null) ? "" : string.Join(",", supported));
			Apps.WhenBound(delegate
			{
				Apps.Orient(id, list);
			});
			return this;
		}

		public AppHandle Badge(int count)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.Badge(id, count);
			});
			return this;
		}

		public AppHandle Notify(string title, string subtitle = "", float seconds = 0f)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.Notify(id, title, subtitle, seconds);
			});
			return this;
		}

		public AppHandle Image(string name, byte[] png)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.SetImage(id, name, png);
			});
			return this;
		}

		public AppHandle Icon(bool visible)
		{
			Apps.HideIcon(_id, !visible);
			return this;
		}

		public AppHandle NoIcon()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.HideIcon(id, hidden: true);
			});
			return this;
		}

		public AppHandle Open()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: true);
			});
			return this;
		}

		public AppHandle Close()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: false);
			});
			return this;
		}

		public bool Show()
		{
			if (!Apps.RaisePhone(raised: true))
			{
				return false;
			}
			Apps.OpenApp(_id, open: true);
			return true;
		}

		public AppHandle OnKey(string keys, Func<string, bool> handler)
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.ClaimKeys(id, keys, (handler == null) ? null : ((Func<string, string, bool>)((string _, string key) => handler(key))));
			});
			return this;
		}

		public AppHandle Hide()
		{
			string id = _id;
			Apps.WhenBound(delegate
			{
				Apps.OpenApp(id, open: false);
				Apps.RaisePhone(raised: false);
			});
			return this;
		}
	}
	public static class PhoneScreen
	{
		public static bool IsRaised
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.PhoneIsRaised();
				}
				return false;
			}
		}

		public static bool Available
		{
			get
			{
				if (Apps.Available)
				{
					return Apps.HasPhone;
				}
				return false;
			}
		}

		public static bool Raise()
		{
			return Apps.RaisePhone(raised: true);
		}

		public static bool Lower()
		{
			return Apps.RaisePhone(raised: false);
		}
	}
}
namespace WhatsDab
{
	public class Core : MelonMod
	{
		internal static Instance Log;

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			ChatBackend.Install(Apps.Register("whatsdab", "WhatsDab.Assets.whatsdab", "WhatsDab", "WhatsDab").Orientation("landscape", "portrait"), ChooseSource());
			Log.Msg(Apps.Available ? "[WhatsDab] registered with Sideload." : "[WhatsDab] Sideload is not loaded yet - registration is queued and replays when it appears.");
		}

		private static IChatSource ChooseSource()
		{
			return new LobbySource();
		}

		public override void OnUpdate()
		{
			ChatBackend.Tick();
		}
	}
}
namespace WhatsDab.Lobby
{
	internal sealed class ChatMessage
	{
		public ulong SenderId;

		public ulong RecipientId;

		public int Seq;

		public long UnixSeconds;

		public string Text = "";

		public bool Mine;

		public bool Pending;
	}
	internal static class ChatEnvelope
	{
		private const string Prefix = "WD1|MSG|";

		internal const int MaxTextChars = 500;

		internal static bool IsOurs(string raw)
		{
			return raw?.StartsWith("WD1|MSG|", StringComparison.Ordinal) ?? false;
		}

		internal static byte[] Encode(ulong recipientId, int seq, long unix, string text)
		{
			string text2 = text ?? "";
			if (text2.Length > 500)
			{
				int num = 500;
				if (char.IsHighSurrogate(text2[num - 1]))
				{
					num--;
				}
				text2 = text2.Substring(0, num);
			}
			string text3 = ((recipientId == 0L) ? "*" : recipientId.ToString());
			string text4 = Convert.ToBase64String(Encoding.UTF8.GetBytes(text2));
			string s = "WD1|MSG|" + text3 + "|" + seq + "|" + unix + "|" + text4 + "\0";
			return Encoding.ASCII.GetBytes(s);
		}

		internal static ChatMessage Decode(string raw, ulong senderId)
		{
			if (!IsOurs(raw))
			{
				return null;
			}
			try
			{
				string[] array = raw.Split('|');
				if (array.Length != 6)
				{
					return null;
				}
				ChatMessage chatMessage = new ChatMessage
				{
					SenderId = senderId
				};
				chatMessage.RecipientId = ((array[2] == "*") ? 0 : (ulong.TryParse(array[2], out var result) ? result : 0));
				int.TryParse(array[3], out chatMessage.Seq);
				long.TryParse(array[4], out chatMessage.UnixSeconds);
				chatMessage.Text = Encoding.UTF8.GetString(Convert.FromBase64String(array[5]));
				return chatMessage;
			}
			catch
			{
				return null;
			}
		}
	}
	internal static class ChatStore
	{
		internal const ulong GroupKey = 0uL;

		internal const ulong NoThread = ulong.MaxValue;

		private static readonly Dictionary<ulong, List<ChatMessage>> _threads = new Dictionary<ulong, List<ChatMessage>>();

		private static readonly Dictionary<ulong, int> _unread = new Dictionary<ulong, int>();

		private const int MaxMessagesPerThread = 200;

		internal static int Revision { get; private set; }

		internal static IReadOnlyList<ChatMessage> Thread(ulong peerKey)
		{
			if (!_threads.TryGetValue(peerKey, out var value))
			{
				return Array.Empty<ChatMessage>();
			}
			return value;
		}

		internal static int Unread(ulong peerKey)
		{
			if (!_unread.TryGetValue(peerKey, out var value))
			{
				return 0;
			}
			return value;
		}

		internal static int TotalUnread()
		{
			int num = 0;
			foreach (KeyValuePair<ulong, int> item in _unread)
			{
				num += item.Value;
			}
			return num;
		}

		private static ulong KeyFor(ChatMessage m, ulong self)
		{
			if (m.RecipientId != 0L)
			{
				if (m.SenderId != self)
				{
					return m.SenderId;
				}
				return m.RecipientId;
			}
			return 0uL;
		}

		internal static ChatMessage AddLocal(ulong recipientKey, int seq, string text)
		{
			ulong senderId = ChatTransport.SelfId();
			ChatMessage chatMessage = new ChatMessage
			{
				SenderId = senderId,
				RecipientId = recipientKey,
				Seq = seq,
				UnixSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
				Text = text,
				Mine = true,
				Pending = true
			};
			List(recipientKey).Add(chatMessage);
			Revision++;
			return chatMessage;
		}

		internal static void Receive(ChatMessage m, ulong activeThreadKey)
		{
			ulong num = ChatTransport.SelfId();
			ulong num2 = KeyFor(m, num);
			if (m.SenderId == num)
			{
				List<ChatMessage> list = List(num2);
				for (int num3 = list.Count - 1; num3 >= 0; num3--)
				{
					if (list[num3].Mine && list[num3].Pending && list[num3].Seq == m.Seq)
					{
						list[num3].Pending = false;
						Revision++;
						return;
					}
				}
				m.Mine = true;
				m.Pending = false;
				list.Add(m);
				Revision++;
			}
			else
			{
				List(num2).Add(m);
				if (num2 != activeThreadKey)
				{
					_unread[num2] = Unread(num2) + 1;
				}
				Revision++;
			}
		}

		internal static void MarkRead(ulong peerKey)
		{
			if (_unread.TryGetValue(peerKey, out var value) && value > 0)
			{
				_unread[peerKey] = 0;
				Revision++;
			}
		}

		internal static void Clear()
		{
			_threads.Clear();
			_unread.Clear();
			Revision++;
		}

		private static List<ChatMessage> List(ulong key)
		{
			if (!_threads.TryGetValue(key, out var value))
			{
				value = new List<ChatMessage>();
				_threads[key] = value;
			}
			if (value.Count > 200)
			{
				value.RemoveRange(0, value.Count - 200);
			}
			return value;
		}
	}
	internal static class ChatTransport
	{
		private const int MinSendIntervalMs = 300;

		private static Callback<LobbyChatMsg_t> _callback;

		private static Callback<LobbyEnter_t> _enterCallback;

		private static Callback<LobbyCreated_t> _createdCallback;

		private static Action<ChatMessage> _onMessage;

		private static long _lastSendTicks;

		private static ulong _seenLobby;

		private static bool _loggedDeadProperty;

		internal static bool InLobby => CurrentLobby() != 0;

		internal static ulong LobbyId => CurrentLobby();

		internal static void Start(Action<ChatMessage> onMessage)
		{
			_onMessage = onMessage;
			if (_callback != null)
			{
				return;
			}
			try
			{
				_callback = Callback<LobbyChatMsg_t>.Create(DispatchDelegate<LobbyChatMsg_t>.op_Implicit((Action<LobbyChatMsg_t>)OnChatMsg));
			}
			catch (Exception ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Error("[WhatsDab] chat callback registration failed: " + ex);
				}
			}
			try
			{
				_enterCallback = Callback<LobbyEnter_t>.Create(DispatchDelegate<LobbyEnter_t>.op_Implicit((Action<LobbyEnter_t>)OnLobbyEnter));
				_createdCallback = Callback<LobbyCreated_t>.Create(DispatchDelegate<LobbyCreated_t>.op_Implicit((Action<LobbyCreated_t>)OnLobbyCreated));
			}
			catch (Exception ex2)
			{
				Instance log2 = Core.Log;
				if (log2 != null)
				{
					log2.Warning("[WhatsDab] lobby callbacks failed: " + ex2.Message);
				}
			}
		}

		private static void OnLobbyEnter(LobbyEnter_t e)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if (e.m_EChatRoomEnterResponse == 1)
			{
				_seenLobby = e.m_ulSteamIDLobby;
			}
		}

		private static void OnLobbyCreated(LobbyCreated_t e)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			if ((int)e.m_eResult == 1)
			{
				_seenLobby = e.m_ulSteamIDLobby;
			}
		}

		internal static ulong SelfId()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				return SteamUser.GetSteamID().m_SteamID;
			}
			catch
			{
				return 0uL;
			}
		}

		private static ulong CurrentLobby()
		{
			ulong num = FromLobbyService();
			if (num != 0L)
			{
				return num;
			}
			if (_seenLobby != 0L && !_loggedDeadProperty)
			{
				_loggedDeadProperty = true;
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[WhatsDab] the game's lobby service gave no id; using our own record " + _seenLobby + " (SteamLobbyService may have changed again).");
				}
			}
			return _seenLobby;
		}

		private static ulong FromLobbyService()
		{
			try
			{
				Lobby instance = Singleton<Lobby>.Instance;
				if ((Object)(object)instance == (Object)null || !instance.IsInLobby)
				{
					return 0uL;
				}
				ILobbyService lobbyService = instance._lobbyService;
				if (lobbyService == null)
				{
					return 0uL;
				}
				SteamLobbyService val = ((Il2CppObjectBase)lobbyService).TryCast<SteamLobbyService>();
				return (val != null) ? val._lobbyID : 0;
			}
			catch
			{
				return 0uL;
			}
		}

		internal static int Send(ulong recipientId, int seq, string text)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			ulong num = CurrentLobby();
			if (num == 0L)
			{
				return -1;
			}
			long ticks = DateTime.UtcNow.Ticks;
			if (ticks - _lastSendTicks < 3000000)
			{
				return -1;
			}
			_lastSendTicks = ticks;
			try
			{
				long unix = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
				byte[] array = ChatEnvelope.Encode(recipientId, seq, unix, text);
				return SteamMatchmaking.SendLobbyChatMsg(new CSteamID(num), Il2CppStructArray<byte>.op_Implicit(array), array.Length) ? seq : (-1);
			}
			catch (Exception ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[WhatsDab] send failed: " + ex.Message);
				}
				return -1;
			}
		}

		private static void OnChatMsg(LobbyChatMsg_t msg)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				CSteamID val = new CSteamID(msg.m_ulSteamIDLobby);
				Il2CppStructArray<byte> val2 = new Il2CppStructArray<byte>(4096L);
				CSteamID val3 = default(CSteamID);
				EChatEntryType val4 = default(EChatEntryType);
				int lobbyChatEntry = SteamMatchmaking.GetLobbyChatEntry(val, (int)msg.m_iChatID, ref val3, val2, ((Il2CppArrayBase<byte>)(object)val2).Length, ref val4);
				int num = Math.Max(0, Math.Min(lobbyChatEntry, ((Il2CppArrayBase<byte>)(object)val2).Length));
				if (num == 0)
				{
					return;
				}
				byte[] array = new byte[num];
				for (int i = 0; i < num; i++)
				{
					array[i] = ((Il2CppArrayBase<byte>)(object)val2)[i];
				}
				string raw = Encoding.ASCII.GetString(array).TrimEnd('\0');
				if (ChatEnvelope.IsOurs(raw))
				{
					ChatMessage chatMessage = ChatEnvelope.Decode(raw, val3.m_SteamID);
					if (chatMessage != null && (chatMessage.RecipientId == 0L || chatMessage.RecipientId == SelfId() || chatMessage.SenderId == SelfId()))
					{
						_onMessage?.Invoke(chatMessage);
					}
				}
			}
			catch (Exception ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[WhatsDab] receive failed: " + ex.Message);
				}
			}
		}
	}
	internal sealed class Contact
	{
		public ulong SteamId;

		public string Name = "";
	}
	internal static class Contacts
	{
		private static readonly List<Contact> _list = new List<Contact>();

		internal static IReadOnlyList<Contact> All => _list;

		internal static string NameOf(ulong steamId)
		{
			foreach (Contact item in _list)
			{
				if (item.SteamId == steamId)
				{
					return item.Name;
				}
			}
			return ResolveName(steamId);
		}

		internal static void Refresh(ulong lobbyId)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			_list.Clear();
			if (lobbyId == 0L)
			{
				return;
			}
			ulong num = ChatTransport.SelfId();
			try
			{
				CSteamID val = default(CSteamID);
				((CSteamID)(ref val))..ctor(lobbyId);
				int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(val);
				for (int i = 0; i < numLobbyMembers; i++)
				{
					CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(val, i);
					if (lobbyMemberByIndex.m_SteamID != 0L && lobbyMemberByIndex.m_SteamID != num)
					{
						_list.Add(new Contact
						{
							SteamId = lobbyMemberByIndex.m_SteamID,
							Name = ResolveName(lobbyMemberByIndex.m_SteamID)
						});
					}
				}
			}
			catch (Exception ex)
			{
				Instance log = Core.Log;
				if (log != null)
				{
					log.Warning("[WhatsDab] contact refresh failed: " + ex.Message);
				}
			}
		}

		private static string ResolveName(ulong steamId)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string text = steamId.ToString();
				List<Player> playerList = Player.PlayerList;
				if (playerList != null)
				{
					for (int i = 0; i < playerList.Count; i++)
					{
						Player val = playerList[i];
						if ((Object)(object)val == (Object)null)
						{
							continue;
						}
						string text2 = null;
						try
						{
							text2 = val.PlayerCode;
						}
						catch
						{
						}
						if (text2 == text)
						{
							string text3 = null;
							try
							{
								text3 = val.PlayerName;
							}
							catch
							{
							}
							if (!string.IsNullOrEmpty(text3))
							{
								return text3;
							}
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				string friendPersonaName = SteamFriends.GetFriendPersonaName(new CSteamID(steamId));
				if (!string.IsNullOrEmpty(friendPersonaName) && friendPersonaName != "[unknown]")
				{
					return friendPersonaName;
				}
			}
			catch
			{
			}
			string text4 = steamId.ToString();
			return "Player " + ((text4.Length > 4) ? text4.Substring(text4.Length - 4) : text4);
		}
	}
	internal sealed class LobbySource : IChatSource
	{
		private const float ContactRefreshSeconds = 5f;

		private readonly List<Thread> _threads = new List<Thread>();

		private int _threadsRevision = -1;

		private int _seq;

		private float _nextRefresh;

		private ulong _lobby;

		private Thread _arrival;

		public string Self => "You";

		public int Revision => ChatStore.Revision;

		public bool Online => ChatTransport.InLobby;

		public int Peers => Contacts.All.Count;

		public string TypingIn => null;

		public IReadOnlyList<Thread> Threads
		{
			get
			{
				Rebuild();
				return _threads;
			}
		}

		internal LobbySource()
		{
			ChatTransport.Start(OnIncoming);
		}

		public Thread Find(string id)
		{
			foreach (Thread thread in Threads)
			{
				if (string.Equals(thread.Id, id, StringComparison.OrdinalIgnoreCase))
				{
					return thread;
				}
			}
			return null;
		}

		public bool Send(string threadId, string text)
		{
			if (!TryKey(threadId, out var key))
			{
				return false;
			}
			int seq = ++_seq;
			if (ChatTransport.Send(key, seq, text) < 0)
			{
				return false;
			}
			ChatStore.AddLocal(key, seq, text);
			return true;
		}

		public void MarkRead(string threadId)
		{
			if (TryKey(threadId, out var key))
			{
				ChatStore.MarkRead(key);
			}
		}

		public int TotalUnread()
		{
			return ChatStore.TotalUnread();
		}

		public Thread TakeArrival()
		{
			Thread arrival = _arrival;
			_arrival = null;
			return arrival;
		}

		public void Tick()
		{
			ulong lobbyId = ChatTransport.LobbyId;
			if (lobbyId != _lobby)
			{
				_lobby = lobbyId;
				ChatStore.Clear();
				Contacts.Refresh(lobbyId);
				_nextRefresh = 5f;
			}
			else if (lobbyId != 0L)
			{
				_nextRefresh -= Time.deltaTime;
				if (!(_nextRefresh > 0f))
				{
					_nextRefresh = 5f;
					Contacts.Refresh(lobbyId);
				}
			}
		}

		private static bool TryKey(string threadId, out ulong key)
		{
			key = 0uL;
			if (string.IsNullOrWhiteSpace(threadId))
			{
				return false;
			}
			if (string.Equals(threadId, "everyone", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return ulong.TryParse(threadId.Trim(), out key);
		}

		private void OnIncoming(ChatMessage message)
		{
			ulong activeThreadKey = ulong.MaxValue;
			ChatStore.Receive(message, activeThreadKey);
			ulong num = ChatTransport.SelfId();
			ulong num2 = ((message.RecipientId == 0L) ? 0 : ((message.SenderId == num) ? message.RecipientId : message.SenderId));
			_threadsRevision = -1;
			_arrival = Find((num2 == 0L) ? "everyone" : num2.ToString());
		}

		private void Rebuild()
		{
			if (_threadsRevision == ChatStore.Revision)
			{
				return;
			}
			_threadsRevision = ChatStore.Revision;
			_threads.Clear();
			_threads.Add(BuildThread("everyone", "Everyone", group: true, 0uL));
			foreach (Contact item in Contacts.All)
			{
				_threads.Add(BuildThread(item.SteamId.ToString(), item.Name, group: false, item.SteamId));
			}
		}

		private static Thread BuildThread(string id, string name, bool group, ulong key)
		{
			Thread thread = new Thread
			{
				Id = id,
				Name = name,
				Group = group,
				Unread = ChatStore.Unread(key)
			};
			foreach (ChatMessage item in ChatStore.Thread(key))
			{
				thread.Messages.Add(new Message
				{
					From = (item.Mine ? "You" : Contacts.NameOf(item.SenderId)),
					Text = item.Text,
					At = DateTimeOffset.FromUnixTimeSeconds(item.UnixSeconds).ToLocalTime().DateTime,
					Mine = item.Mine,
					Pending = item.Pending
				});
			}
			return thread;
		}
	}
}
namespace WhatsDab.Chat
{
	internal static class ChatBackend
	{
		internal const int MaxMessageChars = 500;

		private static AppHandle _app;

		private static IChatSource _source;

		private static int _pushedRevision = -1;

		internal static IChatSource Source => _source;

		internal static void UseSource(IChatSource source)
		{
			_source = source;
			_pushedRevision = -1;
		}

		internal static void Install(AppHandle app, IChatSource source)
		{
			_app = app;
			UseSource(source);
			app.OnCall("chat.threads", (string _) => Threads()).OnCall("chat.thread", Conversation).OnCall("chat.send", Send)
				.OnCall("chat.read", delegate(string id)
				{
					if (_app == null || _app.IsOnScreen)
					{
						_source.MarkRead(id);
					}
					return "ok";
				})
				.OnCall("chat.self", (string _) => _source.Self)
				.OnCall("chat.status", (string _) => Status())
				.OnKey("Enter", (string _) => QuickChat());
		}

		internal static bool QuickChat()
		{
			if (_app == null || _source == null || !_source.Online)
			{
				return false;
			}
			if (!_app.Show())
			{
				return false;
			}
			_app.Emit("chat.compose", "everyone");
			return true;
		}

		internal static void Tick()
		{
			if (_source != null)
			{
				_source.Tick();
				if (_source.Revision != _pushedRevision)
				{
					_pushedRevision = _source.Revision;
					int count = _source.TotalUnread();
					_app?.Emit("chat.changed", count.ToString());
					_app?.Badge(count);
					NotifyIfUnseen();
				}
			}
		}

		private static void NotifyIfUnseen()
		{
			Thread thread = _source.TakeArrival();
			if (thread != null && _app != null && !_app.IsOnScreen)
			{
				Message message = ((thread.Messages.Count > 0) ? thread.Messages[thread.Messages.Count - 1] : null);
				if (message != null && !message.Mine)
				{
					_app.Notify(thread.Group ? (message.From + " - " + thread.Name) : message.From, message.Text);
				}
			}
		}

		internal static string Status()
		{
			return Json.Object().Add("online", _source.Online).Add("peers", _source.Peers)
				.Close();
		}

		internal static string Threads()
		{
			Json json = Json.Array();
			foreach (Thread thread in _source.Threads)
			{
				Message message = ((thread.Messages.Count > 0) ? thread.Messages[thread.Messages.Count - 1] : null);
				json.Item(Json.Object().Add("id", thread.Id).Add("name", thread.Name)
					.Add("group", thread.Group)
					.Add("unread", thread.Unread)
					.Add("typing", string.Equals(_source.TypingIn, thread.Id, StringComparison.OrdinalIgnoreCase))
					.Add("preview", (message == null) ? "" : ((message.Mine ? "You: " : (thread.Group ? (message.From + ": ") : "")) + message.Text))
					.Add("time", (message == null) ? "" : ChatModel.Clock(message.At)));
			}
			return json.Close();
		}

		internal static string Conversation(string id)
		{
			Thread thread = _source.Find(id);
			if (thread == null)
			{
				return Json.Object().Add("id", "").Add("name", "")
					.Close();
			}
			Json json = Json.Array();
			string text = null;
			foreach (Message message in thread.Messages)
			{
				string text2 = ChatModel.DayLabel(message.At);
				if (text2 != text)
				{
					json.Item(Json.Object().Add("kind", "day").Add("text", text2));
					text = text2;
				}
				json.Item(Json.Object().Add("kind", "message").Add("from", message.From)
					.Add("text", message.Text)
					.Add("time", ChatModel.Clock(message.At))
					.Add("mine", message.Mine)
					.Add("pending", message.Pending));
			}
			return Json.Object().Add("id", thread.Id).Add("name", thread.Name)
				.Add("group", thread.Group)
				.Add("typing", string.Equals(_source.TypingIn, thread.Id, StringComparison.OrdinalIgnoreCase))
				.Add("messages", json)
				.Close();
		}

		internal static string Send(string argument)
		{
			if (!_source.Online)
			{
				return "error";
			}
			string text = argument ?? "";
			int num = text.IndexOf('\n');
			if (num <= 0)
			{
				return "error";
			}
			string id = text.Substring(0, num);
			string text2 = text.Substring(num + 1);
			if (text2.IndexOf('\n') >= 0)
			{
				return "error";
			}
			if (string.IsNullOrWhiteSpace(text2))
			{
				return "error";
			}
			if (text2.Trim().Length > 500)
			{
				return "error";
			}
			Thread thread = _source.Find(id);
			if (thread == null)
			{
				return "error";
			}
			if (!_source.Send(thread.Id, text2))
			{
				return "error";
			}
			return "ok";
		}
	}
	internal sealed class Message
	{
		internal string From = "";

		internal string Text = "";

		internal DateTime At;

		internal bool Mine;

		internal bool Pending;
	}
	internal sealed class Thread
	{
		internal string Id = "";

		internal string Name = "";

		internal bool Group;

		internal int Unread;

		internal readonly List<Message> Messages = new List<Message>();
	}
	internal static class ChatModel
	{
		internal const string GroupId = "everyone";

		private static readonly string[] _replies = new string[8] { "on my way", "give me two minutes, dealing with a customer", "did you restock the west van?", "cops were on Marina Ave, take the back road", "that batch came out at 82%, best yet", "no way. seriously?", "sending the cash over now", "can you cover the motel drop tonight?" };

		private static readonly List<Thread> _threads = new List<Thread>();

		private static int _replyIndex;

		internal static Thread LastArrival;

		internal static string Self { get; private set; } = "You";

		internal static IReadOnlyList<Thread> Threads => _threads;

		internal static int Revision { get; private set; }

		internal static bool Online { get; private set; } = true;

		internal static int Peers
		{
			get
			{
				int num = 0;
				foreach (Thread thread in _threads)
				{
					if (!thread.Group)
					{
						num++;
					}
				}
				return num;
			}
		}

		internal static string TypingIn { get; private set; }

		internal static void SetOnline(bool online)
		{
			if (Online != online)
			{
				Online = online;
				Revision++;
			}
		}

		internal static Thread Find(string id)
		{
			foreach (Thread thread in _threads)
			{
				if (string.Equals(thread.Id, id, StringComparison.OrdinalIgnoreCase))
				{
					return thread;
				}
			}
			return null;
		}

		internal static void Seed()
		{
			_threads.Clear();
			TypingIn = null;
			_replyIndex = 0;
			Online = true;
			Thread thread = new Thread
			{
				Id = "everyone",
				Name = "Everyone",
				Group = true
			};
			Add(thread, "coolpaca", "anyone seen my van keys", -46);
			Add(thread, "wesconsin", "they were in the ignition. again.", -44);
			Add(thread, "SirTidez", "meeting at the docks at nine, bring the van", -12);
			thread.Unread = 1;
			_threads.Add(thread);
			Thread thread2 = new Thread
			{
				Id = "tidez",
				Name = "SirTidez"
			};
			Add(thread2, "SirTidez", "you at the warehouse?", -180);
			Add(thread2, Self, "on my way, five minutes", -176, mine: true);
			Add(thread2, "SirTidez", "nice, door is open", -175);
			_threads.Add(thread2);
			Thread thread3 = new Thread
			{
				Id = "bars",
				Name = "Bars",
				Unread = 2
			};
			Add(thread3, "Bars", "we are two short for tonight", -9);
			Add(thread3, "Bars", "can you bring someone?", -8);
			_threads.Add(thread3);
			Thread thread4 = new Thread
			{
				Id = "eternidox",
				Name = "Eternidox"
			};
			Add(thread4, Self, "storage is full, we need a second place", -320, mine: true);
			Add(thread4, "Eternidox", "i know a guy who knows a guy", -318);
			_threads.Add(thread4);
			Revision++;
		}

		internal static void Send(string threadId, string text)
		{
			Thread thread = Find(threadId);
			if (thread != null && !string.IsNullOrWhiteSpace(text))
			{
				thread.Messages.Add(new Message
				{
					From = Self,
					Text = text.Trim(),
					At = DateTime.Now,
					Mine = true,
					Pending = true
				});
				Revision++;
			}
		}

		internal static bool ConfirmPending()
		{
			bool flag = false;
			foreach (Thread thread in _threads)
			{
				foreach (Message message in thread.Messages)
				{
					if (message.Pending)
					{
						message.Pending = false;
						flag = true;
					}
				}
			}
			if (flag)
			{
				Revision++;
			}
			return flag;
		}

		internal static void SetTyping(string threadId)
		{
			string text = Find(threadId)?.Id;
			if (!(TypingIn == text))
			{
				TypingIn = text;
				Revision++;
			}
		}

		internal static void Reply(string threadId)
		{
			Thread thread = Find(threadId);
			if (thread != null)
			{
				TypingIn = null;
				string text = (thread.Group ? "SirTidez" : thread.Name);
				thread.Messages.Add(new Message
				{
					From = text,
					Text = _replies[_replyIndex++ % _replies.Length],
					At = DateTime.Now
				});
				thread.Unread++;
				Revision++;
				LastArrival = thread;
			}
		}

		internal static void MarkRead(string threadId)
		{
			Thread thread = Find(threadId);
			if (thread != null && thread.Unread != 0)
			{
				thread.Unread = 0;
				Revision++;
			}
		}

		internal static int TotalUnread()
		{
			int num = 0;
			foreach (Thread thread in _threads)
			{
				num += thread.Unread;
			}
			return num;
		}

		internal static string Clock(DateTime at)
		{
			return at.ToString("HH:mm", CultureInfo.InvariantCulture);
		}

		internal static string DayLabel(DateTime at)
		{
			return DayLabel(at, DateTime.Now);
		}

		internal static string DayLabel(DateTime at, DateTime now)
		{
			DateTime date = now.Date;
			if (at.Date == date)
			{
				return "Today";
			}
			if (at.Date == date.AddDays(-1.0))
			{
				return "Yesterday";
			}
			return at.ToString("d MMMM", CultureInfo.InvariantCulture);
		}

		private static void Add(Thread thread, string from, string text, int minutesAgo, bool mine = false)
		{
			thread.Messages.Add(new Message
			{
				From = from,
				Text = text,
				At = DateTime.Now.AddMinutes(minutesAgo),
				Mine = mine
			});
		}
	}
	internal sealed class DemoSource : IChatSource
	{
		private DateTime _confirmAt = DateTime.MaxValue;

		private DateTime _typingAt = DateTime.MaxValue;

		private DateTime _replyAt = DateTime.MaxValue;

		private string _replyTo;

		public string Self => ChatModel.Self;

		public int Revision => ChatModel.Revision;

		public bool Online => ChatModel.Online;

		public int Peers => ChatModel.Peers;

		public IReadOnlyList<Thread> Threads => ChatModel.Threads;

		public string TypingIn => ChatModel.TypingIn;

		internal DemoSource()
		{
			ChatModel.Seed();
			_confirmAt = (_typingAt = (_replyAt = DateTime.MaxValue));
			_replyTo = null;
		}

		public Thread Find(string id)
		{
			return ChatModel.Find(id);
		}

		public void MarkRead(string threadId)
		{
			ChatModel.MarkRead(threadId);
		}

		public int TotalUnread()
		{
			return ChatModel.TotalUnread();
		}

		public Thread TakeArrival()
		{
			Thread lastArrival = ChatModel.LastArrival;
			ChatModel.LastArrival = null;
			return lastArrival;
		}

		public bool Send(string threadId, string text)
		{
			Thread thread = ChatModel.Find(threadId);
			if (thread == null)
			{
				return false;
			}
			ChatModel.Send(thread.Id, text);
			_confirmAt = DateTime.Now.AddSeconds(0.9);
			_typingAt = DateTime.Now.AddSeconds(1.8);
			_replyAt = DateTime.Now.AddSeconds(4.0);
			_replyTo = thread.Id;
			return true;
		}

		public void Tick()
		{
			DateTime now = DateTime.Now;
			if (now >= _confirmAt)
			{
				_confirmAt = DateTime.MaxValue;
				ChatModel.ConfirmPending();
			}
			if (now >= _typingAt)
			{
				_typingAt = DateTime.MaxValue;
				ChatModel.SetTyping(_replyTo);
			}
			if (now >= _replyAt)
			{
				_replyAt = DateTime.MaxValue;
				ChatModel.Reply(_replyTo);
			}
		}
	}
	internal interface IChatSource
	{
		string Self { get; }

		int Revision { get; }

		bool Online { get; }

		int Peers { get; }

		IReadOnlyList<Thread> Threads { get; }

		string TypingIn { get; }

		Thread Find(string id);

		bool Send(string threadId, string text);

		void MarkRead(string threadId);

		int TotalUnread();

		Thread TakeArrival();

		void Tick();
	}
	internal sealed class Json
	{
		private readonly StringBuilder _sb = new StringBuilder();

		private bool _empty = true;

		private string _closed;

		private Json(char open)
		{
			_sb.Append(open);
		}

		internal static Json Object()
		{
			return new Json('{');
		}

		internal static Json Array()
		{
			return new Json('[');
		}

		internal Json Add(string key, string value)
		{
			Separate();
			Quote(key);
			_sb.Append(':');
			Quote(value);
			return this;
		}

		internal Json Add(string key, long value)
		{
			Separate();
			Quote(key);
			_sb.Append(':').Append(value);
			return this;
		}

		internal Json Add(string key, bool value)
		{
			Separate();
			Quote(key);
			_sb.Append(':').Append(value ? "true" : "false");
			return this;
		}

		internal Json Add(string key, Json value)
		{
			Separate();
			Quote(key);
			_sb.Append(':').Append(value.Close());
			return this;
		}

		internal Json Item(Json value)
		{
			Separate();
			_sb.Append(value.Close());
			return this;
		}

		internal string Close()
		{
			if (_closed != null)
			{
				return _closed;
			}
			_sb.Append((_sb[0] == '{') ? '}' : ']');
			return _closed = _sb.ToString();
		}

		public override string ToString()
		{
			return Close();
		}

		private void Separate()
		{
			if (_closed != null)
			{
				throw new InvalidOperationException("this JSON value is already closed and cannot be added to");
			}
			if (!_empty)
			{
				_sb.Append(',');
			}
			_empty = false;
		}

		private void Quote(string value)
		{
			_sb.Append('"');
			string text = value ?? "";
			foreach (char c in text)
			{
				switch (c)
				{
				case '"':
					_sb.Append("\\\"");
					continue;
				case '\\':
					_sb.Append("\\\\");
					continue;
				case '\n':
					_sb.Append("\\n");
					continue;
				case '\r':
					_sb.Append("\\r");
					continue;
				case '\t':
					_sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					StringBuilder stringBuilder = _sb.Append("\\u");
					int num = c;
					stringBuilder.Append(num.ToString("x4"));
				}
				else
				{
					_sb.Append(c);
				}
			}
			_sb.Append('"');
		}
	}
}