The BepInEx console will not appear when launching like it does for other games on Thunderstore (you can turn it back on in your BepInEx.cfg file). If your PEAK crashes on startup, add -dx12 to your launch parameters.
Decompiled source of FollowMe v1.0.3
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.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+9dff2c98d9e55f6a97c82bd3e8aab032bda484a8")] [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__DisplayClass28_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 <InitializePathSystem>d__29 : 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__29(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__28 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Plugin <>4__this; private <>c__DisplayClass28_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__28(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__DisplayClass28_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; 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."); } 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__28))] private IEnumerator LoadModUIAssetBundle() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadModUIAssetBundle>d__28(0) { <>4__this = this }; } [IteratorStateMachine(typeof(<InitializePathSystem>d__29))] 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__29(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; SaveQueue(); _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}"); } } SaveQueue(); ((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; public float detectionScore; public string detectionReason; private UnityWebRequest <request>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <UploadClimbWithDetectionCoroutine>d__21(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_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Invalid comparison between Unknown and I4 //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Expected O, but got Unknown //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Expected O, but got Unknown //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0225: 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[] inArray = 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(inArray), compressionVersion = 1, isSuccessful = true, tags = new string[0], ascentLevel = num2,