Decompiled source of TemporalEchoes v2.0.1
Mods/TemporalEchoes.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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