using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Security; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Threading.Tasks; using BoneLib; using BoneLib.BoneMenu; using BoneLib.Notifications; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.Warehouse; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using TemporalEchoes; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "TemporalEchoes", "1.0.0", "CAitStudio", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: MelonOptionalDependencies(new string[] { "BoneLib" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("TemporalEchoes")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("TemporalEchoes")] [assembly: AssemblyTitle("TemporalEchoes")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TemporalEchoes { public class Core : MelonMod { private sealed class OwnedEchoLevel { public string Path; public LevelEchoFile File; public List Messages; } [CompilerGenerated] private sealed class d__25 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int token; public string barcode; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__25(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(2.5f); <>1__state = 1; return true; case 1: <>1__state = -1; if (!ModEnabled || token != _levelLoadToken || barcode != _currentBarcode) { return false; } LoadCurrentLevelFile(); RenderCurrentLevelEchoes(); MelonCoroutines.Start(DownloadCurrentLevelFromServer()); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const string ModName = "TemporalEchoes"; private const float MenuActionCooldownSeconds = 5f; private const float EchoPopupSeconds = 5f; private const float StatusPopupSeconds = 2f; private const int LevelLoadDelayMs = 2500; private const float DuplicateEchoRadius = 0.5f; private const string ServerUrl = "https://temporal-echoes.3260142519.workers.dev"; private static Core _instance; private static string _currentBarcode = string.Empty; private static string _currentLevelTitle = string.Empty; private static int _levelLoadToken; private static string _draftBody = string.Empty; private static LevelEchoFile _currentFile; private static float _lastCreateClickTime = -100f; private static float _lastReloadClickTime = -100f; private static EchoApiClient _api; private static EchoRenderer _renderer; private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = true, PropertyNameCaseInsensitive = true }; private static string DataDir => Path.Combine(MelonEnvironment.UserDataDirectory, "TemporalEchoes"); private static string LevelsDir => Path.Combine(DataDir, "Levels"); private static bool DebugInfoEnabled => EchoPreferences.DebugInfo; private static bool ModEnabled => EchoPreferences.ModEnabled; public override void OnInitializeMelon() { _instance = this; EchoPreferences.Setup(); Directory.CreateDirectory(LevelsDir); _api = new EchoApiClient((Func)(() => ModEnabled), (Func)NormalizeServerUrl, JsonOptions, (Action)((MelonBase)this).LoggerInstance.Warning, (Action)SendDebugNotification); _renderer = new EchoRenderer((Func)(() => ModEnabled), (Action)SendEchoNotification, (Action)((MelonBase)this).LoggerInstance.Warning); _renderer.LoadIcons(); EchoBoneMenu.Setup(SetModEnabled, delegate(string body) { _draftBody = body ?? string.Empty; }, CreateEchoFromMenu, ReloadCurrentLevel, RefreshMyEchoesMenu); RefreshMyEchoesMenu(); Hooking.OnLevelLoaded += OnLevelLoaded; Hooking.OnLevelUnloaded += OnLevelUnloaded; ((MelonBase)this).LoggerInstance.Msg("Initialized."); } public override void OnUpdate() { if (ModEnabled) { _renderer.Update(); } } private static void OnLevelLoaded(LevelInfo info) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) _currentBarcode = info.barcode ?? string.Empty; _currentLevelTitle = info.title ?? string.Empty; int token = ++_levelLoadToken; ((MelonBase)_instance).LoggerInstance.Msg("Level loaded: " + _currentBarcode); if (!ModEnabled) { _renderer.Clear(); } else { MelonCoroutines.Start(LoadEchoesAfterDelay(token, _currentBarcode)); } } [IteratorStateMachine(typeof(d__25))] private static IEnumerator LoadEchoesAfterDelay(int token, string barcode) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__25(0) { token = token, barcode = barcode }; } private static void OnLevelUnloaded() { _levelLoadToken++; _renderer.Clear(); _currentBarcode = string.Empty; _currentLevelTitle = string.Empty; _currentFile = null; } private static void CreateEchoFromMenu() { //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) if (!ModEnabled || !TryStartMenuAction(ref _lastCreateClickTime)) { return; } if (string.IsNullOrWhiteSpace(_currentBarcode)) { SendNotification("Temporal Echoes", "No loaded level barcode yet.", 3f, (NotificationType)1); return; } string text = (_draftBody ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(text)) { SendNotification("Temporal Echoes", "Message body is empty.", 3f, (NotificationType)1); return; } if (EchoContentFilter.TryFindSensitiveWord(text, out var word)) { SendNotification("Temporal Echoes", "Blocked sensitive word: " + word, 2f, (NotificationType)1); return; } string echoAuthorName = GetEchoAuthorName(); text = ComposeEchoBody(text, echoAuthorName); EnsureCurrentLevelFile(); object obj; if (!((Object)(object)Player.Chest != (Object)null)) { if (!((Object)(object)Player.Head != (Object)null)) { RigManager rigManager = Player.RigManager; obj = ((rigManager != null) ? ((Component)rigManager).transform : null); } else { obj = Player.Head; } } else { obj = Player.Chest; } Transform val = (Transform)obj; if ((Object)(object)val == (Object)null) { SendNotification("Temporal Echoes", "Player transform not ready.", 3f, (NotificationType)1); return; } Vector3 position = val.position; if (HasNearbyEcho(position)) { SendNotification("Temporal Echoes", "An echo already exists here.", 2f, (NotificationType)1); return; } string createdAt = DateTimeOffset.Now.ToString("o"); string uuid = Guid.NewGuid().ToString("N"); EchoMessage echoMessage = new EchoMessage { uuid = uuid, body = text, position = SerializableVector3.From(position), createdAt = createdAt, steamId = TryGetSteamId(), authorName = echoAuthorName, authorHash = CurrentAuthorHash(), color = EchoPreferences.BubbleColor.ToString(), syncState = "pending" }; _currentFile.messages.Add(echoMessage); SaveCurrentLevelFile(); _renderer.Render(echoMessage); RefreshMyEchoesMenu(); SendNotification("Echo Created", text, 2f, (NotificationType)3); MelonCoroutines.Start(UploadEchoToServer(echoMessage)); } private static void ReloadCurrentLevel() { if (ModEnabled && TryStartMenuAction(ref _lastReloadClickTime)) { ReloadCurrentLevelNow(); SendNotification("Temporal Echoes", "Reloading echoes.", 2f, (NotificationType)0); } } private static void ReloadCurrentLevelNow() { if (ModEnabled) { LoadCurrentLevelFile(); RenderCurrentLevelEchoes(); MelonCoroutines.Start(DownloadCurrentLevelFromServer()); } } private static string NormalizeServerUrl() { return "https://temporal-echoes.3260142519.workers.dev".Trim().TrimEnd('/'); } private static bool TryStartMenuAction(ref float lastClickTime) { float time = Time.time; if (time - lastClickTime < 5f) { return false; } lastClickTime = time; return true; } private static string CurrentAuthorHash() { return HashString(EchoPreferences.AuthorSecret); } private static void SetModEnabled(bool enabled) { EchoPreferences.SetModEnabled(enabled); if (!enabled) { _levelLoadToken++; _renderer.Clear(); } else { ReloadCurrentLevelNow(); } } private static string GetEchoAuthorName() { string text = EchoText.Limit(EchoPreferences.AuthorName.Trim(), 50); if (!string.Equals(text, "NullMan", StringComparison.Ordinal)) { return text; } string text2 = EchoText.Limit(TryGetSteamName().Trim(), 50); if (string.IsNullOrWhiteSpace(text2)) { return text; } EchoPreferences.SetAuthorName(text2); return text2; } private static string ComposeEchoBody(string body, string authorName) { int maxLength = Math.Max(0, 180 - authorName.Length - 1); string value = authorName + ":" + LimitText(body, maxLength); return LimitText(value, 180); } private static string LimitText(string value, int maxLength) { return EchoText.Limit(value, maxLength); } private static void RenderCurrentLevelEchoes() { _renderer.RenderLevel(_currentFile); } private static void SendDebugNotification(string title, string message, NotificationType type) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (DebugInfoEnabled) { SendNotification(title, message, 2f, type); } } private static string TryGetSteamId() { try { return (((from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("Steamworks.SteamClient")).FirstOrDefault((Type type) => type != null)?.GetProperty("SteamId", BindingFlags.Static | BindingFlags.Public))?.GetValue(null))?.ToString() ?? string.Empty; } catch { return string.Empty; } } private static string TryGetSteamName() { try { return (((from assembly in AppDomain.CurrentDomain.GetAssemblies() select assembly.GetType("Steamworks.SteamClient")).FirstOrDefault((Type type) => type != null)?.GetProperty("Name", BindingFlags.Static | BindingFlags.Public))?.GetValue(null))?.ToString() ?? string.Empty; } catch { return string.Empty; } } private static string FormatNotificationTitle(string createdAt) { if (DateTimeOffset.TryParse(createdAt, out var result)) { return result.ToString("yyyy/MM/dd HH:mm zzz"); } return "Temporal Echo"; } private static void SendNotification(string title, string message, float seconds, NotificationType type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown Notifier.Send(new Notification { Title = NotificationText.op_Implicit(title), Message = NotificationText.op_Implicit(message), Type = type, ShowTitleOnPopup = true, PopupLength = seconds }); } private static void SendEchoNotification(EchoMessage message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown Notifier.Send(new Notification { Title = NotificationText.op_Implicit(FormatNotificationTitle(message.createdAt)), Message = NotificationText.op_Implicit(message.body), Type = (NotificationType)0, ShowTitleOnPopup = true, PopupLength = 5f }); } private static void RefreshMyEchoesMenu() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) Page myEchoesPage = EchoBoneMenu.MyEchoesPage; if (myEchoesPage == null) { return; } myEchoesPage.RemoveAll(); myEchoesPage.CreateFunction("Refresh List", Color.white, (Action)RefreshMyEchoesMenu); List ownedEchoLevels = GetOwnedEchoLevels(); if (ownedEchoLevels.Count == 0) { myEchoesPage.CreateFunction("No echoes found", Color.gray, (Action)null); return; } foreach (OwnedEchoLevel level2 in ownedEchoLevels.OrderBy((OwnedEchoLevel level) => GetLevelMenuName(level.File), StringComparer.OrdinalIgnoreCase)) { string text = $"{GetLevelMenuName(level2.File)} ({level2.Messages.Count})"; Page val = myEchoesPage.CreatePage(text, Color.white, 10, true); val.CreateFunction("Show Barcode", Color.gray, (Action)delegate { Menu.DisplayDialog("Level Barcode", level2.File.levelBarcode, (Texture2D)null, (Action)null, (Action)null); }); foreach (EchoMessage item in level2.Messages.OrderByDescending((EchoMessage message) => message.createdAt)) { EchoMessage capturedMessage = item; string capturedPath = level2.Path; string capturedBarcode = level2.File.levelBarcode; string echoMenuTitle = GetEchoMenuTitle(capturedMessage); Page val2 = val.CreatePage(echoMenuTitle, Color.cyan, 10, true); val2.CreateFunction("Show Message", Color.white, (Action)delegate { Menu.DisplayDialog("Temporal Echo", capturedMessage.body, (Texture2D)null, (Action)null, (Action)null); }); val2.CreateFunction("Delete", Color.yellow, (Action)delegate { AskDeleteOwnedEcho(capturedPath, capturedBarcode, capturedMessage); }); } } } private static List GetOwnedEchoLevels() { List list = new List(); 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(File.ReadAllText(text), JsonOptions); if (levelEchoFile?.messages != null) { List list2 = levelEchoFile.messages.Where((EchoMessage message) => string.Equals(message.authorHash, authorHash, StringComparison.OrdinalIgnoreCase)).ToList(); if (list2.Count != 0) { list.Add(new OwnedEchoLevel { Path = text, File = levelEchoFile, Messages = list2 }); } } } catch (Exception ex) { ((MelonBase)_instance).LoggerInstance.Warning("Could not read echo file " + text + ": " + ex.Message); } } return list; } private static void AskDeleteOwnedEcho(string path, string levelBarcode, EchoMessage message) { string text = EchoText.Limit(message.body, 120); Menu.DisplayDialog("Delete echo?", "This will delete your echo:\n" + text, Dialog.WarningIcon, (Action)delegate { DeleteOwnedEcho(path, levelBarcode, message.uuid); }, (Action)null); } private static void DeleteOwnedEcho(string path, string levelBarcode, string uuid) { if (!DeleteOwnedEchoLocal(path, uuid)) { SendNotification("Temporal Echoes", "Echo was not found or is not yours.", 2f, (NotificationType)1); return; } if (string.Equals(levelBarcode, _currentBarcode, StringComparison.Ordinal)) { LoadCurrentLevelFile(); RenderCurrentLevelEchoes(); } RefreshMyEchoesMenu(); Menu.OpenPage(EchoBoneMenu.MyEchoesPage); SendNotification("Echo Deleted", uuid, 2f, (NotificationType)3); MelonCoroutines.Start(DeleteEchoFromServer(levelBarcode, uuid)); } private static bool DeleteOwnedEchoLocal(string path, string uuid) { try { LevelEchoFile levelEchoFile = JsonSerializer.Deserialize(File.ReadAllText(path), JsonOptions); if (levelEchoFile?.messages == null) { return false; } string authorHash = CurrentAuthorHash(); int num = levelEchoFile.messages.RemoveAll((EchoMessage message) => string.Equals(message.uuid, uuid, StringComparison.OrdinalIgnoreCase) && string.Equals(message.authorHash, authorHash, StringComparison.OrdinalIgnoreCase)); if (num <= 0) { return false; } File.WriteAllText(path, JsonSerializer.Serialize(levelEchoFile, JsonOptions)); return true; } catch (Exception ex) { ((MelonBase)_instance).LoggerInstance.Warning("Could not delete echo " + uuid + ": " + ex.Message); return false; } } private static string GetLevelMenuName(LevelEchoFile file) { string value = TryGetLevelTitle(file.levelBarcode); if (!string.IsNullOrWhiteSpace(value)) { return TrimMenuText(value, 34); } if (!string.IsNullOrWhiteSpace(file.levelTitle)) { return TrimMenuText(file.levelTitle, 34); } if (!string.IsNullOrWhiteSpace(file.levelBarcode)) { return TrimMenuText(file.levelBarcode, 34); } return string.IsNullOrWhiteSpace(file.levelHash) ? "Unknown Level" : file.levelHash; } private static string TryGetLevelTitle(string barcode) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown if (string.IsNullOrWhiteSpace(barcode)) { return string.Empty; } try { Crate val = default(Crate); if (AssetWarehouse.Instance != null && AssetWarehouse.Instance.TryGetCrate(new Barcode(barcode), ref val)) { return ((val != null) ? ((Scannable)val).Title : null) ?? string.Empty; } } catch { return string.Empty; } return string.Empty; } private static string GetEchoMenuTitle(EchoMessage message) { string text = message.createdAt; if (DateTimeOffset.TryParse(message.createdAt, out var result)) { text = result.ToString("MM/dd HH:mm"); } return TrimMenuText(text + " " + message.body, 42); } private static string TrimMenuText(string value, int maxLength) { value = (value ?? string.Empty).Replace('\n', ' ').Replace('\r', ' ').Trim(); if (value.Length <= maxLength) { return value; } return value.Substring(0, Math.Max(0, maxLength - 3)) + "..."; } private static void LoadCurrentLevelFile() { if (string.IsNullOrWhiteSpace(_currentBarcode)) { _currentFile = null; return; } string levelFilePath = GetLevelFilePath(_currentBarcode); if (!File.Exists(levelFilePath)) { _currentFile = new LevelEchoFile { levelBarcode = _currentBarcode, levelHash = HashBarcode(_currentBarcode), levelTitle = _currentLevelTitle, messages = new List() }; SaveCurrentLevelFile(); return; } try { _currentFile = JsonSerializer.Deserialize(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 list2 = (currentFile.messages = new List()); } } 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() }; } } private static void EnsureCurrentLevelFile() { if (_currentFile == null) { LoadCurrentLevelFile(); } } private static void SaveCurrentLevelFile() { if (_currentFile != null && !string.IsNullOrWhiteSpace(_currentBarcode)) { Directory.CreateDirectory(LevelsDir); if (!string.IsNullOrWhiteSpace(_currentLevelTitle)) { _currentFile.levelTitle = _currentLevelTitle; } File.WriteAllText(GetLevelFilePath(_currentBarcode), JsonSerializer.Serialize(_currentFile, JsonOptions)); } } private static bool HasNearbyEcho(Vector3 position) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (_currentFile?.messages == null) { return false; } foreach (EchoMessage message in _currentFile.messages) { if (Vector3.Distance(message.position.ToVector3(), position) < 0.5f) { return true; } } return false; } private static string GetLevelFilePath(string barcode) { return Path.Combine(LevelsDir, HashBarcode(barcode) + ".json"); } private static string HashBarcode(string barcode) { return HashString(barcode).Substring(0, 16); } private static string HashString(string value) { using SHA256 sHA = SHA256.Create(); byte[] inArray = sHA.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty)); return Convert.ToHexString(inArray); } private static IEnumerator DeleteEchoFromServer(string levelBarcode, string uuid) { return _api.DeleteEcho(levelBarcode, uuid, EchoPreferences.AuthorSecret); } private static IEnumerator DownloadCurrentLevelFromServer() { return _api.DownloadLevel(_currentBarcode, delegate(LevelEchoFile downloaded) { _currentFile = downloaded; _currentFile.levelBarcode = _currentBarcode; _currentFile.levelHash = HashBarcode(_currentBarcode); _currentFile.levelTitle = _currentLevelTitle; SaveCurrentLevelFile(); RenderCurrentLevelEchoes(); }); } private static IEnumerator UploadEchoToServer(EchoMessage echo) { return _api.UploadEcho(_currentBarcode, HashBarcode(_currentBarcode), echo, delegate { echo.syncState = "synced"; SaveCurrentLevelFile(); }); } } internal sealed class EchoApiClient { [CompilerGenerated] private sealed class d__8 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string levelBarcode; public string uuid; public string authorSecret; public EchoApiClient <>4__this; private string 5__1; private EchoDeleteRequest 5__2; private HttpClientHandler 5__3; private HttpClient 5__4; private StringContent 5__5; private HttpRequestMessage 5__6; private Task 5__7; private Exception 5__8; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__8(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if ((uint)(num - -6) <= 3u || num == 1) { try { if ((uint)(num - -6) <= 2u || num == 1) { try { if ((uint)(num - -6) <= 1u || num == 1) { try { if (num == -6 || num == 1) { try { } finally { <>m__Finally4(); } } } finally { <>m__Finally3(); } } } finally { <>m__Finally2(); } } } finally { <>m__Finally1(); } } 5__1 = null; 5__2 = null; 5__3 = null; 5__4 = null; 5__5 = null; 5__6 = null; 5__7 = null; 5__8 = null; <>1__state = -2; } private bool MoveNext() { bool result; try { switch (<>1__state) { default: result = false; goto end_IL_0000; case 0: <>1__state = -1; 5__1 = <>4__this._baseUrl(); if (!<>4__this._isEnabled() || string.IsNullOrWhiteSpace(5__1) || string.IsNullOrWhiteSpace(levelBarcode) || string.IsNullOrWhiteSpace(uuid)) { result = false; } else { 5__2 = new EchoDeleteRequest { levelBarcode = levelBarcode, uuid = uuid, authorSecret = authorSecret }; 5__3 = CreateHttpHandler(); <>1__state = -3; 5__4 = new HttpClient(5__3) { Timeout = TimeSpan.FromSeconds(20.0) }; <>1__state = -4; 5__5 = new StringContent(JsonSerializer.Serialize(5__2, <>4__this._jsonOptions), Encoding.UTF8, "application/json"); <>1__state = -5; 5__6 = new HttpRequestMessage(HttpMethod.Delete, 5__1 + "/api/messages") { Content = 5__5 }; <>1__state = -6; try { 5__7 = 5__4.SendAsync(5__6); } catch (Exception ex) { 5__8 = ex; <>4__this.ReportHttpFailure("Delete", uuid, 5__8.Message); result = false; break; } <>2__current = <>4__this.WaitForResponse(5__7); <>1__state = 1; result = true; } goto end_IL_0000; case 1: <>1__state = -6; if (!<>4__this._isEnabled() || !<>4__this.TryAcceptResponse(5__7, "Delete", uuid)) { result = false; break; } <>4__this._debugNotify("Echo Deleted Online", uuid, (NotificationType)3); result = false; break; } <>m__Finally4(); <>m__Finally3(); <>m__Finally2(); <>m__Finally1(); end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (5__3 != null) { ((IDisposable)5__3).Dispose(); } } private void <>m__Finally2() { <>1__state = -3; if (5__4 != null) { ((IDisposable)5__4).Dispose(); } } private void <>m__Finally3() { <>1__state = -4; if (5__5 != null) { ((IDisposable)5__5).Dispose(); } } private void <>m__Finally4() { <>1__state = -5; if (5__6 != null) { ((IDisposable)5__6).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__6 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string barcode; public Action applyDownloadedFile; public EchoApiClient <>4__this; private string 5__1; private string 5__2; private UnityWebRequest 5__3; private string 5__4; private LevelEchoFile 5__5; private Exception 5__6; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__2 = null; 5__3 = null; 5__4 = null; 5__5 = null; 5__6 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__1 = <>4__this._baseUrl(); if (!<>4__this._isEnabled() || string.IsNullOrWhiteSpace(5__1) || string.IsNullOrWhiteSpace(barcode)) { return false; } 5__2 = 5__1 + "/api/messages?barcode=" + Uri.EscapeDataString(barcode); 5__3 = UnityWebRequest.Get(5__2); 5__3.timeout = 20; <>2__current = 5__3.SendWebRequest(); <>1__state = 1; return true; case 1: <>1__state = -1; if (!<>4__this._isEnabled()) { return false; } if (!IsWebRequestSuccess(5__3)) { 5__4 = $"HTTP {5__3.responseCode} {5__3.error}"; <>4__this._warn("Download skipped/failed: " + 5__4); <>4__this._debugNotify("Echo Download Failed", 5__4, (NotificationType)2); return false; } try { 5__5 = JsonSerializer.Deserialize(5__3.downloadHandler.text, <>4__this._jsonOptions); if (5__5?.messages == null) { return false; } applyDownloadedFile(5__5); <>4__this._debugNotify("Echo Downloaded", $"{5__5.messages.Count} echoes loaded.", (NotificationType)3); 5__5 = null; } catch (Exception ex) { 5__6 = ex; <>4__this._warn("Download skipped/failed: " + 5__6.Message); <>4__this._debugNotify("Echo Download Failed", 5__6.Message, (NotificationType)2); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__7 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string levelBarcode; public string levelHash; public EchoMessage echo; public Action onSuccess; public EchoApiClient <>4__this; private string 5__1; private EchoUpload 5__2; private HttpClientHandler 5__3; private HttpClient 5__4; private StringContent 5__5; private Task 5__6; private Exception 5__7; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if ((uint)(num - -5) <= 2u || num == 1) { try { if ((uint)(num - -5) <= 1u || num == 1) { try { if (num == -5 || num == 1) { try { } finally { <>m__Finally3(); } } } finally { <>m__Finally2(); } } } finally { <>m__Finally1(); } } 5__1 = null; 5__2 = null; 5__3 = null; 5__4 = null; 5__5 = null; 5__6 = null; 5__7 = null; <>1__state = -2; } private bool MoveNext() { bool result; try { switch (<>1__state) { default: result = false; goto end_IL_0000; case 0: <>1__state = -1; 5__1 = <>4__this._baseUrl(); if (!<>4__this._isEnabled() || string.IsNullOrWhiteSpace(5__1)) { result = false; } else { 5__2 = new EchoUpload { levelBarcode = levelBarcode, levelHash = levelHash, message = echo }; 5__3 = CreateHttpHandler(); <>1__state = -3; 5__4 = new HttpClient(5__3) { Timeout = TimeSpan.FromSeconds(20.0) }; <>1__state = -4; 5__5 = new StringContent(JsonSerializer.Serialize(5__2, <>4__this._jsonOptions), Encoding.UTF8, "application/json"); <>1__state = -5; try { 5__6 = 5__4.PostAsync(5__1 + "/api/messages", 5__5); } catch (Exception ex) { 5__7 = ex; <>4__this.ReportHttpFailure("Upload", echo.uuid, 5__7.Message); result = false; break; } <>2__current = <>4__this.WaitForResponse(5__6); <>1__state = 1; result = true; } goto end_IL_0000; case 1: <>1__state = -5; if (!<>4__this._isEnabled()) { result = false; break; } if (!<>4__this.TryAcceptResponse(5__6, "Upload", echo.uuid)) { result = false; break; } onSuccess(); <>4__this._debugNotify("Echo Uploaded", echo.uuid, (NotificationType)3); result = false; break; } <>m__Finally3(); <>m__Finally2(); <>m__Finally1(); end_IL_0000:; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } return result; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (5__3 != null) { ((IDisposable)5__3).Dispose(); } } private void <>m__Finally2() { <>1__state = -3; if (5__4 != null) { ((IDisposable)5__4).Dispose(); } } private void <>m__Finally3() { <>1__state = -4; if (5__5 != null) { ((IDisposable)5__5).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__9 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Task responseTask; public EchoApiClient <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if (!responseTask.IsCompleted) { if (!<>4__this._isEnabled()) { return false; } <>2__current = null; <>1__state = 1; return true; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly Func _isEnabled; private readonly Func _baseUrl; private readonly JsonSerializerOptions _jsonOptions; private readonly Action _warn; private readonly Action _debugNotify; public EchoApiClient(Func isEnabled, Func baseUrl, JsonSerializerOptions jsonOptions, Action warn, Action debugNotify) { _isEnabled = isEnabled; _baseUrl = baseUrl; _jsonOptions = jsonOptions; _warn = warn; _debugNotify = debugNotify; } [IteratorStateMachine(typeof(d__6))] public IEnumerator DownloadLevel(string barcode, Action applyDownloadedFile) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__6(0) { <>4__this = this, barcode = barcode, applyDownloadedFile = applyDownloadedFile }; } [IteratorStateMachine(typeof(d__7))] public IEnumerator UploadEcho(string levelBarcode, string levelHash, EchoMessage echo, Action onSuccess) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__7(0) { <>4__this = this, levelBarcode = levelBarcode, levelHash = levelHash, echo = echo, onSuccess = onSuccess }; } [IteratorStateMachine(typeof(d__8))] public IEnumerator DeleteEcho(string levelBarcode, string uuid, string authorSecret) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__8(0) { <>4__this = this, levelBarcode = levelBarcode, uuid = uuid, authorSecret = authorSecret }; } [IteratorStateMachine(typeof(d__9))] private IEnumerator WaitForResponse(Task responseTask) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(0) { <>4__this = this, responseTask = responseTask }; } private bool TryAcceptResponse(Task responseTask, string operation, string uuid) { if (!responseTask.IsCompletedSuccessfully) { string reason = responseTask.Exception?.GetBaseException().Message ?? "Unknown error"; ReportHttpFailure(operation, uuid, reason); return false; } HttpResponseMessage result = responseTask.Result; if (result.IsSuccessStatusCode) { return true; } string reason2 = $"HTTP {result.StatusCode} {result.ReasonPhrase}"; ReportHttpFailure(operation, uuid, reason2); return false; } private void ReportHttpFailure(string operation, string uuid, string reason) { _warn($"{operation} failed for {uuid}: {reason}"); _debugNotify("Echo " + operation + " Failed", reason, (NotificationType)2); } private static bool IsWebRequestSuccess(UnityWebRequest request) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 return (int)request.result == 1 && request.responseCode >= 200 && request.responseCode < 300; } private static HttpClientHandler CreateHttpHandler() { return new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Automatic, ServerCertificateCustomValidationCallback = (HttpRequestMessage _, X509Certificate2? _, X509Chain? _, SslPolicyErrors _) => true }; } } internal static class EchoBoneMenu { private const string DiscordUrl = "https://discord.gg/Qf3r2zKwx"; public static Page MyEchoesPage { get; private set; } public static void Setup(Action setModEnabled, Action setDraftBody, Action createEcho, Action reloadEchoes, Action refreshMyEchoes) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) Page val = Page.Root.CreatePage("Temporal Echoes", Color.white, 0, true); val.CreateBool("Enable Mod", Color.green, EchoPreferences.ModEnabled, setModEnabled); val.CreateString("Your Name", Color.cyan, EchoPreferences.AuthorName, (Action)EchoPreferences.SetAuthorName); val.CreateEnum("Echo Color", Color.white, (Enum)EchoPreferences.BubbleColor, (Action)EchoPreferences.SetBubbleColor); val.CreateString("Message Body", Color.white, string.Empty, setDraftBody); val.CreateBool("Debug Info", Color.cyan, EchoPreferences.DebugInfo, (Action)EchoPreferences.SetDebugInfo); val.CreateFunction("Create Echo", Color.green, createEcho); val.CreateFunction("Reload Echoes", Color.yellow, reloadEchoes); val.CreateFunction("Join Discord", Color.blue, (Action)delegate { Application.OpenURL("https://discord.gg/Qf3r2zKwx"); }); Page val2 = val.CreatePage("Sensitive Words", Color.red, 10, true); string[] sensitiveWords = EchoContentFilter.SensitiveWords; foreach (string text in sensitiveWords) { val2.CreateFunction(text, Color.red, (Action)null); } MyEchoesPage = val.CreatePage("My Echoes", Color.cyan, 10, true); MyEchoesPage.CreateFunction("Refresh List", Color.white, refreshMyEchoes); } } public enum EchoBubbleColor { Red, Orange, Yellow, Green, Cyan, Blue, Purple } internal static class EchoBubbleColors { public static EchoBubbleColor Parse(string value) { EchoBubbleColor result; return (!Enum.TryParse(value, ignoreCase: true, out result)) ? EchoBubbleColor.Orange : result; } public static Color LightColor(EchoBubbleColor color) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) if (1 == 0) { } Color result = (Color)(color switch { EchoBubbleColor.Red => new Color(1f, 0.22f, 0.22f), EchoBubbleColor.Yellow => new Color(1f, 0.86f, 0.2f), EchoBubbleColor.Green => new Color(0.32f, 0.95f, 0.38f), EchoBubbleColor.Cyan => new Color(0.2f, 0.86f, 1f), EchoBubbleColor.Blue => new Color(0.3f, 0.52f, 1f), EchoBubbleColor.Purple => new Color(0.78f, 0.38f, 1f), _ => new Color(1f, 0.72f, 0.22f), }); if (1 == 0) { } return result; } public static string ResourceName(EchoBubbleColor color) { if (1 == 0) { } string text = color switch { EchoBubbleColor.Red => "echo-chat-red.png", EchoBubbleColor.Yellow => "echo-chat-yellow.png", EchoBubbleColor.Green => "echo-chat-green.png", EchoBubbleColor.Cyan => "echo-chat-cyan.png", EchoBubbleColor.Blue => "echo-chat-blue.png", EchoBubbleColor.Purple => "echo-chat-purple.png", _ => "echo-chat.png", }; if (1 == 0) { } string text2 = text; return "TemporalEchoes.Assets." + text2; } } internal static class EchoContentFilter { public static readonly string[] SensitiveWords = new string[26] { "fuck", "porn", "ponr", "dick", "vagina", "NSFW", "ejaculation", "nigger", "nigga", "faggot", "retard", "kike", "chink", "spic", "password", "passwd", "pwd", "token", "api_key", "apikey", "secret", "private_key", "discord.gg", "http://", "https://", "www." }; public static bool TryFindSensitiveWord(string text, out string word) { if (text == null) { text = string.Empty; } string[] sensitiveWords = SensitiveWords; foreach (string text2 in sensitiveWords) { if (text.IndexOf(text2, StringComparison.OrdinalIgnoreCase) >= 0) { word = text2; return true; } } word = string.Empty; return false; } } internal static class EchoLimits { public const int MaxAuthorNameLength = 50; public const int MaxEchoBodyLength = 180; } public class LevelEchoFile { public string levelBarcode { get; set; } = string.Empty; public string levelHash { get; set; } = string.Empty; public string levelTitle { get; set; } = string.Empty; public List messages { get; set; } = new List(); } public class EchoMessage { public string uuid { get; set; } = string.Empty; public string body { get; set; } = string.Empty; public SerializableVector3 position { get; set; } = new SerializableVector3(); public string createdAt { get; set; } = string.Empty; public string steamId { get; set; } = string.Empty; public string authorName { get; set; } = string.Empty; public string authorHash { get; set; } = string.Empty; public string color { get; set; } = string.Empty; public string syncState { get; set; } = "pending"; } public class SerializableVector3 { public float x { get; set; } public float y { get; set; } public float z { get; set; } public static SerializableVector3 From(Vector3 value) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) return new SerializableVector3 { x = value.x, y = value.y, z = value.z }; } public Vector3 ToVector3() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return new Vector3(x, y, z); } } public class EchoUpload { public string levelBarcode { get; set; } = string.Empty; public string levelHash { get; set; } = string.Empty; public EchoMessage message { get; set; } } public class EchoDeleteRequest { public string levelBarcode { get; set; } = string.Empty; public string uuid { get; set; } = string.Empty; public string authorSecret { get; set; } = string.Empty; } internal class EchoMarker { public EchoMessage Message; public GameObject Root; public Vector3 BasePosition; public float FloatPhase; public float LastNotifyTime; } internal static class EchoPreferences { private const string CategoryName = "TemporalEchoes"; public const string DefaultAuthorName = "NullMan"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry _modEnabled; private static MelonPreferences_Entry _debugInfo; private static MelonPreferences_Entry _authorName; private static MelonPreferences_Entry _authorSecret; private static MelonPreferences_Entry _bubbleColor; public static bool ModEnabled => _modEnabled?.Value ?? true; public static bool DebugInfo => _debugInfo?.Value ?? false; public static string AuthorName => _authorName?.Value ?? "NullMan"; public static string AuthorSecret => _authorSecret?.Value ?? string.Empty; public static EchoBubbleColor BubbleColor => EchoBubbleColors.Parse(_bubbleColor?.Value); public static void Setup() { _category = MelonPreferences.CreateCategory("TemporalEchoes", "Temporal Echoes"); _modEnabled = _category.CreateEntry("ModEnabled", true, "Enable Mod", (string)null, false, false, (ValueValidator)null, (string)null); _debugInfo = _category.CreateEntry("DebugInfo", false, "Debug Info", (string)null, false, false, (ValueValidator)null, (string)null); _authorName = _category.CreateEntry("AuthorName", "NullMan", "Your Name", (string)null, false, false, (ValueValidator)null, (string)null); _authorSecret = _category.CreateEntry("AuthorSecret", string.Empty, "Author Secret", (string)null, false, false, (ValueValidator)null, (string)null); _bubbleColor = _category.CreateEntry("BubbleColor", EchoBubbleColor.Orange.ToString(), "Bubble Color", (string)null, false, false, (ValueValidator)null, (string)null); if (string.IsNullOrWhiteSpace(_authorSecret.Value)) { _authorSecret.Value = Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N"); MelonPreferences.Save(); } } public static void SetModEnabled(bool value) { _modEnabled.Value = value; MelonPreferences.Save(); } public static void SetDebugInfo(bool value) { _debugInfo.Value = value; MelonPreferences.Save(); } public static void SetAuthorName(string value) { _authorName.Value = EchoText.Limit(value ?? string.Empty, 50); MelonPreferences.Save(); } public static void SetBubbleColor(Enum value) { _bubbleColor.Value = ((EchoBubbleColor)(object)value).ToString(); MelonPreferences.Save(); } } internal sealed class EchoRenderer { private const float FloatAmplitude = 0.12f; private const float FloatSpeed = 1.4f; private const float SpinSpeed = 55f; private const float TouchRadius = 0.35f; private const float NotifyCooldownSeconds = 6f; private readonly Func _isEnabled; private readonly Action _onEchoTouched; private readonly Action _logWarning; private readonly List _markers = new List(); private readonly Dictionary _textures = new Dictionary(); private readonly Dictionary _sprites = new Dictionary(); private float _lastNotifyTime = -100f; public EchoRenderer(Func isEnabled, Action onEchoTouched, Action logWarning) { _isEnabled = isEnabled; _onEchoTouched = onEchoTouched; _logWarning = logWarning; } public void LoadIcons() { foreach (EchoBubbleColor value in Enum.GetValues(typeof(EchoBubbleColor))) { Texture2D val = LoadEmbeddedTexture(EchoBubbleColors.ResourceName(value)); if (!((Object)(object)val == (Object)null)) { _textures[value] = val; _sprites[value] = CreateIconSprite(val); } } } public void RenderLevel(LevelEchoFile file) { Clear(); if (!_isEnabled() || file?.messages == null) { return; } foreach (EchoMessage message in file.messages) { Render(message); } } public void Render(EchoMessage echo) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) if (_isEnabled()) { EchoBubbleColor echoBubbleColor = EchoBubbleColors.Parse(echo.color); GameObject val = new GameObject("Temporal Echo " + echo.uuid); val.transform.position = echo.position.ToVector3(); val.transform.localScale = Vector3.one * 0.5f; SpriteRenderer val2 = val.AddComponent(); val2.sprite = GetIconSprite(echoBubbleColor); val2.color = Color.white; ((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val2).receiveShadows = false; Material val3 = CreateSpriteMaterial(GetIconTexture(echoBubbleColor)); if ((Object)(object)val3 != (Object)null) { ((Renderer)val2).material = val3; } AddGlowLight(val.transform, echoBubbleColor); _markers.Add(new EchoMarker { Message = echo, Root = val, BasePosition = val.transform.position, FloatPhase = Random.Range(0f, (float)Math.PI * 2f), LastNotifyTime = -100f }); } } public void Clear() { foreach (EchoMarker marker in _markers) { if ((Object)(object)marker.Root != (Object)null) { Object.Destroy((Object)(object)marker.Root); } } _markers.Clear(); } public void Update() { if (_isEnabled() && _markers.Count != 0) { Animate(); CheckTouches(); } } private void Animate() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) float time = Time.time; foreach (EchoMarker marker in _markers) { if ((Object)(object)marker.Root == (Object)null) { continue; } Vector3 basePosition = marker.BasePosition; basePosition.y += Mathf.Sin(time * 1.4f + marker.FloatPhase) * 0.12f; marker.Root.transform.position = basePosition; if ((Object)(object)Player.Head != (Object)null) { Vector3 val = marker.Root.transform.position - Player.Head.position; if (((Vector3)(ref val)).sqrMagnitude > 0.001f) { marker.Root.transform.rotation = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up); } } marker.Root.transform.Rotate(Vector3.up, 55f * Time.deltaTime, (Space)0); } } private void CheckTouches() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) Vector3? val = (((Object)(object)Player.LeftHand != (Object)null) ? new Vector3?(((Component)Player.LeftHand).transform.position) : null); Vector3? val2 = (((Object)(object)Player.RightHand != (Object)null) ? new Vector3?(((Component)Player.RightHand).transform.position) : null); float time = Time.time; if (time - _lastNotifyTime < 6f) { return; } foreach (EchoMarker marker in _markers) { if (!((Object)(object)marker.Root == (Object)null)) { Vector3 position = marker.Root.transform.position; if ((val.HasValue && Vector3.Distance(val.Value, position) < 0.35f) || (val2.HasValue && Vector3.Distance(val2.Value, position) < 0.35f)) { marker.LastNotifyTime = time; _lastNotifyTime = time; _onEchoTouched(marker.Message); break; } } } } private static Material CreateSpriteMaterial(Texture2D texture) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) Shader val = Shader.Find("Sprites/Default"); if ((Object)(object)val == (Object)null) { val = Shader.Find("UI/Default"); } if ((Object)(object)val == (Object)null) { val = Shader.Find("Unlit/Transparent"); } if ((Object)(object)val == (Object)null) { return null; } Material val2 = new Material(val); val2.mainTexture = (Texture)(object)texture; val2.SetTexture("_MainTex", (Texture)(object)texture); val2.SetColor("_Color", Color.white); val2.renderQueue = 3000; return val2; } private static void AddGlowLight(Transform parent, EchoBubbleColor bubbleColor) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Echo Glow"); val.transform.SetParent(parent, false); val.transform.localPosition = Vector3.zero; Light val2 = val.AddComponent(); val2.type = (LightType)2; val2.color = EchoBubbleColors.LightColor(bubbleColor); val2.range = 0.85f; val2.intensity = 0.8f; val2.shadows = (LightShadows)0; } private Texture2D GetIconTexture(EchoBubbleColor color) { if (_textures.TryGetValue(color, out var value)) { return value; } Texture2D value2; return _textures.TryGetValue(EchoBubbleColor.Orange, out value2) ? value2 : null; } private Sprite GetIconSprite(EchoBubbleColor color) { if (_sprites.TryGetValue(color, out var value)) { return value; } Sprite value2; return _sprites.TryGetValue(EchoBubbleColor.Orange, out value2) ? value2 : null; } private Texture2D LoadEmbeddedTexture(string resourceName) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown using Stream stream = typeof(EchoRenderer).Assembly.GetManifestResourceStream(resourceName); if (stream == null) { _logWarning("Embedded icon not found: " + resourceName); return null; } byte[] array = new byte[stream.Length]; stream.Read(array, 0, array.Length); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { name = resourceName, hideFlags = (HideFlags)32 }; if (!ImageConversion.LoadImage(val, Il2CppStructArray.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); } } }