Decompiled source of TemporalEchoes v2.0.1

Mods/TemporalEchoes.dll

Decompiled 3 weeks 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.Attributes;
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]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TemporalEchoes
{
	public class Core : MelonMod
	{
		private sealed class OwnedEchoLevel
		{
			public string Path;

			public LevelEchoFile File;

			public List<EchoMessage> Messages;
		}

		private sealed class OwnedPhotoEchoLevel
		{
			public string Path;

			public LevelPhotoEchoFile File;

			public List<PhotoEchoMetadata> Photos;
		}

		private const string ModName = "TemporalEchoes";

		private const float MenuActionCooldownSeconds = 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 LevelPhotoEchoFile _currentPhotoFile;

		private static float _lastCreateClickTime = -100f;

		private static float _lastPhotoClickTime = -100f;

		private static float _lastReloadClickTime = -100f;

		private static EchoApiClient _api;

		private static EchoRenderer _renderer;

		private static PhotoEchoRenderer _photoRenderer;

		private static PhotoEchoController _photoEcho;

		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 string PhotosDir => Path.Combine(DataDir, "Photos");

		private static string PhotoLevelsDir => Path.Combine(PhotosDir, "Levels");

		private static string PhotoImagesDir => Path.Combine(PhotosDir, "Images");

		private static bool DebugInfoEnabled => EchoPreferences.DebugInfo;

		private static bool ModEnabled => EchoPreferences.ModEnabled;

		public override void OnInitializeMelon()
		{
			_instance = this;
			EchoPreferences.Setup();
			Directory.CreateDirectory(LevelsDir);
			Directory.CreateDirectory(PhotoLevelsDir);
			Directory.CreateDirectory(PhotoImagesDir);
			_api = new EchoApiClient((Func<bool>)(() => ModEnabled), (Func<string>)NormalizeServerUrl, JsonOptions, (Action<string>)((MelonBase)this).LoggerInstance.Warning, (Action<string, string, NotificationType>)SendDebugNotification, (Action<string, string, NotificationType>)delegate(string title, string message, NotificationType type)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				SendNotification(title, message, 2f, type);
			});
			_renderer = new EchoRenderer((Func<bool>)(() => ModEnabled), (Func<float>)(() => EchoPreferences.EchoNotifyCooldownSeconds), (Action<EchoMessage>)SendEchoNotification, (Action<string>)((MelonBase)this).LoggerInstance.Warning);
			_photoRenderer = new PhotoEchoRenderer((Func<bool>)(() => ModEnabled), (Func<string>)(() => PhotoImagesDir), (Func<float>)(() => EchoPreferences.PhotoEchoNotifyCooldownSeconds), (Action<PhotoEchoMetadata>)SendPhotoEchoNotification, (Action<PhotoEchoMetadata>)SendMissingPhotoEchoNotification, (Action<string>)((MelonBase)this).LoggerInstance.Warning);
			_photoEcho = new PhotoEchoController((Func<bool>)(() => ModEnabled), (Func<string>)(() => PhotoImagesDir), (Action<string>)((MelonBase)this).LoggerInstance.Warning, JsonOptions);
			_renderer.LoadIcons();
			_photoRenderer.LoadIcons();
			_photoEcho.LoadAssets();
			EchoBoneMenu.Setup(SetModEnabled, UseSteamName, delegate(string body)
			{
				_draftBody = body ?? string.Empty;
			}, CreateEchoFromMenu, PlaceOrDeletePhotoCamera, TogglePhotoCameraFollow, CreatePhotoEchoFromMenu, ReloadCurrentLevel, RefreshMyEchoesMenu);
			EchoBoneMenu.RefreshPhotoCameraButtons(hasCamera: false, isFollowing: false);
			RefreshMyEchoesMenu();
			Hooking.OnLevelLoaded += OnLevelLoaded;
			Hooking.OnLevelUnloaded += OnLevelUnloaded;
			((MelonBase)this).LoggerInstance.Msg("Initialized.");
		}

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

		public override void OnApplicationQuit()
		{
			ClearPhotoCamera();
		}

		public override void OnDeinitializeMelon()
		{
			ClearPhotoCamera();
		}

		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();
				_photoRenderer.Clear();
			}
			else
			{
				MelonCoroutines.Start(LoadEchoesAfterDelay(token, _currentBarcode));
			}
		}

		private static IEnumerator LoadEchoesAfterDelay(int token, string barcode)
		{
			yield return (object)new WaitForSeconds(2.5f);
			if (ModEnabled && token == _levelLoadToken && !(barcode != _currentBarcode))
			{
				LoadCurrentLevelFile();
				LoadCurrentPhotoLevelFile();
				RenderCurrentLevelEchoes();
				RenderCurrentLevelPhotos();
				MelonCoroutines.Start(DownloadCurrentLevelFromServer());
				MelonCoroutines.Start(DownloadCurrentPhotosFromServer());
			}
		}

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

		private static void CreateEchoFromMenu()
		{
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: 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();
			EnsureCurrentPhotoLevelFile();
			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) || HasNearbyPhotoEcho(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,
				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 PlaceOrDeletePhotoCamera()
		{
			if (ModEnabled)
			{
				_photoEcho.PlaceOrDeleteCamera();
				EchoBoneMenu.RefreshPhotoCameraButtons(_photoEcho.HasCamera, _photoEcho.IsFollowingRightHand);
			}
		}

		private static void TogglePhotoCameraFollow()
		{
			if (ModEnabled)
			{
				if (!_photoEcho.HasCamera)
				{
					SendNotification("Temporal Echoes", "You must create a camera first.", 2f, (NotificationType)1);
					return;
				}
				_photoEcho.ToggleFollowRightHand();
				EchoBoneMenu.RefreshPhotoCameraButtons(_photoEcho.HasCamera, _photoEcho.IsFollowingRightHand);
			}
		}

		private static void CreatePhotoEchoFromMenu()
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: 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)
			if (!ModEnabled || !TryStartMenuAction(ref _lastPhotoClickTime))
			{
				return;
			}
			if (!_photoEcho.HasCamera)
			{
				SendNotification("Temporal Echoes", "You must create a camera first.", 2f, (NotificationType)1);
				return;
			}
			if (string.IsNullOrWhiteSpace(_currentBarcode))
			{
				SendNotification("Temporal Echoes", "No loaded level barcode yet.", 2f, (NotificationType)1);
				return;
			}
			PhotoEchoMetadata photoEchoMetadata = CreatePhotoEchoMetadata();
			EnsureCurrentPhotoLevelFile();
			Vector3 position = photoEchoMetadata.position.ToVector3();
			if (HasNearbyEcho(position) || HasNearbyPhotoEcho(position))
			{
				SendNotification("Temporal Echoes", "An echo already exists here.", 2f, (NotificationType)1);
			}
			else
			{
				MelonCoroutines.Start(CreatePhotoEchoRoutine(photoEchoMetadata));
			}
		}

		private static IEnumerator CreatePhotoEchoRoutine(PhotoEchoMetadata metadata)
		{
			yield return _photoEcho.Capture(metadata);
			if (_photoEcho.LastCaptureSucceeded)
			{
				AddPhotoEcho(metadata);
				SaveCurrentPhotoLevelFile();
				_photoRenderer.Render(metadata);
				RefreshMyEchoesMenu();
				SendNotification("Photo Echo", "Photo saved locally.", 2f, (NotificationType)3);
				MelonCoroutines.Start(UploadPhotoToServer(metadata));
			}
			else
			{
				SendNotification("Photo Echo", "Failed to save photo.", 2f, (NotificationType)1);
			}
		}

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

		private static PhotoEchoMetadata CreatePhotoEchoMetadata()
		{
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			string uuid = Guid.NewGuid().ToString("N");
			string createdAt = DateTimeOffset.Now.ToString("o");
			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;
			return new PhotoEchoMetadata
			{
				uuid = uuid,
				levelBarcode = _currentBarcode,
				levelHash = (string.IsNullOrWhiteSpace(_currentBarcode) ? string.Empty : HashBarcode(_currentBarcode)),
				levelTitle = _currentLevelTitle,
				position = SerializableVector3.From(((Object)(object)val != (Object)null) ? val.position : Vector3.zero),
				createdAt = createdAt,
				authorName = GetEchoAuthorName(),
				authorHash = CurrentAuthorHash(),
				color = EchoPreferences.BubbleColor.ToString(),
				syncState = "pending"
			};
		}

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

		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();
				_photoRenderer.Clear();
				ClearPhotoCamera();
			}
			else
			{
				ReloadCurrentLevelNow();
			}
		}

		private static string GetEchoAuthorName()
		{
			return EchoText.Limit(EchoPreferences.AuthorName.Trim(), 50);
		}

		private static void UseSteamName()
		{
			if (!TryGetSteamName(out var steamName))
			{
				SendNotification("Temporal Echoes", "Steam name is only available on PC.", 2f, (NotificationType)1);
				return;
			}
			string text = EchoText.Limit(steamName.Trim(), 50);
			if (string.IsNullOrWhiteSpace(text))
			{
				SendNotification("Temporal Echoes", "Steam name not found.", 2f, (NotificationType)1);
				return;
			}
			EchoPreferences.SetAuthorName(text);
			EchoBoneMenu.RefreshAuthorName();
			SendNotification("Temporal Echoes", "Name set to " + text + ".", 2f, (NotificationType)3);
		}

		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 RenderCurrentLevelPhotos()
		{
			_photoRenderer.RenderLevel(_currentPhotoFile);
		}

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

		internal static void SendPhotoCountdownNotification(int count)
		{
			SendNotification("Photo Incoming", count.ToString(), 0.2f, (NotificationType)0);
		}

		private static void ClearPhotoCamera()
		{
			_photoEcho?.Clear();
			EchoBoneMenu.RefreshPhotoCameraButtons(hasCamera: false, isFollowing: false);
		}

		private static bool TryGetSteamName(out string steamName)
		{
			if (TryGetSteamClientValue("Name", out var value))
			{
				steamName = value?.ToString() ?? string.Empty;
				return true;
			}
			steamName = string.Empty;
			return false;
		}

		private static bool TryGetSteamClientValue(string propertyName, out object value)
		{
			value = null;
			Type type = FindSteamClientType();
			if (type == null)
			{
				return false;
			}
			try
			{
				PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public);
				if (property == null)
				{
					return false;
				}
				value = property.GetValue(null);
				return true;
			}
			catch (Exception ex)
			{
				Core instance = _instance;
				if (instance != null)
				{
					((MelonBase)instance).LoggerInstance.Warning("Failed reading SteamClient." + propertyName + ": " + ex.Message);
				}
				return false;
			}
		}

		private static Type FindSteamClientType()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (string.Equals(assembly.GetName().Name, "Il2CppFacepunch.Steamworks.Win64", StringComparison.Ordinal))
				{
					return assembly.GetType("Il2CppSteamworks.SteamClient", throwOnError: false);
				}
			}
			try
			{
				return Type.GetType("Il2CppSteamworks.SteamClient, Il2CppFacepunch.Steamworks.Win64", throwOnError: false);
			}
			catch
			{
				return null;
			}
		}

		private static string FormatNotificationTitle(string createdAt)
		{
			if (DateTimeOffset.TryParse(createdAt, out var result))
			{
				return result.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
			}
			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 = EchoPreferences.EchoDisplaySeconds
			});
		}

		private static void SendPhotoEchoNotification(PhotoEchoMetadata photo)
		{
			//IL_001c: 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)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			string text = (string.IsNullOrWhiteSpace(photo.authorName) ? "Unknown Author" : photo.authorName);
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit(FormatNotificationTitle(photo.createdAt)),
				Message = NotificationText.op_Implicit(text),
				Type = (NotificationType)0,
				ShowTitleOnPopup = true,
				PopupLength = EchoPreferences.PhotoEchoDisplaySeconds
			});
		}

		private static void SendMissingPhotoEchoNotification(PhotoEchoMetadata photo)
		{
			MelonCoroutines.Start(DownloadPhotoFileFromServer(photo));
		}

		private static void RefreshMyEchoesMenu()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: 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)
			Page myEchoesPage = EchoBoneMenu.MyEchoesPage;
			if (myEchoesPage == null)
			{
				return;
			}
			myEchoesPage.RemoveAll();
			myEchoesPage.CreateFunction("Refresh List", Color.white, (Action)RefreshMyEchoesMenu);
			List<OwnedEchoLevel> ownedEchoLevels = GetOwnedEchoLevels();
			List<OwnedPhotoEchoLevel> ownedPhotoEchoLevels = GetOwnedPhotoEchoLevels();
			if (ownedEchoLevels.Count == 0 && ownedPhotoEchoLevels.Count == 0)
			{
				myEchoesPage.CreateFunction("No echoes found", Color.gray, (Action)null);
				return;
			}
			if (ownedEchoLevels.Count > 0)
			{
				Page page = myEchoesPage.CreatePage("Text Echoes", Color.cyan, 10, true);
				PopulateTextEchoMenu(page, ownedEchoLevels);
			}
			if (ownedPhotoEchoLevels.Count > 0)
			{
				Page page2 = myEchoesPage.CreatePage("Photo Echoes", Color.yellow, 10, true);
				PopulatePhotoEchoMenu(page2, ownedPhotoEchoLevels);
			}
		}

		private static void PopulateTextEchoMenu(Page page, List<OwnedEchoLevel> levels)
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			foreach (OwnedEchoLevel level in levels.OrderBy<OwnedEchoLevel, string>((OwnedEchoLevel ownedEchoLevel) => GetLevelMenuName(ownedEchoLevel.File), StringComparer.OrdinalIgnoreCase))
			{
				string text = $"{GetLevelMenuName(level.File)} ({level.Messages.Count})";
				Page val = page.CreatePage(text, Color.white, 10, true);
				val.CreateFunction("Show Barcode", Color.gray, (Action)delegate
				{
					Menu.DisplayDialog("Level Barcode", level.File.levelBarcode, (Texture2D)null, (Action)null, (Action)null);
				});
				foreach (EchoMessage item in level.Messages.OrderByDescending((EchoMessage message) => message.createdAt))
				{
					EchoMessage capturedMessage = item;
					string capturedPath = level.Path;
					string capturedBarcode = level.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 void PopulatePhotoEchoMenu(Page page, List<OwnedPhotoEchoLevel> levels)
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			foreach (OwnedPhotoEchoLevel level in levels.OrderBy<OwnedPhotoEchoLevel, string>((OwnedPhotoEchoLevel ownedPhotoEchoLevel) => GetLevelMenuName(ownedPhotoEchoLevel.File), StringComparer.OrdinalIgnoreCase))
			{
				string text = $"{GetLevelMenuName(level.File)} ({level.Photos.Count})";
				Page val = page.CreatePage(text, Color.white, 10, true);
				val.CreateFunction("Show Barcode", Color.gray, (Action)delegate
				{
					Menu.DisplayDialog("Level Barcode", level.File.levelBarcode, (Texture2D)null, (Action)null, (Action)null);
				});
				foreach (PhotoEchoMetadata item in level.Photos.OrderByDescending((PhotoEchoMetadata photo) => photo.createdAt))
				{
					PhotoEchoMetadata capturedPhoto = item;
					string capturedPath = level.Path;
					string capturedBarcode = level.File.levelBarcode;
					string photoEchoMenuTitle = GetPhotoEchoMenuTitle(capturedPhoto);
					Page val2 = val.CreatePage(photoEchoMenuTitle, Color.yellow, 10, true);
					val2.CreateFunction("Show Info", Color.white, (Action)delegate
					{
						Menu.DisplayDialog("Photo Echo", GetPhotoEchoInfo(capturedPhoto), (Texture2D)null, (Action)null, (Action)null);
					});
					val2.CreateFunction("Delete", Color.yellow, (Action)delegate
					{
						AskDeleteOwnedPhotoEcho(capturedPath, capturedBarcode, capturedPhoto);
					});
				}
			}
		}

		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 List<OwnedPhotoEchoLevel> GetOwnedPhotoEchoLevels()
		{
			List<OwnedPhotoEchoLevel> list = new List<OwnedPhotoEchoLevel>();
			string authorHash = CurrentAuthorHash();
			if (string.IsNullOrWhiteSpace(authorHash) || !Directory.Exists(PhotoLevelsDir))
			{
				return list;
			}
			string[] files = Directory.GetFiles(PhotoLevelsDir, "*.json");
			foreach (string text in files)
			{
				try
				{
					LevelPhotoEchoFile levelPhotoEchoFile = JsonSerializer.Deserialize<LevelPhotoEchoFile>(File.ReadAllText(text), JsonOptions);
					if (levelPhotoEchoFile?.photos != null)
					{
						List<PhotoEchoMetadata> list2 = levelPhotoEchoFile.photos.Where((PhotoEchoMetadata photo) => string.Equals(photo.authorHash, authorHash, StringComparison.OrdinalIgnoreCase)).ToList();
						if (list2.Count != 0)
						{
							list.Add(new OwnedPhotoEchoLevel
							{
								Path = text,
								File = levelPhotoEchoFile,
								Photos = list2
							});
						}
					}
				}
				catch (Exception ex)
				{
					((MelonBase)_instance).LoggerInstance.Warning("Could not read photo 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 void AskDeleteOwnedPhotoEcho(string path, string levelBarcode, PhotoEchoMetadata photo)
		{
			Menu.DisplayDialog("Delete photo echo?", "This will permanently delete your photo echo.", Dialog.WarningIcon, (Action)delegate
			{
				DeleteOwnedPhotoEcho(path, levelBarcode, photo.uuid);
			}, (Action)null);
		}

		private static void DeleteOwnedPhotoEcho(string path, string levelBarcode, string uuid)
		{
			if (!DeleteOwnedPhotoEchoLocal(path, uuid))
			{
				SendNotification("Temporal Echoes", "Photo echo was not found or is not yours.", 2f, (NotificationType)1);
				return;
			}
			if (string.Equals(levelBarcode, _currentBarcode, StringComparison.Ordinal))
			{
				LoadCurrentPhotoLevelFile();
				RenderCurrentLevelPhotos();
			}
			RefreshMyEchoesMenu();
			Menu.OpenPage(EchoBoneMenu.MyEchoesPage);
			SendNotification("Photo Echo Deleted", uuid, 2f, (NotificationType)3);
			MelonCoroutines.Start(DeletePhotoFromServer(levelBarcode, uuid));
		}

		private static bool DeleteOwnedPhotoEchoLocal(string path, string uuid)
		{
			try
			{
				LevelPhotoEchoFile levelPhotoEchoFile = JsonSerializer.Deserialize<LevelPhotoEchoFile>(File.ReadAllText(path), JsonOptions);
				if (levelPhotoEchoFile?.photos == null)
				{
					return false;
				}
				string authorHash = CurrentAuthorHash();
				PhotoEchoMetadata photoEchoMetadata = levelPhotoEchoFile.photos.FirstOrDefault((PhotoEchoMetadata candidate) => string.Equals(candidate.uuid, uuid, StringComparison.OrdinalIgnoreCase) && string.Equals(candidate.authorHash, authorHash, StringComparison.OrdinalIgnoreCase));
				if (photoEchoMetadata == null)
				{
					return false;
				}
				levelPhotoEchoFile.photos.Remove(photoEchoMetadata);
				File.WriteAllText(path, JsonSerializer.Serialize(levelPhotoEchoFile, JsonOptions));
				DeletePhotoImage(photoEchoMetadata.imageFileName);
				return true;
			}
			catch (Exception ex)
			{
				((MelonBase)_instance).LoggerInstance.Warning("Could not delete photo echo " + uuid + ": " + ex.Message);
				return false;
			}
		}

		private static void DeletePhotoImage(string imageFileName)
		{
			if (string.IsNullOrWhiteSpace(imageFileName))
			{
				return;
			}
			try
			{
				string path = Path.Combine(PhotoImagesDir, imageFileName);
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception ex)
			{
				((MelonBase)_instance).LoggerInstance.Warning("Could not delete photo image " + imageFileName + ": " + ex.Message);
			}
		}

		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 GetLevelMenuName(LevelPhotoEchoFile 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.ToLocalTime().ToString("MM/dd HH:mm");
			}
			return TrimMenuText(text + " " + message.body, 42);
		}

		private static string GetPhotoEchoMenuTitle(PhotoEchoMetadata photo)
		{
			string text = photo.createdAt;
			if (DateTimeOffset.TryParse(photo.createdAt, out var result))
			{
				text = result.ToLocalTime().ToString("MM/dd HH:mm");
			}
			return TrimMenuText(text + " Photo Echo", 42);
		}

		private static string GetPhotoEchoInfo(PhotoEchoMetadata photo)
		{
			string value = photo.createdAt;
			if (DateTimeOffset.TryParse(photo.createdAt, out var result))
			{
				value = result.ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");
			}
			return $"Created: {value}\nImage: {photo.imageFileName}\nSize: {photo.imageBytes} bytes";
		}

		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> list = (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();
			});
		}

		private static IEnumerator DownloadCurrentPhotosFromServer()
		{
			return _api.DownloadPhotos(_currentBarcode, delegate(LevelPhotoEchoFile downloaded)
			{
				ApplyDownloadedPhotoLevel(downloaded);
				SaveCurrentPhotoLevelFile();
				RenderCurrentLevelPhotos();
				RefreshMyEchoesMenu();
			});
		}

		private static IEnumerator UploadPhotoToServer(PhotoEchoMetadata photo)
		{
			string photoImagePath = GetPhotoImagePath(photo);
			return _api.UploadPhoto(_currentBarcode, photo, photoImagePath, delegate(PhotoEchoMetadata syncedPhoto)
			{
				CopySyncedPhotoFields(photo, syncedPhoto);
				UpdatePhotoEcho(photo);
				SaveCurrentPhotoLevelFile();
				RefreshMyEchoesMenu();
			});
		}

		private static IEnumerator DownloadPhotoFileFromServer(PhotoEchoMetadata photo)
		{
			if (photo == null || string.IsNullOrWhiteSpace(_currentBarcode))
			{
				yield break;
			}
			string destinationPath = GetPhotoImagePath(photo);
			yield return _api.DownloadPhotoFile(_currentBarcode, photo, destinationPath, delegate
			{
				UpdatePhotoEcho(photo);
				SaveCurrentPhotoLevelFile();
				if (_photoRenderer.ReloadPhotoFile(photo.uuid))
				{
					_photoRenderer.ShowPhoto(photo.uuid);
				}
			});
		}

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

		private static string GetPhotoImagePath(PhotoEchoMetadata photo)
		{
			string path = ((!string.IsNullOrWhiteSpace(photo.imageFileName)) ? photo.imageFileName : ("photo-" + photo.uuid + ".jpg"));
			return Path.Combine(PhotoImagesDir, path);
		}

		private static void LoadCurrentPhotoLevelFile()
		{
			if (string.IsNullOrWhiteSpace(_currentBarcode))
			{
				_currentPhotoFile = null;
				return;
			}
			string photoLevelFilePath = GetPhotoLevelFilePath(_currentBarcode);
			if (!File.Exists(photoLevelFilePath))
			{
				_currentPhotoFile = CreateEmptyPhotoLevelFile();
				SaveCurrentPhotoLevelFile();
				return;
			}
			try
			{
				_currentPhotoFile = JsonSerializer.Deserialize<LevelPhotoEchoFile>(File.ReadAllText(photoLevelFilePath), JsonOptions) ?? CreateEmptyPhotoLevelFile();
				_currentPhotoFile.levelBarcode = _currentBarcode;
				_currentPhotoFile.levelHash = HashBarcode(_currentBarcode);
				if (!string.IsNullOrWhiteSpace(_currentLevelTitle))
				{
					_currentPhotoFile.levelTitle = _currentLevelTitle;
				}
				LevelPhotoEchoFile currentPhotoFile = _currentPhotoFile;
				if (currentPhotoFile.photos == null)
				{
					List<PhotoEchoMetadata> list = (currentPhotoFile.photos = new List<PhotoEchoMetadata>());
				}
			}
			catch (Exception value)
			{
				((MelonBase)_instance).LoggerInstance.Error($"Failed to read photo echo file {photoLevelFilePath}: {value}");
				_currentPhotoFile = CreateEmptyPhotoLevelFile();
			}
		}

		private static void EnsureCurrentPhotoLevelFile()
		{
			if (_currentPhotoFile == null)
			{
				LoadCurrentPhotoLevelFile();
			}
		}

		private static void AddPhotoEcho(PhotoEchoMetadata photo)
		{
			EnsureCurrentPhotoLevelFile();
			_currentPhotoFile.photos.RemoveAll((PhotoEchoMetadata existing) => string.Equals(existing.uuid, photo.uuid, StringComparison.OrdinalIgnoreCase));
			_currentPhotoFile.photos.Add(photo);
		}

		private static void ApplyDownloadedPhotoLevel(LevelPhotoEchoFile downloaded)
		{
			List<PhotoEchoMetadata> list = _currentPhotoFile?.photos?.FindAll((PhotoEchoMetadata photoEchoMetadata) => !string.Equals(photoEchoMetadata.syncState, "synced", StringComparison.OrdinalIgnoreCase)) ?? new List<PhotoEchoMetadata>();
			_currentPhotoFile = downloaded ?? CreateEmptyPhotoLevelFile();
			_currentPhotoFile.levelBarcode = _currentBarcode;
			_currentPhotoFile.levelHash = HashBarcode(_currentBarcode);
			_currentPhotoFile.levelTitle = _currentLevelTitle;
			LevelPhotoEchoFile currentPhotoFile = _currentPhotoFile;
			if (currentPhotoFile.photos == null)
			{
				List<PhotoEchoMetadata> list2 = (currentPhotoFile.photos = new List<PhotoEchoMetadata>());
			}
			foreach (PhotoEchoMetadata photo in list)
			{
				if (!_currentPhotoFile.photos.Exists((PhotoEchoMetadata existing) => string.Equals(existing.uuid, photo.uuid, StringComparison.OrdinalIgnoreCase)))
				{
					_currentPhotoFile.photos.Add(photo);
				}
			}
		}

		private static void UpdatePhotoEcho(PhotoEchoMetadata photo)
		{
			EnsureCurrentPhotoLevelFile();
			int num = _currentPhotoFile.photos.FindIndex((PhotoEchoMetadata existing) => string.Equals(existing.uuid, photo.uuid, StringComparison.OrdinalIgnoreCase));
			if (num >= 0)
			{
				_currentPhotoFile.photos[num] = photo;
			}
			else
			{
				_currentPhotoFile.photos.Add(photo);
			}
		}

		private static void CopySyncedPhotoFields(PhotoEchoMetadata target, PhotoEchoMetadata synced)
		{
			if (target != null && synced != null)
			{
				target.levelBarcode = (string.IsNullOrWhiteSpace(synced.levelBarcode) ? target.levelBarcode : synced.levelBarcode);
				target.levelHash = (string.IsNullOrWhiteSpace(synced.levelHash) ? target.levelHash : synced.levelHash);
				target.levelTitle = (string.IsNullOrWhiteSpace(synced.levelTitle) ? target.levelTitle : synced.levelTitle);
				target.imageFileName = (string.IsNullOrWhiteSpace(synced.imageFileName) ? target.imageFileName : synced.imageFileName);
				target.imageKey = (string.IsNullOrWhiteSpace(synced.imageKey) ? target.imageKey : synced.imageKey);
				target.imageBytes = ((synced.imageBytes > 0) ? synced.imageBytes : target.imageBytes);
				target.createdAt = (string.IsNullOrWhiteSpace(synced.createdAt) ? target.createdAt : synced.createdAt);
				target.authorName = (string.IsNullOrWhiteSpace(synced.authorName) ? target.authorName : synced.authorName);
				target.authorHash = (string.IsNullOrWhiteSpace(synced.authorHash) ? target.authorHash : synced.authorHash);
				target.color = (string.IsNullOrWhiteSpace(synced.color) ? target.color : synced.color);
				target.syncState = (string.IsNullOrWhiteSpace(synced.syncState) ? "synced" : synced.syncState);
			}
		}

		private static void SaveCurrentPhotoLevelFile()
		{
			if (_currentPhotoFile != null && !string.IsNullOrWhiteSpace(_currentBarcode))
			{
				Directory.CreateDirectory(PhotoLevelsDir);
				if (!string.IsNullOrWhiteSpace(_currentLevelTitle))
				{
					_currentPhotoFile.levelTitle = _currentLevelTitle;
				}
				File.WriteAllText(GetPhotoLevelFilePath(_currentBarcode), JsonSerializer.Serialize(_currentPhotoFile, JsonOptions));
			}
		}

		private static bool HasNearbyPhotoEcho(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 (_currentPhotoFile?.photos == null)
			{
				return false;
			}
			foreach (PhotoEchoMetadata photo in _currentPhotoFile.photos)
			{
				if (Vector3.Distance(photo.position.ToVector3(), position) < 0.5f)
				{
					return true;
				}
			}
			return false;
		}

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

		private static LevelPhotoEchoFile CreateEmptyPhotoLevelFile()
		{
			return new LevelPhotoEchoFile
			{
				levelBarcode = _currentBarcode,
				levelHash = HashBarcode(_currentBarcode),
				levelTitle = _currentLevelTitle,
				photos = new List<PhotoEchoMetadata>()
			};
		}
	}
	internal sealed class EchoApiClient
	{
		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;

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

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

		public IEnumerator DownloadLevel(string barcode, Action<LevelEchoFile> applyDownloadedFile)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(barcode))
			{
				yield break;
			}
			string url = baseUrl + "/api/messages?barcode=" + Uri.EscapeDataString(barcode);
			UnityWebRequest request = UnityWebRequest.Get(url);
			request.timeout = 20;
			yield return request.SendWebRequest();
			if (!_isEnabled())
			{
				yield break;
			}
			if (!IsWebRequestSuccess(request))
			{
				string reason = $"HTTP {request.responseCode} {request.error}";
				_warn("Download skipped/failed: " + reason);
				_debugNotify("Echo Download Failed", reason, (NotificationType)2);
				yield break;
			}
			try
			{
				LevelEchoFile downloaded = JsonSerializer.Deserialize<LevelEchoFile>(request.downloadHandler.text, _jsonOptions);
				if (downloaded?.messages != null)
				{
					applyDownloadedFile(downloaded);
					_debugNotify("Echo Downloaded", $"{downloaded.messages.Count} echoes loaded.", (NotificationType)3);
				}
			}
			catch (Exception ex)
			{
				_warn("Download skipped/failed: " + ex.Message);
				_debugNotify("Echo Download Failed", ex.Message, (NotificationType)2);
			}
		}

		public IEnumerator UploadEcho(string levelBarcode, string levelHash, EchoMessage echo, Action onSuccess)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl))
			{
				yield break;
			}
			EchoUpload payload = new EchoUpload
			{
				levelBarcode = levelBarcode,
				levelHash = levelHash,
				message = echo
			};
			using HttpClientHandler handler = CreateHttpHandler();
			using HttpClient client = new HttpClient(handler)
			{
				Timeout = TimeSpan.FromSeconds(20.0)
			};
			using StringContent content = new StringContent(JsonSerializer.Serialize(payload, _jsonOptions), Encoding.UTF8, "application/json");
			Task<HttpResponseMessage> responseTask;
			try
			{
				responseTask = client.PostAsync(baseUrl + "/api/messages", content);
			}
			catch (Exception ex)
			{
				Exception ex2 = ex;
				ReportHttpFailure("Upload", echo.uuid, ex2.Message);
				yield break;
			}
			yield return WaitForResponse(responseTask);
			if (_isEnabled() && TryAcceptResponse(responseTask, "Upload", echo.uuid))
			{
				onSuccess();
				_debugNotify("Echo Uploaded", echo.uuid, (NotificationType)3);
			}
		}

		public IEnumerator DeleteEcho(string levelBarcode, string uuid, string authorSecret)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(levelBarcode) || string.IsNullOrWhiteSpace(uuid))
			{
				yield break;
			}
			EchoDeleteRequest payload = new EchoDeleteRequest
			{
				levelBarcode = levelBarcode,
				uuid = uuid,
				authorSecret = authorSecret
			};
			using HttpClientHandler handler = CreateHttpHandler();
			using HttpClient client = new HttpClient(handler)
			{
				Timeout = TimeSpan.FromSeconds(20.0)
			};
			using StringContent content = new StringContent(JsonSerializer.Serialize(payload, _jsonOptions), Encoding.UTF8, "application/json");
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, baseUrl + "/api/messages")
			{
				Content = content
			};
			Task<HttpResponseMessage> responseTask;
			try
			{
				responseTask = client.SendAsync(request);
			}
			catch (Exception ex)
			{
				Exception ex2 = ex;
				ReportHttpFailure("Delete", uuid, ex2.Message);
				yield break;
			}
			yield return WaitForResponse(responseTask);
			if (_isEnabled() && TryAcceptResponse(responseTask, "Delete", uuid))
			{
				_debugNotify("Echo Deleted Online", uuid, (NotificationType)3);
			}
		}

		public IEnumerator DownloadPhotos(string barcode, Action<LevelPhotoEchoFile> applyDownloadedFile)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(barcode))
			{
				yield break;
			}
			string url = baseUrl + "/api/photos?barcode=" + Uri.EscapeDataString(barcode);
			UnityWebRequest request = UnityWebRequest.Get(url);
			request.timeout = 20;
			yield return request.SendWebRequest();
			if (!_isEnabled())
			{
				yield break;
			}
			if (!IsWebRequestSuccess(request))
			{
				string reason = $"HTTP {request.responseCode} {request.error}";
				_warn("Photo metadata download failed: " + reason);
				_debugNotify("Photo Echo Download Failed", reason, (NotificationType)2);
				yield break;
			}
			try
			{
				LevelPhotoEchoFile downloaded = JsonSerializer.Deserialize<LevelPhotoEchoFile>(request.downloadHandler.text, _jsonOptions);
				if (downloaded?.photos != null)
				{
					applyDownloadedFile(downloaded);
					_debugNotify("Photo Echoes Downloaded", $"{downloaded.photos.Count} photo echoes loaded.", (NotificationType)3);
				}
			}
			catch (Exception ex)
			{
				_warn("Photo metadata download failed: " + ex.Message);
				_debugNotify("Photo Echo Download Failed", ex.Message, (NotificationType)2);
			}
		}

		public IEnumerator UploadPhoto(string levelBarcode, PhotoEchoMetadata photo, string imagePath, Action<PhotoEchoMetadata> onSuccess)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl))
			{
				yield break;
			}
			if (!File.Exists(imagePath))
			{
				NotifyPhotoUploadFailure(0, "Photo source file is missing.");
				yield break;
			}
			PhotoEchoUpload payload = new PhotoEchoUpload
			{
				levelBarcode = levelBarcode,
				photo = photo,
				imageBase64 = Convert.ToBase64String(File.ReadAllBytes(imagePath))
			};
			using HttpClientHandler handler = CreateHttpHandler();
			using HttpClient client = new HttpClient(handler)
			{
				Timeout = TimeSpan.FromSeconds(30.0)
			};
			using StringContent content = new StringContent(JsonSerializer.Serialize(payload, _jsonOptions), Encoding.UTF8, "application/json");
			Task<HttpResponseMessage> responseTask;
			try
			{
				responseTask = client.PostAsync(baseUrl + "/api/photos", content);
			}
			catch (Exception ex)
			{
				Exception ex2 = ex;
				NotifyPhotoUploadFailure(0, ex2.Message);
				yield break;
			}
			yield return WaitForResponse(responseTask);
			if (!_isEnabled())
			{
				yield break;
			}
			if (!responseTask.IsCompletedSuccessfully)
			{
				string reason = responseTask.Exception?.GetBaseException().Message ?? "Unknown error";
				NotifyPhotoUploadFailure(0, reason);
				yield break;
			}
			HttpResponseMessage response = responseTask.Result;
			string responseText = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
			if (!response.IsSuccessStatusCode)
			{
				NotifyPhotoUploadFailure((int)response.StatusCode, responseText);
				yield break;
			}
			try
			{
				PhotoEchoUploadResponse result = JsonSerializer.Deserialize<PhotoEchoUploadResponse>(responseText, _jsonOptions);
				PhotoEchoMetadata syncedPhoto = result?.photo ?? photo;
				if (result?.duplicate ?? false)
				{
					syncedPhoto.syncState = "synced";
				}
				onSuccess(syncedPhoto);
				_debugNotify("Photo Echo Uploaded", photo.uuid, (NotificationType)3);
			}
			catch (Exception ex3)
			{
				NotifyPhotoUploadFailure(0, ex3.Message);
			}
		}

		public IEnumerator DownloadPhotoFile(string levelBarcode, PhotoEchoMetadata photo, string destinationPath, Action onSuccess)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(levelBarcode) || photo == null)
			{
				yield break;
			}
			string url = $"{baseUrl}/api/photos/file?barcode={Uri.EscapeDataString(levelBarcode)}&uuid={Uri.EscapeDataString(photo.uuid)}";
			UnityWebRequest request = UnityWebRequest.Get(url);
			request.timeout = 30;
			yield return request.SendWebRequest();
			if (!_isEnabled())
			{
				yield break;
			}
			if (IsWebRequestSuccess(request))
			{
				try
				{
					Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
					File.WriteAllBytes(destinationPath, Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)request.downloadHandler.data));
					photo.imageFileName = Path.GetFileName(destinationPath);
					photo.imageBytes = ((long?)((Il2CppArrayBase<byte>)(object)request.downloadHandler.data)?.Length) ?? photo.imageBytes;
					onSuccess();
					_debugNotify("Photo Downloaded", photo.uuid, (NotificationType)3);
					yield break;
				}
				catch (Exception ex)
				{
					NotifyPhotoDownloadFailure(photo.uuid, ex.Message);
					yield break;
				}
			}
			NotifyPhotoDownloadFailure(reason: $"HTTP {request.responseCode} {request.error}", uuid: photo.uuid);
		}

		public IEnumerator DeletePhoto(string levelBarcode, string uuid, string authorSecret)
		{
			string baseUrl = _baseUrl();
			if (!_isEnabled() || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(levelBarcode) || string.IsNullOrWhiteSpace(uuid))
			{
				yield break;
			}
			PhotoEchoDeleteRequest payload = new PhotoEchoDeleteRequest
			{
				levelBarcode = levelBarcode,
				uuid = uuid,
				authorSecret = authorSecret
			};
			using HttpClientHandler handler = CreateHttpHandler();
			using HttpClient client = new HttpClient(handler)
			{
				Timeout = TimeSpan.FromSeconds(20.0)
			};
			using StringContent content = new StringContent(JsonSerializer.Serialize(payload, _jsonOptions), Encoding.UTF8, "application/json");
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, baseUrl + "/api/photos")
			{
				Content = content
			};
			Task<HttpResponseMessage> responseTask;
			try
			{
				responseTask = client.SendAsync(request);
			}
			catch (Exception ex)
			{
				Exception ex2 = ex;
				ReportHttpFailure("Photo Delete", uuid, ex2.Message);
				yield break;
			}
			yield return WaitForResponse(responseTask);
			if (_isEnabled() && TryAcceptResponse(responseTask, "Photo Delete", uuid))
			{
				_debugNotify("Photo Echo Deleted Online", uuid, (NotificationType)3);
			}
		}

		private IEnumerator WaitForResponse(Task<HttpResponseMessage> responseTask)
		{
			while (!responseTask.IsCompleted && _isEnabled())
			{
				yield return null;
			}
		}

		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 void NotifyPhotoUploadFailure(int statusCode, string responseText)
		{
			string arg = FriendlyPhotoUploadError(statusCode, responseText);
			_warn($"Photo upload failed: HTTP {statusCode} {responseText}");
			_userNotify("Photo Upload Failed", arg, (NotificationType)1);
			_debugNotify("Photo Upload Failed", responseText, (NotificationType)2);
		}

		private void NotifyPhotoDownloadFailure(string uuid, string reason)
		{
			_warn("Photo file download failed for " + uuid + ": " + reason);
			_userNotify("Photo Download Failed", "Could not download the photo source file.", (NotificationType)1);
			_debugNotify("Photo Download Failed", reason, (NotificationType)2);
		}

		private static string FriendlyPhotoUploadError(int statusCode, string responseText)
		{
			if (1 == 0)
			{
			}
			string result = statusCode switch
			{
				507 => "Photo storage is full. Upload disabled.", 
				429 => "Daily photo upload limit reached.", 
				409 => "This level has reached the photo limit.", 
				413 => "Photo is too large.", 
				403 => "You can only delete your own photo echoes.", 
				_ => ExtractJsonError(responseText) ?? "Could not upload photo echo.", 
			};
			if (1 == 0)
			{
			}
			return result;
		}

		private static string ExtractJsonError(string responseText)
		{
			if (string.IsNullOrWhiteSpace(responseText))
			{
				return null;
			}
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(responseText);
				if (jsonDocument.RootElement.TryGetProperty("error", out var value) && value.ValueKind == JsonValueKind.String)
				{
					return value.GetString();
				}
			}
			catch
			{
				return null;
			}
			return null;
		}

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

		private static StringElement _authorNameElement;

		private static FunctionElement _placeCameraElement;

		private static FunctionElement _followCameraElement;

		public static Page MyEchoesPage { get; private set; }

		public static void Setup(Action<bool> setModEnabled, Action useSteamName, Action<string> setDraftBody, Action createTextEcho, Action placeOrDeleteCamera, Action toggleCameraFollow, Action createPhotoEcho, 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: 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_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: 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);
			_authorNameElement = val.CreateString("Your Name", Color.cyan, EchoPreferences.AuthorName, (Action<string>)EchoPreferences.SetAuthorName);
			val.CreateFunction("Use Steam Name", Color.cyan, useSteamName);
			val.CreateEnum("Echo Color", Color.white, (Enum)EchoPreferences.BubbleColor, (Action<Enum>)EchoPreferences.SetBubbleColor);
			val.CreateBool("Debug Info", Color.cyan, EchoPreferences.DebugInfo, (Action<bool>)EchoPreferences.SetDebugInfo);
			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("Text Echo", Color.white, 10, true);
			val2.CreateString("Message Body", Color.white, string.Empty, setDraftBody);
			val2.CreateFunction("Create Text Echo", Color.green, createTextEcho);
			val2.CreateFloat("Text Echo Display Time", Color.cyan, EchoPreferences.EchoDisplaySeconds, 0.5f, 0.5f, 10f, (Action<float>)EchoPreferences.SetEchoDisplaySeconds);
			Page val3 = val.CreatePage("Photo Echo", Color.yellow, 10, true);
			_placeCameraElement = val3.CreateFunction("Place Camera", Color.yellow, placeOrDeleteCamera);
			val3.CreateFunction("Create Photo Echo", Color.green, createPhotoEcho);
			val3.CreateFloat("Photo Echo Display Time", Color.cyan, EchoPreferences.PhotoEchoDisplaySeconds, 0.5f, 0.5f, 10f, (Action<float>)EchoPreferences.SetPhotoEchoDisplaySeconds);
			Page val4 = val3.CreatePage("Camera Controls", Color.yellow, 10, true);
			_followCameraElement = val4.CreateFunction("Follow Right Hand", Color.yellow, toggleCameraFollow);
			MyEchoesPage = val.CreatePage("My Echoes", Color.cyan, 10, true);
			MyEchoesPage.CreateFunction("Refresh List", Color.white, refreshMyEchoes);
		}

		public static void RefreshAuthorName()
		{
			if (_authorNameElement != null)
			{
				_authorNameElement.Value = EchoPreferences.AuthorName;
			}
		}

		public static void RefreshPhotoCameraButtons(bool hasCamera, bool isFollowing)
		{
			if (_placeCameraElement != null)
			{
				((Element)_placeCameraElement).ElementName = (hasCamera ? "Delete Camera" : "Place Camera");
			}
			if (_followCameraElement != null)
			{
				((Element)_followCameraElement).ElementName = (isFollowing ? "Stop Following" : "Follow Right Hand");
			}
		}
	}
	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 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;
		}

		public static string AlbumResourceName(EchoBubbleColor color)
		{
			if (1 == 0)
			{
			}
			string text = color switch
			{
				EchoBubbleColor.Red => "echo-album-red.png", 
				EchoBubbleColor.Yellow => "echo-album-yellow.png", 
				EchoBubbleColor.Green => "echo-album-green.png", 
				EchoBubbleColor.Cyan => "echo-album-cyan.png", 
				EchoBubbleColor.Blue => "echo-album-blue.png", 
				EchoBubbleColor.Purple => "echo-album-purple.png", 
				_ => "echo-album.png", 
			};
			if (1 == 0)
			{
			}
			string text2 = text;
			return "TemporalEchoes.Assets." + text2;
		}
	}
	internal static class EchoContentFilter
	{
		public static readonly string[] SensitiveWords = new string[25]
		{
			"fuck", "fxxk", "porn", "ponr", "dick", "dik", "vagina", "NSFW", "ejaculation", "nigger",
			"nigga", "faggot", "retard", "kike", "chink", "spic", "password", "passwd", "pwd", "api_key",
			"apikey", "private_key", "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 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 SerializableQuaternion
	{
		public float x { get; set; }

		public float y { get; set; }

		public float z { get; set; }

		public float w { get; set; }

		public static SerializableQuaternion From(Quaternion 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)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			return new SerializableQuaternion
			{
				x = value.x,
				y = value.y,
				z = value.z,
				w = value.w
			};
		}

		public Quaternion ToQuaternion()
		{
			//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_0021: Unknown result type (might be due to invalid IL or missing references)
			return new Quaternion(x, y, z, w);
		}
	}
	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;
	}
	public class PhotoEchoMetadata
	{
		public string uuid { get; set; } = string.Empty;

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

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

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

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

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

		public long imageBytes { get; set; }

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

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

		public SerializableQuaternion cameraRotation { get; set; } = new SerializableQuaternion();

		public string createdAt { 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; } = "local";
	}
	public class LevelPhotoEchoFile
	{
		public string levelBarcode { get; set; } = string.Empty;

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

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

		public List<PhotoEchoMetadata> photos { get; set; } = new List<PhotoEchoMetadata>();
	}
	public class PhotoEchoUpload
	{
		public string levelBarcode { get; set; } = string.Empty;

		public PhotoEchoMetadata photo { get; set; }

		public string imageBase64 { get; set; } = string.Empty;
	}
	public class PhotoEchoUploadResponse
	{
		public bool ok { get; set; }

		public bool duplicate { get; set; }

		public bool accepted { get; set; }

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

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

		public PhotoEchoMetadata photo { get; set; }
	}
	public class PhotoEchoDeleteRequest
	{
		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 class PhotoEchoMarker
	{
		public PhotoEchoMetadata Photo;

		public GameObject Root;

		public SpriteRenderer Renderer;

		public Sprite AlbumSprite;

		public Sprite PhotoSprite;

		public Texture2D PhotoTexture;

		public Vector3 BasePosition;

		public float FloatPhase;

		public float LastNotifyTime;

		public bool ShowingPhoto;

		public float HideAtTime;
	}
	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;

		private static MelonPreferences_Entry<float> _echoDisplaySeconds;

		private static MelonPreferences_Entry<float> _photoEchoDisplaySeconds;

		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 float EchoDisplaySeconds => ClampEchoDisplaySeconds(_echoDisplaySeconds?.Value ?? 5f);

		public static float EchoNotifyCooldownSeconds => EchoDisplaySeconds + 1f;

		public static float PhotoEchoDisplaySeconds => ClampEchoDisplaySeconds(_photoEchoDisplaySeconds?.Value ?? 5f);

		public static float PhotoEchoNotifyCooldownSeconds => PhotoEchoDisplaySeconds + 1f;

		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);
			_echoDisplaySeconds = _category.CreateEntry<float>("EchoDisplaySeconds", 5f, "Echo Display Time", (string)null, false, false, (ValueValidator)null, (string)null);
			_photoEchoDisplaySeconds = _category.CreateEntry<float>("PhotoEchoDisplaySeconds", 5f, "Photo Echo Display Time", (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/*cast due to .constrained prefix*/).ToString();
			MelonPreferences.Save();
		}

		public static void SetEchoDisplaySeconds(float value)
		{
			_echoDisplaySeconds.Value = ClampEchoDisplaySeconds(value);
			MelonPreferences.Save();
		}

		public static void SetPhotoEchoDisplaySeconds(float value)
		{
			_photoEchoDisplaySeconds.Value = ClampEchoDisplaySeconds(value);
			MelonPreferences.Save();
		}

		private static float ClampEchoDisplaySeconds(float value)
		{
			return Math.Clamp(value, 0.5f, 10f);
		}
	}
	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 static readonly Vector3 TriggerSize = new Vector3(1.4f, 1.4f, 0.5f);

		private readonly Func<bool> _isEnabled;

		private readonly Func<float> _notifyCooldownSeconds;

		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, Func<float> notifyCooldownSeconds, Action<EchoMessage> onEchoTouched, Action<string> logWarning)
		{
			_isEnabled = isEnabled;
			_notifyCooldownSeconds = notifyCooldownSeconds;
			_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_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			if (_isEnabled())
			{
				EchoBubbleColor color = 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(color);
				val2.color = Color.white;
				((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)val2).receiveShadows = false;
				Material val3 = CreateSpriteMaterial(GetIconTexture(color));
				if ((Object)(object)val3 != (Object)null)
				{
					((Renderer)val2).material = val3;
				}
				BoxCollider val4 = val.AddComponent<BoxCollider>();
				((Collider)val4).isTrigger = true;
				val4.size = TriggerSize;
				EchoTrigger echoTrigger = val.AddComponent<EchoTrigger>();
				echoTrigger.Initialize(this, echo);
				_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();
			}
		}

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

		internal void NotifyFromTrigger(EchoMessage message)
		{
			float time = Time.time;
			if (!(time - _lastNotifyTime < _notifyCooldownSeconds()))
			{
				_lastNotifyTime = time;
				_onEchoTouched(message);
			}
		}

		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 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);
		}
	}
	[RegisterTypeInIl2Cpp]
	internal sealed class EchoTrigger : MonoBehaviour
	{
		private const float HandFallbackRadius = 0.55f;

		private EchoRenderer _renderer;

		private EchoMessage _message;

		public EchoTrigger(IntPtr intPtr)
			: base(intPtr)
		{
		}

		[HideFromIl2Cpp]
		public void Initialize(EchoRenderer renderer, EchoMessage message)
		{
			_renderer = renderer;
			_message = message;
		}

		private void OnTriggerEnter(Collider other)
		{
			if (_renderer != null && _message != null && !((Object)(object)other == (Object)null) && Object.op_Implicit((Object)(object)other.attachedRigidbody) && IsPlayerHand(other))
			{
				_renderer.NotifyFromTrigger(_message);
			}
		}

		[HideFromIl2Cpp]
		private bool IsPlayerHand(Collider other)
		{
			Transform val = (((Object)(object)Player.LeftHand != (Object)null) ? ((Component)Player.LeftHand).transform : null);
			Transform val2 = (((Object)(object)Player.RightHand != (Object)null) ? ((Component)Player.RightHand).transform : null);
			Transform transform = ((Component)other).transform;
			Transform candidate = (((Object)(object)other.attachedRigidbody != (Object)null) ? ((Component)other.attachedRigidbody).transform : null);
			if (IsSameOrChild(transform, val) || IsSameOrChild(candidate, val))
			{
				return true;
			}
			if (IsSameOrChild(transform, val2) || IsSameOrChild(candidate, val2))
			{
				return true;
			}
			return IsHandNear(val) || IsHandNear(val2);
		}

		[HideFromIl2Cpp]
		private bool IsHandNear(Transform hand)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			return (Object)(object)hand != (Object)null && Vector3.Distance(hand.position, ((Component)this).transform.position) <= 0.55f;
		}

		[HideFromIl2Cpp]
		private static bool IsSameOrChild(Transform candidate, Transform parent)
		{
			return (Object)(object)candidate != (Object)null && (Object)(object)parent != (Object)null && ((Object)(object)candidate == (Object)(object)parent || candidate.IsChildOf(parent));
		}
	}
	internal sealed class PhotoEchoController
	{
		private const string CameraIconResource = "TemporalEchoes.Assets.echo-camera-simple.png";

		private const string ShutterSoundResource = "TemporalEchoes.Assets.CameraShot.wav";

		private const int PhotoSize = 256;

		private const int MaxPhotoBytes = 153600;

		private const float CameraSpawnDistance = 1.25f;

		private const float CameraIconScale = 0.24f;

		private const float DirectionArrowDistance = 0.9f;

		private const float DirectionArrowScale = 0.12f;

		private readonly Func<bool> _isEnabled;

		private readonly Func<string> _photoImagesDir;

		private readonly Action<string> _logWarning;

		private readonly JsonSerializerOptions _jsonOptions;

		private Texture2D _cameraIconTexture;

		private Sprite _cameraIconSprite;

		private AudioClip _shutterClip;

		private GameObject _cameraMarker;

		private GameObject _directionArrow;

		private AudioSource _audioSource;

		public bool HasCamera => (Object)(object)_cameraMarker != (Object)null;

		public bool IsFollowingRightHand { get; private set; }

		public bool LastCaptureSucceeded { get; private set; }

		public string LastSavedPath { get; private set; } = string.Empty;

		public PhotoEchoController(Func<bool> isEnabled, Func<string> photoImagesDir, Action<string> logWarning, JsonSerializerOptions jsonOptions)
		{
			_isEnabled = isEnabled;
			_photoImagesDir = photoImagesDir;
			_logWarning = logWarning;
			_jsonOptions = jsonOptions;
		}

		public void LoadAssets()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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)
			_cameraIconTexture = LoadEmbeddedTexture("TemporalEchoes.Assets.echo-camera-simple.png");
			if ((Object)(object)_cameraIconTexture != (Object)null)
			{
				_cameraIconSprite = Sprite.Create(_cameraIconTexture, new Rect(0f, 0f, (float)((Texture)_cameraIconTexture).width, (float)((Texture)_cameraIconTexture).height), Vector2.one * 0.5f, 1000f);
				((Object)_cameraIconSprite).hideFlags = (HideFlags)32;
			}
			_shutterClip = LoadEmbeddedWav("TemporalEchoes.Assets.CameraShot.wav", "Temporal Echoes Camera Shot");
		}

		public void Update()
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			if (!_isEnabled())
			{
				SetCameraVisible(visible: false);
				return;
			}
			SetCameraVisible(visible: true);
			if (IsFollowingRightHand && (Object)(object)_cameraMarker != (Object)null)
			{
				Transform val = (((Object)(object)Player.RightHand != (Object)null) ? ((Component)Player.RightHand).transform : null);
				if ((Object)(object)val != (Object)null)
				{
					_cameraMarker.transform.SetPositionAndRotation(val.position, val.rotation);
				}
			}
		}

		public void PlaceOrDeleteCamera()
		{
			if ((Object)(object)_cameraMarker != (Object)null)
			{
				DeleteCamera();
			}
			else
			{
				PlaceCamera();
			}
		}

		public void ToggleFollowRightHand()
		{
			if (!((Object)(object)_cameraMarker == (Object)null))
			{
				IsFollowingRightHand = !IsFollowingRightHand;
			}
		}

		public void Clear()
		{
			DeleteCamera();
		}

		public IEnumerator Capture(PhotoEchoMetadata metadata)
		{
			LastCaptureSucceeded = false;
			LastSavedPath = string.Empty;
			if (!_isEnabled() || (Object)(object)_cameraMarker == (Object)null)
			{
				yield break;
			}
			for (int count = 3; count >= 1; count--)
			{
				Core.SendPhotoCountdownNotification(count);
				yield return (object)new WaitForSeconds(1f);
			}
			bool arrowWasVisible = (Object)(object)_directionArrow != (Object)null && _directionArrow.activeSelf;
			SetDirectionArrowVisible(visible: false);
			PlayShutterSound();
			yield return (object)new WaitForEndOfFrame();
			Vector3 capturePosition = _cameraMarker.transform.position;
			Quaternion captureRotation = GetCaptureRotation();
			Camera camera = CreateCamera(capturePosition, captureRotation);
			RenderTexture renderTexture = null;
			Texture2D photo = null;
			try
			{
				int depth = (IsQuest() ? 32 : 0);
				renderTexture = (camera.targetTexture = new RenderTexture(256, 256, depth));
				yield return null;
				camera.Render();
				yield return null;
				photo = ReadTexture(renderTexture);
				byte[] bytes = EncodeUnderLimit(photo);
				if (bytes == null)
				{
					_logWarning("Failed to encode photo under 150KB.");
					yield break;
				}
				Directory.CreateDirectory(_photoImagesDir());
				string fileName = "photo-" + metadata.uuid + ".jpg";
				LastSavedPath = Path.Combine(_photoImagesDir(), fileName);
				File.WriteAllBytes(LastSavedPath, bytes);
				metadata.imageFileName = fileName;
				metadata.imageBytes = bytes.Length;
				metadata.cameraPosition = SerializableVector3.From(capturePosition);
				metadata.cameraRotation = SerializableQuaternion.From(captureRotation);
				LastCaptureSucceeded = true;
			}
			finally
			{
				SetDirectionArrowVisible(arrowWasVisible);
				if ((Object)(object)camera != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)camera).gameObject);
				}
				if ((Object)(object)renderTexture != (Object)null)
				{
					renderTexture.Release();
					Object.Destroy((Object)(object)renderTexture);
				}
				if ((Object)(object)photo != (Object)null)
				{
					Object.Destroy((Object)(object)photo);
				}
			}
		}

		private void PlaceCamera()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			Transform val = (((Object)(object)Player.Head != (Object)null) ? Player.Head : (((Object)(object)Player.RigManager != (Object)null) ? ((Component)Player.RigManager).transform : null));
			if (!((Object)(object)val == (Object)null))
			{
				Vector3 val2 = val.position + val.forward * 1.25f;
				Vector3 val3 = val.position - val2;
				Quaternion val4 = Quaternion.LookRotation(((Vector3)(ref val3)).normalized, Vector3.up);
				_cameraMarker = new GameObject("Temporal Echoes Photo Camera");
				_cameraMarker.transform.SetPositionAndRotation(val2, val4);
				_cameraMarker.transform.localScale = Vector3.one * 0.24f;
				SpriteRenderer val5 = _cameraMarker.AddComponent<SpriteRenderer>();
				val5.sprite = _cameraIconSprite;
				val5.color = Color.white;
				((Renderer)val5).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)val5).receiveShadows = false;
				Material val6 = CreateSpriteMaterial(_cameraIconTexture);
				if ((Object)(object)val6 != (Object)null)
				{
					((Renderer)val5).material = val6;
				}
				_audioSource = _cameraMarker.AddComponent<AudioSource>();
				_audioSource.playOnAwake = false;
				_audioSource.spatialBlend = 0f;
				_audioSource.volume = 1f;
				_audioSource.dopplerLevel = 0f;
				_audioSource.clip = _shutterClip;
				CreateDirectionArrow();
				IsFollowingRightHand = false;
			}
		}

		private void DeleteCamera()
		{
			IsFollowingRightHand = false;
			if ((Object)(object)_cameraMarker != (Object)null)
			{
				Object.Destroy((Object)(object)_cameraMarker);
				_cameraMarker = null;
				_directionArrow = null;
				_audioSource = null;
			}
		}

		private void SetDirectionArrowVisible(bool visible)
		{
			if ((Object)(object)_directionArrow != (Object)null)
			{
				_directionArrow.SetActive(visible);
			}
		}

		private void SetCameraVisible(bool visible)
		{
			if ((Object)(object)_cameraMarker != (Object)null)
			{
				_cameraMarker.SetActive(visible);
			}
		}

		private void PlayShutterSound()
		{
			if (!((Object)(object)_audioSource == (Object)null) && !((Object)(object)_shutterClip == (Object)null))
			{
				_audioSource.clip = _shutterClip;
				_audioSource.PlayOneShot(_shutterClip, 1f);
			}
		}

		private Quaternion GetCaptureRotation()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: 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)
			return _cameraMarker.transform.rotation;
		}

		private static Camera CreateCamera(Vector3 position, Quaternion rotation)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Temporal Echoes Photo Capture Camera");
			val.transform.SetPositionAndR