Decompiled source of FollowMe v1.0.6

FollowMePeak.dll

Decompiled a month ago
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.6.0")]
[assembly: AssemblyInformationalVersion("1.0.6+dd52ca85ede498e0ed4a4c33f6f170e8edfc26d3")]
[assembly: AssemblyProduct("FollowMePeak")]
[assembly: AssemblyTitle("FollowMePeak")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.6.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FollowMePeak
{
	[BepInPlugin("com.thomasaushh.followmepeak", "FollowMe-Peak", "1.0.6")]
	public class Plugin : BaseUnityPlugin
	{
		public static readonly string MOD_VERSION = typeof(Plugin).Assembly.GetName().Version.ToString(3);

		public static ConfigEntry<KeyCode> ModMenuToggleKey;

		public static ConfigEntry<KeyCode> ToggleRoutesVisibilityKey;

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

		private IEnumerator DelayedLogInstalledMods()
		{
			_modLogger.Info("[Plugin] Starting delayed mod listing coroutine...");
			yield return (object)new WaitForSeconds(3f);
			_modLogger.Info("[Plugin] 3 seconds elapsed, logging installed mods...");
			LogInstalledMods();
		}

		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");
			ToggleRoutesVisibilityKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "ToggleRoutesVisibilityKey", (KeyCode)283, "Key to toggle visibility of selected routes");
			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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: 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();
			}
			if (Input.GetKeyDown(ToggleRoutesVisibilityKey.Value))
			{
				_modLogger.Info($"[Plugin] {ToggleRoutesVisibilityKey.Value} pressed - Toggling route visibility");
				_visualizationManager?.ToggleAllVisibleRoutes();
			}
			_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();
			}
		}

		private IEnumerator LoadModUIAssetBundle()
		{
			_modLogger.Info("[AssetBundle] Starting AssetBundle load coroutine...");
			_modLogger.Info("[AssetBundle] Current Directory: " + Directory.GetCurrentDirectory());
			_modLogger.Info("[AssetBundle] BepInEx Plugin Path: " + Paths.PluginPath);
			_modLogger.Info("[AssetBundle] Application.dataPath: " + Application.dataPath);
			bool loadComplete = false;
			bool loadSuccess = false;
			_modLogger.Info("[AssetBundle] Calling AssetBundleService.Instance.LoadModUIBundle...");
			yield return AssetBundleService.Instance.LoadModUIBundle(delegate(bool success)
			{
				_modLogger.Info($"[AssetBundle] LoadModUIBundle callback received: success={success}");
				loadComplete = true;
				loadSuccess = success;
			});
			float timeout = 5f;
			float elapsed = 0f;
			_modLogger.Info($"[AssetBundle] Waiting for load completion (max {timeout} seconds)...");
			while (!loadComplete && elapsed < timeout)
			{
				elapsed += Time.deltaTime;
				yield return null;
			}
			if (!loadComplete)
			{
				_modLogger.Error($"[AssetBundle] Timeout waiting for AssetBundle load after {timeout} seconds");
				_modLogger.Error($"[AssetBundle] Service instance exists: {AssetBundleService.Instance != null}");
				_modLogger.Error($"[AssetBundle] Service IsLoaded: {AssetBundleService.Instance?.IsLoaded ?? false}");
			}
			else if (loadSuccess)
			{
				_modLogger.Info("[AssetBundle] Successfully loaded, notifying ModMenuManager");
				_modLogger.Info($"[AssetBundle] ModMenuManager exists: {_modMenuManager != null}");
				_modMenuManager?.OnAssetBundleLoaded();
				_modLogger.Info("[AssetBundle] OnAssetBundleLoaded called");
			}
			else
			{
				_modLogger.Error("[AssetBundle] AssetBundle loading failed - check AssetBundleService logs for details");
			}
			_modLogger.Info($"[AssetBundle] LoadModUIAssetBundle coroutine finished - Success: {loadSuccess}");
		}

		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 (object)new WaitForSeconds(0.5f);
			NextLevelService service = GameHandler.GetService<NextLevelService>();
			if (service != null && service.Data.IsSome)
			{
				int currentLevelIndex = service.Data.Value.CurrentLevelIndex;
				_climbDataService.CurrentLevelID = $"{((Scene)(ref scene)).name}_{currentLevelIndex}";
				_modLogger.Info("Level erkannt: " + _climbDataService.CurrentLevelID);
				_climbDataService.LoadClimbsFromFile();
				if (_serverConfigService.Config.EnableCloudSync && _serverConfigService.Config.AutoDownload)
				{
					_modLogger.Info("Server-side pagination enabled - data will be loaded per page in UI");
				}
				_visualizationManager.InitializeClimbVisibility();
			}
			else
			{
				_modLogger.Error("NextLevelService or its data could not be found!");
				_climbDataService.CurrentLevelID = ((Scene)(ref scene)).name + "_unknown";
			}
		}

		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)
		{
			return new HalfFloat
			{
				RawValue = BinaryPrimitives.ReadUInt16LittleEndian(span)
			};
		}

		private static void EncodeQuarterFloat(Span<byte> span, QuarterFloat quarter)
		{
			span[0] = quarter.RawValue;
		}

		private static QuarterFloat DecodeQuarterFloat(Span<byte> span)
		{
			return new QuarterFloat
			{
				RawValue = span[0]
			};
		}
	}
	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 => new HalfFloat
		{
			RawValue = 30721
		};

		public ushort ExponentBits => (ushort)((uint)(RawValue & 0x7800) >> 11);

		public ushort MantissaBits => (ushort)(RawValue & 0x7FF);

		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 & 0x7FFFFF);
			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 |= 0x800000;
					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);
			return new HalfFloat
			{
				RawValue = (ushort)(num8 | num9 | num10)
			};
		}

		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 &= 0x7FF;
				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 & 0x7FF);
			return new HalfFloat
			{
				RawValue = (ushort)((num << 15) | 0x7800 | num2)
			};
		}

		public ushort DecodeNaN()
		{
			if (!IsNaN())
			{
				throw new InvalidOperationException("Not a NaN value");
			}
			ushort num = (ushort)((RawValue & 0x8000) >>> 15);
			ushort num2 = (ushort)(RawValue & 0x7FF);
			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 => new QuarterFloat
		{
			RawValue = 97
		};

		public byte ExponentBits => (byte)((uint)(RawValue & 0x60) >> 5);

		public byte MantissaBits => (byte)(RawValue & 0x1F);

		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 & 0x7FFFFF);
			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 |= 0x800000;
					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);
			return new QuarterFloat
			{
				RawValue = (byte)(b | b2 | b3)
			};
		}

		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 &= 0x1F;
				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 & 0x1F);
			return new QuarterFloat
			{
				RawValue = (byte)((b << 7) | 0x60 | b2)
			};
		}

		public byte DecodeNaN()
		{
			if (!IsNaN())
			{
				throw new InvalidOperationException("Not a NaN value");
			}
			byte num = (byte)((RawValue & 0x80) >>> 7);
			byte b = (byte)(RawValue & 0x1F);
			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
	{
		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()
		{
		}

		public IEnumerator LoadModUIBundle(Action<bool> onComplete = null)
		{
			if (_isLoaded)
			{
				ModLogger.Instance?.Info("Mod UI bundle is already loaded.");
				onComplete?.Invoke(obj: true);
				yield break;
			}
			if (_isLoading)
			{
				ModLogger.Instance?.Warning("Mod UI bundle is already being loaded.");
				yield break;
			}
			_isLoading = true;
			byte[] array = null;
			string[] manifestResourceNames;
			try
			{
				Assembly executingAssembly = Assembly.GetExecutingAssembly();
				string text = "FollowMePeak.modui";
				using Stream stream = executingAssembly.GetManifestResourceStream(text);
				if (stream == null)
				{
					ModLogger.Instance?.Error("Embedded resource '" + text + "' not found! Make sure the Build Action is set to 'Embedded Resource'.");
					ModLogger.Instance?.Info("Available embedded resources:");
					manifestResourceNames = executingAssembly.GetManifestResourceNames();
					foreach (string text2 in manifestResourceNames)
					{
						ModLogger.Instance?.Info(" -> " + text2);
					}
					_isLoading = false;
					onComplete?.Invoke(obj: false);
					yield break;
				}
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				array = memoryStream.ToArray();
			}
			catch (Exception arg)
			{
				ModLogger.Instance?.Error($"An error occurred while reading the embedded asset bundle: {arg}");
				_isLoading = false;
				onComplete?.Invoke(obj: false);
				yield break;
			}
			AssetBundleCreateRequest bundleLoadRequest = AssetBundle.LoadFromMemoryAsync(array);
			yield return bundleLoadRequest;
			if ((Object)(object)bundleLoadRequest.assetBundle == (Object)null)
			{
				ModLogger.Instance?.Error("Failed to load AssetBundle from memory. The bundle might be corrupt or incompatible.");
				_isLoading = false;
				onComplete?.Invoke(obj: false);
				yield break;
			}
			_modUIBundle = bundleLoadRequest.assetBundle;
			ModLogger.Instance?.Info("Successfully loaded AssetBundle '" + ((Object)_modUIBundle).name + "' from embedded resource.");
			string[] allAssetNames = _modUIBundle.GetAllAssetNames();
			ModLogger.Instance?.Info($"AssetBundle contains {allAssetNames.Length} assets:");
			manifestResourceNames = allAssetNames;
			foreach (string text3 in manifestResourceNames)
			{
				ModLogger.Instance?.Info("  - " + text3);
			}
			_isLoaded = true;
			_isLoading = false;
			onComplete?.Invoke(obj: true);
		}

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

		private IEnumerator WaitAndProcessNext(List<UploadQueueItem> items, int nextIndex, float delay)
		{
			yield return (object)new WaitForSeconds(delay);
			ProcessNextItem(items, nextIndex);
		}

		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
	{
		private readonly ModLogger _logger;

		private readonly ServerConfig _config;

		private readonly MonoBehaviour _coroutineRunner;

		private UpdateMessage _cachedUpdateMessage;

		private readonly object _updateCacheLock = new object();

		public bool IsServerReachable { get; private set; }

		public DateTime LastHealthCheck { get; private set; } = DateTime.MinValue;

		public VPSApiService(ModLogger logger, ServerConfig config, MonoBehaviour coroutineRunner)
		{
			_logger = logger;
			_config = config;
			_coroutineRunner = coroutineRunner;
		}

		public void CheckServerHealth(Action<bool> callback = null)
		{
			_coroutineRunner.StartCoroutine(CheckServerHealthCoroutine(callback));
		}

		private IEnumerator CheckServerHealthCoroutine(Action<bool> callback)
		{
			string text = _config.BaseUrl + "/api/health";
			UnityWebRequest request = UnityWebRequest.Get(text);
			try
			{
				request.timeout = _config.TimeoutSeconds;
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				yield return request.SendWebRequest();
				LastHealthCheck = DateTime.Now;
				if ((int)request.result == 1)
				{
					try
					{
						HealthResponse healthResponse = JsonConvert.DeserializeObject<HealthResponse>(request.downloadHandler.text, CommonJsonSettings.Default);
						IsServerReachable = healthResponse.Status == "healthy";
						if (IsServerReachable)
						{
							_logger.Info($"Server health check successful. {healthResponse.Stats.TotalClimbs} climbs in database.");
						}
						else
						{
							_logger.Warning("Server unhealthy: " + healthResponse.Status);
							IsServerReachable = false;
						}
					}
					catch (Exception ex)
					{
						_logger.Error("Failed to parse health response: " + ex.Message);
						IsServerReachable = false;
					}
				}
				else
				{
					_logger.Error("Health check failed: " + request.error);
					IsServerReachable = false;
				}
				callback?.Invoke(IsServerReachable);
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public void CheckForUpdateMessage(string modVersion, Action<UpdateMessage> callback)
		{
			lock (_updateCacheLock)
			{
				if (_cachedUpdateMessage != null && _cachedUpdateMessage.IsCacheValid())
				{
					_logger.Info("[UpdateMessage] Using cached message");
					callback?.Invoke(_cachedUpdateMessage);
					return;
				}
			}
			if (!_config.EnableCloudSync)
			{
				callback?.Invoke(new UpdateMessage
				{
					HasUpdate = false
				});
			}
			else
			{
				_coroutineRunner.StartCoroutine(CheckForUpdateMessageCoroutine(modVersion, callback));
			}
		}

		private IEnumerator CheckForUpdateMessageCoroutine(string modVersion, Action<UpdateMessage> callback)
		{
			string text = _config.BaseUrl + "/api/updates/check/" + UnityWebRequest.EscapeURL(modVersion);
			UnityWebRequest request = UnityWebRequest.Get(text);
			try
			{
				request.timeout = _config.TimeoutSeconds;
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				yield return request.SendWebRequest();
				if ((int)request.result == 1)
				{
					try
					{
						UpdateMessageResponse updateMessageResponse = JsonConvert.DeserializeObject<UpdateMessageResponse>(request.downloadHandler.text, CommonJsonSettings.Default);
						UpdateMessage updateMessage = new UpdateMessage
						{
							HasUpdate = updateMessageResponse.HasUpdate,
							Message = updateMessageResponse.Message,
							Type = (updateMessageResponse.Type ?? "info"),
							LastChecked = DateTime.Now
						};
						lock (_updateCacheLock)
						{
							_cachedUpdateMessage = updateMessage;
						}
						_logger.Info($"[UpdateMessage] Check complete - HasUpdate: {updateMessage.HasUpdate}");
						callback?.Invoke(updateMessage);
					}
					catch (Exception ex)
					{
						_logger.Error("[UpdateMessage] Failed to parse response: " + ex.Message);
						callback?.Invoke(new UpdateMessage
						{
							HasUpdate = false
						});
					}
				}
				else
				{
					_logger.Warning("[UpdateMessage] Check failed: " + request.error);
					callback?.Invoke(new UpdateMessage
					{
						HasUpdate = false
					});
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public void UploadClimb(ClimbData climbData, string levelId, Action<bool, string> callback)
		{
			if (!_config.EnableCloudSync)
			{
				callback?.Invoke(arg1: false, "Cloud sync disabled");
			}
			else if (!_config.CanUpload())
			{
				callback?.Invoke(arg1: false, "Upload rate limit exceeded");
			}
			else
			{
				_coroutineRunner.StartCoroutine(UploadClimbCoroutine(climbData, levelId, callback));
			}
		}

		public void UploadClimbWithDetection(ClimbData climbData, string levelId, bool isFlagged, float detectionScore, string detectionReason, Action<bool, string> callback)
		{
			if (!_config.EnableCloudSync)
			{
				callback?.Invoke(arg1: false, "Cloud sync disabled");
			}
			else if (!_config.CanUpload())
			{
				callback?.Invoke(arg1: false, "Upload rate limit exceeded");
			}
			else
			{
				_coroutineRunner.StartCoroutine(UploadClimbWithDetectionCoroutine(climbData, levelId, isFlagged, detectionScore, detectionReason, callback));
			}
		}

		private IEnumerator UploadClimbCoroutine(ClimbData climbData, string levelId, Action<bool, string> callback)
		{
			if (!InputValidator.IsValidLevelId(levelId))
			{
				callback?.Invoke(arg1: false, "Invalid level ID format");
				yield break;
			}
			if (!InputValidator.IsValidPointCount(climbData.Points?.Count ?? 0))
			{
				callback?.Invoke(arg1: false, "Invalid climb data - point count out of range");
				yield break;
			}
			string text = _config.BaseUrl + "/api/climbs";
			int num = InputValidator.ClampAscentLevel(climbData.AscentLevel);
			_logger.Info($"Upload data: AscentLevel from ClimbData: {climbData.AscentLevel}, Clamped: {num}");
			MemoryStream memoryStream = new MemoryStream();
			ClimbDataCrusher.WriteClimbData(memoryStream, climbData);
			byte[] array = memoryStream.ToArray();
			string text2 = JsonConvert.SerializeObject((object)new
			{
				levelId = levelId,
				playerName = InputValidator.SanitizePlayerName(_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 = num
			}, CommonJsonSettings.Compact);
			_logger.Info($"Using compressed upload format. Original points: {climbData.Points.Count}, Compressed size: {array.Length} bytes");
			int num2 = text2.IndexOf("\"ascentLevel\":");
			if (num2 >= 0)
			{
				int num3 = Math.Min(num2 + 50, text2.Length);
				string text3 = text2.Substring(num2, num3 - num2);
				_logger.Info("Upload JSON contains ascentLevel: " + text3);
			}
			else
			{
				_logger.Error("Upload JSON does NOT contain ascentLevel field!");
			}
			_logger.Info("Upload JSON payload (first 500 chars): " + text2.Substring(0, Math.Min(500, text2.Length)) + "...");
			if (text2.Length > GetMaxPayloadSize())
			{
				string text4 = $"Payload too large ({text2.Length} bytes). Consider reducing climb complexity.";
				_logger.Error(text4);
				callback?.Invoke(arg1: false, text4);
				yield break;
			}
			byte[] bytes = Encoding.UTF8.GetBytes(text2);
			UnityWebRequest request = new UnityWebRequest(text, "POST");
			try
			{
				request.uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes);
				request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
				request.SetRequestHeader("Content-Type", "application/json");
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				request.timeout = _config.TimeoutSeconds;
				yield return request.SendWebRequest();
				if ((int)request.result == 1)
				{
					try
					{
						ApiResponse<ClimbUploadResponse> apiResponse = JsonConvert.DeserializeObject<ApiResponse<ClimbUploadResponse>>(request.downloadHandler.text, CommonJsonSettings.Default);
						if (apiResponse.Success)
						{
							_config.IncrementUploadCount();
							_logger.Info("Climb uploaded successfully: " + apiResponse.Data.ClimbId);
							callback?.Invoke(arg1: true, apiResponse.Data.ClimbId);
						}
						else
						{
							_logger.Error("Upload failed: " + apiResponse.Error);
							callback?.Invoke(arg1: false, apiResponse.Error);
						}
					}
					catch (Exception ex)
					{
						_logger.Error("Failed to parse upload response: " + ex.Message);
						callback?.Invoke(arg1: false, "Parse error");
					}
				}
				else
				{
					string text5 = $"Upload request failed: {request.error} (HTTP {request.responseCode})";
					_logger.Error(text5);
					callback?.Invoke(arg1: false, text5);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private IEnumerator UploadClimbWithDetectionCoroutine(ClimbData climbData, string levelId, bool isFlagged, float detectionScore, string detectionReason, Action<bool, string> callback)
		{
			if (!InputValidator.IsValidLevelId(levelId))
			{
				callback?.Invoke(arg1: false, "Invalid level ID format");
				yield break;
			}
			if (!InputValidator.IsValidPointCount(climbData.Points?.Count ?? 0))
			{
				callback?.Invoke(arg1: false, "Invalid climb data - point count out of range");
				yield break;
			}
			string text = _config.BaseUrl + "/api/climbs";
			int num = InputValidator.ClampAscentLevel(climbData.AscentLevel);
			_logger.Info($"Upload data: AscentLevel from ClimbData: {climbData.AscentLevel}, Clamped: {num}");
			MemoryStream memoryStream = new MemoryStream();
			ClimbDataCrusher.WriteClimbData(memoryStream, climbData);
			byte[] inArray = memoryStream.ToArray();
			string text2 = JsonConvert.SerializeObject((object)new
			{
				levelId = levelId,
				playerName = InputValidator.SanitizePlayerName(_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 = num,
				modVersion = Plugin.MOD_VERSION,
				isFlagged = isFlagged,
				detectionScore = detectionScore,
				detectionReason = detectionReason
			}, CommonJsonSettings.Compact);
			_logger.Info($"Using compressed upload format with detection data. Version: {Plugin.MOD_VERSION}, Flagged: {isFlagged}");
			if (text2.Length > GetMaxPayloadSize())
			{
				string text3 = $"Payload too large ({text2.Length} bytes). Consider reducing climb complexity.";
				_logger.Error(text3);
				callback?.Invoke(arg1: false, text3);
				yield break;
			}
			byte[] bytes = Encoding.UTF8.GetBytes(text2);
			UnityWebRequest request = new UnityWebRequest(text, "POST");
			try
			{
				request.uploadHandler = (UploadHandler)new UploadHandlerRaw(bytes);
				request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
				request.SetRequestHeader("Content-Type", "application/json");
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				request.timeout = _config.TimeoutSeconds;
				yield return request.SendWebRequest();
				if ((int)request.result == 1)
				{
					try
					{
						ApiResponse<ClimbUploadResponse> apiResponse = JsonConvert.DeserializeObject<ApiResponse<ClimbUploadResponse>>(request.downloadHandler.text, CommonJsonSettings.Default);
						if (apiResponse.Success)
						{
							_config.IncrementUploadCount();
							_logger.Info("Climb uploaded successfully: " + apiResponse.Data.ClimbId);
							callback?.Invoke(arg1: true, apiResponse.Data.ClimbId);
						}
						else
						{
							_logger.Error("Upload failed: " + apiResponse.Error);
							callback?.Invoke(arg1: false, apiResponse.Error);
						}
					}
					catch (Exception ex)
					{
						_logger.Error("Failed to parse upload response: " + ex.Message);
						callback?.Invoke(arg1: false, "Parse error");
					}
				}
				else
				{
					string text4 = $"Upload request failed: {request.error} (HTTP {request.responseCode})";
					_logger.Error(text4);
					callback?.Invoke(arg1: false, text4);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public void DownloadClimbs(string levelId, Action<List<ClimbData>, string, ClimbListMeta> callback, int limit = 10, int offset = 0, string playerName = "", string biomeName = "", string peakCode = "", string sortBy = "created_at", string sortOrder = "desc", int? ascentLevel = null)
		{
			if (!_config.EnableCloudSync)
			{
				callback?.Invoke(new List<ClimbData>(), "Cloud sync disabled", null);
			}
			else
			{
				_coroutineRunner.StartCoroutine(DownloadClimbsCoroutine(levelId, callback, limit, offset, playerName, biomeName, peakCode, sortBy, sortOrder, ascentLevel));
			}
		}

		private IEnumerator DownloadClimbsCoroutine(string levelId, Action<List<ClimbData>, string, ClimbListMeta> callback, int limit = 10, int offset = 0, string playerName = "", string biomeName = "", string peakCode = "", string sortBy = "created_at", string sortOrder = "desc", int? ascentLevel = null)
		{
			if (!InputValidator.IsValidLevelId(levelId))
			{
				callback?.Invoke(new List<ClimbData>(), "Invalid level ID format", null);
				yield break;
			}
			StringBuilder stringBuilder = new StringBuilder($"{_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 text = stringBuilder.ToString();
			UnityWebRequest request = UnityWebRequest.Get(text);
			try
			{
				request.timeout = _config.TimeoutSeconds;
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				yield return request.SendWebRequest();
				if ((int)request.result == 1)
				{
					try
					{
						ClimbListResponse climbListResponse = JsonConvert.DeserializeObject<ClimbListResponse>(request.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))
								{
									_logger.Info($"Processing compressed climb {datum.Id}: PointData length={datum.PointData.Length}, Points count={climbData.Points?.Count ?? 0}");
								}
								list.Add(climbData);
							}
						}
						_logger.Info($"Downloaded {list.Count} climbs for level {levelId}");
						callback?.Invoke(list, null, climbListResponse.Meta);
					}
					catch (Exception ex)
					{
						string text2 = "Failed to parse download response: " + ex.Message;
						_logger.Error(text2);
						callback?.Invoke(new List<ClimbData>(), text2, null);
					}
				}
				else if (request.responseCode == 404)
				{
					_logger.Info("No climbs found for level " + levelId);
					callback?.Invoke(new List<ClimbData>(), null, null);
				}
				else
				{
					string text3 = $"Download request failed: {request.error} (HTTP {request.responseCode})";
					_logger.Error(text3);
					callback?.Invoke(new List<ClimbData>(), text3, null);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public void SearchClimbByPeakCode(string peakCode, Action<ClimbData, string> callback)
		{
			if (!_config.EnableCloudSync)
			{
				callback?.Invoke(null, "Cloud sync disabled");
				return;
			}
			string peakCode2 = InputValidator.SanitizePeakCode(peakCode);
			if (!InputValidator.IsValidPeakCode(peakCode2))
			{
				callback?.Invoke(null, "Peak code must be 8 alphanumeric characters");
			}
			else
			{
				_coroutineRunner.StartCoroutine(SearchClimbByPeakCodeCoroutine(peakCode2, callback));
			}
		}

		private IEnumerator SearchClimbByPeakCodeCoroutine(string peakCode, Action<ClimbData, string> callback)
		{
			string text = _config.BaseUrl + "/api/climbs/search/" + peakCode + "?format=compressed";
			UnityWebRequest request = UnityWebRequest.Get(text);
			try
			{
				request.timeout = _config.TimeoutSeconds;
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				yield return request.SendWebRequest();
				if ((int)request.result == 1)
				{
					try
					{
						ClimbSearchResponse climbSearchResponse = JsonConvert.DeserializeObject<ClimbSearchResponse>(request.downloadHandler.text, CommonJsonSettings.Default);
						if (climbSearchResponse.Success && climbSearchResponse.Data != null)
						{
							ClimbData climbData = climbSearchResponse.Data.ToClimbData();
							_logger.Info("Found climb with peak code " + peakCode + ": " + climbData.GetDisplayName());
							callback?.Invoke(climbData, null);
						}
						else
						{
							_logger.Info("No climb found with peak code " + peakCode);
							callback?.Invoke(null, "Climb not found");
						}
					}
					catch (Exception ex)
					{
						string text2 = "Failed to parse search response: " + ex.Message;
						_logger.Error(text2);
						callback?.Invoke(null, text2);
					}
				}
				else if (request.responseCode == 404)
				{
					_logger.Info("No climb found with peak code " + peakCode);
					callback?.Invoke(null, "Climb not found");
				}
				else
				{
					string text3 = $"Search request failed: {request.error} (HTTP {request.responseCode})";
					_logger.Error(text3);
					callback?.Invoke(null, text3);
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		public void GetServerStats(Action<string, string> callback)
		{
			_coroutineRunner.StartCoroutine(GetServerStatsCoroutine(callback));
		}

		private IEnumerator GetServerStatsCoroutine(Action<string, string> callback)
		{
			string text = _config.BaseUrl + "/api/stats";
			UnityWebRequest request = UnityWebRequest.Get(text);
			try
			{
				request.timeout = _config.TimeoutSeconds;
				request.SetRequestHeader("X-API-Key", _config.ApiKey);
				yield return request.SendWebRequest();
				if ((int)request.result == 1)
				{
					callback?.Invoke(request.downloadHandler.text, null);
					yield break;
				}
				string text2 = "Stats request failed: " + request.error;
				_logger.Error(text2);
				callback?.Invoke(null, text2);
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private int GetMaxPayloadSize()
		{
			return 2097152;
		}
	}
}
namespace FollowMePeak.Patches
{
	public static class EndGamePatch
	{
		private static bool _gameEndingDetected;

		public static void ApplyPatch(Harmony harmony)
		{
			try
			{
				if (TryPatchCharacterEndGame(harmony))
				{
					ModLogger.Instance?.Info("Successfully patched Character.EndGame");
				}
				else if (TryPatchRunManagerEndGame(harmony))
				{
					ModLogger.Instance?.Info("Successfully patched RunManager.EndGame");
				}
				else if (TryPatchPeakSequence(harmony))
				{
					ModLogger.Instance?.Info("Successfully patched PeakSequence.CheckGameComplete as fallback");
				}
				else
				{
					ModLogger.Instance?.Error("Failed to apply any EndGame patches - Peak ending detection will not work");
				}
			}
			catch (Exception arg)
			{
				ModLogger.Instance?.Error($"Failed to patch EndGame: {arg}");
			}
		}

		private static bool TryPatchCharacterEndGame(Harmony harmony)
		{
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Expected O, but got Unknown
			try
			{
				Type type = Type.GetType("Character, Assembly-CSharp");
				if (type == null)
				{
					ModLogger.Instance?.Warning("Could not find Character type");
					return false;
				}
				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				ModLogger.Instance?.Info($"Found {methods.Length} methods in Character class");
				MethodInfo methodInfo = methods.FirstOrDefault((MethodInfo m) => m.Name == "EndGame");
				if (methodInfo == null)
				{
					IEnumerable<string> values = methods.Select((MethodInfo m) => m.Name).Distinct().Take(20);
					ModLogger.Instance?.Warning("EndGame not found. Sample methods: " + string.Join(", ", values));
					return false;
				}
				ModLogger.Instance?.Info($"Found EndGame method: {methodInfo.Name}, IsPublic: {methodInfo.IsPublic}");
				HarmonyMethod val = new HarmonyMethod(typeof(EndGamePatch).GetMethod("CharacterEndGamePrefix", BindingFlags.Static | BindingFlags.Public))
				{
					priority = 200
				};
				harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception arg)
			{
				ModLogger.Instance?.Error($"TryPatchCharacterEndGame failed: {arg}");
				return false;
			}
		}

		private static bool TryPatchRunManagerEndGame(Harmony harmony)
		{
			//IL_008b: 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_009c: Expected O, but got Unknown
			try
			{
				Type type = Type.GetType("RunManager, Assembly-CSharp");
				if (type == null)
				{
					ModLogger.Instance?.Warning("Could not find RunManager type");
					return false;
				}
				MethodInfo method = type.GetMethod("EndGame", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (method == null)
				{
					ModLogger.Instance?.Warning("Could not find RunManager.EndGame method");
					return false;
				}
				ModLogger.Instance?.Info("Found RunManager.EndGame method");
				HarmonyMethod val = new HarmonyMethod(typeof(EndGamePatch).GetMethod("RunManagerEndGamePrefix", BindingFlags.Static | BindingFlags.Public))
				{
					priority = 200
				};
				harmony.Patch((MethodBase)method, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception arg)
			{
				ModLogger.Instance?.Error($"TryPatchRunManagerEndGame failed: {arg}");
				return false;
			}
		}

		private static bool TryPatchPeakSequence(Harmony harmony)
		{
			//IL_008b: 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_009c: Expected O, but got Unknown
			try
			{
				Type type = Type.GetType("PeakSequence, Assembly-CSharp");
				if (type == null)
				{
					ModLogger.Instance?.Warning("Could not find PeakSequence type");
					return false;
				}
				MethodInfo method = type.GetMethod("CheckGameComplete", BindingFlags.Instance | BindingFlags.NonPublic);
				if (method == null)
				{
					ModLogger.Instance?.Warning("Could not find CheckGameComplete method");
					return false;
				}
				ModLogger.Instance?.Info("Found CheckGameComplete method");
				HarmonyMethod val = new HarmonyMethod(typeof(EndGamePatch).GetMethod("PeakSequenceCheckGameCompletePostfix", BindingFlags.Static | BindingFlags.Public))
				{
					priority = 200
				};
				harmony.Patch((MethodBase)method, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception arg)
			{
				ModLogger.Instance?.Error($"TryPatchPeakSequence failed: {arg}");
				return false;
			}
		}

		public static void CharacterEndGamePrefix()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (ClimbRecordingManager.PlayerDiedThisSession)
				{
					ModLogger.Instance?.Info("[EndGamePatch] EndGame called but player already died - not a helicopter ending");
					return;
				}
				MapHandler instance = Singleton<MapHandler>.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					Segment currentSegment = instance.GetCurrentSegment();
					ModLogger.Instance?.Info($"[EndGamePatch] Character.EndGame called in segment: {currentSegment}");
					if ((int)currentSegment == 4)
					{
						ModLogger.Instance?.Info("[EndGamePatch] Helicopter ending detected at TheKiln (Peak)!");
						NotifyPlugin();
					}
					else
					{
						ModLogger.Instance?.Info($"[EndGamePatch] EndGame in {currentSegment} - not at Peak");
					}
				}
				else
				{
					ModLogger.Instance?.Warning("[EndGamePatch] MapHandler not available, cannot verify segment");
				}
			}
			catch (Exception ex)
			{
				ModLogger.Instance?.Error("[EndGamePatch] Error checking segment: " + ex.Message);
			}
		}

		public static void RunManagerEndGamePrefix()
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (ClimbRecordingManager.PlayerDiedThisSession)
				{
					ModLogger.Instance?.Info("[EndGamePatch] RunManager.EndGame called but player already died - not a helicopter ending");
					return;
				}
				MapHandler instance = Singleton<MapHandler>.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					Segment currentSegment = instance.GetCurrentSegment();
					ModLogger.Instance?.Info($"[EndGamePatch] RunManager.EndGame called in segment: {currentSegment}");
					if ((int)currentSegment == 4)
					{
						ModLogger.Instance?.Info("[EndGamePatch] Helicopter ending detected at TheKiln (Peak)!");
						NotifyPlugin();
					}
					else
					{
						ModLogger.Instance?.Info($"[EndGamePatch] EndGame in {currentSegment} - not at Peak");
					}
				}
				else
				{
					ModLogger.Instance?.Warning("[EndGamePatch] MapHandler not available, cannot verify segment");
				}
			}
			catch (Exception ex)
			{
				ModLogger.Instance?.Error("[EndGamePatch] Error checking segment: " + ex.Message);
			}
		}

		public static void PeakSequenceCheckGameCompletePostfix(ref bool ___endingGame)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Invalid comparison between Unknown and I4
			if (!___endingGame || _gameEndingDetected)
			{
				return;
			}
			if (ClimbRecordingManager.PlayerDiedThisSession)
			{
				ModLogger.Instance?.Info("[EndGamePatch] PeakSequence ending but player already died - not a helicopter ending");
				return;
			}
			try
			{
				MapHandler instance = Singleton<MapHandler>.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					Segment currentSegment = instance.GetCurrentSegment();
					ModLogger.Instance?.Info($"[EndGamePatch] PeakSequence ending in segment: {currentSegment}");
					if ((int)currentSegment == 4)
					{
						_gameEndingDetected = true;
						ModLogger.Instance?.Info("[EndGamePatch] PeakSequence helicopter ending confirmed at TheKiln!");
						NotifyPlugin();
					}
				}
				else
				{
					_gameEndingDetected = true;
					ModLogger.Instance?.Info("[EndGamePatch] PeakSequence ending detected (MapHandler unavailable)");
					NotifyPlugin();
				}
			}
			catch (Exception ex)
			{
				ModLogger.Instance?.Error("[EndGamePatch] Error in PeakSequence check: " + ex.Message);
				_gameEndingDetected = true;
				NotifyPlugin();
			}
		}

		private static void NotifyPlugin()
		{
			Plugin.Instance?.OnHelicopterEnding();
		}
	}
	public class PlayerDeathPatch
	{
		public static void ApplyPatch(Harmony harmony)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Expected O, but got Unknown
			try
			{
				Type type = Type.GetType("Character, Assembly-CSharp") ?? AccessTools.TypeByName("Character");
				if (type != null)
				{
					MethodInfo methodInfo = AccessTools.Method(type, "RPCA_Die", (Type[])null, (Type[])null);
					if (methodInfo != null)
					{
						HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(PlayerDeathPatch), "OnPlayerDied_Postfix", (Type[])null, (Type[])null))
						{
							priority = 600
						};
						harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						ModLogger.Instance?.Info("Successfully patched RPCA_Die method for death detection with high priority");
					}
					else
					{
						ModLogger.Instance?.Warning("Could not find RPCA_Die method");
					}
					MethodInfo methodInfo2 = AccessTools.Method(type, "Die", (Type[])null, (Type[])null);
					if (methodInfo2 != null)
					{
						HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(PlayerDeathPatch), "OnPlayerDied_Postfix", (Type[])null, (Type[])null))
						{
							priority = 600
						};
						harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						ModLogger.Instance?.Info("Successfully patched Die method for death detection with high priority");
					}
				}
				else
				{
					ModLogger.Instance?.Info("Could not find Character type for patching - using direct detection instead");
				}
			}
			catch (Exception ex)
			{
				ModLogger.Instance?.Error("Failed to apply death detection patches: " + ex.Message);
			}
		}

		private static void OnPlayerDied_Postfix(object __instance)
		{
			try
			{
				PropertyInfo propertyInfo = __instance.GetType().GetProperty("photonView") ?? __instance.GetType().GetProperty("view");
				if (propertyInfo != null)
				{
					object value = propertyInfo.GetValue(__instance);
					if (value != null)
					{
						PropertyInfo property = value.GetType().GetProperty("IsMine");
						if (property != null && !(bool)property.GetValue(value))
						{
							return;
						}
					}
				}
				FieldInfo field = __instance.GetType().GetField("localCharacter", BindingFlags.Static | BindingFlags.Public);
				if (field != null)
				{
					object value2 = field.GetValue(null);
					if (value2 != null && value2 != __instance)
					{
						return;
					}
				}
				ModLogger.Instance?.Info("[Death] Local player death detected via Harmony patch");
				if ((Object)(object)Plugin.Instance != (Object)null)
				{
					Plugin.Instance.GetRecordingManager()?.OnPlayerDeath();
				}
			}
			catch (Exception ex)
			{
				ModLogger.Instance?.Error("Error in death postfix: " + ex.Message);
			}
		}
	}
	public static class RunManagerPatch
	{
		public static void ApplyPatch(Harmony harmony)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			try
			{
				Type type = Type.GetType("RunManager, Assembly-CSharp");
				if (type == null)
				{
					ModLogger.Instance?.Error("Could not find RunManager type");
					return;
				}
				MethodInfo method = type.GetMethod("StartRun", BindingFlags.Instance | BindingFlags.Public);
				if (method == null)
				{
					ModLogger.Instance?.Error("Could not find StartRun method in RunManager");
					return;
				}
				MethodInfo method2 = typeof(RunManagerPatch).GetMethod("StartRunPostfix", BindingFlags.Static | BindingFlags.Public);
				harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				ModLogger.Instance?.Info("Successfully patched RunManager.StartRun");
			}
			catch (Exception arg)
			{
				ModLogger.Instance?.Error($"Failed to patch RunManager: {arg}");
			}
		}

		public static void StartRunPostfix()
		{
			ModLogger.Instance?.Info("[RunManagerPatch] RunManager.StartRun called - RUN STARTED!");
			Plugin.Instance?.OnRunStartedFromPatch();
		}
	}
	public class PluginPatches
	{
		public static string BiomeNameOfCompletedSegment { get; set; }

		[HarmonyPatch(typeof(Campfire), "Light_Rpc")]
		[HarmonyPrefix]
		public static void CaptureBiomeNameBeforeCompletion()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Singleton<MapHandler>.Instance != (Object)null)
			{
				Segment currentSegment = Singleton<MapHandler>.Instance.GetCurrentSegment();
				BiomeNameOfCompletedSegment = Enum.GetName(typeof(Segment), currentSegment);
				Debug.Log((object)("[FollowMe-Peak] Biome captured: " + BiomeNameOfCompletedSegment));
			}
		}

		[HarmonyPatch(typeof(Campfire), "Light_Rpc")]
		[HarmonyPostfix]
		public static void SavePathAfterCampfireLit()
		{
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				Debug.Log((object)"[FollowMe-Peak] Campfire.Light_Rpc() completed. Saving triggered.");
				Plugin.Instance.OnCampfireLit(BiomeNameOfCompletedSegment);
			}
		}
	}
}
namespace FollowMePeak.ModMenu
{
	public enum MenuPosition
	{
		RightEdge,
		LeftEdge,
		Cente