Decompiled source of TemporalEchoes v1.0.0

Mods/TemporalEchoes.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BoneLib;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Warehouse;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using TemporalEchoes;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(Core), "TemporalEchoes", "1.0.0", "CAitStudio", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonOptionalDependencies(new string[] { "BoneLib" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("TemporalEchoes")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("TemporalEchoes")]
[assembly: AssemblyTitle("TemporalEchoes")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 TemporalEchoes
{
	public class Core : MelonMod
	{
		private sealed class OwnedEchoLevel
		{
			public string Path;

			public LevelEchoFile File;

			public List<EchoMessage> Messages;
		}

		[CompilerGenerated]
		private sealed class <LoadEchoesAfterDelay>d__25 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public int token;

			public string barcode;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadEchoesAfterDelay>d__25(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(2.5f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if (!ModEnabled || token != _levelLoadToken || barcode != _currentBarcode)
					{
						return false;
					}
					LoadCurrentLevelFile();
					RenderCurrentLevelEchoes();
					MelonCoroutines.Start(DownloadCurrentLevelFromServer());
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const string ModName = "TemporalEchoes";

		private const float MenuActionCooldownSeconds = 5f;

		private const float EchoPopupSeconds = 5f;

		private const float StatusPopupSeconds = 2f;

		private const int LevelLoadDelayMs = 2500;

		private const float DuplicateEchoRadius = 0.5f;

		private const string ServerUrl = "https://temporal-echoes.3260142519.workers.dev";

		private static Core _instance;

		private static string _currentBarcode = string.Empty;

		private static string _currentLevelTitle = string.Empty;

		private static int _levelLoadToken;

		private static string _draftBody = string.Empty;

		private static LevelEchoFile _currentFile;

		private static float _lastCreateClickTime = -100f;

		private static float _lastReloadClickTime = -100f;

		private static EchoApiClient _api;

		private static EchoRenderer _renderer;

		private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
		{
			WriteIndented = true,
			PropertyNameCaseInsensitive = true
		};

		private static string DataDir => Path.Combine(MelonEnvironment.UserDataDirectory, "TemporalEchoes");

		private static string LevelsDir => Path.Combine(DataDir, "Levels");

		private static bool DebugInfoEnabled => EchoPreferences.DebugInfo;

		private static bool ModEnabled => EchoPreferences.ModEnabled;

		public override void OnInitializeMelon()
		{
			_instance = this;
			EchoPreferences.Setup();
			Directory.CreateDirectory(LevelsDir);
			_api = new EchoApiClient((Func<bool>)(() => ModEnabled), (Func<string>)NormalizeServerUrl, JsonOptions, (Action<string>)((MelonBase)this).LoggerInstance.Warning, (Action<string, string, NotificationType>)SendDebugNotification);
			_renderer = new EchoRenderer((Func<bool>)(() => ModEnabled), (Action<EchoMessage>)SendEchoNotification, (Action<string>)((MelonBase)this).LoggerInstance.Warning);
			_renderer.LoadIcons();
			EchoBoneMenu.Setup(SetModEnabled, delegate(string body)
			{
				_draftBody = body ?? string.Empty;
			}, CreateEchoFromMenu, ReloadCurrentLevel, RefreshMyEchoesMenu);
			RefreshMyEchoesMenu();
			Hooking.OnLevelLoaded += OnLevelLoaded;
			Hooking.OnLevelUnloaded += OnLevelUnloaded;
			((MelonBase)this).LoggerInstance.Msg("Initialized.");
		}

		public override void OnUpdate()
		{
			if (ModEnabled)
			{
				_renderer.Update();
			}
		}

		private static void OnLevelLoaded(LevelInfo info)
		{
			//IL_0001: 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)
			_currentBarcode = info.barcode ?? string.Empty;
			_currentLevelTitle = info.title ?? string.Empty;
			int token = ++_levelLoadToken;
			((MelonBase)_instance).LoggerInstance.Msg("Level loaded: " + _currentBarcode);
			if (!ModEnabled)
			{
				_renderer.Clear();
			}
			else
			{
				MelonCoroutines.Start(LoadEchoesAfterDelay(token, _currentBarcode));
			}
		}

		[IteratorStateMachine(typeof(<LoadEchoesAfterDelay>d__25))]
		private static IEnumerator LoadEchoesAfterDelay(int token, string barcode)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadEchoesAfterDelay>d__25(0)
			{
				token = token,
				barcode = barcode
			};
		}

		private static void OnLevelUnloaded()
		{
			_levelLoadToken++;
			_renderer.Clear();
			_currentBarcode = string.Empty;
			_currentLevelTitle = string.Empty;
			_currentFile = null;
		}

		private static void CreateEchoFromMenu()
		{
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			if (!ModEnabled || !TryStartMenuAction(ref _lastCreateClickTime))
			{
				return;
			}
			if (string.IsNullOrWhiteSpace(_currentBarcode))
			{
				SendNotification("Temporal Echoes", "No loaded level barcode yet.", 3f, (NotificationType)1);
				return;
			}
			string text = (_draftBody ?? string.Empty).Trim();
			if (string.IsNullOrWhiteSpace(text))
			{
				SendNotification("Temporal Echoes", "Message body is empty.", 3f, (NotificationType)1);
				return;
			}
			if (EchoContentFilter.TryFindSensitiveWord(text, out var word))
			{
				SendNotification("Temporal Echoes", "Blocked sensitive word: " + word, 2f, (NotificationType)1);
				return;
			}
			string echoAuthorName = GetEchoAuthorName();
			text = ComposeEchoBody(text, echoAuthorName);
			EnsureCurrentLevelFile();
			object obj;
			if (!((Object)(object)Player.Chest != (Object)null))
			{
				if (!((Object)(object)Player.Head != (Object)null))
				{
					RigManager rigManager = Player.RigManager;
					obj = ((rigManager != null) ? ((Component)rigManager).transform : null);
				}
				else
				{
					obj = Player.Head;
				}
			}
			else
			{
				obj = Player.Chest;
			}
			Transform val = (Transform)obj;
			if ((Object)(object)val == (Object)null)
			{
				SendNotification("Temporal Echoes", "Player transform not ready.", 3f, (NotificationType)1);
				return;
			}
			Vector3 position = val.position;
			if (HasNearbyEcho(position))
			{
				SendNotification("Temporal Echoes", "An echo already exists here.", 2f, (NotificationType)1);
				return;
			}
			string createdAt = DateTimeOffset.Now.ToString("o");
			string uuid = Guid.NewGuid().ToString("N");
			EchoMessage echoMessage = new EchoMessage
			{
				uuid = uuid,
				body = text,
				position = SerializableVector3.From(position),
				createdAt = createdAt,
				steamId = TryGetSteamId(),
				authorName = echoAuthorName,
				authorHash = CurrentAuthorHash(),
				color = EchoPreferences.BubbleColor.ToString(),
				syncState = "pending"
			};
			_currentFile.messages.Add(echoMessage);
			SaveCurrentLevelFile();
			_renderer.Render(echoMessage);
			RefreshMyEchoesMenu();
			SendNotification("Echo Created", text, 2f, (NotificationType)3);
			MelonCoroutines.Start(UploadEchoToServer(echoMessage));
		}

		private static void ReloadCurrentLevel()
		{
			if (ModEnabled && TryStartMenuAction(ref _lastReloadClickTime))
			{
				ReloadCurrentLevelNow();
				SendNotification("Temporal Echoes", "Reloading echoes.", 2f, (NotificationType)0);
			}
		}

		private static void ReloadCurrentLevelNow()
		{
			if (ModEnabled)
			{
				LoadCurrentLevelFile();
				RenderCurrentLevelEchoes();
				MelonCoroutines.Start(DownloadCurrentLevelFromServer());
			}
		}

		private static string NormalizeServerUrl()
		{
			return "https://temporal-echoes.3260142519.workers.dev".Trim().TrimEnd('/');
		}

		private static bool TryStartMenuAction(ref float lastClickTime)
		{
			float time = Time.time;
			if (time - lastClickTime < 5f)
			{
				return false;
			}
			lastClickTime = time;
			return true;
		}

		private static string CurrentAuthorHash()
		{
			return HashString(EchoPreferences.AuthorSecret);
		}

		private static void SetModEnabled(bool enabled)
		{
			EchoPreferences.SetModEnabled(enabled);
			if (!enabled)
			{
				_levelLoadToken++;
				_renderer.Clear();
			}
			else
			{
				ReloadCurrentLevelNow();
			}
		}

		private static string GetEchoAuthorName()
		{
			string text = EchoText.Limit(EchoPreferences.AuthorName.Trim(), 50);
			if (!string.Equals(text, "NullMan", StringComparison.Ordinal))
			{
				return text;
			}
			string text2 = EchoText.Limit(TryGetSteamName().Trim(), 50);
			if (string.IsNullOrWhiteSpace(text2))
			{
				return text;
			}
			EchoPreferences.SetAuthorName(text2);
			return text2;
		}

		private static string ComposeEchoBody(string body, string authorName)
		{
			int maxLength = Math.Max(0, 180 - authorName.Length - 1);
			string value = authorName + ":" + LimitText(body, maxLength);
			return LimitText(value, 180);
		}

		private static string LimitText(string value, int maxLength)
		{
			return EchoText.Limit(value, maxLength);
		}

		private static void RenderCurrentLevelEchoes()
		{
			_renderer.RenderLevel(_currentFile);
		}

		private static void SendDebugNotification(string title, string message, NotificationType type)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			if (DebugInfoEnabled)
			{
				SendNotification(title, message, 2f, type);
			}
		}

		private static string TryGetSteamId()
		{
			try
			{
				return (((from assembly in AppDomain.CurrentDomain.GetAssemblies()
					select assembly.GetType("Steamworks.SteamClient")).FirstOrDefault((Type type) => type != null)?.GetProperty("SteamId", BindingFlags.Static | BindingFlags.Public))?.GetValue(null))?.ToString() ?? string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string TryGetSteamName()
		{
			try
			{
				return (((from assembly in AppDomain.CurrentDomain.GetAssemblies()
					select assembly.GetType("Steamworks.SteamClient")).FirstOrDefault((Type type) => type != null)?.GetProperty("Name", BindingFlags.Static | BindingFlags.Public))?.GetValue(null))?.ToString() ?? string.Empty;
			}
			catch
			{
				return string.Empty;
			}
		}

		private static string FormatNotificationTitle(string createdAt)
		{
			if (DateTimeOffset.TryParse(createdAt, out var result))
			{
				return result.ToString("yyyy/MM/dd HH:mm zzz");
			}
			return "Temporal Echo";
		}

		private static void SendNotification(string title, string message, float seconds, NotificationType type)
		{
			//IL_0001: 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_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit(title),
				Message = NotificationText.op_Implicit(message),
				Type = type,
				ShowTitleOnPopup = true,
				PopupLength = seconds
			});
		}

		private static void SendEchoNotification(EchoMessage message)
		{
			//IL_0001: 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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit(FormatNotificationTitle(message.createdAt)),
				Message = NotificationText.op_Implicit(message.body),
				Type = (NotificationType)0,
				ShowTitleOnPopup = true,
				PopupLength = 5f
			});
		}

		private static void RefreshMyEchoesMenu()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			Page myEchoesPage = EchoBoneMenu.MyEchoesPage;
			if (myEchoesPage == null)
			{
				return;
			}
			myEchoesPage.RemoveAll();
			myEchoesPage.CreateFunction("Refresh List", Color.white, (Action)RefreshMyEchoesMenu);
			List<OwnedEchoLevel> ownedEchoLevels = GetOwnedEchoLevels();
			if (ownedEchoLevels.Count == 0)
			{
				myEchoesPage.CreateFunction("No echoes found", Color.gray, (Action)null);
				return;
			}
			foreach (OwnedEchoLevel level2 in ownedEchoLevels.OrderBy<OwnedEchoLevel, string>((OwnedEchoLevel level) => GetLevelMenuName(level.File), StringComparer.OrdinalIgnoreCase))
			{
				string text = $"{GetLevelMenuName(level2.File)} ({level2.Messages.Count})";
				Page val = myEchoesPage.CreatePage(text, Color.white, 10, true);
				val.CreateFunction("Show Barcode", Color.gray, (Action)delegate
				{
					Menu.DisplayDialog("Level Barcode", level2.File.levelBarcode, (Texture2D)null, (Action)null, (Action)null);
				});
				foreach (EchoMessage item in level2.Messages.OrderByDescending((EchoMessage message) => message.createdAt))
				{
					EchoMessage capturedMessage = item;
					string capturedPath = level2.Path;
					string capturedBarcode = level2.File.levelBarcode;
					string echoMenuTitle = GetEchoMenuTitle(capturedMessage);
					Page val2 = val.CreatePage(echoMenuTitle, Color.cyan, 10, true);
					val2.CreateFunction("Show Message", Color.white, (Action)delegate
					{
						Menu.DisplayDialog("Temporal Echo", capturedMessage.body, (Texture2D)null, (Action)null, (Action)null);
					});
					val2.CreateFunction("Delete", Color.yellow, (Action)delegate
					{
						AskDeleteOwnedEcho(capturedPath, capturedBarcode, capturedMessage);
					});
				}
			}
		}

		private static List<OwnedEchoLevel> GetOwnedEchoLevels()
		{
			List<OwnedEchoLevel> list = new List<OwnedEchoLevel>();
			string authorHash = CurrentAuthorHash();
			if (string.IsNullOrWhiteSpace(authorHash) || !Directory.Exists(LevelsDir))
			{
				return list;
			}
			string[] files = Directory.GetFiles(LevelsDir, "*.json");
			foreach (string text in files)
			{
				try
				{
					LevelEchoFile levelEchoFile = JsonSerializer.Deserialize<LevelEchoFile>(File.ReadAllText(text), JsonOptions);
					if (levelEchoFile?.messages != null)
					{
						List<EchoMessage> list2 = levelEchoFile.messages.Where((EchoMessage message) => string.Equals(message.authorHash, authorHash, StringComparison.OrdinalIgnoreCase)).ToList();
						if (list2.Count != 0)
						{
							list.Add(new OwnedEchoLevel
							{
								Path = text,
								File = levelEchoFile,
								Messages = list2
							});
						}
					}
				}
				catch (Exception ex)
				{
					((MelonBase)_instance).LoggerInstance.Warning("Could not read echo file " + text + ": " + ex.Message);
				}
			}
			return list;
		}

		private static void AskDeleteOwnedEcho(string path, string levelBarcode, EchoMessage message)
		{
			string text = EchoText.Limit(message.body, 120);
			Menu.DisplayDialog("Delete echo?", "This will delete your echo:\n" + text, Dialog.WarningIcon, (Action)delegate
			{
				DeleteOwnedEcho(path, levelBarcode, message.uuid);
			}, (Action)null);
		}

		private static void DeleteOwnedEcho(string path, string levelBarcode, string uuid)
		{
			if (!DeleteOwnedEchoLocal(path, uuid))
			{
				SendNotification("Temporal Echoes", "Echo was not found or is not yours.", 2f, (NotificationType)1);
				return;
			}
			if (string.Equals(levelBarcode, _currentBarcode, StringComparison.Ordinal))
			{
				LoadCurrentLevelFile();
				RenderCurrentLevelEchoes();
			}
			RefreshMyEchoesMenu();
			Menu.OpenPage(EchoBoneMenu.MyEchoesPage);
			SendNotification("Echo Deleted", uuid, 2f, (NotificationType)3);
			MelonCoroutines.Start(DeleteEchoFromServer(levelBarcode, uuid));
		}

		private static bool DeleteOwnedEchoLocal(string path, string uuid)
		{
			try
			{
				LevelEchoFile levelEchoFile = JsonSerializer.Deserialize<LevelEchoFile>(File.ReadAllText(path), JsonOptions);
				if (levelEchoFile?.messages == null)
				{
					return false;
				}
				string authorHash = CurrentAuthorHash();
				int num = levelEchoFile.messages.RemoveAll((EchoMessage message) => string.Equals(message.uuid, uuid, StringComparison.OrdinalIgnoreCase) && string.Equals(message.authorHash, authorHash, StringComparison.OrdinalIgnoreCase));
				if (num <= 0)
				{
					return false;
				}
				File.WriteAllText(path, JsonSerializer.Serialize(levelEchoFile, JsonOptions));
				return true;
			}
			catch (Exception ex)
			{
				((MelonBase)_instance).LoggerInstance.Warning("Could not delete echo " + uuid + ": " + ex.Message);
				return false;
			}
		}

		private static string GetLevelMenuName(LevelEchoFile file)
		{
			string value = TryGetLevelTitle(file.levelBarcode);
			if (!string.IsNullOrWhiteSpace(value))
			{
				return TrimMenuText(value, 34);
			}
			if (!string.IsNullOrWhiteSpace(file.levelTitle))
			{
				return TrimMenuText(file.levelTitle, 34);
			}
			if (!string.IsNullOrWhiteSpace(file.levelBarcode))
			{
				return TrimMenuText(file.levelBarcode, 34);
			}
			return string.IsNullOrWhiteSpace(file.levelHash) ? "Unknown Level" : file.levelHash;
		}

		private static string TryGetLevelTitle(string barcode)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			if (string.IsNullOrWhiteSpace(barcode))
			{
				return string.Empty;
			}
			try
			{
				Crate val = default(Crate);
				if (AssetWarehouse.Instance != null && AssetWarehouse.Instance.TryGetCrate(new Barcode(barcode), ref val))
				{
					return ((val != null) ? ((Scannable)val).Title : null) ?? string.Empty;
				}
			}
			catch
			{
				return string.Empty;
			}
			return string.Empty;
		}

		private static string GetEchoMenuTitle(EchoMessage message)
		{
			string text = message.createdAt;
			if (DateTimeOffset.TryParse(message.createdAt, out var result))
			{
				text = result.ToString("MM/dd HH:mm");
			}
			return TrimMenuText(text + " " + message.body, 42);
		}

		private static string TrimMenuText(string value, int maxLength)
		{
			value = (value ?? string.Empty).Replace('\n', ' ').Replace('\r', ' ').Trim();
			if (value.Length <= maxLength)
			{
				return value;
			}
			return value.Substring(0, Math.Max(0, maxLength - 3)) + "...";
		}

		private static void LoadCurrentLevelFile()
		{
			if (string.IsNullOrWhiteSpace(_currentBarcode))
			{
				_currentFile = null;
				return;
			}
			string levelFilePath = GetLevelFilePath(_currentBarcode);
			if (!File.Exists(levelFilePath))
			{
				_currentFile = new LevelEchoFile
				{
					levelBarcode = _currentBarcode,
					levelHash = HashBarcode(_currentBarcode),
					levelTitle = _currentLevelTitle,
					messages = new List<EchoMessage>()
				};
				SaveCurrentLevelFile();
				return;
			}
			try
			{
				_currentFile = JsonSerializer.Deserialize<LevelEchoFile>(File.ReadAllText(levelFilePath), JsonOptions) ?? new LevelEchoFile();
				_currentFile.levelBarcode = _currentBarcode;
				_currentFile.levelHash = HashBarcode(_currentBarcode);
				if (!string.IsNullOrWhiteSpace(_currentLevelTitle))
				{
					_currentFile.levelTitle = _currentLevelTitle;
				}
				LevelEchoFile currentFile = _currentFile;
				if (currentFile.messages == null)
				{
					List<EchoMessage> list2 = (currentFile.messages = new List<EchoMessage>());
				}
			}
			catch (Exception value)
			{
				((MelonBase)_instance).LoggerInstance.Error($"Failed to read echo file {levelFilePath}: {value}");
				_currentFile = new LevelEchoFile
				{
					levelBarcode = _currentBarcode,
					levelHash = HashBarcode(_currentBarcode),
					levelTitle = _currentLevelTitle,
					messages = new List<EchoMessage>()
				};
			}
		}

		private static void EnsureCurrentLevelFile()
		{
			if (_currentFile == null)
			{
				LoadCurrentLevelFile();
			}
		}

		private static void SaveCurrentLevelFile()
		{
			if (_currentFile != null && !string.IsNullOrWhiteSpace(_currentBarcode))
			{
				Directory.CreateDirectory(LevelsDir);
				if (!string.IsNullOrWhiteSpace(_currentLevelTitle))
				{
					_currentFile.levelTitle = _currentLevelTitle;
				}
				File.WriteAllText(GetLevelFilePath(_currentBarcode), JsonSerializer.Serialize(_currentFile, JsonOptions));
			}
		}

		private static bool HasNearbyEcho(Vector3 position)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (_currentFile?.messages == null)
			{
				return false;
			}
			foreach (EchoMessage message in _currentFile.messages)
			{
				if (Vector3.Distance(message.position.ToVector3(), position) < 0.5f)
				{
					return true;
				}
			}
			return false;
		}

		private static string GetLevelFilePath(string barcode)
		{
			return Path.Combine(LevelsDir, HashBarcode(barcode) + ".json");
		}

		private static string HashBarcode(string barcode)
		{
			return HashString(barcode).Substring(0, 16);
		}

		private static string HashString(string value)
		{
			using SHA256 sHA = SHA256.Create();
			byte[] inArray = sHA.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty));
			return Convert.ToHexString(inArray);
		}

		private static IEnumerator DeleteEchoFromServer(string levelBarcode, string uuid)
		{
			return _api.DeleteEcho(levelBarcode, uuid, EchoPreferences.AuthorSecret);
		}

		private static IEnumerator DownloadCurrentLevelFromServer()
		{
			return _api.DownloadLevel(_currentBarcode, delegate(LevelEchoFile downloaded)
			{
				_currentFile = downloaded;
				_currentFile.levelBarcode = _currentBarcode;
				_currentFile.levelHash = HashBarcode(_currentBarcode);
				_currentFile.levelTitle = _currentLevelTitle;
				SaveCurrentLevelFile();
				RenderCurrentLevelEchoes();
			});
		}

		private static IEnumerator UploadEchoToServer(EchoMessage echo)
		{
			return _api.UploadEcho(_currentBarcode, HashBarcode(_currentBarcode), echo, delegate
			{
				echo.syncState = "synced";
				SaveCurrentLevelFile();
			});
		}
	}
	internal sealed class EchoApiClient
	{
		[CompilerGenerated]
		private sealed class <DeleteEcho>d__8 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string levelBarcode;

			public string uuid;

			public string authorSecret;

			public EchoApiClient <>4__this;

			private string <baseUrl>5__1;

			private EchoDeleteRequest <payload>5__2;

			private HttpClientHandler <handler>5__3;

			private HttpClient <client>5__4;

			private StringContent <content>5__5;

			private HttpRequestMessage <request>5__6;

			private Task<HttpResponseMessage> <responseTask>5__7;

			private Exception <ex>5__8;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DeleteEcho>d__8(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if ((uint)(num - -6) <= 3u || num == 1)
				{
					try
					{
						if ((uint)(num - -6) <= 2u || num == 1)
						{
							try
							{
								if ((uint)(num - -6) <= 1u || num == 1)
								{
									try
									{
										if (num == -6 || num == 1)
										{
											try
											{
											}
											finally
											{
												<>m__Finally4();
											}
										}
									}
									finally
									{
										<>m__Finally3();
									}
								}
							}
							finally
							{
								<>m__Finally2();
							}
						}
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<baseUrl>5__1 = null;
				<payload>5__2 = null;
				<handler>5__3 = null;
				<client>5__4 = null;
				<content>5__5 = null;
				<request>5__6 = null;
				<responseTask>5__7 = null;
				<ex>5__8 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				bool result;
				try
				{
					switch (<>1__state)
					{
					default:
						result = false;
						goto end_IL_0000;
					case 0:
						<>1__state = -1;
						<baseUrl>5__1 = <>4__this._baseUrl();
						if (!<>4__this._isEnabled() || string.IsNullOrWhiteSpace(<baseUrl>5__1) || string.IsNullOrWhiteSpace(levelBarcode) || string.IsNullOrWhiteSpace(uuid))
						{
							result = false;
						}
						else
						{
							<payload>5__2 = new EchoDeleteRequest
							{
								levelBarcode = levelBarcode,
								uuid = uuid,
								authorSecret = authorSecret
							};
							<handler>5__3 = CreateHttpHandler();
							<>1__state = -3;
							<client>5__4 = new HttpClient(<handler>5__3)
							{
								Timeout = TimeSpan.FromSeconds(20.0)
							};
							<>1__state = -4;
							<content>5__5 = new StringContent(JsonSerializer.Serialize(<payload>5__2, <>4__this._jsonOptions), Encoding.UTF8, "application/json");
							<>1__state = -5;
							<request>5__6 = new HttpRequestMessage(HttpMethod.Delete, <baseUrl>5__1 + "/api/messages")
							{
								Content = <content>5__5
							};
							<>1__state = -6;
							try
							{
								<responseTask>5__7 = <client>5__4.SendAsync(<request>5__6);
							}
							catch (Exception ex)
							{
								<ex>5__8 = ex;
								<>4__this.ReportHttpFailure("Delete", uuid, <ex>5__8.Message);
								result = false;
								break;
							}
							<>2__current = <>4__this.WaitForResponse(<responseTask>5__7);
							<>1__state = 1;
							result = true;
						}
						goto end_IL_0000;
					case 1:
						<>1__state = -6;
						if (!<>4__this._isEnabled() || !<>4__this.TryAcceptResponse(<responseTask>5__7, "Delete", uuid))
						{
							result = false;
							break;
						}
						<>4__this._debugNotify("Echo Deleted Online", uuid, (NotificationType)3);
						result = false;
						break;
					}
					<>m__Finally4();
					<>m__Finally3();
					<>m__Finally2();
					<>m__Finally1();
					end_IL_0000:;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
				return result;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<handler>5__3 != null)
				{
					((IDisposable)<handler>5__3).Dispose();
				}
			}

			private void <>m__Finally2()
			{
				<>1__state = -3;
				if (<client>5__4 != null)
				{
					((IDisposable)<client>5__4).Dispose();
				}
			}

			private void <>m__Finally3()
			{
				<>1__state = -4;
				if (<content>5__5 != null)
				{
					((IDisposable)<content>5__5).Dispose();
				}
			}

			private void <>m__Finally4()
			{
				<>1__state = -5;
				if (<request>5__6 != null)
				{
					((IDisposable)<request>5__6).Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <DownloadLevel>d__6 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string barcode;

			public Action<LevelEchoFile> applyDownloadedFile;

			public EchoApiClient <>4__this;

			private string <baseUrl>5__1;

			private string <url>5__2;

			private UnityWebRequest <request>5__3;

			private string <reason>5__4;

			private LevelEchoFile <downloaded>5__5;

			private Exception <ex>5__6;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <DownloadLevel>d__6(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<baseUrl>5__1 = null;
				<url>5__2 = null;
				<request>5__3 = null;
				<reason>5__4 = null;
				<downloaded>5__5 = null;
				<ex>5__6 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<baseUrl>5__1 = <>4__this._baseUrl();
					if (!<>4__this._isEnabled() || string.IsNullOrWhiteSpace(<baseUrl>5__1) || string.IsNullOrWhiteSpace(barcode))
					{
						return false;
					}
					<url>5__2 = <baseUrl>5__1 + "/api/messages?barcode=" + Uri.EscapeDataString(barcode);
					<request>5__3 = UnityWebRequest.Get(<url>5__2);
					<request>5__3.timeout = 20;
					<>2__current = <request>5__3.SendWebRequest();
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if (!<>4__this._isEnabled())
					{
						return false;
					}
					if (!IsWebRequestSuccess(<request>5__3))
					{
						<reason>5__4 = $"HTTP {<request>5__3.responseCode} {<request>5__3.error}";
						<>4__this._warn("Download skipped/failed: " + <reason>5__4);
						<>4__this._debugNotify("Echo Download Failed", <reason>5__4, (NotificationType)2);
						return false;
					}
					try
					{
						<downloaded>5__5 = JsonSerializer.Deserialize<LevelEchoFile>(<request>5__3.downloadHandler.text, <>4__this._jsonOptions);
						if (<downloaded>5__5?.messages == null)
						{
							return false;
						}
						applyDownloadedFile(<downloaded>5__5);
						<>4__this._debugNotify("Echo Downloaded", $"{<downloaded>5__5.messages.Count} echoes loaded.", (NotificationType)3);
						<downloaded>5__5 = null;
					}
					catch (Exception ex)
					{
						<ex>5__6 = ex;
						<>4__this._warn("Download skipped/failed: " + <ex>5__6.Message);
						<>4__this._debugNotify("Echo Download Failed", <ex>5__6.Message, (NotificationType)2);
					}
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <UploadEcho>d__7 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string levelBarcode;

			public string levelHash;

			public EchoMessage echo;

			public Action onSuccess;

			public EchoApiClient <>4__this;

			private string <baseUrl>5__1;

			private EchoUpload <payload>5__2;

			private HttpClientHandler <handler>5__3;

			private HttpClient <client>5__4;

			private StringContent <content>5__5;

			private Task<HttpResponseMessage> <responseTask>5__6;

			private Exception <ex>5__7;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <UploadEcho>d__7(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if ((uint)(num - -5) <= 2u || num == 1)
				{
					try
					{
						if ((uint)(num - -5) <= 1u || num == 1)
						{
							try
							{
								if (num == -5 || num == 1)
								{
									try
									{
									}
									finally
									{
										<>m__Finally3();
									}
								}
							}
							finally
							{
								<>m__Finally2();
							}
						}
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<baseUrl>5__1 = null;
				<payload>5__2 = null;
				<handler>5__3 = null;
				<client>5__4 = null;
				<content>5__5 = null;
				<responseTask>5__6 = null;
				<ex>5__7 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				bool result;
				try
				{
					switch (<>1__state)
					{
					default:
						result = false;
						goto end_IL_0000;
					case 0:
						<>1__state = -1;
						<baseUrl>5__1 = <>4__this._baseUrl();
						if (!<>4__this._isEnabled() || string.IsNullOrWhiteSpace(<baseUrl>5__1))
						{
							result = false;
						}
						else
						{
							<payload>5__2 = new EchoUpload
							{
								levelBarcode = levelBarcode,
								levelHash = levelHash,
								message = echo
							};
							<handler>5__3 = CreateHttpHandler();
							<>1__state = -3;
							<client>5__4 = new HttpClient(<handler>5__3)
							{
								Timeout = TimeSpan.FromSeconds(20.0)
							};
							<>1__state = -4;
							<content>5__5 = new StringContent(JsonSerializer.Serialize(<payload>5__2, <>4__this._jsonOptions), Encoding.UTF8, "application/json");
							<>1__state = -5;
							try
							{
								<responseTask>5__6 = <client>5__4.PostAsync(<baseUrl>5__1 + "/api/messages", <content>5__5);
							}
							catch (Exception ex)
							{
								<ex>5__7 = ex;
								<>4__this.ReportHttpFailure("Upload", echo.uuid, <ex>5__7.Message);
								result = false;
								break;
							}
							<>2__current = <>4__this.WaitForResponse(<responseTask>5__6);
							<>1__state = 1;
							result = true;
						}
						goto end_IL_0000;
					case 1:
						<>1__state = -5;
						if (!<>4__this._isEnabled())
						{
							result = false;
							break;
						}
						if (!<>4__this.TryAcceptResponse(<responseTask>5__6, "Upload", echo.uuid))
						{
							result = false;
							break;
						}
						onSuccess();
						<>4__this._debugNotify("Echo Uploaded", echo.uuid, (NotificationType)3);
						result = false;
						break;
					}
					<>m__Finally3();
					<>m__Finally2();
					<>m__Finally1();
					end_IL_0000:;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
				return result;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<handler>5__3 != null)
				{
					((IDisposable)<handler>5__3).Dispose();
				}
			}

			private void <>m__Finally2()
			{
				<>1__state = -3;
				if (<client>5__4 != null)
				{
					((IDisposable)<client>5__4).Dispose();
				}
			}

			private void <>m__Finally3()
			{
				<>1__state = -4;
				if (<content>5__5 != null)
				{
					((IDisposable)<content>5__5).Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <WaitForResponse>d__9 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public Task<HttpResponseMessage> responseTask;

			public EchoApiClient <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <WaitForResponse>d__9(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (!responseTask.IsCompleted)
				{
					if (!<>4__this._isEnabled())
					{
						return false;
					}
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private readonly Func<bool> _isEnabled;

		private readonly Func<string> _baseUrl;

		private readonly JsonSerializerOptions _jsonOptions;

		private readonly Action<string> _warn;

		private readonly Action<string, string, NotificationType> _debugNotify;

		public EchoApiClient(Func<bool> isEnabled, Func<string> baseUrl, JsonSerializerOptions jsonOptions, Action<string> warn, Action<string, string, NotificationType> debugNotify)
		{
			_isEnabled = isEnabled;
			_baseUrl = baseUrl;
			_jsonOptions = jsonOptions;
			_warn = warn;
			_debugNotify = debugNotify;
		}

		[IteratorStateMachine(typeof(<DownloadLevel>d__6))]
		public IEnumerator DownloadLevel(string barcode, Action<LevelEchoFile> applyDownloadedFile)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DownloadLevel>d__6(0)
			{
				<>4__this = this,
				barcode = barcode,
				applyDownloadedFile = applyDownloadedFile
			};
		}

		[IteratorStateMachine(typeof(<UploadEcho>d__7))]
		public IEnumerator UploadEcho(string levelBarcode, string levelHash, EchoMessage echo, Action onSuccess)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <UploadEcho>d__7(0)
			{
				<>4__this = this,
				levelBarcode = levelBarcode,
				levelHash = levelHash,
				echo = echo,
				onSuccess = onSuccess
			};
		}

		[IteratorStateMachine(typeof(<DeleteEcho>d__8))]
		public IEnumerator DeleteEcho(string levelBarcode, string uuid, string authorSecret)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <DeleteEcho>d__8(0)
			{
				<>4__this = this,
				levelBarcode = levelBarcode,
				uuid = uuid,
				authorSecret = authorSecret
			};
		}

		[IteratorStateMachine(typeof(<WaitForResponse>d__9))]
		private IEnumerator WaitForResponse(Task<HttpResponseMessage> responseTask)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WaitForResponse>d__9(0)
			{
				<>4__this = this,
				responseTask = responseTask
			};
		}

		private bool TryAcceptResponse(Task<HttpResponseMessage> responseTask, string operation, string uuid)
		{
			if (!responseTask.IsCompletedSuccessfully)
			{
				string reason = responseTask.Exception?.GetBaseException().Message ?? "Unknown error";
				ReportHttpFailure(operation, uuid, reason);
				return false;
			}
			HttpResponseMessage result = responseTask.Result;
			if (result.IsSuccessStatusCode)
			{
				return true;
			}
			string reason2 = $"HTTP {result.StatusCode} {result.ReasonPhrase}";
			ReportHttpFailure(operation, uuid, reason2);
			return false;
		}

		private void ReportHttpFailure(string operation, string uuid, string reason)
		{
			_warn($"{operation} failed for {uuid}: {reason}");
			_debugNotify("Echo " + operation + " Failed", reason, (NotificationType)2);
		}

		private static bool IsWebRequestSuccess(UnityWebRequest request)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Invalid comparison between Unknown and I4
			return (int)request.result == 1 && request.responseCode >= 200 && request.responseCode < 300;
		}

		private static HttpClientHandler CreateHttpHandler()
		{
			return new HttpClientHandler
			{
				ClientCertificateOptions = ClientCertificateOption.Automatic,
				ServerCertificateCustomValidationCallback = (HttpRequestMessage _, X509Certificate2? _, X509Chain? _, SslPolicyErrors _) => true
			};
		}
	}
	internal static class EchoBoneMenu
	{
		private const string DiscordUrl = "https://discord.gg/Qf3r2zKwx";

		public static Page MyEchoesPage { get; private set; }

		public static void Setup(Action<bool> setModEnabled, Action<string> setDraftBody, Action createEcho, Action reloadEchoes, Action refreshMyEchoes)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: 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_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: 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_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			Page val = Page.Root.CreatePage("Temporal Echoes", Color.white, 0, true);
			val.CreateBool("Enable Mod", Color.green, EchoPreferences.ModEnabled, setModEnabled);
			val.CreateString("Your Name", Color.cyan, EchoPreferences.AuthorName, (Action<string>)EchoPreferences.SetAuthorName);
			val.CreateEnum("Echo Color", Color.white, (Enum)EchoPreferences.BubbleColor, (Action<Enum>)EchoPreferences.SetBubbleColor);
			val.CreateString("Message Body", Color.white, string.Empty, setDraftBody);
			val.CreateBool("Debug Info", Color.cyan, EchoPreferences.DebugInfo, (Action<bool>)EchoPreferences.SetDebugInfo);
			val.CreateFunction("Create Echo", Color.green, createEcho);
			val.CreateFunction("Reload Echoes", Color.yellow, reloadEchoes);
			val.CreateFunction("Join Discord", Color.blue, (Action)delegate
			{
				Application.OpenURL("https://discord.gg/Qf3r2zKwx");
			});
			Page val2 = val.CreatePage("Sensitive Words", Color.red, 10, true);
			string[] sensitiveWords = EchoContentFilter.SensitiveWords;
			foreach (string text in sensitiveWords)
			{
				val2.CreateFunction(text, Color.red, (Action)null);
			}
			MyEchoesPage = val.CreatePage("My Echoes", Color.cyan, 10, true);
			MyEchoesPage.CreateFunction("Refresh List", Color.white, refreshMyEchoes);
		}
	}
	public enum EchoBubbleColor
	{
		Red,
		Orange,
		Yellow,
		Green,
		Cyan,
		Blue,
		Purple
	}
	internal static class EchoBubbleColors
	{
		public static EchoBubbleColor Parse(string value)
		{
			EchoBubbleColor result;
			return (!Enum.TryParse<EchoBubbleColor>(value, ignoreCase: true, out result)) ? EchoBubbleColor.Orange : result;
		}

		public static Color LightColor(EchoBubbleColor color)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			if (1 == 0)
			{
			}
			Color result = (Color)(color switch
			{
				EchoBubbleColor.Red => new Color(1f, 0.22f, 0.22f), 
				EchoBubbleColor.Yellow => new Color(1f, 0.86f, 0.2f), 
				EchoBubbleColor.Green => new Color(0.32f, 0.95f, 0.38f), 
				EchoBubbleColor.Cyan => new Color(0.2f, 0.86f, 1f), 
				EchoBubbleColor.Blue => new Color(0.3f, 0.52f, 1f), 
				EchoBubbleColor.Purple => new Color(0.78f, 0.38f, 1f), 
				_ => new Color(1f, 0.72f, 0.22f), 
			});
			if (1 == 0)
			{
			}
			return result;
		}

		public static string ResourceName(EchoBubbleColor color)
		{
			if (1 == 0)
			{
			}
			string text = color switch
			{
				EchoBubbleColor.Red => "echo-chat-red.png", 
				EchoBubbleColor.Yellow => "echo-chat-yellow.png", 
				EchoBubbleColor.Green => "echo-chat-green.png", 
				EchoBubbleColor.Cyan => "echo-chat-cyan.png", 
				EchoBubbleColor.Blue => "echo-chat-blue.png", 
				EchoBubbleColor.Purple => "echo-chat-purple.png", 
				_ => "echo-chat.png", 
			};
			if (1 == 0)
			{
			}
			string text2 = text;
			return "TemporalEchoes.Assets." + text2;
		}
	}
	internal static class EchoContentFilter
	{
		public static readonly string[] SensitiveWords = new string[26]
		{
			"fuck", "porn", "ponr", "dick", "vagina", "NSFW", "ejaculation", "nigger", "nigga", "faggot",
			"retard", "kike", "chink", "spic", "password", "passwd", "pwd", "token", "api_key", "apikey",
			"secret", "private_key", "discord.gg", "http://", "https://", "www."
		};

		public static bool TryFindSensitiveWord(string text, out string word)
		{
			if (text == null)
			{
				text = string.Empty;
			}
			string[] sensitiveWords = SensitiveWords;
			foreach (string text2 in sensitiveWords)
			{
				if (text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					word = text2;
					return true;
				}
			}
			word = string.Empty;
			return false;
		}
	}
	internal static class EchoLimits
	{
		public const int MaxAuthorNameLength = 50;

		public const int MaxEchoBodyLength = 180;
	}
	public class LevelEchoFile
	{
		public string levelBarcode { get; set; } = string.Empty;


		public string levelHash { get; set; } = string.Empty;


		public string levelTitle { get; set; } = string.Empty;


		public List<EchoMessage> messages { get; set; } = new List<EchoMessage>();

	}
	public class EchoMessage
	{
		public string uuid { get; set; } = string.Empty;


		public string body { get; set; } = string.Empty;


		public SerializableVector3 position { get; set; } = new SerializableVector3();


		public string createdAt { get; set; } = string.Empty;


		public string steamId { get; set; } = string.Empty;


		public string authorName { get; set; } = string.Empty;


		public string authorHash { get; set; } = string.Empty;


		public string color { get; set; } = string.Empty;


		public string syncState { get; set; } = "pending";

	}
	public class SerializableVector3
	{
		public float x { get; set; }

		public float y { get; set; }

		public float z { get; set; }

		public static SerializableVector3 From(Vector3 value)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			return new SerializableVector3
			{
				x = value.x,
				y = value.y,
				z = value.z
			};
		}

		public Vector3 ToVector3()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(x, y, z);
		}
	}
	public class EchoUpload
	{
		public string levelBarcode { get; set; } = string.Empty;


		public string levelHash { get; set; } = string.Empty;


		public EchoMessage message { get; set; }
	}
	public class EchoDeleteRequest
	{
		public string levelBarcode { get; set; } = string.Empty;


		public string uuid { get; set; } = string.Empty;


		public string authorSecret { get; set; } = string.Empty;

	}
	internal class EchoMarker
	{
		public EchoMessage Message;

		public GameObject Root;

		public Vector3 BasePosition;

		public float FloatPhase;

		public float LastNotifyTime;
	}
	internal static class EchoPreferences
	{
		private const string CategoryName = "TemporalEchoes";

		public const string DefaultAuthorName = "NullMan";

		private static MelonPreferences_Category _category;

		private static MelonPreferences_Entry<bool> _modEnabled;

		private static MelonPreferences_Entry<bool> _debugInfo;

		private static MelonPreferences_Entry<string> _authorName;

		private static MelonPreferences_Entry<string> _authorSecret;

		private static MelonPreferences_Entry<string> _bubbleColor;

		public static bool ModEnabled => _modEnabled?.Value ?? true;

		public static bool DebugInfo => _debugInfo?.Value ?? false;

		public static string AuthorName => _authorName?.Value ?? "NullMan";

		public static string AuthorSecret => _authorSecret?.Value ?? string.Empty;

		public static EchoBubbleColor BubbleColor => EchoBubbleColors.Parse(_bubbleColor?.Value);

		public static void Setup()
		{
			_category = MelonPreferences.CreateCategory("TemporalEchoes", "Temporal Echoes");
			_modEnabled = _category.CreateEntry<bool>("ModEnabled", true, "Enable Mod", (string)null, false, false, (ValueValidator)null, (string)null);
			_debugInfo = _category.CreateEntry<bool>("DebugInfo", false, "Debug Info", (string)null, false, false, (ValueValidator)null, (string)null);
			_authorName = _category.CreateEntry<string>("AuthorName", "NullMan", "Your Name", (string)null, false, false, (ValueValidator)null, (string)null);
			_authorSecret = _category.CreateEntry<string>("AuthorSecret", string.Empty, "Author Secret", (string)null, false, false, (ValueValidator)null, (string)null);
			_bubbleColor = _category.CreateEntry<string>("BubbleColor", EchoBubbleColor.Orange.ToString(), "Bubble Color", (string)null, false, false, (ValueValidator)null, (string)null);
			if (string.IsNullOrWhiteSpace(_authorSecret.Value))
			{
				_authorSecret.Value = Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N");
				MelonPreferences.Save();
			}
		}

		public static void SetModEnabled(bool value)
		{
			_modEnabled.Value = value;
			MelonPreferences.Save();
		}

		public static void SetDebugInfo(bool value)
		{
			_debugInfo.Value = value;
			MelonPreferences.Save();
		}

		public static void SetAuthorName(string value)
		{
			_authorName.Value = EchoText.Limit(value ?? string.Empty, 50);
			MelonPreferences.Save();
		}

		public static void SetBubbleColor(Enum value)
		{
			_bubbleColor.Value = ((EchoBubbleColor)(object)value).ToString();
			MelonPreferences.Save();
		}
	}
	internal sealed class EchoRenderer
	{
		private const float FloatAmplitude = 0.12f;

		private const float FloatSpeed = 1.4f;

		private const float SpinSpeed = 55f;

		private const float TouchRadius = 0.35f;

		private const float NotifyCooldownSeconds = 6f;

		private readonly Func<bool> _isEnabled;

		private readonly Action<EchoMessage> _onEchoTouched;

		private readonly Action<string> _logWarning;

		private readonly List<EchoMarker> _markers = new List<EchoMarker>();

		private readonly Dictionary<EchoBubbleColor, Texture2D> _textures = new Dictionary<EchoBubbleColor, Texture2D>();

		private readonly Dictionary<EchoBubbleColor, Sprite> _sprites = new Dictionary<EchoBubbleColor, Sprite>();

		private float _lastNotifyTime = -100f;

		public EchoRenderer(Func<bool> isEnabled, Action<EchoMessage> onEchoTouched, Action<string> logWarning)
		{
			_isEnabled = isEnabled;
			_onEchoTouched = onEchoTouched;
			_logWarning = logWarning;
		}

		public void LoadIcons()
		{
			foreach (EchoBubbleColor value in Enum.GetValues(typeof(EchoBubbleColor)))
			{
				Texture2D val = LoadEmbeddedTexture(EchoBubbleColors.ResourceName(value));
				if (!((Object)(object)val == (Object)null))
				{
					_textures[value] = val;
					_sprites[value] = CreateIconSprite(val);
				}
			}
		}

		public void RenderLevel(LevelEchoFile file)
		{
			Clear();
			if (!_isEnabled() || file?.messages == null)
			{
				return;
			}
			foreach (EchoMessage message in file.messages)
			{
				Render(message);
			}
		}

		public void Render(EchoMessage echo)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			if (_isEnabled())
			{
				EchoBubbleColor echoBubbleColor = EchoBubbleColors.Parse(echo.color);
				GameObject val = new GameObject("Temporal Echo " + echo.uuid);
				val.transform.position = echo.position.ToVector3();
				val.transform.localScale = Vector3.one * 0.5f;
				SpriteRenderer val2 = val.AddComponent<SpriteRenderer>();
				val2.sprite = GetIconSprite(echoBubbleColor);
				val2.color = Color.white;
				((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)val2).receiveShadows = false;
				Material val3 = CreateSpriteMaterial(GetIconTexture(echoBubbleColor));
				if ((Object)(object)val3 != (Object)null)
				{
					((Renderer)val2).material = val3;
				}
				AddGlowLight(val.transform, echoBubbleColor);
				_markers.Add(new EchoMarker
				{
					Message = echo,
					Root = val,
					BasePosition = val.transform.position,
					FloatPhase = Random.Range(0f, (float)Math.PI * 2f),
					LastNotifyTime = -100f
				});
			}
		}

		public void Clear()
		{
			foreach (EchoMarker marker in _markers)
			{
				if ((Object)(object)marker.Root != (Object)null)
				{
					Object.Destroy((Object)(object)marker.Root);
				}
			}
			_markers.Clear();
		}

		public void Update()
		{
			if (_isEnabled() && _markers.Count != 0)
			{
				Animate();
				CheckTouches();
			}
		}

		private void Animate()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			float time = Time.time;
			foreach (EchoMarker marker in _markers)
			{
				if ((Object)(object)marker.Root == (Object)null)
				{
					continue;
				}
				Vector3 basePosition = marker.BasePosition;
				basePosition.y += Mathf.Sin(time * 1.4f + marker.FloatPhase) * 0.12f;
				marker.Root.transform.position = basePosition;
				if ((Object)(object)Player.Head != (Object)null)
				{
					Vector3 val = marker.Root.transform.position - Player.Head.position;
					if (((Vector3)(ref val)).sqrMagnitude > 0.001f)
					{
						marker.Root.transform.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up);
					}
				}
				marker.Root.transform.Rotate(Vector3.up, 55f * Time.deltaTime, (Space)0);
			}
		}

		private void CheckTouches()
		{
			//IL_0023: 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_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			Vector3? val = (((Object)(object)Player.LeftHand != (Object)null) ? new Vector3?(((Component)Player.LeftHand).transform.position) : null);
			Vector3? val2 = (((Object)(object)Player.RightHand != (Object)null) ? new Vector3?(((Component)Player.RightHand).transform.position) : null);
			float time = Time.time;
			if (time - _lastNotifyTime < 6f)
			{
				return;
			}
			foreach (EchoMarker marker in _markers)
			{
				if (!((Object)(object)marker.Root == (Object)null))
				{
					Vector3 position = marker.Root.transform.position;
					if ((val.HasValue && Vector3.Distance(val.Value, position) < 0.35f) || (val2.HasValue && Vector3.Distance(val2.Value, position) < 0.35f))
					{
						marker.LastNotifyTime = time;
						_lastNotifyTime = time;
						_onEchoTouched(marker.Message);
						break;
					}
				}
			}
		}

		private static Material CreateSpriteMaterial(Texture2D texture)
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			Shader val = Shader.Find("Sprites/Default");
			if ((Object)(object)val == (Object)null)
			{
				val = Shader.Find("UI/Default");
			}
			if ((Object)(object)val == (Object)null)
			{
				val = Shader.Find("Unlit/Transparent");
			}
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Material val2 = new Material(val);
			val2.mainTexture = (Texture)(object)texture;
			val2.SetTexture("_MainTex", (Texture)(object)texture);
			val2.SetColor("_Color", Color.white);
			val2.renderQueue = 3000;
			return val2;
		}

		private static void AddGlowLight(Transform parent, EchoBubbleColor bubbleColor)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Echo Glow");
			val.transform.SetParent(parent, false);
			val.transform.localPosition = Vector3.zero;
			Light val2 = val.AddComponent<Light>();
			val2.type = (LightType)2;
			val2.color = EchoBubbleColors.LightColor(bubbleColor);
			val2.range = 0.85f;
			val2.intensity = 0.8f;
			val2.shadows = (LightShadows)0;
		}

		private Texture2D GetIconTexture(EchoBubbleColor color)
		{
			if (_textures.TryGetValue(color, out var value))
			{
				return value;
			}
			Texture2D value2;
			return _textures.TryGetValue(EchoBubbleColor.Orange, out value2) ? value2 : null;
		}

		private Sprite GetIconSprite(EchoBubbleColor color)
		{
			if (_sprites.TryGetValue(color, out var value))
			{
				return value;
			}
			Sprite value2;
			return _sprites.TryGetValue(EchoBubbleColor.Orange, out value2) ? value2 : null;
		}

		private Texture2D LoadEmbeddedTexture(string resourceName)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			using Stream stream = typeof(EchoRenderer).Assembly.GetManifestResourceStream(resourceName);
			if (stream == null)
			{
				_logWarning("Embedded icon not found: " + resourceName);
				return null;
			}
			byte[] array = new byte[stream.Length];
			stream.Read(array, 0, array.Length);
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
			{
				name = resourceName,
				hideFlags = (HideFlags)32
			};
			if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array)))
			{
				_logWarning("Failed to load embedded icon: " + resourceName);
				return null;
			}
			val.Apply();
			return val;
		}

		private static Sprite CreateIconSprite(Texture2D texture)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Sprite val = Sprite.Create(texture, new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), Vector2.one * 0.5f, 1000f);
			((Object)val).hideFlags = (HideFlags)32;
			return val;
		}
	}
	internal static class EchoText
	{
		public static string Limit(string value, int maxLength)
		{
			if (value == null)
			{
				value = string.Empty;
			}
			return (value.Length <= maxLength) ? value : value.Substring(0, maxLength);
		}
	}
}