Decompiled source of FollowMe v1.0.5
FollowMePeak.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Buffers.Binary; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using FollowMePeak.Detection; using FollowMePeak.Managers; using FollowMePeak.ModMenu; using FollowMePeak.ModMenu.UI; using FollowMePeak.ModMenu.UI.Helpers; using FollowMePeak.ModMenu.UI.Tabs; using FollowMePeak.ModMenu.UI.Tabs.Components; using FollowMePeak.Models; using FollowMePeak.Patches; using FollowMePeak.Services; using FollowMePeak.Utils; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; using Zorro.Core; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("FollowMePeak")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("FollowMe-Peak - Climb tracking plugin for PEAK")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3+13a4ff943df63f46a05a8882069f39347cfe991e")] [assembly: AssemblyProduct("FollowMePeak")] [assembly: AssemblyTitle("FollowMePeak")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FollowMePeak { [BepInPlugin("com.thomasaushh.followmepeak", "FollowMe-Peak", "1.0.3")] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <>c__DisplayClass32_0 { public Plugin <>4__this; public bool loadComplete; public bool loadSuccess; internal void <LoadModUIAssetBundle>b__0(bool success) { <>4__this._modLogger.Info($"[AssetBundle] LoadModUIBundle callback received: success={success}"); loadComplete = true; loadSuccess = success; } } [CompilerGenerated] private sealed class <DelayedLogInstalledMods>d__26 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedLogInstalledMods>d__26(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; plugin._modLogger.Info("[Plugin] Starting delayed mod listing coroutine..."); <>2__current = (object)new WaitForSeconds(3f); <>1__state = 1; return true; case 1: <>1__state = -1; plugin._modLogger.Info("[Plugin] 3 seconds elapsed, logging installed mods..."); plugin.LogInstalledMods(); 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 <InitializePathSystem>d__33 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; public Scene scene; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InitializePathSystem>d__33(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; case 1: { <>1__state = -1; NextLevelService service = GameHandler.GetService<NextLevelService>(); if (service != null && service.Data.IsSome) { int currentLevelIndex = service.Data.Value.CurrentLevelIndex; plugin._climbDataService.CurrentLevelID = $"{((Scene)(ref scene)).name}_{currentLevelIndex}"; plugin._modLogger.Info("Level erkannt: " + plugin._climbDataService.CurrentLevelID); plugin._climbDataService.LoadClimbsFromFile(); if (plugin._serverConfigService.Config.EnableCloudSync && plugin._serverConfigService.Config.AutoDownload) { plugin._modLogger.Info("Server-side pagination enabled - data will be loaded per page in UI"); } plugin._visualizationManager.InitializeClimbVisibility(); } else { plugin._modLogger.Error("NextLevelService or its data could not be found!"); plugin._climbDataService.CurrentLevelID = ((Scene)(ref scene)).name + "_unknown"; } 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 <LoadModUIAssetBundle>d__32 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; private <>c__DisplayClass32_0 <>8__1; private float <timeout>5__2; private float <elapsed>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadModUIAssetBundle>d__32(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; Plugin plugin = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass32_0(); <>8__1.<>4__this = <>4__this; plugin._modLogger.Info("[AssetBundle] Starting AssetBundle load coroutine..."); plugin._modLogger.Info("[AssetBundle] Current Directory: " + Directory.GetCurrentDirectory()); plugin._modLogger.Info("[AssetBundle] BepInEx Plugin Path: " + Paths.PluginPath); plugin._modLogger.Info("[AssetBundle] Application.dataPath: " + Application.dataPath); <>8__1.loadComplete = false; <>8__1.loadSuccess = false; plugin._modLogger.Info("[AssetBundle] Calling AssetBundleService.Instance.LoadModUIBundle..."); <>2__current = AssetBundleService.Instance.LoadModUIBundle(delegate(bool success) { <>8__1.<>4__this._modLogger.Info($"[AssetBundle] LoadModUIBundle callback received: success={success}"); <>8__1.loadComplete = true; <>8__1.loadSuccess = success; }); <>1__state = 1; return true; case 1: <>1__state = -1; <timeout>5__2 = 5f; <elapsed>5__3 = 0f; plugin._modLogger.Info($"[AssetBundle] Waiting for load completion (max {<timeout>5__2} seconds)..."); break; case 2: <>1__state = -1; break; } if (!<>8__1.loadComplete && <elapsed>5__3 < <timeout>5__2) { <elapsed>5__3 += Time.deltaTime; <>2__current = null; <>1__state = 2; return true; } if (!<>8__1.loadComplete) { plugin._modLogger.Error($"[AssetBundle] Timeout waiting for AssetBundle load after {<timeout>5__2} seconds"); plugin._modLogger.Error($"[AssetBundle] Service instance exists: {AssetBundleService.Instance != null}"); plugin._modLogger.Error($"[AssetBundle] Service IsLoaded: {AssetBundleService.Instance?.IsLoaded ?? false}"); } else if (<>8__1.loadSuccess) { plugin._modLogger.Info("[AssetBundle] Successfully loaded, notifying ModMenuManager"); plugin._modLogger.Info($"[AssetBundle] ModMenuManager exists: {plugin._modMenuManager != null}"); plugin._modMenuManager?.OnAssetBundleLoaded(); plugin._modLogger.Info("[AssetBundle] OnAssetBundleLoaded called"); } else { plugin._modLogger.Error("[AssetBundle] AssetBundle loading failed - check AssetBundleService logs for details"); } plugin._modLogger.Info($"[AssetBundle] LoadModUIAssetBundle coroutine finished - Success: {<>8__1.loadSuccess}"); 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(); } } public const string MOD_VERSION = "1.0.3"; public static ConfigEntry<KeyCode> ModMenuToggleKey; public static ConfigEntry<bool> SaveDeathClimbs; public static ConfigEntry<LogLevel> LoggingLevel; private ModLogger _modLogger; private ClimbDataService _climbDataService; private ClimbRecordingManager _recordingManager; private ClimbVisualizationManager _visualizationManager; private ServerConfigService _serverConfigService; private VPSApiService _vpsApiService; private ClimbUploadService _climbUploadService; private ClimbDownloadService _climbDownloadService; private ModMenuManager _modMenuManager; private Harmony _harmony; private bool _gameEndedThisSession; private Dictionary<string, DateTime> _lastModActivity = new Dictionary<string, DateTime>(); private Dictionary<string, int> _modUsageCount = new Dictionary<string, int>(); public static Plugin Instance { get; private set; } public ClimbDataService ClimbDataService => _climbDataService; public ClimbRecordingManager GetRecordingManager() { return _recordingManager; } private void Awake() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown Instance = this; _modLogger = new ModLogger(((BaseUnityPlugin)this).Logger); ModLogger.Instance = _modLogger; _modLogger.Info("Plugin " + ((BaseUnityPlugin)this).Info.Metadata.GUID + " loaded!"); InitializeControlsConfig(); _modLogger.Info("[FlyDetection] System initialized with fixed configuration"); _modLogger.Info($"[FlyDetection] Threshold: {50f}, CheckInterval: {0.5f}"); InitializeServices(); SceneManager.sceneLoaded += OnSceneLoaded; _harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID); _harmony.PatchAll(typeof(PluginPatches)); PlayerDeathPatch.ApplyPatch(_harmony); RunManagerPatch.ApplyPatch(_harmony); EndGamePatch.ApplyPatch(_harmony); _modLogger.Info("Harmony Patches applied. TEST"); _modLogger.Info("[Plugin] About to start DelayedLogInstalledMods coroutine..."); ((MonoBehaviour)this).StartCoroutine(DelayedLogInstalledMods()); _modLogger.Info("[Plugin] DelayedLogInstalledMods coroutine started!"); } private void LogInstalledMods() { _modLogger.Info("=== Installed BepInEx Mods ==="); Dictionary<string, PluginInfo> pluginInfos = Chainloader.PluginInfos; _modLogger.Info($"Total installed plugins: {pluginInfos.Count}"); foreach (PluginInfo value in pluginInfos.Values) { BepInPlugin metadata = value.Metadata; _modLogger.Info($"Plugin: {metadata.Name} v{metadata.Version} (GUID: {metadata.GUID})"); } _modLogger.Info("=============================="); } [IteratorStateMachine(typeof(<DelayedLogInstalledMods>d__26))] private IEnumerator DelayedLogInstalledMods() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedLogInstalledMods>d__26(0) { <>4__this = this }; } private void InitializeServices() { _climbDataService = new ClimbDataService(_modLogger); _recordingManager = new ClimbRecordingManager(_climbDataService, _modLogger, (MonoBehaviour)(object)this); _visualizationManager = new ClimbVisualizationManager(_climbDataService); _serverConfigService = new ServerConfigService(_modLogger); _vpsApiService = new VPSApiService(_modLogger, _serverConfigService.Config, (MonoBehaviour)(object)this); _climbUploadService = new ClimbUploadService(_modLogger, _vpsApiService, _serverConfigService); _climbDownloadService = new ClimbDownloadService(_modLogger, _vpsApiService, _serverConfigService, _climbDataService); ModMenuManager.ServerConfig = _serverConfigService; ModMenuManager.ApiService = _vpsApiService; ModMenuManager.UploadService = _climbUploadService; ModMenuManager.DownloadService = _climbDownloadService; ModMenuManager.ClimbDataService = _climbDataService; ModMenuManager.VisualizationManager = _visualizationManager; _modMenuManager = new ModMenuManager(); ((MonoBehaviour)this).StartCoroutine(LoadModUIAssetBundle()); _modLogger.Info("All services initialized successfully"); if (_serverConfigService.Config.EnableCloudSync) { _vpsApiService.CheckServerHealth(delegate(bool isHealthy) { _modLogger.Info("Initial server health check: " + (isHealthy ? "Connected" : "Failed")); }); } } private void InitializeControlsConfig() { ModMenuToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "ModMenuToggleKey", (KeyCode)282, "Key to toggle the mod menu"); SaveDeathClimbs = ((BaseUnityPlugin)this).Config.Bind<bool>("Gameplay", "SaveDeathClimbs", false, "Save climbs where the player died (these will not be uploaded to cloud)"); LoggingLevel = ((BaseUnityPlugin)this).Config.Bind<LogLevel>("Logging", "LogLevel", LogLevel.Error, "Logging level: None=0, Error=1, Warning=2, Info=3, Debug=4, Verbose=5"); ModLogger.CurrentLevel = LoggingLevel.Value; LoggingLevel.SettingChanged += delegate { ModLogger.CurrentLevel = LoggingLevel.Value; _modLogger.Info($"[Config] LogLevel changed to: {LoggingLevel.Value}"); }; FlyDetectionConfig.ValidateConfig(); } private void OnDestroy() { _modLogger.Info("Plugin " + ((BaseUnityPlugin)this).Info.Metadata.GUID + " unloading..."); SceneManager.sceneLoaded -= OnSceneLoaded; Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _modLogger.Info("Harmony patches removed."); _modMenuManager?.Cleanup(); _modMenuManager = null; _visualizationManager?.ClearVisuals(); _visualizationManager = null; _recordingManager?.StopRecording(); _recordingManager = null; _climbUploadService = null; _climbDownloadService = null; _vpsApiService = null; _serverConfigService = null; _climbDataService = null; ModMenuManager.ServerConfig = null; ModMenuManager.ApiService = null; ModMenuManager.UploadService = null; ModMenuManager.DownloadService = null; ModMenuManager.ClimbDataService = null; ModMenuManager.VisualizationManager = null; AssetBundleService.Instance.Unload(); Instance = null; _modLogger.Info("Plugin " + ((BaseUnityPlugin)this).Info.Metadata.GUID + " unloaded!"); } private void Update() { //IL_0005: 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) if (Input.GetKeyDown(ModMenuToggleKey.Value)) { _modLogger.Info($"[Plugin] {ModMenuToggleKey.Value} pressed - Toggling Mod Menu"); _modMenuManager?.ToggleAssetBundleMenu(); } _modMenuManager?.Update(); SimpleFlyDetector.PerformDetection(); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) SimpleFlyDetector.OnSceneChanged(((Scene)(ref scene)).name); if (((Scene)(ref scene)).name.StartsWith("Level_")) { if (_gameEndedThisSession) { _gameEndedThisSession = false; _modLogger.Info("[Level] Resetting game ended flag - new level started"); } if (_recordingManager != null && _recordingManager.IsRecording) { _modLogger.Info("Stopping previous recording due to scene change to " + ((Scene)(ref scene)).name); _recordingManager.StopRecording(); } ((MonoBehaviour)this).StartCoroutine(InitializePathSystem(scene)); _modLogger.Info("Level " + ((Scene)(ref scene)).name + " loaded - waiting for RUN STARTED event"); } else { if (_recordingManager != null && _recordingManager.IsRecording) { _modLogger.Info("Stopping recording due to leaving level (new scene: " + ((Scene)(ref scene)).name); _recordingManager.StopRecording(); } _climbDataService.CurrentLevelID = ""; _visualizationManager.ClearVisuals(); } } [IteratorStateMachine(typeof(<LoadModUIAssetBundle>d__32))] private IEnumerator LoadModUIAssetBundle() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadModUIAssetBundle>d__32(0) { <>4__this = this }; } [IteratorStateMachine(typeof(<InitializePathSystem>d__33))] private IEnumerator InitializePathSystem(Scene scene) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitializePathSystem>d__33(0) { <>4__this = this, scene = scene }; } public void OnRunStartedFromPatch() { if (_gameEndedThisSession) { _modLogger.Info("[RunManager] Ignoring RUN STARTED after helicopter ending"); return; } _modLogger.Info("[RunManager] RUN STARTED - Activating fly detection and climb recording"); SimpleFlyDetector.OnRunStarted(); if (_recordingManager != null) { _recordingManager.StartRecording(); } else { _modLogger.Error("RecordingManager is null when trying to start recording!"); } } public void OnHelicopterEnding() { //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) if (ClimbRecordingManager.PlayerDiedThisSession) { _modLogger.Info("[Helicopter] Ignoring helicopter ending - player already died this session"); return; } _modLogger.Info("[Helicopter] Game ending detected - saving Peak climb"); Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; _modLogger.Info("[Helicopter] Current scene: " + name); if (_recordingManager != null && _recordingManager.IsRecording) { _modLogger.Info("[Helicopter] Recording active, saving as Peak"); _recordingManager.SaveCurrentClimb("Peak"); _modLogger.Info("[Helicopter] Final Peak climb saved"); } else if (_recordingManager != null) { _modLogger.Warning("[Helicopter] No active recording to save for Peak"); } else { _modLogger.Error("[Helicopter] RecordingManager is null!"); } SimpleFlyDetector.OnSceneChanged("GameEnded"); _gameEndedThisSession = true; _modLogger.Info("[Helicopter] Recording disabled until next level load"); } public void OnCampfireLit(string biomeName) { _recordingManager.SaveCurrentClimb(biomeName); SimpleFlyDetector.ResetForNewRecording(); _recordingManager.StartRecording(); } public void ShowTagSelectionForNewClimb(ClimbData climbData) { UploadIfAutoUploadEnabled(climbData); } private void UploadIfAutoUploadEnabled(ClimbData climbData) { if (climbData.WasDeathClimb) { _modLogger.Info($"[Death] Death climb {climbData.Id} will not be uploaded to cloud"); } else if (_serverConfigService.Config.EnableCloudSync && _serverConfigService.Config.AutoUpload) { _climbUploadService.QueueForUpload(climbData, _climbDataService.CurrentLevelID); _modLogger.Info($"Queued climb for upload: {climbData.Id}"); } } } } namespace FollowMePeak.Utils { public static class ClimbDataCrusher { private static class NanCodes { internal const byte AbsoluteReposition = 1; internal const byte AsHalfFloats = 2; } public static void WriteClimbData(MemoryStream dataStream, ClimbData climb) { //IL_001e: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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_00d6: 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_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if (climb.Points.Count < 2) { return; } byte[] array = new byte[14]; Vector3 val = climb.Points[0]; EncodePointAsFloats(array, val); dataStream.Write(array.AsSpan(0, 12)); foreach (Vector3 item in climb.Points.Skip(1)) { QuarterFloat quarter = QuarterFloat.FromFloat(item.x - val.x); QuarterFloat quarter2 = QuarterFloat.FromFloat(item.y - val.y); QuarterFloat quarter3 = QuarterFloat.FromFloat(item.z - val.z); if (quarter.IsInfinity() || quarter2.IsInfinity() || quarter3.IsInfinity()) { HalfFloat half = HalfFloat.FromFloat(item.x - val.x); HalfFloat half2 = HalfFloat.FromFloat(item.y - val.y); HalfFloat half3 = HalfFloat.FromFloat(item.z - val.z); if (half.IsInfinity() || half2.IsInfinity() || half3.IsInfinity()) { EncodeQuarterFloat(array.AsSpan(0, 1), QuarterFloat.EncodeNaN(1)); EncodePointAsFloats(array.AsSpan(1, 12), item); val = item; dataStream.Write(array.AsSpan(0, 13)); continue; } EncodeQuarterFloat(array.AsSpan(0, 1), QuarterFloat.EncodeNaN(2)); EncodeHalfFloat(array.AsSpan(1, 2), half); EncodeHalfFloat(array.AsSpan(3, 2), half2); EncodeHalfFloat(array.AsSpan(5, 2), half3); val.x += half.ToFloat(); val.y += half2.ToFloat(); val.z += half3.ToFloat(); dataStream.Write(array.AsSpan(0, 7)); } else if (!quarter.IsZero() || !quarter2.IsZero() || !quarter3.IsZero()) { EncodeQuarterFloat(array.AsSpan(0, 1), quarter); EncodeQuarterFloat(array.AsSpan(1, 1), quarter2); EncodeQuarterFloat(array.AsSpan(2, 1), quarter3); val.x += quarter.ToFloat(); val.y += quarter2.ToFloat(); val.z += quarter3.ToFloat(); dataStream.Write(array.AsSpan(0, 3)); } } } public static void ReadClimbData(byte[] data, ClimbData climb) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) climb.Points = new List<Vector3>(); if (data == null || data.Length < 12) { return; } Vector3 item = DecodePointFromFloats(data.AsSpan(0, 12)); climb.Points.Add(item); int num = 12; while (num < data.Length) { QuarterFloat quarterFloat = DecodeQuarterFloat(data.AsSpan(num, 1)); if (quarterFloat.IsNaN()) { switch (quarterFloat.DecodeNaN()) { case 1: item = DecodePointFromFloats(data.AsSpan(num + 1, 12)); climb.Points.Add(item); num += 13; break; case 2: item.x += DecodeHalfFloat(data.AsSpan(num + 1, 2)).ToFloat(); item.y += DecodeHalfFloat(data.AsSpan(num + 3, 2)).ToFloat(); item.z += DecodeHalfFloat(data.AsSpan(num + 5, 2)).ToFloat(); climb.Points.Add(item); num += 7; break; default: throw new InvalidOperationException($"Unexpected NaN code in climb data: {quarterFloat.DecodeNaN()}"); } } else { item.x += quarterFloat.ToFloat(); item.y += DecodeQuarterFloat(data.AsSpan(num + 1, 1)).ToFloat(); item.z += DecodeQuarterFloat(data.AsSpan(num + 2, 1)).ToFloat(); climb.Points.Add(item); num += 3; } } } private static void EncodePointAsFloats(Span<byte> span, Vector3 firstPoint) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) EncodeFloat(span.Slice(0, 4), firstPoint.x); EncodeFloat(span.Slice(4, 4), firstPoint.y); EncodeFloat(span.Slice(8, 4), firstPoint.z); } private static Vector3 DecodePointFromFloats(Span<byte> data) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) return new Vector3(DecodeFloat(data.Slice(0, 4)), DecodeFloat(data.Slice(4, 4)), DecodeFloat(data.Slice(8, 4))); } private static void EncodeFloat(Span<byte> span, float value) { BinaryPrimitives.WriteInt32LittleEndian(span, BitConverter.SingleToInt32Bits(value)); } private static float DecodeFloat(Span<byte> span) { return BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(span)); } private static void EncodeHalfFloat(Span<byte> span, HalfFloat half) { BinaryPrimitives.WriteUInt16LittleEndian(span, half.RawValue); } private static HalfFloat DecodeHalfFloat(Span<byte> span) { HalfFloat result = default(HalfFloat); result.RawValue = BinaryPrimitives.ReadUInt16LittleEndian(span); return result; } private static void EncodeQuarterFloat(Span<byte> span, QuarterFloat quarter) { span[0] = quarter.RawValue; } private static QuarterFloat DecodeQuarterFloat(Span<byte> span) { QuarterFloat result = default(QuarterFloat); result.RawValue = span[0]; return result; } } public static class CommonJsonSettings { public class ApiVector3Converter : JsonConverter<Vector3> { public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer serializer) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) writer.WriteStartObject(); writer.WritePropertyName("x"); writer.WriteValue(value.x); writer.WritePropertyName("y"); writer.WriteValue(value.y); writer.WritePropertyName("z"); writer.WriteValue(value.z); writer.WriteEndObject(); } public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_00cc: 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) //IL_001b: Invalid comparison between Unknown and I4 Vector3 result = existingValue; while (reader.Read() && (int)reader.TokenType != 13) { if ((int)reader.TokenType != 4) { continue; } string text = (string)reader.Value; if (reader.Read()) { switch (text) { case "x": case "X": result.x = Convert.ToSingle(reader.Value); break; case "y": case "Y": result.y = Convert.ToSingle(reader.Value); break; case "z": case "Z": result.z = Convert.ToSingle(reader.Value); break; } } } return result; } } public static readonly JsonSerializerSettings Default = new JsonSerializerSettings { Formatting = (Formatting)1, Converters = new List<JsonConverter>(1) { (JsonConverter)(object)new ApiVector3Converter() } }; public static readonly JsonSerializerSettings Compact = new JsonSerializerSettings { Formatting = (Formatting)0, NullValueHandling = (NullValueHandling)0, DefaultValueHandling = (DefaultValueHandling)0, Converters = new List<JsonConverter>(1) { (JsonConverter)(object)new ApiVector3Converter() } }; } public struct HalfFloat { public const byte TotalNumBits = 16; public const byte NumExponentBits = 4; public const byte NumMantissaBits = 11; public const byte SignBitPos = 15; private const ushort SignBit = 32768; private const ushort ExponentMask = 30720; private const ushort MantissaMask = 2047; private const ushort ExponentInfinity = 15; private const int ExponentBias = 7; private const int SingleExponentBias = 127; private const byte SingleNumExponentBits = 8; private const byte SingleNumMantissaBits = 23; private const byte SingleSignBitPos = 31; private const uint SingleExponentMask = 2139095040u; private const uint SingleMantissaMask = 8388607u; private const uint SingleExponentInfinity = 255u; public ushort RawValue; public static HalfFloat NaN { get { HalfFloat result = default(HalfFloat); result.RawValue = 30721; return result; } } public ushort ExponentBits => (ushort)((uint)(RawValue & 0x7800) >> 11); public ushort MantissaBits => (ushort)(RawValue & 0x7FFu); public static HalfFloat FromFloat(float value) { int num = BitConverter.SingleToInt32Bits(value); uint num2 = (uint)num >> 31; uint num3 = (uint)(num & 0x7F800000) >> 23; uint num4 = (uint)num & 0x7FFFFFu; int num5 = (int)(num3 - 127 + 7); if (num5 == -11) { num4 = 0u; num5++; } else if (num5 > -11) { uint num6 = (uint)(1 << 11 + Math.Max(0, 1 - num5)); if ((num4 & num6) != 0) { num4 += num6; if ((num4 & 0xFF800000u) != 0) { num4 = 0u; num5++; } } } if (num5 >= 15) { if (num4 != 0 && num3 == 255) { return NaN; } num5 = 15; num4 = 0u; } else if (num5 <= 0) { if (num5 > -11) { num4 |= 0x800000u; int num7 = 1 - num5; num4 >>= num7; num5 = 0; } else { num5 = 0; num4 = 0u; } } ushort num8 = (ushort)(num2 << 15); ushort num9 = (ushort)(num5 << 11); ushort num10 = (ushort)(num4 >> 12); HalfFloat result = default(HalfFloat); result.RawValue = (ushort)(num8 | num9 | num10); return result; } public float ToFloat() { uint num = (uint)((RawValue & 0x8000) << 16); uint exponentBits = ExponentBits; uint num2 = MantissaBits; if (exponentBits == 0 && num2 == 0) { return BitConverter.Int32BitsToSingle((int)num); } uint num3 = exponentBits + 127 - 7; if (exponentBits == 15) { num3 = 255u; } else if (exponentBits == 0) { while ((num2 & 0x800) == 0) { num2 <<= 1; num3--; } num2 &= 0x7FFu; num3++; } return BitConverter.Int32BitsToSingle((int)(num | (num3 << 23) | (num2 << 12))); } public static HalfFloat EncodeNaN(ushort value) { value++; ushort num = (ushort)((uint)value >> 11); if (num > 1) { throw new ArgumentOutOfRangeException("value", "Value too large to encode as NaN"); } ushort num2 = (ushort)(value & 0x7FFu); HalfFloat result = default(HalfFloat); result.RawValue = (ushort)((uint)(num << 15) | 0x7800u | num2); return result; } public ushort DecodeNaN() { if (!IsNaN()) { throw new InvalidOperationException("Not a NaN value"); } ushort num = (ushort)((RawValue & 0x8000) >>> 15); ushort num2 = (ushort)(RawValue & 0x7FFu); return (ushort)(((num << 11) | num2) - 1); } public bool IsNaN() { if (ExponentBits == 15) { return MantissaBits != 0; } return false; } public bool IsInfinity() { if (ExponentBits == 15) { return MantissaBits == 0; } return false; } public bool IsPositiveInfinity() { if (IsInfinity()) { return (RawValue & 0x8000) == 0; } return false; } public bool IsNegativeInfinity() { if (IsInfinity()) { return (RawValue & 0x8000) != 0; } return false; } public bool IsZero() { return (RawValue & -32769) == 0; } public bool IsPositiveZero() { return RawValue == 0; } public bool IsNegativeZero() { return RawValue == 32768; } public bool IsDenormalized() { if (ExponentBits == 0) { return MantissaBits != 0; } return false; } public override string ToString() { return ToFloat().ToString(CultureInfo.InvariantCulture); } } public struct QuarterFloat { public const byte TotalNumBits = 8; public const byte NumExponentBits = 2; public const byte NumMantissaBits = 5; public const byte SignBitPos = 7; private const byte SignBit = 128; private const byte ExponentMask = 96; private const byte MantissaMask = 31; private const byte ExponentInfinity = 3; private const int ExponentBias = 1; private const int SingleExponentBias = 127; private const byte SingleNumExponentBits = 8; private const byte SingleNumMantissaBits = 23; private const byte SingleSignBitPos = 31; private const uint SingleExponentMask = 2139095040u; private const uint SingleMantissaMask = 8388607u; private const uint SingleExponentInfinity = 255u; public byte RawValue; public static QuarterFloat NaN { get { QuarterFloat result = default(QuarterFloat); result.RawValue = 97; return result; } } public byte ExponentBits => (byte)((uint)(RawValue & 0x60) >> 5); public byte MantissaBits => (byte)(RawValue & 0x1Fu); public static QuarterFloat FromFloat(float value) { int num = BitConverter.SingleToInt32Bits(value); uint num2 = (uint)num >> 31; uint num3 = (uint)(num & 0x7F800000) >> 23; uint num4 = (uint)num & 0x7FFFFFu; int num5 = (int)(num3 - 127 + 1); if (num5 == -5) { num4 = 0u; num5++; } else if (num5 > -5) { uint num6 = (uint)(1 << 17 + Math.Max(0, 1 - num5)); if ((num4 & num6) != 0) { num4 += num6; if ((num4 & 0xFF800000u) != 0) { num4 = 0u; num5++; } } } if (num5 >= 3) { if (num4 != 0 && num3 == 255) { return NaN; } num5 = 3; num4 = 0u; } else if (num5 <= 0) { if (num5 > -5) { num4 |= 0x800000u; int num7 = 1 - num5; num4 >>= num7; num5 = 0; } else { num5 = 0; num4 = 0u; } } byte b = (byte)(num2 << 7); byte b2 = (byte)(num5 << 5); byte b3 = (byte)(num4 >> 18); QuarterFloat result = default(QuarterFloat); result.RawValue = (byte)(b | b2 | b3); return result; } public float ToFloat() { uint num = (uint)((RawValue & 0x80) << 24); uint exponentBits = ExponentBits; uint num2 = MantissaBits; if (exponentBits == 0 && num2 == 0) { return BitConverter.Int32BitsToSingle((int)num); } uint num3 = exponentBits + 127 - 1; if (exponentBits == 3) { num3 = 255u; } else if (exponentBits == 0) { while ((num2 & 0x20) == 0) { num2 <<= 1; num3--; } num2 &= 0x1Fu; num3++; } return BitConverter.Int32BitsToSingle((int)(num | (num3 << 23) | (num2 << 18))); } public static QuarterFloat EncodeNaN(byte value) { value++; byte b = (byte)((uint)value >> 5); if (b > 1) { throw new ArgumentOutOfRangeException("value", "Value too large to encode as NaN"); } byte b2 = (byte)(value & 0x1Fu); QuarterFloat result = default(QuarterFloat); result.RawValue = (byte)((uint)(b << 7) | 0x60u | b2); return result; } public byte DecodeNaN() { if (!IsNaN()) { throw new InvalidOperationException("Not a NaN value"); } byte num = (byte)((RawValue & 0x80) >>> 7); byte b = (byte)(RawValue & 0x1Fu); return (byte)(((num << 5) | b) - 1); } public bool IsNaN() { if (ExponentBits == 3) { return MantissaBits != 0; } return false; } public bool IsInfinity() { if (ExponentBits == 3) { return MantissaBits == 0; } return false; } public bool IsPositiveInfinity() { if (IsInfinity()) { return (RawValue & 0x80) == 0; } return false; } public bool IsNegativeInfinity() { if (IsInfinity()) { return (RawValue & 0x80) != 0; } return false; } public bool IsZero() { return (RawValue & -129) == 0; } public bool IsPositiveZero() { return RawValue == 0; } public bool IsNegativeZero() { return RawValue == 128; } public bool IsDenormalized() { if (ExponentBits == 0) { return MantissaBits != 0; } return false; } public override string ToString() { return ToFloat().ToString(CultureInfo.InvariantCulture); } } public static class FileUtils { public static Task WriteJsonFileInBackground(ModLogger logger, string filePath, object payload) { return Task.Run((Func<Task?>)SerializeAndSave); async Task SerializeAndSave() { try { string json = JsonConvert.SerializeObject(payload, (Formatting)1); await WriteFileAtomically(filePath, json); } catch (Exception ex) { Exception ex2 = ex; Exception e = ex2; ThreadingHelper.Instance.StartSyncInvoke((Action)delegate { logger.Error("Failed to save " + filePath + ": " + e.Message); }); } } } private static async Task WriteFileAtomically(string filePath, string json) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); string newFileName = filePath + ".new"; await File.WriteAllTextAsync(newFileName, json); ReplaceFileAtomically(newFileName, filePath); } private static void ReplaceFileAtomically(string tempFileName, string targetFileName) { try { File.Move(tempFileName, targetFileName); } catch (IOException) { File.Replace(tempFileName, targetFileName, null); } } } public static class InputValidator { private static readonly Regex LevelIdPattern = new Regex("^[a-zA-Z0-9_\\-\\.]{1,100}$"); private static readonly Regex SpecialCharacterPattern = new Regex("[^a-zA-Z0-9_\\-\\s]"); private static readonly Regex ValidPeakCodePattern = new Regex("^[A-Z0-9]{8}$"); private static readonly Regex InvalidPeakCodeCharacters = new Regex("[^A-Z0-9]"); public static string SanitizePlayerName(string input) { if (string.IsNullOrWhiteSpace(input)) { return "Anonymous"; } string text = SpecialCharacterPattern.Replace(input, ""); if (text.Length > 50) { text = text.Substring(0, 50); } if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return "Anonymous"; } public static string SanitizeBiomeName(string input) { if (string.IsNullOrWhiteSpace(input)) { return "Unknown"; } string text = SpecialCharacterPattern.Replace(input, ""); if (text.Length > 100) { text = text.Substring(0, 100); } if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return "Unknown"; } public static bool IsValidLevelId(string levelId) { if (string.IsNullOrWhiteSpace(levelId)) { return false; } return LevelIdPattern.IsMatch(levelId); } public static bool IsValidPeakCode(string peakCode) { if (string.IsNullOrWhiteSpace(peakCode)) { return false; } return ValidPeakCodePattern.IsMatch(peakCode); } public static string SanitizePeakCode(string input) { if (string.IsNullOrWhiteSpace(input)) { return ""; } string text = InvalidPeakCodeCharacters.Replace(input.ToUpper(), ""); if (text.Length != 8) { return ""; } return text; } public static float ClampDuration(float duration) { return Math.Max(1f, Math.Min(3600f, duration)); } public static bool IsValidPointCount(int pointCount) { if (pointCount >= 2) { return pointCount <= 10000; } return false; } public static int ClampAscentLevel(int ascentLevel) { return Math.Max(-1, Math.Min(8, ascentLevel)); } public static bool IsValidAscentLevel(int ascentLevel) { if (ascentLevel >= -1) { return ascentLevel <= 8; } return false; } } public enum LogLevel { None, Error, Warning, Info, Debug, Verbose } public class ModLogger { private readonly ManualLogSource _logger; private static LogLevel _currentLevel = LogLevel.Error; public static ModLogger Instance { get; set; } public static LogLevel CurrentLevel { get { return _currentLevel; } set { _currentLevel = value; } } public ModLogger(ManualLogSource logger) { _logger = logger; } public void Error(string message) { if (_currentLevel >= LogLevel.Error) { _logger.LogError((object)message); } } public void Warning(string message) { if (_currentLevel >= LogLevel.Warning) { _logger.LogWarning((object)message); } } public void Info(string message) { if (_currentLevel >= LogLevel.Info) { _logger.LogInfo((object)message); } } public void Debug(string message) { if (_currentLevel >= LogLevel.Debug) { _logger.LogInfo((object)("[DEBUG] " + message)); } } public void Verbose(string message) { if (_currentLevel >= LogLevel.Verbose) { _logger.LogInfo((object)("[VERBOSE] " + message)); } } } } namespace FollowMePeak.Services { public class AssetBundleService { [CompilerGenerated] private sealed class <LoadModUIBundle>d__11 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public AssetBundleService <>4__this; public Action<bool> onComplete; private AssetBundleCreateRequest <bundleLoadRequest>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadModUIBundle>d__11(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <bundleLoadRequest>5__2 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; AssetBundleService assetBundleService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; if (assetBundleService._isLoaded) { ModLogger.Instance?.Info("Mod UI bundle is already loaded."); onComplete?.Invoke(obj: true); return false; } if (assetBundleService._isLoading) { ModLogger.Instance?.Warning("Mod UI bundle is already being loaded."); return false; } assetBundleService._isLoading = true; byte[] array2 = null; try { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text2 = "FollowMePeak.modui"; using Stream stream = executingAssembly.GetManifestResourceStream(text2); if (stream == null) { ModLogger.Instance?.Error("Embedded resource '" + text2 + "' not found! Make sure the Build Action is set to 'Embedded Resource'."); ModLogger.Instance?.Info("Available embedded resources:"); string[] array = executingAssembly.GetManifestResourceNames(); foreach (string text3 in array) { ModLogger.Instance?.Info(" -> " + text3); } assetBundleService._isLoading = false; onComplete?.Invoke(obj: false); return false; } using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array2 = memoryStream.ToArray(); } catch (Exception arg) { ModLogger.Instance?.Error($"An error occurred while reading the embedded asset bundle: {arg}"); assetBundleService._isLoading = false; onComplete?.Invoke(obj: false); return false; } <bundleLoadRequest>5__2 = AssetBundle.LoadFromMemoryAsync(array2); <>2__current = <bundleLoadRequest>5__2; <>1__state = 1; return true; } case 1: { <>1__state = -1; if ((Object)(object)<bundleLoadRequest>5__2.assetBundle == (Object)null) { ModLogger.Instance?.Error("Failed to load AssetBundle from memory. The bundle might be corrupt or incompatible."); assetBundleService._isLoading = false; onComplete?.Invoke(obj: false); return false; } assetBundleService._modUIBundle = <bundleLoadRequest>5__2.assetBundle; ModLogger.Instance?.Info("Successfully loaded AssetBundle '" + ((Object)assetBundleService._modUIBundle).name + "' from embedded resource."); string[] allAssetNames = assetBundleService._modUIBundle.GetAllAssetNames(); ModLogger.Instance?.Info($"AssetBundle contains {allAssetNames.Length} assets:"); string[] array = allAssetNames; foreach (string text in array) { ModLogger.Instance?.Info(" - " + text); } assetBundleService._isLoaded = true; assetBundleService._isLoading = false; onComplete?.Invoke(obj: 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 static AssetBundleService _instance; private AssetBundle _modUIBundle; private readonly Dictionary<string, Object> _cachedAssets = new Dictionary<string, Object>(); private bool _isLoaded; private bool _isLoading; public const string MOD_MENU_CANVAS_PREFAB = "ModMenuCanvas"; public const string MOD_MENU_PANEL_PREFAB = "MyModMenuPanel"; public const string MOD_MENU_MAIN_PREFAB = "ModMenuMain"; public static AssetBundleService Instance { get { if (_instance == null) { _instance = new AssetBundleService(); } return _instance; } } public bool IsLoaded => _isLoaded; private AssetBundleService() { } [IteratorStateMachine(typeof(<LoadModUIBundle>d__11))] public IEnumerator LoadModUIBundle(Action<bool> onComplete = null) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadModUIBundle>d__11(0) { <>4__this = this, onComplete = onComplete }; } public GameObject GetPrefab(string prefabName) { if (!_isLoaded || (Object)(object)_modUIBundle == (Object)null) { ModLogger.Instance?.Error("AssetBundle is not loaded. Call LoadModUIBundle first."); return null; } if (_cachedAssets.TryGetValue(prefabName, out var value)) { ModLogger.Instance?.Info("Returning cached prefab: " + prefabName); return (GameObject)(object)((value is GameObject) ? value : null); } try { ModLogger.Instance?.Info("Attempting to load prefab: " + prefabName); GameObject val = null; val = _modUIBundle.LoadAsset<GameObject>(prefabName); if ((Object)(object)val == (Object)null) { string text = prefabName.ToLower(); ModLogger.Instance?.Info("Trying lowercase variant: " + text); val = _modUIBundle.LoadAsset<GameObject>(text); } if ((Object)(object)val == (Object)null) { ModLogger.Instance?.Info("Searching through all assets for partial match..."); string[] allAssetNames = _modUIBundle.GetAllAssetNames(); foreach (string text2 in allAssetNames) { if (text2.ToLower().Contains(prefabName.ToLower())) { ModLogger.Instance?.Info("Found potential match: " + text2); Object val2 = _modUIBundle.LoadAsset(text2); if (val2 is GameObject) { val = (GameObject)(object)((val2 is GameObject) ? val2 : null); ModLogger.Instance?.Info("Successfully loaded from path: " + text2); break; } } } } if ((Object)(object)val != (Object)null) { _cachedAssets[prefabName] = (Object)(object)val; ModLogger.Instance?.Info("Successfully loaded and cached prefab: " + prefabName); ModLogger.Instance?.Info("Prefab components:"); Component[] components = val.GetComponents<Component>(); foreach (Component val3 in components) { ModLogger.Instance?.Info(" - " + ((object)val3).GetType().Name); } } else { ModLogger.Instance?.Warning("Prefab '" + prefabName + "' not found in the AssetBundle."); ModLogger.Instance?.Info("Available GameObjects in bundle:"); string[] allAssetNames = _modUIBundle.GetAllAssetNames(); foreach (string text3 in allAssetNames) { if (_modUIBundle.LoadAsset(text3) is GameObject) { ModLogger.Instance?.Info(" - " + text3 + " (GameObject)"); } } } return val; } catch (Exception ex) { ModLogger.Instance?.Error("Error loading prefab '" + prefabName + "': " + ex.Message); return null; } } public T GetAsset<T>(string assetName) where T : Object { if (!_isLoaded || (Object)(object)_modUIBundle == (Object)null) { ModLogger.Instance?.Error("AssetBundle is not loaded. Call LoadModUIBundle first."); return default(T); } if (_cachedAssets.TryGetValue(assetName, out var value)) { return (T)(object)((value is T) ? value : null); } try { T val = _modUIBundle.LoadAsset<T>(assetName); if ((Object)(object)val != (Object)null) { _cachedAssets[assetName] = (Object)(object)val; } else { ModLogger.Instance?.Warning("Asset '" + assetName + "' of type " + typeof(T).Name + " not found in AssetBundle"); } return val; } catch (Exception ex) { ModLogger.Instance?.Error("Error loading asset '" + assetName + "': " + ex.Message); return default(T); } } public void Unload() { if ((Object)(object)_modUIBundle != (Object)null) { ModLogger.Instance?.Info("Unloading Mod UI AssetBundle"); _modUIBundle.Unload(true); _modUIBundle = null; } _cachedAssets.Clear(); _isLoaded = false; _isLoading = false; } } public class ClimbDataService { private readonly ModLogger _logger; private List<ClimbData> _allLoadedClimbs = new List<ClimbData>(); private string _currentLevelID = ""; public string CurrentLevelID { get { return _currentLevelID; } set { _currentLevelID = value; } } public ClimbDataService(ModLogger logger) { _logger = logger; } public List<ClimbData> GetAllClimbs() { return _allLoadedClimbs; } public void AddClimb(ClimbData climbData) { _allLoadedClimbs.Add(climbData); } public void DeleteClimbs(List<Guid> climbIds) { _allLoadedClimbs.RemoveAll((ClimbData c) => climbIds.Contains(c.Id)); SaveClimbsToFile(addNewClimb: false); } public void SaveClimbsToFile(bool addNewClimb = true) { if (!string.IsNullOrEmpty(_currentLevelID) && !_currentLevelID.EndsWith("_unknown")) { List<ClimbData> payload = new List<ClimbData>(_allLoadedClimbs); string filePath = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", _currentLevelID + ".json"); FileUtils.WriteJsonFileInBackground(_logger, filePath, payload); } } public void LoadClimbsFromFile() { _allLoadedClimbs.Clear(); if (string.IsNullOrEmpty(_currentLevelID) || _currentLevelID.EndsWith("_unknown")) { return; } string path = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", _currentLevelID + ".json"); if (!File.Exists(path)) { _logger.Info("No climb file found for '" + _currentLevelID + "'."); return; } try { string text = File.ReadAllText(path); _allLoadedClimbs = JsonConvert.DeserializeObject<List<ClimbData>>(text, CommonJsonSettings.Default) ?? new List<ClimbData>(); _logger.Info($"{_allLoadedClimbs.Count} climbs loaded for level '{_currentLevelID}'."); } catch (Exception ex) { _logger.Error("Error loading climbs (possibly old format?): " + ex.Message); } } public void ClearClimbs() { _allLoadedClimbs.Clear(); } } public class ClimbDownloadService { private readonly ModLogger _logger; private readonly VPSApiService _apiService; private readonly ServerConfigService _configService; private readonly ClimbDataService _climbDataService; private DateTime _lastDownload = DateTime.MinValue; private readonly Dictionary<string, DateTime> _levelDownloadTimes = new Dictionary<string, DateTime>(); public bool IsDownloading { get; private set; } public DateTime LastDownload => _lastDownload; public ClimbDownloadService(ModLogger logger, VPSApiService apiService, ServerConfigService configService, ClimbDataService climbDataService) { _logger = logger; _apiService = apiService; _configService = configService; _climbDataService = climbDataService; } public void DownloadAndMergeClimbs(string levelId, Action<int, string, ClimbListMeta> callback = null, int limit = 10, int offset = 0) { if (!_configService.Config.EnableCloudSync || !_configService.Config.AutoDownload) { _logger.Info("Cloud sync or auto-download disabled, skipping download"); callback?.Invoke(0, "Cloud sync disabled", null); return; } if (IsDownloading) { _logger.Info("Download already in progress"); callback?.Invoke(0, "Download in progress", null); return; } if (_levelDownloadTimes.ContainsKey(levelId) && DateTime.Now - _levelDownloadTimes[levelId] < TimeSpan.FromMinutes(5.0)) { _logger.Info("Downloaded " + levelId + " recently, skipping"); callback?.Invoke(0, "Downloaded recently", null); return; } IsDownloading = true; _logger.Info("Starting download for level: " + levelId); _apiService.DownloadClimbs(levelId, delegate(List<ClimbData> downloadedClimbs, string error, ClimbListMeta meta) { IsDownloading = false; _lastDownload = DateTime.Now; _levelDownloadTimes[levelId] = DateTime.Now; if (error != null) { _logger.Error("Download failed for level " + levelId + ": " + error); callback?.Invoke(0, error, null); return; } try { int num = MergeDownloadedClimbs(downloadedClimbs, levelId); _logger.Info($"Downloaded and merged {num} new climbs for level {levelId}"); callback?.Invoke(num, null, meta); } catch (Exception ex) { _logger.Error("Failed to merge downloaded climbs: " + ex.Message); callback?.Invoke(0, ex.Message, null); } }, limit, offset); } private int MergeDownloadedClimbs(List<ClimbData> downloadedClimbs, string levelId) { if (downloadedClimbs == null || downloadedClimbs.Count == 0) { return 0; } List<ClimbData> allClimbs = _climbDataService.GetAllClimbs(); int num = 0; foreach (ClimbData downloadedClimb in downloadedClimbs) { if (!allClimbs.Any((ClimbData x) => x.Id == downloadedClimb.Id)) { if (IsSimilarClimbExists(downloadedClimb, allClimbs)) { _logger.Debug($"Skipping similar climb: {downloadedClimb.Id}"); continue; } _climbDataService.AddClimb(downloadedClimb); num++; _logger.Debug($"Merged downloaded climb: {downloadedClimb.Id} from {downloadedClimb.BiomeName}"); } } if (num > 0) { _climbDataService.SaveClimbsToFile(addNewClimb: false); } return num; } private bool IsSimilarClimbExists(ClimbData newClimb, List<ClimbData> existingClimbs) { //IL_0065: 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_0075: 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_0081: 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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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) foreach (ClimbData existingClimb in existingClimbs) { if (!(existingClimb.BiomeName != newClimb.BiomeName) && !(Math.Abs(existingClimb.DurationInSeconds - newClimb.DurationInSeconds) > 5f) && newClimb.Points.Count >= 2 && existingClimb.Points.Count >= 2) { Vector3 val = newClimb.Points.First(); Vector3 val2 = newClimb.Points.Last(); Vector3 val3 = existingClimb.Points.First(); Vector3 val4 = existingClimb.Points.Last(); float num = Vector3.Distance(val, val3); float num2 = Vector3.Distance(val2, val4); if (num < 2f && num2 < 2f) { return true; } } } return false; } public void DownloadRecentClimbs(Action<int, string, ClimbListMeta> callback = null) { if (!_configService.Config.EnableCloudSync) { callback?.Invoke(0, "Cloud sync disabled", null); return; } if (IsDownloading) { callback?.Invoke(0, "Download in progress", null); return; } IsDownloading = true; _logger.Info("Downloading recent climbs from all levels"); _apiService.DownloadClimbs("recent", delegate(List<ClimbData> recentClimbs, string error, ClimbListMeta meta) { IsDownloading = false; _lastDownload = DateTime.Now; if (error != null) { _logger.Error("Failed to download recent climbs: " + error); callback?.Invoke(0, error, null); return; } try { int num = MergeDownloadedClimbs(recentClimbs, "all_levels"); _logger.Info($"Downloaded and merged {num} recent climbs"); callback?.Invoke(num, null, meta); } catch (Exception ex) { _logger.Error("Failed to merge recent climbs: " + ex.Message); callback?.Invoke(0, ex.Message, null); } }); } public void CheckForUpdates(string levelId) { if (!_configService.Config.EnableCloudSync || !_configService.Config.AutoDownload || (_levelDownloadTimes.ContainsKey(levelId) && DateTime.Now - _levelDownloadTimes[levelId] < TimeSpan.FromMinutes(10.0))) { return; } DownloadAndMergeClimbs(levelId, delegate(int count, string error, ClimbListMeta meta) { if (error == null && count > 0) { _logger.Info($"Auto-update found {count} new climbs for {levelId}"); } }); } public string GetDownloadStats() { int count = _levelDownloadTimes.Count; string arg = ((_lastDownload == DateTime.MinValue) ? "Never" : $"{(DateTime.Now - _lastDownload).TotalMinutes:F0}m ago"); return $"Downloads: {count} levels, last: {arg}"; } public void ClearDownloadHistory() { _levelDownloadTimes.Clear(); _lastDownload = DateTime.MinValue; _logger.Info("Download history cleared"); } } public class ClimbUploadService { [CompilerGenerated] private sealed class <WaitAndProcessNext>d__16 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delay; public ClimbUploadService <>4__this; public List<UploadQueueItem> items; public int nextIndex; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitAndProcessNext>d__16(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown int num = <>1__state; ClimbUploadService climbUploadService = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; climbUploadService.ProcessNextItem(items, nextIndex); 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 ModLogger _logger; private readonly VPSApiService _apiService; private readonly ServerConfigService _configService; private readonly string _queueFilePath; private List<UploadQueueItem> _uploadQueue = new List<UploadQueueItem>(); private bool _isProcessingQueue; public int QueuedUploads => _uploadQueue.Count((UploadQueueItem x) => x.Status == UploadStatus.Pending || x.Status == UploadStatus.Failed); public int CompletedUploads => _uploadQueue.Count((UploadQueueItem x) => x.Status == UploadStatus.Completed); public int FailedUploads => _uploadQueue.Count((UploadQueueItem x) => x.Status == UploadStatus.Failed && !x.ShouldRetry()); public ClimbUploadService(ModLogger logger, VPSApiService apiService, ServerConfigService configService) { _logger = logger; _apiService = apiService; _configService = configService; _queueFilePath = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", "upload_queue.json"); LoadQueue(); } public void QueueForUpload(ClimbData climbData, string levelId) { if (climbData.WasDeathClimb) { _logger.Info("[Death] Death climb will not be uploaded to cloud"); return; } if (climbData.WasFlagged) { _logger.Warning("[FlyDetection] Climb was flagged during recording - will upload as private!"); _logger.Warning($"[FlyDetection] Score: {climbData.FlaggedScore}/100"); _logger.Warning("[FlyDetection] Reason: " + climbData.FlaggedReason); _logger.Warning("[FlyDetection] Climb will be uploaded as private due to fly mod detection"); } if (!_configService.Config.EnableCloudSync || !_configService.Config.AutoUpload) { _logger.Info("Cloud sync or auto-upload disabled, skipping upload"); return; } if (climbData == null) { _logger.Error("Cannot queue null climb data for upload"); return; } if (!InputValidator.IsValidLevelId(levelId)) { _logger.Error("Cannot queue climb - invalid level ID: " + levelId); return; } if (!InputValidator.IsValidPointCount(climbData.Points?.Count ?? 0)) { _logger.Error($"Cannot queue climb - invalid point count: {climbData.Points?.Count ?? 0}"); return; } if (_uploadQueue.Any((UploadQueueItem x) => x.ClimbData.Id == climbData.Id)) { _logger.Info($"Climb {climbData.Id} already in upload queue"); return; } UploadQueueItem uploadQueueItem = new UploadQueueItem { ClimbData = climbData }; uploadQueueItem.ClimbData.BiomeName = climbData.BiomeName + "|" + levelId; _uploadQueue.Add(uploadQueueItem); SaveQueue(); _logger.Info($"Added climb {climbData.Id} to upload queue. Queue size: {QueuedUploads}"); if (!_isProcessingQueue) { ProcessQueue(); } } public void ProcessQueue() { if (_isProcessingQueue) { _logger.Info("Upload queue already being processed"); return; } _isProcessingQueue = true; _logger.Info($"Starting upload queue processing. {QueuedUploads} items to process"); CleanupExpiredItems(); List<UploadQueueItem> list = (from x in _uploadQueue where x.Status == UploadStatus.Pending || (x.Status == UploadStatus.Failed && x.ShouldRetry()) orderby x.CreatedAt select x).ToList(); if (list.Count == 0) { _logger.Info("No items to process in upload queue"); _isProcessingQueue = false; } else { ProcessNextItem(list, 0); } } private void ProcessNextItem(List<UploadQueueItem> items, int index) { if (index >= items.Count) { _logger.Info("Upload queue processing completed"); _isProcessingQueue = false; SaveQueue(); return; } UploadQueueItem item = items[index]; if (item == null || item.ClimbData == null) { _logger.Warning($"Skipping invalid upload queue item at index {index}"); ProcessNextItem(items, index + 1); return; } if (item.ClimbData.WasFlagged) { _logger.Warning($"[FlyDetection] Processing flagged climb {item.ClimbData.Id}"); _logger.Warning($"[FlyDetection] Score: {item.ClimbData.FlaggedScore}/100"); _logger.Warning("[FlyDetection] Will upload as private to server"); } if (!_configService.Config.CanUpload()) { _logger.Warning("Upload rate limit exceeded, pausing queue processing"); _isProcessingQueue = false; return; } string text = item.ClimbData.BiomeName; string levelId = "unknown"; if (text.Contains("|")) { string[] array = text.Split('|'); text = array[0]; levelId = array[1]; } item.Status = UploadStatus.Uploading; item.LastAttempt = DateTime.Now; _logger.Info($"Uploading climb {item.ClimbData.Id} (attempt {item.RetryCount + 1})"); ClimbData climbData = new ClimbData { Id = item.ClimbData.Id, CreationTime = item.ClimbData.CreationTime, BiomeName = text, DurationInSeconds = item.ClimbData.DurationInSeconds, Points = item.ClimbData.Points, AscentLevel = item.ClimbData.AscentLevel }; bool wasFlagged = item.ClimbData.WasFlagged; float flaggedScore = item.ClimbData.FlaggedScore; string flaggedReason = item.ClimbData.FlaggedReason; _apiService.UploadClimbWithDetection(climbData, levelId, wasFlagged, flaggedScore, flaggedReason, delegate(bool success, string error) { if (success) { item.Status = UploadStatus.Completed; _logger.Info($"Successfully uploaded climb {item.ClimbData.Id}"); } else { item.RetryCount++; item.LastError = error; if (item.ShouldRetry(_configService.Config.RetryAttempts)) { item.Status = UploadStatus.Failed; _logger.Warning($"Upload failed for climb {item.ClimbData.Id}: {error}. Will retry ({item.RetryCount}/{_configService.Config.RetryAttempts})"); } else { item.Status = UploadStatus.Failed; _logger.Error($"Upload permanently failed for climb {item.ClimbData.Id}: {error}"); } } ((MonoBehaviour)Plugin.Instance).StartCoroutine(WaitAndProcessNext(items, index + 1, 2f)); }); } [IteratorStateMachine(typeof(<WaitAndProcessNext>d__16))] private IEnumerator WaitAndProcessNext(List<UploadQueueItem> items, int nextIndex, float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitAndProcessNext>d__16(0) { <>4__this = this, items = items, nextIndex = nextIndex, delay = delay }; } public void RetryFailedUploads() { List<UploadQueueItem> list = _uploadQueue.Where((UploadQueueItem x) => x.Status == UploadStatus.Failed && x.ShouldRetry()).ToList(); if (list.Count == 0) { _logger.Info("No failed uploads to retry"); return; } _logger.Info($"Retrying {list.Count} failed uploads"); foreach (UploadQueueItem item in list) { item.Status = UploadStatus.Pending; } SaveQueue(); ProcessQueue(); } public void ClearCompletedUploads() { int count = _uploadQueue.Count; _uploadQueue.RemoveAll((UploadQueueItem x) => x.Status == UploadStatus.Completed); int num = count - _uploadQueue.Count; if (num > 0) { _logger.Info($"Cleared {num} completed uploads from queue"); SaveQueue(); } } private void CleanupExpiredItems() { TimeSpan maxAge = TimeSpan.FromDays(7.0); List<UploadQueueItem> list = _uploadQueue.Where((UploadQueueItem x) => x.IsExpired(maxAge)).ToList(); foreach (UploadQueueItem item in list) { item.Status = UploadStatus.Expired; } _uploadQueue.RemoveAll((UploadQueueItem x) => x.Status == UploadStatus.Expired); if (list.Count > 0) { _logger.Info($"Removed {list.Count} expired items from upload queue"); } } private void SaveQueue() { List<UploadQueueItem> payload = new List<UploadQueueItem>(_uploadQueue); FileUtils.WriteJsonFileInBackground(_logger, _queueFilePath, payload); } private void LoadQueue() { try { if (!File.Exists(_queueFilePath)) { return; } string text = File.ReadAllText(_queueFilePath); _uploadQueue = JsonConvert.DeserializeObject<List<UploadQueueItem>>(text, CommonJsonSettings.Default) ?? new List<UploadQueueItem>(); _uploadQueue.RemoveAll((UploadQueueItem item) => item.ClimbData == null); foreach (UploadQueueItem item in _uploadQueue.Where((UploadQueueItem x) => x.Status == UploadStatus.Uploading)) { item.Status = UploadStatus.Pending; } _logger.Info($"Loaded upload queue with {_uploadQueue.Count} items"); } catch (Exception ex) { _logger.Error("Failed to load upload queue: " + ex.Message); _uploadQueue = new List<UploadQueueItem>(); } } public string GetQueueStatus() { return $"Queue: {QueuedUploads} pending, {CompletedUploads} completed, {FailedUploads} failed"; } } public class ServerConfigService { private readonly ModLogger _logger; private readonly string _configPath; private ServerConfig _config; public ServerConfig Config => _config ?? LoadConfig(); public ServerConfigService(ModLogger logger) { _logger = logger; _configPath = Path.Combine(Paths.PluginPath, "FollowMePeak_Data", "server_config.json"); } public ServerConfig LoadConfig() { try { if (File.Exists(_configPath)) { string text = File.ReadAllText(_configPath); _config = JsonConvert.DeserializeObject<ServerConfig>(text, CommonJsonSettings.Default); _logger.Info("Server configuration loaded successfully"); } else { _config = CreateDefaultConfig(); SaveConfig(); _logger.Info("Created default server configuration"); } } catch (Exception ex) { _logger.Error("Failed to load server config: " + ex.Message); _config = CreateDefaultConfig(); } return _config; } public void SaveConfig() { FileUtils.WriteJsonFileInBackground(_logger, _configPath, _config); } private ServerConfig CreateDefaultConfig() { return new ServerConfig { EnableCloudSync = true, PlayerName = GenerateRandomPlayerName(), LastUploadReset = DateTime.Now, UploadsThisHour = 0 }; } private string GenerateRandomPlayerName() { string[] array = new string[70] { "Quick", "Swift", "Fast", "Rapid", "Lightning", "Turbo", "Blazing", "Speedy", "Nimble", "Agile", "Rocky", "Steep", "Alpine", "Icy", "Snowy", "Windy", "High", "Rugged", "Vertical", "Frozen", "Summit", "Peak", "Ridge", "Cliff", "Stone", "Granite", "Crystal", "Misty", "Foggy", "Stormy", "Brave", "Bold", "Fearless", "Daring", "Wild", "Crazy", "Mad", "Epic", "Legendary", "Mighty", "Strong", "Tough", "Hardcore", "Extreme", "Silent", "Clever", "Smart", "Wise", "Sharp", "Keen", "Dizzy", "Wobbly", "Clumsy", "Sleepy", "Hungry", "Thirsty", "Lost", "Confused", "Lucky", "Unlucky", "Weird", "Strange", "Funky", "Silly", "Goofy", "Bouncy", "Fuzzy", "Sparkly", "Shiny", "Glowing" }; string[] array2 = new string[65] { "Climber", "Mountaineer", "Alpinist", "Scrambler", "Boulderer", "Summiteer", "Peakbagger", "Ridgewalker", "FollowMe", "Trailblazer", "Explorer", "Navigator", "Scout", "Wanderer", "Seeker", "Hiker", "Trekker", "Adventurer", "Voyager", "Pioneer", "Ranger", "Guide", "Sherpa", "Basecamp", "Expedition", "Goat", "Yak", "Eagle", "Hawk", "Raven", "Bear", "Wolf", "Fox", "Lynx", "Marmot", "Ibex", "Chamois", "Falcon", "Condor", "Vulture", "SnowLeopard", "Bighorn", "Pika", "Ptarmigan", "Wolverine", "Potato", "Pretzel", "Pickle", "Pancake", "Waffle", "Burrito", "Taco", "Pizza", "Bagel", "Donut", "Penguin", "Llama", "Walrus", "Hedgehog", "Squirrel", "Narwhal", "Unicorn", "Dragon", "Phoenix", "Yeti" }; Random random = new Random(); string arg = array[random.Next(array.Length)]; string arg2 = array2[random.Next(array2.Length)]; int num = random.Next(100, 999); return $"{arg}{arg2}_{num}"; } public void SetCloudSyncEnabled(bool enabled) { _config.EnableCloudSync = enabled; SaveConfig(); _logger.Info("Cloud sync " + (enabled ? "enabled" : "disabled")); } public void SetPlayerName(string playerName) { if (string.IsNullOrWhiteSpace(playerName)) { playerName = "Anonymous"; } else { playerName = Regex.Replace(playerName, "[^a-zA-Z0-9_-]", ""); if (playerName.Length > 50) { playerName = playerName.Substring(0, 50); } if (string.IsNullOrWhiteSpace(playerName)) { playerName = "Anonymous"; } } _config.PlayerName = playerName; SaveConfig(); _logger.Info("Player name updated to: " + _config.PlayerName); } public void ResetUploadRateLimit() { _config.UploadsThisHour = 0; _config.LastUploadReset = DateTime.Now; SaveConfig(); _logger.Info("Upload rate limit reset"); } public bool ValidateConfig() { return true; } } public class VPSApiService { [CompilerGenerated] private sealed class <CheckForUpdateMessageCoroutine>d__17 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public VPSApiService <>4__this; public string modVersion; public Action<UpdateMessage> callback; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CheckForUpdateMessageCoroutine>d__17(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <request>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Invalid comparison between Unknown and I4 try { int num = <>1__state; VPSApiService vPSApiService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; string text = vPSApiService._config.BaseUrl + "/api/updates/check/" + UnityWebRequest.EscapeURL(modVersion); <request>5__2 = UnityWebRequest.Get(text); <>1__state = -3; <request>5__2.timeout = vPSApiService._config.TimeoutSeconds; <request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey); <>2__current = <request>5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)<request>5__2.result == 1) { try { UpdateMessageResponse updateMessageResponse = JsonConvert.DeserializeObject<UpdateMessageResponse>(<request>5__2.downloadHandler.text, CommonJsonSettings.Default); UpdateMessage updateMessage = new UpdateMessage { HasUpdate = updateMessageResponse.HasUpdate, Message = updateMessageResponse.Message, Type = (updateMessageResponse.Type ?? "info"), LastChecked = DateTime.Now }; lock (vPSApiService._updateCacheLock) { vPSApiService._cachedUpdateMessage = updateMessage; } vPSApiService._logger.Info($"[UpdateMessage] Check complete - HasUpdate: {updateMessage.HasUpdate}"); callback?.Invoke(updateMessage); } catch (Exception ex) { vPSApiService._logger.Error("[UpdateMessage] Failed to parse response: " + ex.Message); callback?.Invoke(new UpdateMessage { HasUpdate = false }); } } else { vPSApiService._logger.Warning("[UpdateMessage] Check failed: " + <request>5__2.error); callback?.Invoke(new UpdateMessage { HasUpdate = false }); } <>m__Finally1(); <request>5__2 = null; return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } 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 (<request>5__2 != null) { ((IDisposable)<request>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <CheckServerHealthCoroutine>d__15 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public VPSApiService <>4__this; public Action<bool> callback; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CheckServerHealthCoroutine>d__15(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <request>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Invalid comparison between Unknown and I4 try { int num = <>1__state; VPSApiService vPSApiService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; string text = vPSApiService._config.BaseUrl + "/api/health"; <request>5__2 = UnityWebRequest.Get(text); <>1__state = -3; <request>5__2.timeout = vPSApiService._config.TimeoutSeconds; <request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey); <>2__current = <request>5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; vPSApiService.LastHealthCheck = DateTime.Now; if ((int)<request>5__2.result == 1) { try { HealthResponse healthResponse = JsonConvert.DeserializeObject<HealthResponse>(<request>5__2.downloadHandler.text, CommonJsonSettings.Default); vPSApiService.IsServerReachable = healthResponse.Status == "healthy"; if (vPSApiService.IsServerReachable) { vPSApiService._logger.Info($"Server health check successful. {healthResponse.Stats.TotalClimbs} climbs in database."); } else { vPSApiService._logger.Warning("Server unhealthy: " + healthResponse.Status); vPSApiService.IsServerReachable = false; } } catch (Exception ex) { vPSApiService._logger.Error("Failed to parse health response: " + ex.Message); vPSApiService.IsServerReachable = false; } } else { vPSApiService._logger.Error("Health check failed: " + <request>5__2.error); vPSApiService.IsServerReachable = false; } callback?.Invoke(vPSApiService.IsServerReachable); <>m__Finally1(); <request>5__2 = null; return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } 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 (<request>5__2 != null) { ((IDisposable)<request>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <DownloadClimbsCoroutine>d__23 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string levelId; public Action<List<ClimbData>, string, ClimbListMeta> callback; public VPSApiService <>4__this; public int limit; public int offset; public string playerName; public string biomeName; public string peakCode; public int? ascentLevel; public string sortBy; public string sortOrder; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DownloadClimbsCoroutine>d__23(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <request>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Invalid comparison between Unknown and I4 try { int num = <>1__state; VPSApiService vPSApiService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; if (!InputValidator.IsValidLevelId(levelId)) { callback?.Invoke(new List<ClimbData>(), "Invalid level ID format", null); return false; } StringBuilder stringBuilder = new StringBuilder($"{vPSApiService._config.BaseUrl}/api/climbs/{levelId}?limit={limit}&offset={offset}"); if (!string.IsNullOrEmpty(playerName)) { stringBuilder.Append("&player_name=" + UnityWebRequest.EscapeURL(playerName)); } if (!string.IsNullOrEmpty(biomeName)) { stringBuilder.Append("&biome_name=" + UnityWebRequest.EscapeURL(biomeName)); } if (!string.IsNullOrEmpty(peakCode)) { stringBuilder.Append("&peak_code=" + UnityWebRequest.EscapeURL(peakCode)); } if (ascentLevel.HasValue) { stringBuilder.Append($"&ascent_level={ascentLevel.Value}"); } stringBuilder.Append("&sort_by=" + sortBy + "&sort_order=" + sortOrder); stringBuilder.Append("&format=compressed"); string text3 = stringBuilder.ToString(); <request>5__2 = UnityWebRequest.Get(text3); <>1__state = -3; <request>5__2.timeout = vPSApiService._config.TimeoutSeconds; <request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey); <>2__current = <request>5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)<request>5__2.result == 1) { try { ClimbListResponse climbListResponse = JsonConvert.DeserializeObject<ClimbListResponse>(<request>5__2.downloadHandler.text, CommonJsonSettings.Default); List<ClimbData> list = new List<ClimbData>(); if (climbListResponse.Data != null) { foreach (ServerClimbData datum in climbListResponse.Data) { ClimbData climbData = datum.ToClimbData(); if (!string.IsNullOrEmpty(datum.PointData)) { vPSApiService._logger.Info($"Processing compressed climb {datum.Id}: PointData length={datum.PointData.Length}, Points count={climbData.Points?.Count ?? 0}"); } list.Add(climbData); } } vPSApiService._logger.Info($"Downloaded {list.Count} climbs for level {levelId}"); callback?.Invoke(list, null, climbListResponse.Meta); } catch (Exception ex) { string text = "Failed to parse download response: " + ex.Message; vPSApiService._logger.Error(text); callback?.Invoke(new List<ClimbData>(), text, null); } } else if (<request>5__2.responseCode == 404) { vPSApiService._logger.Info("No climbs found for level " + levelId); callback?.Invoke(new List<ClimbData>(), null, null); } else { string text2 = $"Download request failed: {<request>5__2.error} (HTTP {<request>5__2.responseCode})"; vPSApiService._logger.Error(text2); callback?.Invoke(new List<ClimbData>(), text2, null); } <>m__Finally1(); <request>5__2 = null; return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } 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 (<request>5__2 != null) { ((IDisposable)<request>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <GetServerStatsCoroutine>d__27 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public VPSApiService <>4__this; public Action<string, string> callback; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetServerStatsCoroutine>d__27(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <request>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Invalid comparison between Unknown and I4 try { int num = <>1__state; VPSApiService vPSApiService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; string text2 = vPSApiService._config.BaseUrl + "/api/stats"; <request>5__2 = UnityWebRequest.Get(text2); <>1__state = -3; <request>5__2.timeout = vPSApiService._config.TimeoutSeconds; <request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey); <>2__current = <request>5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)<request>5__2.result == 1) { callback?.Invoke(<request>5__2.downloadHandler.text, null); } else { string text = "Stats request failed: " + <request>5__2.error; vPSApiService._logger.Error(text); callback?.Invoke(null, text); } <>m__Finally1(); <request>5__2 = null; return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } 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 (<request>5__2 != null) { ((IDisposable)<request>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <SearchClimbByPeakCodeCoroutine>d__25 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public VPSApiService <>4__this; public string peakCode; public Action<ClimbData, string> callback; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SearchClimbByPeakCodeCoroutine>d__25(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <request>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Invalid comparison between Unknown and I4 try { int num = <>1__state; VPSApiService vPSApiService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; string text3 = vPSApiService._config.BaseUrl + "/api/climbs/search/" + peakCode + "?format=compressed"; <request>5__2 = UnityWebRequest.Get(text3); <>1__state = -3; <request>5__2.timeout = vPSApiService._config.TimeoutSeconds; <request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey); <>2__current = <request>5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)<request>5__2.result == 1) { try { ClimbSearchResponse climbSearchResponse = JsonConvert.DeserializeObject<ClimbSearchResponse>(<request>5__2.downloadHandler.text, CommonJsonSettings.Default); if (climbSearchResponse.Success && climbSearchResponse.Data != null) { ClimbData climbData = climbSearchResponse.Data.ToClimbData(); vPSApiService._logger.Info("Found climb with peak code " + peakCode + ": " + climbData.GetDisplayName()); callback?.Invoke(climbData, null); } else { vPSApiService._logger.Info("No climb found with peak code " + peakCode); callback?.Invoke(null, "Climb not found"); } } catch (Exception ex) { string text = "Failed to parse search response: " + ex.Message; vPSApiService._logger.Error(text); callback?.Invoke(null, text); } } else if (<request>5__2.responseCode == 404) { vPSApiService._logger.Info("No climb found with peak code " + peakCode); callback?.Invoke(null, "Climb not found"); } else { string text2 = $"Search request failed: {<request>5__2.error} (HTTP {<request>5__2.responseCode})"; vPSApiService._logger.Error(text2); callback?.Invoke(null, text2); } <>m__Finally1(); <request>5__2 = null; return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } 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 (<request>5__2 != null) { ((IDisposable)<request>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <UploadClimbCoroutine>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string levelId; public Action<bool, string> callback; public ClimbData climbData; public VPSApiService <>4__this; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <UploadClimbCoroutine>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <request>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Invalid comparison between Unknown and I4 //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Expected O, but got Unknown //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Expected O, but got Unknown try { int num = <>1__state; VPSApiService vPSApiService = <>4__this; switch (num) { default: return false; case 0: { <>1__state = -1; if (!InputValidator.IsValidLevelId(levelId)) { callback?.Invoke(arg1: false, "Invalid level ID format"); return false; } if (!InputValidator.IsValidPointCount(climbData.Points?.Count ?? 0)) { callback?.Invoke(arg1: false, "Invalid climb data - point count out of range"); return false; } string text2 = vPSApiService._config.BaseUrl + "/api/climbs"; int num2 = InputValidator.ClampAscentLevel(climbData.AscentLevel); vPSApiService._logger.Info($"Upload data: AscentLevel from ClimbData: {climbData.AscentLevel}, Clamped: {num2}"); MemoryStream memoryStream = new MemoryStream(); ClimbDataCrusher.WriteClimbData(memoryStream, climbData); byte[] array = memoryStream.ToArray(); string text3 = JsonConvert.SerializeObject((object)new { levelId = levelId, playerName = InputValidator.SanitizePlayerName(vPSApiService._config.PlayerName), biomeName = InputValidator.SanitizeBiomeName(climbData.BiomeName), duration = InputValidator.ClampDuration(climbData.DurationInSeconds), pointData = Convert.ToBase64String(array), compressionVersion = 1, isSuccessful = true, tags = new string[0], ascentLevel = num2 }, CommonJsonSettings.Compact); vPSApiService._logger.Info($"Using compressed upload format. Original points: {climbData.Points.Count}, Compressed size: {array.Length} bytes"); int num3 = text3.IndexOf("\"ascentLevel\":"); if (num3 >= 0) { int num4 = Math.Min(num3 + 50, text3.Length); string text4 = text3.Substring(num3, num4 - num3); vPSApiService._logger.Info("Upload JSON contains ascentLevel: " + text4); } else { vPSApiService._logger.Error("Upload JSON does NOT contain ascentLevel field!"); } vPSApiService._logger.Info("Upload JSON payload (first 500 chars): " + text3.Substring(0, Math.Min(500, text3.Length)) + "..."); if (text3.Length > vPSApiService.GetMaxPayloadSize()) { string text5 = $"Payload too large ({text3.Length} bytes). Consider reducing climb complexity."; vPSApiService._logger.Error(text5); callback?.Invoke(arg1: false, text5); return false; } byte[] bytes = Encoding.UTF8.GetBytes(text3); <request>5__2 = new UnityWebRequest(text2, "POST"); <>1__state = -3; <request>5__2.uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes); <request>5__2.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); <request>5__2.SetRequestHeader("Content-Type", "application/json"); <request>5__2.SetRequestHeader("X-API-Key", vPSApiService._config.ApiKey); <request>5__2.timeout = vPSApiService._config.TimeoutSeconds; <>2__current = <request>5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)<request>5__2.result == 1) { try { ApiResponse<ClimbUploadResponse> apiResponse = JsonConvert.DeserializeObject<ApiResponse<ClimbUploadResponse>>(<request>5__2.downloadHandler.text, CommonJsonSettings.Default); if (apiResponse.Success) { vPSApiService._config.IncrementUploadCount(); vPSApiService._logger.Info("Climb uploaded successfully: " + apiResponse.Data.ClimbId); callback?.Invoke(arg1: true, apiResponse.Data.ClimbId); } else { vPSApiService._logger.Error("Upload failed: " + apiResponse.Error); callback?.Invoke(arg1: false, apiResponse.Error); } } catch (Exception ex) { vPSApiService._logger.Error("Failed to parse upload response: " + ex.Message); callback?.Invoke(arg1: false, "Parse error"); } } else { string text = $"Upload request failed: {<request>5__2.error} (HTTP {<request>5__2.responseCode})"; vPSApiService._logger.Error(text); callback?.Invoke(arg1: false, text); } <>m__Finally1(); <request>5__2 = null; return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } 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 (<request>5__2 != null) { ((IDisposable)<request>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <UploadClimbWithDetectionCoroutine>d__21 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string levelId; public Action<bool, string> callback; public ClimbData climbData; public VPSApiService <>4__this; public bool isFlagged;