Decompiled source of LocalMapMarkers v1.0.2

plugins/LocalMapMarkers/LocalMapMarkers.dll

Decompiled 13 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Local Map Markers")]
[assembly: AssemblyDescription("Local custom map markers with per-world persistence for Muck")]
[assembly: AssemblyCompany("PigeonsMods")]
[assembly: AssemblyProduct("Local Map Markers")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: ComVisible(false)]
[assembly: TargetFramework(".NETFramework,Version=v4.0", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("0.0.4.0")]
[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 PigeonsMods.LocalMapMarkers
{
	[BepInPlugin("com.pigeonsmods.localmapmarkers", "Local Map Markers", "1.0.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class LocalMapMarkersPlugin : BaseUnityPlugin
	{
		private enum MarkerKind
		{
			House,
			Cave,
			Village,
			Revive,
			Boss,
			Generic
		}

		private sealed class LocalMarker
		{
			public string RecordId { get; private set; }

			public GameObject Visual { get; private set; }

			public RectTransform RectTransform { get; private set; }

			public LocalMarker(string recordId, GameObject visual, RectTransform rectTransform)
			{
				RecordId = recordId;
				Visual = visual;
				RectTransform = rectTransform;
			}
		}

		public const string PluginGuid = "com.pigeonsmods.localmapmarkers";

		public const string PluginName = "Local Map Markers";

		public const string PluginVersion = "1.0.2";

		private TMP_Text[] allTexts;

		private const float DragThresholdPixels = 8f;

		private const float RemovalRadiusPixels = 28f;

		private const float MarkerVisualScale = 0.68f;

		private readonly List<LocalMarker> markers = new List<LocalMarker>();

		private readonly List<MarkerRecord> markerRecords = new List<MarkerRecord>();

		private readonly Texture2D[] markerTextures = (Texture2D[])(object)new Texture2D[6];

		private MarkerStorage markerStorage;

		private Map currentMap;

		private int currentMapInstanceId;

		private int currentMapTextureInstanceId;

		private string currentWorldKey;

		private bool markerViewsHydrated;

		private bool persistenceDirty;

		private MarkerKind selectedKind;

		private bool leftClickArmed;

		private Vector2 leftButtonDownPosition;

		private GameObject selectorRoot;

		private RectTransform selectorRect;

		private RawImage selectorIcon;

		private GameObject hintTextPanel;

		private TMP_Text selectorText;

		private GameObject clonedHudText;

		public static bool sessionWasLoaded;

		private Harmony harmony;

		private bool persistenceEnabled;

		private string currentSavePath;

		private bool checkedFirstSave;

		private DateTime sessionStartTime;

		private float nextSaveCheckTime;

		private bool baselineFileExists;

		private DateTime baselineLastWriteTime;

		private long baselineLength;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			try
			{
				MuckSaveGameIntegration.Initialize(((BaseUnityPlugin)this).Logger);
				harmony = new Harmony("com.pigeonsmods.localmapmarkers");
				harmony.PatchAll(Assembly.GetExecutingAssembly());
				markerTextures[0] = LoadEmbeddedTexture("house");
				markerTextures[1] = LoadEmbeddedTexture("cave");
				markerTextures[2] = LoadEmbeddedTexture("village");
				markerTextures[3] = LoadEmbeddedTexture("revive");
				markerTextures[4] = LoadEmbeddedTexture("boss");
				markerTextures[5] = LoadEmbeddedTexture("generic");
				markerStorage = new MarkerStorage(Path.Combine(Paths.BepInExRootPath, "LocalMapMarkersData", "v2"), ((BaseUnityPlugin)this).Logger);
				RunOrphanCleanup();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Local Map Markers 1.0.2 loaded with Harmony patches. No networking is active.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not load local map markers: " + ex));
				((Behaviour)this).enabled = false;
			}
		}

		private void LateUpdate()
		{
			Map instance = Map.Instance;
			int num = (((Object)(object)instance != (Object)null) ? ((Object)instance).GetInstanceID() : 0);
			if (num != currentMapInstanceId)
			{
				SwitchToMap(instance, num);
			}
			if ((Object)(object)currentMap == (Object)null)
			{
				CancelPendingClick();
				return;
			}
			DetectWorldMapChange();
			TryBindCurrentWorld();
			if (MuckSaveGameIntegration.IsInstalled && !persistenceEnabled && !checkedFirstSave && !string.IsNullOrEmpty(currentSavePath) && Time.time >= nextSaveCheckTime)
			{
				nextSaveCheckTime = Time.time + 3f;
				CheckForFirstSave();
			}
			bool active = currentMap.active;
			if ((Object)(object)selectorRoot != (Object)null)
			{
				selectorRoot.SetActive(active);
			}
			if (!active)
			{
				CancelPendingClick();
				return;
			}
			HydrateMarkerViews();
			HandleSelectionKeys();
			HandlePointerInput();
			RemoveDestroyedMarkerReferences();
		}

		private void OnDestroy()
		{
			CancelPendingClick();
			SaveMarkersIfDirty();
			DestroySelector();
			ClearRuntimeMarkers();
			markerRecords.Clear();
			if (markerStorage != null)
			{
				markerStorage.Unbind();
			}
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			for (int i = 0; i < markerTextures.Length; i++)
			{
				if ((Object)(object)markerTextures[i] != (Object)null)
				{
					Object.Destroy((Object)(object)markerTextures[i]);
					markerTextures[i] = null;
				}
			}
		}

		private void SwitchToMap(Map map, int mapInstanceId)
		{
			CancelPendingClick();
			RunFinalSaveCheck();
			SaveMarkersIfDirty();
			DestroySelector();
			ClearRuntimeMarkers();
			markerRecords.Clear();
			currentWorldKey = null;
			persistenceDirty = false;
			if (markerStorage != null)
			{
				markerStorage.Unbind();
			}
			currentMap = map;
			currentMapInstanceId = mapInstanceId;
			currentMapTextureInstanceId = GetCurrentMapTextureInstanceId();
			if ((Object)(object)currentMap != (Object)null)
			{
				CreateSelector();
			}
		}

		private void DetectWorldMapChange()
		{
			int num = GetCurrentMapTextureInstanceId();
			if (num != 0 && currentMapTextureInstanceId != 0 && num != currentMapTextureInstanceId)
			{
				CancelPendingClick();
				ClearRuntimeMarkers();
				markerViewsHydrated = false;
			}
			if (num != 0)
			{
				currentMapTextureInstanceId = num;
			}
		}

		private int GetCurrentMapTextureInstanceId()
		{
			if ((Object)(object)currentMap == (Object)null || (Object)(object)currentMap.mapTextureMaterial == (Object)null)
			{
				return 0;
			}
			Texture mainTexture = currentMap.mapTextureMaterial.mainTexture;
			if (!((Object)(object)mainTexture != (Object)null))
			{
				return 0;
			}
			return ((Object)mainTexture).GetInstanceID();
		}

		private void TryBindCurrentWorld()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected I4, but got Unknown
			if (markerStorage == null || (Object)(object)currentMap == (Object)null || (Object)(object)currentMap.map == (Object)null || GetCurrentMapTextureInstanceId() == 0)
			{
				return;
			}
			Rect rect = currentMap.map.rect;
			if (((Rect)(ref rect)).width <= 1f)
			{
				return;
			}
			rect = currentMap.map.rect;
			if (((Rect)(ref rect)).height <= 1f || GameManager.gameSettings == null)
			{
				return;
			}
			int seed = GameManager.GetSeed();
			string text = string.Format(CultureInfo.InvariantCulture, "seed={0}|mode={1}|chunk={2}|scale={3}", seed, (int)GameManager.gameSettings.gameMode, MapGenerator.mapChunkSize, MapGenerator.worldScale);
			string text2;
			if (MuckSaveGameIntegration.IsInstalled)
			{
				if (sessionWasLoaded)
				{
					persistenceEnabled = true;
					currentSavePath = MuckSaveGameIntegration.GetSelectedSavePath();
					text2 = "v2|" + text + "|save=persistent";
				}
				else
				{
					persistenceEnabled = false;
					currentSavePath = MuckSaveGameIntegration.GetPathForSeed(seed);
					text2 = "v2|" + text + "|save=session";
				}
			}
			else
			{
				persistenceEnabled = false;
				currentSavePath = null;
				text2 = "v2|" + text + "|save=session";
			}
			if (string.Equals(text2, currentWorldKey, StringComparison.Ordinal))
			{
				return;
			}
			SaveMarkersIfDirty();
			ClearRuntimeMarkers();
			markerRecords.Clear();
			IList<MarkerRecord> list;
			if (persistenceEnabled)
			{
				if (!markerStorage.PersistentExists(text2, seed))
				{
					string text3 = Path.Combine(Paths.BepInExRootPath, "LocalMapMarkersData", "v1");
					string text4 = "v1|" + text;
					if (Directory.Exists(text3))
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Attempting v1 to v2 migration for key: " + text4));
						list = markerStorage.MigrateFromV1(text3, text4, text2, seed);
					}
					else
					{
						list = markerStorage.Bind(text2, seed);
					}
				}
				else
				{
					list = markerStorage.Bind(text2, seed);
				}
			}
			else
			{
				list = markerStorage.BindSession(text2);
				InitializeFirstSaveBaseline();
			}
			for (int i = 0; i < list.Count; i++)
			{
				markerRecords.Add(list[i]);
			}
			currentWorldKey = text2;
			markerViewsHydrated = false;
			persistenceDirty = false;
			if (markerRecords.Count > 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Loaded " + markerRecords.Count + " map marker(s) for the current session."));
			}
		}

		private void HydrateMarkerViews()
		{
			if (markerViewsHydrated || string.IsNullOrEmpty(currentWorldKey) || !CanCreateMarkerViews())
			{
				return;
			}
			for (int i = 0; i < markerRecords.Count; i++)
			{
				MarkerRecord markerRecord = markerRecords[i];
				if (!HasVisualForRecord(markerRecord.Id))
				{
					CreateMarkerView(markerRecord);
				}
			}
			markerViewsHydrated = true;
		}

		private bool HasVisualForRecord(string recordId)
		{
			for (int i = 0; i < markers.Count; i++)
			{
				if (markers[i] != null && markers[i].RecordId == recordId && (Object)(object)markers[i].Visual != (Object)null)
				{
					return true;
				}
			}
			return false;
		}

		private bool CanCreateMarkerViews()
		{
			//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_0071: 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)
			if ((Object)(object)currentMap != (Object)null && (Object)(object)currentMap.map != (Object)null && (Object)(object)currentMap.markerParent != (Object)null && (Object)(object)currentMap.mapMarkerPrefab != (Object)null)
			{
				Rect rect = currentMap.map.rect;
				if (((Rect)(ref rect)).width > 1f)
				{
					rect = currentMap.map.rect;
					return ((Rect)(ref rect)).height > 1f;
				}
			}
			return false;
		}

		private void HandleSelectionKeys()
		{
			if (Input.GetKeyDown((KeyCode)49) || Input.GetKeyDown((KeyCode)257))
			{
				SelectMarker(MarkerKind.House);
			}
			else if (Input.GetKeyDown((KeyCode)50) || Input.GetKeyDown((KeyCode)258))
			{
				SelectMarker(MarkerKind.Cave);
			}
			else if (Input.GetKeyDown((KeyCode)51) || Input.GetKeyDown((KeyCode)259))
			{
				SelectMarker(MarkerKind.Village);
			}
			else if (Input.GetKeyDown((KeyCode)52) || Input.GetKeyDown((KeyCode)260))
			{
				SelectMarker(MarkerKind.Revive);
			}
			else if (Input.GetKeyDown((KeyCode)53) || Input.GetKeyDown((KeyCode)261))
			{
				SelectMarker(MarkerKind.Boss);
			}
			else if (Input.GetKeyDown((KeyCode)54) || Input.GetKeyDown((KeyCode)262))
			{
				SelectMarker(MarkerKind.Generic);
			}
		}

		private void SelectMarker(MarkerKind kind)
		{
			selectedKind = kind;
			if ((Object)(object)selectorIcon != (Object)null)
			{
				selectorIcon.texture = (Texture)(object)markerTextures[(int)selectedKind];
			}
		}

		private void HandlePointerInput()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: 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_001e: 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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: 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)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = Vector2.op_Implicit(Input.mousePosition);
			if (Input.GetMouseButtonDown(0))
			{
				leftClickArmed = IsPointerInsideMap(val) && !IsPointerInsideSelector(val);
				leftButtonDownPosition = val;
			}
			if (Input.GetMouseButtonUp(0))
			{
				bool num = leftClickArmed && Vector2.Distance(leftButtonDownPosition, val) <= 8f && IsPointerInsideMap(val) && !IsPointerInsideSelector(val);
				leftClickArmed = false;
				if (num)
				{
					PlaceMarker(val);
				}
			}
			if (Input.GetMouseButtonDown(1) && IsPointerInsideMap(val) && !IsPointerInsideSelector(val))
			{
				RemoveNearestMarker(val);
			}
		}

		private bool IsPointerInsideMap(Vector2 screenPosition)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)currentMap != (Object)null && (Object)(object)currentMap.map != (Object)null)
			{
				return RectTransformUtility.RectangleContainsScreenPoint(currentMap.map, screenPosition, (Camera)null);
			}
			return false;
		}

		private bool IsPointerInsideSelector(Vector2 screenPosition)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)selectorRect != (Object)null && (Object)(object)selectorRoot != (Object)null && selectorRoot.activeInHierarchy)
			{
				return RectTransformUtility.RectangleContainsScreenPoint(selectorRect, screenPosition, (Camera)null);
			}
			return false;
		}

		private void PlaceMarker(Vector2 screenPosition)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			Vector2 val = default(Vector2);
			if (CanCreateMarkerViews() && !string.IsNullOrEmpty(currentWorldKey) && markerRecords.Count < 500 && RectTransformUtility.ScreenPointToLocalPointInRectangle(currentMap.map, screenPosition, (Camera)null, ref val))
			{
				Rect rect = currentMap.map.rect;
				float normalizedX = Mathf.Clamp01((val.x - ((Rect)(ref rect)).xMin) / ((Rect)(ref rect)).width);
				float normalizedY = Mathf.Clamp01((val.y - ((Rect)(ref rect)).yMin) / ((Rect)(ref rect)).height);
				MarkerRecord markerRecord = new MarkerRecord(Guid.NewGuid().ToString("N"), (int)selectedKind, normalizedX, normalizedY);
				if (CreateMarkerView(markerRecord))
				{
					markerRecords.Add(markerRecord);
					MarkPersistenceDirtyAndSave();
				}
			}
		}

		private bool CreateMarkerView(MarkerRecord record)
		{
			//IL_0046: 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_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: 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_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			if (!CanCreateMarkerViews() || record == null || record.Kind < 0 || record.Kind >= markerTextures.Length || (Object)(object)markerTextures[record.Kind] == (Object)null)
			{
				return false;
			}
			Rect rect = currentMap.map.rect;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(Mathf.Lerp(((Rect)(ref rect)).xMin, ((Rect)(ref rect)).xMax, record.NormalizedX), Mathf.Lerp(((Rect)(ref rect)).yMin, ((Rect)(ref rect)).yMax, record.NormalizedY));
			GameObject val2 = Object.Instantiate<GameObject>(currentMap.mapMarkerPrefab, currentMap.markerParent);
			((Object)val2).name = "LocalMapMarker." + (MarkerKind)record.Kind/*cast due to .constrained prefix*/;
			Transform transform = val2.transform;
			transform.localPosition = new Vector3(val.x, val.y, 0f);
			transform.localRotation = Quaternion.identity;
			transform.localScale *= 0.68f;
			RawImage component = val2.GetComponent<RawImage>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				((BaseUnityPlugin)this).Logger.LogWarning((object)"The game's map marker prefab no longer contains a RawImage; marker was not created.");
				return false;
			}
			component.texture = (Texture)(object)markerTextures[record.Kind];
			((Graphic)component).color = Color.white;
			((Graphic)component).raycastTarget = false;
			for (int i = 0; i < transform.childCount; i++)
			{
				((Component)transform.GetChild(i)).gameObject.SetActive(false);
			}
			val2.SetActive(true);
			markers.Add(new LocalMarker(record.Id, val2, ((Graphic)component).rectTransform));
			return true;
		}

		private void RemoveNearestMarker(Vector2 screenPosition)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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_0044: Unknown result type (might be due to invalid IL or missing references)
			int num = -1;
			float num2 = 28f;
			for (int i = 0; i < markers.Count; i++)
			{
				LocalMarker localMarker = markers[i];
				if (localMarker != null && !((Object)(object)localMarker.RectTransform == (Object)null))
				{
					Vector2 val = RectTransformUtility.WorldToScreenPoint((Camera)null, ((Transform)localMarker.RectTransform).position);
					float num3 = Vector2.Distance(screenPosition, val);
					if (num3 <= num2)
					{
						num2 = num3;
						num = i;
					}
				}
			}
			if (num < 0)
			{
				return;
			}
			LocalMarker localMarker2 = markers[num];
			markers.RemoveAt(num);
			if (localMarker2 != null && (Object)(object)localMarker2.Visual != (Object)null)
			{
				Object.Destroy((Object)(object)localMarker2.Visual);
			}
			bool flag = false;
			if (localMarker2 != null)
			{
				for (int num4 = markerRecords.Count - 1; num4 >= 0; num4--)
				{
					if (string.Equals(markerRecords[num4].Id, localMarker2.RecordId, StringComparison.Ordinal))
					{
						markerRecords.RemoveAt(num4);
						flag = true;
						break;
					}
				}
			}
			if (flag)
			{
				MarkPersistenceDirtyAndSave();
			}
		}

		private void RemoveDestroyedMarkerReferences()
		{
			bool flag = false;
			for (int num = markers.Count - 1; num >= 0; num--)
			{
				LocalMarker localMarker = markers[num];
				if (localMarker == null || (Object)(object)localMarker.Visual == (Object)null)
				{
					markers.RemoveAt(num);
					flag = true;
				}
			}
			if (flag && markers.Count < markerRecords.Count)
			{
				markerViewsHydrated = false;
			}
		}

		private void ClearRuntimeMarkers()
		{
			for (int i = 0; i < markers.Count; i++)
			{
				LocalMarker localMarker = markers[i];
				if (localMarker != null && (Object)(object)localMarker.Visual != (Object)null)
				{
					Object.Destroy((Object)(object)localMarker.Visual);
				}
			}
			markers.Clear();
			markerViewsHydrated = false;
		}

		private void MarkPersistenceDirtyAndSave()
		{
			persistenceDirty = true;
			SaveMarkersIfDirty();
		}

		private void SaveMarkersIfDirty()
		{
			if (persistenceDirty && markerStorage != null && markerStorage.IsBound && !string.IsNullOrEmpty(currentWorldKey) && markerStorage.Save(markerRecords))
			{
				persistenceDirty = false;
			}
		}

		private void CancelPendingClick()
		{
			leftClickArmed = false;
		}

		private void CreateSelector()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Expected O, but got Unknown
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_026d: Expected O, but got Unknown
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_03af: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_04c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_04da: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0504: Unknown result type (might be due to invalid IL or missing references)
			//IL_050f: Unknown result type (might be due to invalid IL or missing references)
			//IL_051a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0547: Unknown result type (might be due to invalid IL or missing references)
			//IL_0565: Unknown result type (might be due to invalid IL or missing references)
			//IL_0576: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)currentMap == (Object)null || (Object)(object)currentMap.mapParent == (Object)null)
			{
				return;
			}
			selectorRoot = new GameObject("LocalMapMarkers.SelectedIcon", new Type[2]
			{
				typeof(RectTransform),
				typeof(RawImage)
			});
			selectorRect = selectorRoot.GetComponent<RectTransform>();
			if ((Object)(object)selectorRect == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"selectorRect is null after creating GameObject with RectTransform!");
				return;
			}
			((Transform)selectorRect).SetParent(currentMap.mapParent, false);
			selectorRect.anchorMin = new Vector2(0f, 1f);
			selectorRect.anchorMax = new Vector2(0f, 1f);
			selectorRect.pivot = new Vector2(0f, 1f);
			selectorRect.anchoredPosition = new Vector2(18f, -18f);
			selectorRect.sizeDelta = new Vector2(54f, 54f);
			((Transform)selectorRect).localScale = Vector3.one;
			RawImage component = selectorRoot.GetComponent<RawImage>();
			component.texture = (Texture)(object)Texture2D.whiteTexture;
			((Graphic)component).color = new Color(0.08f, 0.05f, 0.025f, 0.78f);
			((Graphic)component).raycastTarget = false;
			GameObject val = new GameObject("Icon", new Type[2]
			{
				typeof(RectTransform),
				typeof(RawImage)
			});
			RectTransform component2 = val.GetComponent<RectTransform>();
			((Transform)component2).SetParent((Transform)(object)selectorRect, false);
			component2.anchorMin = new Vector2(0.5f, 0.5f);
			component2.anchorMax = new Vector2(0.5f, 0.5f);
			component2.pivot = new Vector2(0.5f, 0.5f);
			component2.anchoredPosition = Vector2.zero;
			component2.sizeDelta = new Vector2(44f, 44f);
			selectorIcon = val.GetComponent<RawImage>();
			selectorIcon.texture = (Texture)(object)markerTextures[(int)selectedKind];
			((Graphic)selectorIcon).color = Color.white;
			((Graphic)selectorIcon).raycastTarget = false;
			GameObject val2 = new GameObject("TextPanel", new Type[2]
			{
				typeof(RectTransform),
				typeof(RawImage)
			});
			RectTransform component3 = val2.GetComponent<RectTransform>();
			((Transform)component3).SetParent((Transform)(object)selectorRect, false);
			component3.anchorMin = new Vector2(0f, 0.5f);
			component3.anchorMax = new Vector2(0f, 0.5f);
			component3.pivot = new Vector2(0f, 0.5f);
			component3.anchoredPosition = new Vector2(53f, 0f);
			component3.sizeDelta = new Vector2(200f, 44f);
			((Transform)component3).localScale = Vector3.one;
			((Transform)component3).localRotation = Quaternion.identity;
			RawImage component4 = val2.GetComponent<RawImage>();
			component4.texture = (Texture)(object)Texture2D.whiteTexture;
			((Graphic)component4).color = new Color(0.08f, 0.05f, 0.025f, 0.78f);
			((Graphic)component4).raycastTarget = false;
			TMP_Text val3 = null;
			GameObject val4 = null;
			allTexts = (TMP_Text[])(object)Object.FindObjectsOfTypeAll(typeof(TMP_Text));
			TMP_Text[] array = allTexts;
			foreach (TMP_Text val5 in array)
			{
				GameObject gameObject = ((Component)val5).gameObject;
				Canvas component5 = gameObject.GetComponent<Canvas>();
				if ((Object)(object)component5 == (Object)null)
				{
					if ((Object)(object)gameObject.GetComponent<RectTransform>() != (Object)null)
					{
						gameObject.GetComponent<CanvasGroup>();
						_ = ((Object)gameObject).name;
						val3 = val5;
						val4 = gameObject;
						break;
					}
				}
				else if ((int)component5.renderMode == 0 && (Object)(object)gameObject.GetComponent<RectTransform>() != (Object)null)
				{
					_ = ((Object)gameObject).name;
					val3 = val5;
					val4 = gameObject;
					break;
				}
			}
			if ((Object)(object)val3 != (Object)null && (Object)(object)val4 != (Object)null)
			{
				if ((Object)(object)selectorRect != (Object)null && (Object)(object)val2 != (Object)null)
				{
					clonedHudText = Object.Instantiate<GameObject>(val4, val2.transform, false);
					((Object)clonedHudText).name = "ControlHints_Clone";
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogError((object)"ERROR: selectorRect or hintTextPanel is null at Instantiate! Cannot clone HUD text.");
				}
				TMP_Text component6 = clonedHudText.GetComponent<TMP_Text>();
				if ((Object)(object)component6 != (Object)null)
				{
					component6.text = "1–6 Select  •  LMB Place  •  LMB Drag Map  •  RMB Remove  •  Wheel Zoom";
					component6.fontSize = 16f;
					((Graphic)component6).color = Color.white;
					component6.alignment = (TextAlignmentOptions)513;
					component6.enableWordWrapping = false;
					component6.overflowMode = (TextOverflowModes)0;
					((Behaviour)component6).enabled = true;
					RectTransform component7 = clonedHudText.GetComponent<RectTransform>();
					component7.anchorMin = new Vector2(0f, 0.5f);
					component7.anchorMax = new Vector2(0f, 0.5f);
					component7.pivot = new Vector2(0f, 0.5f);
					component7.anchoredPosition = new Vector2(14f, 1f);
					((Transform)component7).localScale = Vector3.one;
					((Transform)component7).localRotation = Quaternion.identity;
					clonedHudText.transform.SetAsLastSibling();
					float x = component6.GetPreferredValues(component6.text, float.PositiveInfinity, 44f).x;
					float num = 28f;
					component3.sizeDelta = new Vector2(x + num, 44f);
					component7.sizeDelta = new Vector2(x, 44f);
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogError((object)"Cloned GameObject has no TMP_Text component!");
				}
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogError((object)"No HUD text found to clone!");
			}
			selectorRoot.transform.SetAsLastSibling();
			selectorRoot.SetActive(currentMap.active);
		}

		private void DestroySelector()
		{
			selectorIcon = null;
			selectorText = null;
			selectorRect = null;
			hintTextPanel = null;
			if ((Object)(object)clonedHudText != (Object)null)
			{
				Object.Destroy((Object)(object)clonedHudText);
				clonedHudText = null;
			}
			if ((Object)(object)selectorRoot != (Object)null)
			{
				Object.Destroy((Object)(object)selectorRoot);
				selectorRoot = null;
			}
		}

		private static Texture2D LoadEmbeddedTexture(string assetName)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			string text = "LocalMapMarkers.Assets." + assetName + ".png";
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(text);
			if (stream == null)
			{
				throw new InvalidOperationException("Embedded resource not found: " + text);
			}
			byte[] array = new byte[stream.Length];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = stream.Read(array, i, array.Length - i);
				if (num <= 0)
				{
					throw new EndOfStreamException("Could not read embedded resource: " + text);
				}
			}
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			((Object)val).name = "LocalMapMarkers." + assetName;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			((Texture)val).filterMode = (FilterMode)1;
			if (!DecodePng(val, array))
			{
				Object.Destroy((Object)(object)val);
				throw new InvalidOperationException("Unity could not decode embedded texture: " + text);
			}
			return val;
		}

		private static bool DecodePng(Texture2D texture, byte[] data)
		{
			Type? type = Type.GetType("UnityEngine.ImageConversion, UnityEngine.ImageConversionModule");
			if (type == null)
			{
				throw new InvalidOperationException("Unity's ImageConversion module is unavailable.");
			}
			MethodInfo? method = type.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3]
			{
				typeof(Texture2D),
				typeof(byte[]),
				typeof(bool)
			}, null);
			if (method == null)
			{
				throw new MissingMethodException("UnityEngine.ImageConversion.LoadImage was not found.");
			}
			object obj = method.Invoke(null, new object[3] { texture, data, true });
			if (obj is bool)
			{
				return (bool)obj;
			}
			return false;
		}

		private void InitializeFirstSaveBaseline()
		{
			checkedFirstSave = false;
			sessionStartTime = DateTime.UtcNow;
			nextSaveCheckTime = 0f;
			if (currentSavePath == null)
			{
				return;
			}
			try
			{
				baselineFileExists = File.Exists(currentSavePath);
				if (baselineFileExists)
				{
					baselineLastWriteTime = File.GetLastWriteTimeUtc(currentSavePath);
					baselineLength = new FileInfo(currentSavePath).Length;
				}
				else
				{
					baselineLastWriteTime = DateTime.MinValue;
					baselineLength = 0L;
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to read baseline for " + currentSavePath + ": " + ex.Message));
				baselineFileExists = false;
				baselineLastWriteTime = DateTime.MinValue;
				baselineLength = 0L;
			}
		}

		private void CheckForFirstSave()
		{
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Expected I4, but got Unknown
			if (checkedFirstSave || persistenceEnabled || string.IsNullOrEmpty(currentSavePath))
			{
				return;
			}
			try
			{
				bool num = File.Exists(currentSavePath);
				bool flag = false;
				if (num)
				{
					if (!baselineFileExists)
					{
						flag = true;
					}
					else
					{
						DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(currentSavePath);
						long length = new FileInfo(currentSavePath).Length;
						if (lastWriteTimeUtc != baselineLastWriteTime || length != baselineLength)
						{
							flag = true;
						}
					}
				}
				if (flag)
				{
					int seed = GameManager.GetSeed();
					if (MuckSaveGameIntegration.IsSaveFileValid(currentSavePath, seed))
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("First save detected and validated! Transitioning to persistent mode for seed: " + seed));
						string newWorldKey = string.Format(CultureInfo.InvariantCulture, "v2|seed={0}|mode={1}|chunk={2}|scale={3}|save=persistent", seed, (int)GameManager.gameSettings.gameMode, MapGenerator.mapChunkSize, MapGenerator.worldScale);
						markerStorage.BindNewPersistent(newWorldKey, seed);
						persistenceEnabled = true;
						checkedFirstSave = true;
						MarkPersistenceDirtyAndSave();
					}
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Error in CheckForFirstSave (will retry): " + ex.Message));
			}
		}

		private void RunFinalSaveCheck()
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected I4, but got Unknown
			if (!MuckSaveGameIntegration.IsInstalled || persistenceEnabled || checkedFirstSave || string.IsNullOrEmpty(currentSavePath) || GameManager.gameSettings == null)
			{
				return;
			}
			try
			{
				bool num = File.Exists(currentSavePath);
				bool flag = false;
				if (num)
				{
					if (!baselineFileExists)
					{
						flag = true;
					}
					else
					{
						DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(currentSavePath);
						long length = new FileInfo(currentSavePath).Length;
						if (lastWriteTimeUtc != baselineLastWriteTime || length != baselineLength)
						{
							flag = true;
						}
					}
				}
				if (flag)
				{
					int seed = GameManager.GetSeed();
					if (MuckSaveGameIntegration.IsSaveFileValid(currentSavePath, seed))
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("Final exit check: Save detected! Persisting markers for seed: " + seed));
						string newWorldKey = string.Format(CultureInfo.InvariantCulture, "v2|seed={0}|mode={1}|chunk={2}|scale={3}|save=persistent", seed, (int)GameManager.gameSettings.gameMode, MapGenerator.mapChunkSize, MapGenerator.worldScale);
						markerStorage.BindNewPersistent(newWorldKey, seed);
						persistenceEnabled = true;
						checkedFirstSave = true;
						markerStorage.Save(markerRecords);
					}
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Error in final save check: " + ex.Message));
			}
		}

		private void RunOrphanCleanup()
		{
			if (!MuckSaveGameIntegration.IsInstalled)
			{
				return;
			}
			try
			{
				string path = Path.Combine(Paths.BepInExRootPath, "LocalMapMarkersData", "v2");
				if (!Directory.Exists(path))
				{
					return;
				}
				string savesBasePath = MuckSaveGameIntegration.GetSavesBasePath();
				if (string.IsNullOrEmpty(savesBasePath) || !Directory.Exists(savesBasePath))
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Saves directory not found or unreadable. Skipping orphan cleanup.");
					return;
				}
				HashSet<int> existingSaveSeeds = MuckSaveGameIntegration.GetExistingSaveSeeds(((BaseUnityPlugin)this).Logger);
				int cleanedCount = 0;
				string[] files = Directory.GetFiles(path, "world_*.a.xml");
				foreach (string filePath in files)
				{
					CleanOrphanPair(filePath, existingSaveSeeds, ref cleanedCount);
				}
				files = Directory.GetFiles(path, "world_*.b.xml");
				foreach (string filePath2 in files)
				{
					CleanOrphanPair(filePath2, existingSaveSeeds, ref cleanedCount);
				}
				if (cleanedCount > 0)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Cleaned up " + cleanedCount + " orphaned marker file(s) for deleted saves."));
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to run orphan cleanup: " + ex.Message));
			}
		}

		private void CleanOrphanPair(string filePath, HashSet<int> activeSeeds, ref int cleanedCount)
		{
			try
			{
				string[] array = Path.GetFileName(filePath).Split(new char[1] { '_' });
				if (array.Length >= 2 && int.TryParse(array[1], out var result) && !activeSeeds.Contains(result))
				{
					File.Delete(filePath);
					string path = filePath + ".tmp";
					if (File.Exists(path))
					{
						File.Delete(path);
					}
					cleanedCount++;
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Removed LocalMapMarkers data for deleted save seed: " + result));
				}
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch]
	internal static class GameLoopStartLoopPatch
	{
		[HarmonyPatch(typeof(GameLoop), "StartLoop")]
		[HarmonyPrefix]
		public static void Prefix()
		{
			if (MuckSaveGameIntegration.IsInstalled)
			{
				LocalMapMarkersPlugin.sessionWasLoaded = !string.IsNullOrEmpty(MuckSaveGameIntegration.GetSelectedSavePath());
			}
			else
			{
				LocalMapMarkersPlugin.sessionWasLoaded = false;
			}
		}
	}
	internal sealed class MarkerRecord
	{
		public string Id { get; private set; }

		public int Kind { get; private set; }

		public float NormalizedX { get; private set; }

		public float NormalizedY { get; private set; }

		public MarkerRecord(string id, int kind, float normalizedX, float normalizedY)
		{
			Id = id;
			Kind = kind;
			NormalizedX = normalizedX;
			NormalizedY = normalizedY;
		}
	}
	internal sealed class MarkerStorage
	{
		private enum StorageSlot
		{
			None,
			A,
			B
		}

		private enum CandidateReadStatus
		{
			Missing,
			Valid,
			Invalid,
			FutureVersion
		}

		private sealed class Candidate
		{
			public StorageSlot Slot { get; private set; }

			public bool IsTemporary { get; private set; }

			public long Revision { get; private set; }

			public IList<MarkerRecord> Records { get; private set; }

			public Candidate(StorageSlot slot, bool isTemporary, long revision, IList<MarkerRecord> records)
			{
				Slot = slot;
				IsTemporary = isTemporary;
				Revision = revision;
				Records = records;
			}
		}

		public const int MaxMarkers = 500;

		private const int CurrentFormatVersion = 1;

		private readonly string dataDirectory;

		private readonly ManualLogSource logger;

		private string worldKey;

		private string slotAPath;

		private string slotBPath;

		private long revision;

		private StorageSlot activeSlot;

		private bool readOnly;

		private bool readOnlyWarningLogged;

		private bool diskless;

		public bool IsBound => !string.IsNullOrEmpty(worldKey);

		public MarkerStorage(string dataDirectory, ManualLogSource logger)
		{
			this.dataDirectory = dataDirectory;
			this.logger = logger;
		}

		public IList<MarkerRecord> Bind(string newWorldKey, int seed)
		{
			Unbind();
			worldKey = newWorldKey;
			string text = "world_" + seed.ToString(CultureInfo.InvariantCulture) + "_" + ComputeSha256(newWorldKey).Substring(0, 16).ToLowerInvariant();
			slotAPath = Path.Combine(dataDirectory, text + ".a.xml");
			slotBPath = Path.Combine(dataDirectory, text + ".b.xml");
			Candidate best = null;
			bool futureVersionFound = false;
			ReadAndConsider(slotAPath, StorageSlot.A, isTemporary: false, ref best, ref futureVersionFound);
			ReadAndConsider(slotBPath, StorageSlot.B, isTemporary: false, ref best, ref futureVersionFound);
			ReadAndConsider(slotAPath + ".tmp", StorageSlot.A, isTemporary: true, ref best, ref futureVersionFound);
			ReadAndConsider(slotBPath + ".tmp", StorageSlot.B, isTemporary: true, ref best, ref futureVersionFound);
			if (futureVersionFound)
			{
				readOnly = true;
				logger.LogWarning((object)"Local Map Markers found data written by a newer mod version. Persistence is read-only for this world so that data is not overwritten.");
			}
			if (best == null)
			{
				return new List<MarkerRecord>();
			}
			revision = best.Revision;
			activeSlot = best.Slot;
			if (best.IsTemporary)
			{
				logger.LogWarning((object)"Recovered Local Map Markers data from an interrupted save.");
			}
			return CloneRecords(best.Records);
		}

		public bool Save(IList<MarkerRecord> records)
		{
			if (!IsBound)
			{
				return false;
			}
			if (diskless)
			{
				return true;
			}
			if (readOnly)
			{
				if (!readOnlyWarningLogged)
				{
					logger.LogWarning((object)"Marker changes will remain in memory because this world's data is read-only.");
					readOnlyWarningLogged = true;
				}
				return false;
			}
			try
			{
				List<MarkerRecord> list = ValidateAndCloneRecords(records);
				long num = checked(revision + 1);
				StorageSlot storageSlot = ((activeSlot != StorageSlot.A) ? StorageSlot.A : StorageSlot.B);
				string text = ((storageSlot == StorageSlot.A) ? slotAPath : slotBPath);
				string text2 = text + ".tmp";
				Directory.CreateDirectory(dataDirectory);
				WriteCandidate(text2, num, list);
				if (TryReadCandidate(text2, storageSlot, isTemporary: true, out var candidate, out var _) != CandidateReadStatus.Valid || candidate == null || candidate.Revision != num || !RecordsEqual(list, candidate.Records))
				{
					throw new InvalidDataException("The temporary marker file did not pass verification.");
				}
				CommitTemporaryFile(text2, text);
				revision = num;
				activeSlot = storageSlot;
				return true;
			}
			catch (Exception ex)
			{
				logger.LogWarning((object)("Could not persist Local Map Markers data; markers remain active for the current world session. " + ex.Message));
				return false;
			}
		}

		public void Unbind()
		{
			worldKey = null;
			slotAPath = null;
			slotBPath = null;
			revision = 0L;
			activeSlot = StorageSlot.None;
			readOnly = false;
			readOnlyWarningLogged = false;
			diskless = false;
		}

		public IList<MarkerRecord> BindSession(string newWorldKey)
		{
			Unbind();
			worldKey = newWorldKey;
			diskless = true;
			return new List<MarkerRecord>();
		}

		public void BindNewPersistent(string newWorldKey, int seed)
		{
			Unbind();
			worldKey = newWorldKey;
			string text = "world_" + seed.ToString(CultureInfo.InvariantCulture) + "_" + ComputeSha256(newWorldKey).Substring(0, 16).ToLowerInvariant();
			slotAPath = Path.Combine(dataDirectory, text + ".a.xml");
			slotBPath = Path.Combine(dataDirectory, text + ".b.xml");
			try
			{
				DeleteFileIfExists(slotAPath);
			}
			catch
			{
			}
			try
			{
				DeleteFileIfExists(slotBPath);
			}
			catch
			{
			}
			try
			{
				DeleteFileIfExists(slotAPath + ".tmp");
			}
			catch
			{
			}
			try
			{
				DeleteFileIfExists(slotBPath + ".tmp");
			}
			catch
			{
			}
			revision = 0L;
			activeSlot = StorageSlot.None;
			readOnly = false;
		}

		public IList<MarkerRecord> MigrateFromV1(string v1Directory, string v1WorldKey, string newWorldKey, int seed)
		{
			IList<MarkerRecord> list = new MarkerStorage(v1Directory, logger).Bind(v1WorldKey, seed);
			BindNewPersistent(newWorldKey, seed);
			if (list != null && list.Count > 0)
			{
				Save(list);
				logger.LogInfo((object)("Successfully migrated " + list.Count + " markers from legacy v1 persistence for seed " + seed));
			}
			return list;
		}

		public bool PersistentExists(string persistentWorldKey, int seed)
		{
			string text = "world_" + seed.ToString(CultureInfo.InvariantCulture) + "_" + ComputeSha256(persistentWorldKey).Substring(0, 16).ToLowerInvariant();
			string path = Path.Combine(dataDirectory, text + ".a.xml");
			string path2 = Path.Combine(dataDirectory, text + ".b.xml");
			if (!File.Exists(path))
			{
				return File.Exists(path2);
			}
			return true;
		}

		private static void DeleteFileIfExists(string path)
		{
			if (File.Exists(path))
			{
				File.Delete(path);
			}
		}

		private void ReadAndConsider(string path, StorageSlot slot, bool isTemporary, ref Candidate best, ref bool futureVersionFound)
		{
			Candidate candidate;
			string failureReason;
			switch (TryReadCandidate(path, slot, isTemporary, out candidate, out failureReason))
			{
			case CandidateReadStatus.Missing:
				return;
			case CandidateReadStatus.FutureVersion:
				futureVersionFound = true;
				return;
			case CandidateReadStatus.Invalid:
				logger.LogWarning((object)("Ignored invalid marker data file '" + Path.GetFileName(path) + "': " + failureReason));
				return;
			}
			if (best == null || candidate.Revision > best.Revision || (candidate.Revision == best.Revision && best.IsTemporary && !candidate.IsTemporary))
			{
				best = candidate;
			}
		}

		private CandidateReadStatus TryReadCandidate(string path, StorageSlot slot, bool isTemporary, out Candidate candidate, out string failureReason)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Expected O, but got Unknown
			candidate = null;
			failureReason = null;
			if (!File.Exists(path))
			{
				return CandidateReadStatus.Missing;
			}
			try
			{
				XmlReaderSettings val = new XmlReaderSettings
				{
					DtdProcessing = (DtdProcessing)0,
					XmlResolver = null,
					IgnoreComments = true,
					IgnoreWhitespace = true,
					MaxCharactersInDocument = 1048576L
				};
				XmlDocument val2 = new XmlDocument
				{
					XmlResolver = null
				};
				using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
				{
					XmlReader val3 = XmlReader.Create((Stream)fileStream, val);
					try
					{
						val2.Load(val3);
					}
					finally
					{
						((IDisposable)val3)?.Dispose();
					}
				}
				XmlElement documentElement = val2.DocumentElement;
				if (documentElement == null || ((XmlNode)documentElement).Name != "LocalMapMarkers")
				{
					failureReason = "unexpected root element";
					return CandidateReadStatus.Invalid;
				}
				if (!int.TryParse(documentElement.GetAttribute("formatVersion"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
				{
					failureReason = "invalid format version";
					return CandidateReadStatus.Invalid;
				}
				if (result > 1)
				{
					return CandidateReadStatus.FutureVersion;
				}
				if (!string.Equals(documentElement.GetAttribute("worldKey"), worldKey, StringComparison.Ordinal))
				{
					failureReason = "world identity does not match";
					return CandidateReadStatus.Invalid;
				}
				if (result != 1)
				{
					failureReason = "unsupported format version";
					return CandidateReadStatus.Invalid;
				}
				if (!long.TryParse(documentElement.GetAttribute("revision"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 < 0)
				{
					failureReason = "invalid revision";
					return CandidateReadStatus.Invalid;
				}
				string attribute = documentElement.GetAttribute("checksum");
				if (attribute.Length != 64)
				{
					failureReason = "invalid checksum";
					return CandidateReadStatus.Invalid;
				}
				List<MarkerRecord> list = new List<MarkerRecord>();
				HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
				foreach (XmlNode childNode in ((XmlNode)documentElement).ChildNodes)
				{
					object obj = (object)childNode;
					XmlElement val4 = (XmlElement)((obj is XmlElement) ? obj : null);
					if (val4 != null)
					{
						if (((XmlNode)val4).Name != "Marker" || list.Count >= 500)
						{
							failureReason = ((((XmlNode)val4).Name != "Marker") ? "unexpected child element" : "too many markers");
							return CandidateReadStatus.Invalid;
						}
						if (!Guid.TryParseExact(val4.GetAttribute("id"), "N", out var result3))
						{
							failureReason = "invalid marker id";
							return CandidateReadStatus.Invalid;
						}
						string text = result3.ToString("N");
						if (!hashSet.Add(text))
						{
							failureReason = "duplicate marker id";
							return CandidateReadStatus.Invalid;
						}
						if (!int.TryParse(val4.GetAttribute("kind"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result4) || result4 < 0 || result4 > 5 || !float.TryParse(val4.GetAttribute("x"), NumberStyles.Float, CultureInfo.InvariantCulture, out var result5) || !float.TryParse(val4.GetAttribute("y"), NumberStyles.Float, CultureInfo.InvariantCulture, out var result6) || !IsFiniteUnitValue(result5) || !IsFiniteUnitValue(result6))
						{
							failureReason = "invalid marker values";
							return CandidateReadStatus.Invalid;
						}
						list.Add(new MarkerRecord(text, result4, result5, result6));
					}
				}
				string b = ComputePayloadChecksum(result2, worldKey, list);
				if (!string.Equals(attribute, b, StringComparison.OrdinalIgnoreCase))
				{
					failureReason = "checksum mismatch";
					return CandidateReadStatus.Invalid;
				}
				candidate = new Candidate(slot, isTemporary, result2, list);
				return CandidateReadStatus.Valid;
			}
			catch (Exception ex)
			{
				failureReason = ex.GetType().Name + ": " + ex.Message;
				return CandidateReadStatus.Invalid;
			}
		}

		private void WriteCandidate(string path, long candidateRevision, IList<MarkerRecord> records)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Expected O, but got Unknown
			string text = ComputePayloadChecksum(candidateRevision, worldKey, records);
			XmlWriterSettings val = new XmlWriterSettings
			{
				Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
				Indent = true,
				NewLineChars = "\n",
				NewLineHandling = (NewLineHandling)0,
				CloseOutput = false
			};
			using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
			XmlWriter val2 = XmlWriter.Create((Stream)fileStream, val);
			try
			{
				val2.WriteStartDocument();
				val2.WriteStartElement("LocalMapMarkers");
				val2.WriteAttributeString("formatVersion", 1.ToString(CultureInfo.InvariantCulture));
				val2.WriteAttributeString("revision", candidateRevision.ToString(CultureInfo.InvariantCulture));
				val2.WriteAttributeString("worldKey", worldKey);
				val2.WriteAttributeString("checksum", text);
				for (int i = 0; i < records.Count; i++)
				{
					MarkerRecord markerRecord = records[i];
					val2.WriteStartElement("Marker");
					val2.WriteAttributeString("id", markerRecord.Id);
					val2.WriteAttributeString("kind", markerRecord.Kind.ToString(CultureInfo.InvariantCulture));
					val2.WriteAttributeString("x", markerRecord.NormalizedX.ToString("R", CultureInfo.InvariantCulture));
					val2.WriteAttributeString("y", markerRecord.NormalizedY.ToString("R", CultureInfo.InvariantCulture));
					val2.WriteEndElement();
				}
				val2.WriteEndElement();
				val2.WriteEndDocument();
				val2.Flush();
				fileStream.Flush(flushToDisk: true);
			}
			finally
			{
				((IDisposable)val2)?.Dispose();
			}
		}

		private static void CommitTemporaryFile(string temporaryPath, string targetPath)
		{
			if (!File.Exists(targetPath))
			{
				File.Move(temporaryPath, targetPath);
				return;
			}
			try
			{
				File.Replace(temporaryPath, targetPath, null, ignoreMetadataErrors: true);
			}
			catch (PlatformNotSupportedException)
			{
				ReplaceInactiveSlotByMove(temporaryPath, targetPath);
			}
			catch (IOException)
			{
				ReplaceInactiveSlotByMove(temporaryPath, targetPath);
			}
		}

		private static void ReplaceInactiveSlotByMove(string temporaryPath, string targetPath)
		{
			File.Delete(targetPath);
			File.Move(temporaryPath, targetPath);
		}

		private List<MarkerRecord> ValidateAndCloneRecords(IList<MarkerRecord> records)
		{
			if (records == null || records.Count > 500)
			{
				throw new InvalidDataException("Marker count is outside the supported range.");
			}
			List<MarkerRecord> list = new List<MarkerRecord>(records.Count);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			for (int i = 0; i < records.Count; i++)
			{
				MarkerRecord markerRecord = records[i];
				if (markerRecord == null || !Guid.TryParseExact(markerRecord.Id, "N", out var result) || markerRecord.Kind < 0 || markerRecord.Kind > 5 || !IsFiniteUnitValue(markerRecord.NormalizedX) || !IsFiniteUnitValue(markerRecord.NormalizedY))
				{
					throw new InvalidDataException("A marker contains unsupported values.");
				}
				string text = result.ToString("N");
				if (!hashSet.Add(text))
				{
					throw new InvalidDataException("Duplicate marker id.");
				}
				list.Add(new MarkerRecord(text, markerRecord.Kind, markerRecord.NormalizedX, markerRecord.NormalizedY));
			}
			return list;
		}

		private static IList<MarkerRecord> CloneRecords(IList<MarkerRecord> records)
		{
			List<MarkerRecord> list = new List<MarkerRecord>(records.Count);
			for (int i = 0; i < records.Count; i++)
			{
				MarkerRecord markerRecord = records[i];
				list.Add(new MarkerRecord(markerRecord.Id, markerRecord.Kind, markerRecord.NormalizedX, markerRecord.NormalizedY));
			}
			return list;
		}

		private static bool RecordsEqual(IList<MarkerRecord> left, IList<MarkerRecord> right)
		{
			if (left == null || right == null || left.Count != right.Count)
			{
				return false;
			}
			for (int i = 0; i < left.Count; i++)
			{
				MarkerRecord markerRecord = left[i];
				MarkerRecord markerRecord2 = right[i];
				if (!string.Equals(markerRecord.Id, markerRecord2.Id, StringComparison.Ordinal) || markerRecord.Kind != markerRecord2.Kind || markerRecord.NormalizedX != markerRecord2.NormalizedX || markerRecord.NormalizedY != markerRecord2.NormalizedY)
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsFiniteUnitValue(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value) && value >= 0f)
			{
				return value <= 1f;
			}
			return false;
		}

		private static string ComputePayloadChecksum(long candidateRevision, string candidateWorldKey, IList<MarkerRecord> records)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(1).Append('\n');
			stringBuilder.Append(candidateRevision.ToString(CultureInfo.InvariantCulture)).Append('\n');
			stringBuilder.Append(candidateWorldKey.Length.ToString(CultureInfo.InvariantCulture)).Append(':').Append(candidateWorldKey)
				.Append('\n');
			stringBuilder.Append(records.Count.ToString(CultureInfo.InvariantCulture)).Append('\n');
			for (int i = 0; i < records.Count; i++)
			{
				MarkerRecord markerRecord = records[i];
				stringBuilder.Append(markerRecord.Id).Append('|');
				stringBuilder.Append(markerRecord.Kind.ToString(CultureInfo.InvariantCulture)).Append('|');
				stringBuilder.Append(markerRecord.NormalizedX.ToString("R", CultureInfo.InvariantCulture)).Append('|');
				stringBuilder.Append(markerRecord.NormalizedY.ToString("R", CultureInfo.InvariantCulture)).Append('\n');
			}
			return ComputeSha256(stringBuilder.ToString());
		}

		private static string ComputeSha256(string value)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(value);
			byte[] array;
			using (SHA256 sHA = SHA256.Create())
			{
				array = sHA.ComputeHash(bytes);
			}
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			for (int i = 0; i < array.Length; i++)
			{
				stringBuilder.Append(array[i].ToString("X2", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}
	}
	internal static class MuckSaveGameIntegration
	{
		public const string MuckSaveGameGuid = "MuckSaveGame.MichMcb";

		private static FieldInfo selectedSavePathField;

		private static MethodInfo getPathForSeedMethod;

		private static MethodInfo getSavesBasePathMethod;

		private static bool reflectionFailed;

		public static bool IsInstalled => Chainloader.PluginInfos.ContainsKey("MuckSaveGame.MichMcb");

		public static void Initialize(ManualLogSource logger)
		{
			if (!IsInstalled)
			{
				return;
			}
			try
			{
				Assembly assembly = null;
				foreach (PluginInfo value in Chainloader.PluginInfos.Values)
				{
					if (value.Metadata.GUID == "MuckSaveGame.MichMcb")
					{
						assembly = ((object)value.Instance).GetType().Assembly;
						break;
					}
				}
				if (assembly == null)
				{
					throw new InvalidOperationException("MuckSaveGame assembly not found in Chainloader.");
				}
				Type type = assembly.GetType("MuckSaveGame.LoadManager");
				if (type == null)
				{
					throw new TypeLoadException("Could not find MuckSaveGame.LoadManager");
				}
				Type type2 = assembly.GetType("MuckSaveGame.SaveSystem");
				if (type2 == null)
				{
					throw new TypeLoadException("Could not find MuckSaveGame.SaveSystem");
				}
				selectedSavePathField = type.GetField("selectedSavePath", BindingFlags.Static | BindingFlags.Public);
				if (selectedSavePathField == null)
				{
					throw new MissingFieldException("Could not find selectedSavePath field");
				}
				getPathForSeedMethod = type2.GetMethod("GetPathForSeed", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(int) }, null);
				if (getPathForSeedMethod == null)
				{
					throw new MissingMethodException("Could not find GetPathForSeed method");
				}
				getSavesBasePathMethod = type2.GetMethod("GetSavesBasePath", BindingFlags.Static | BindingFlags.Public);
				if (getSavesBasePathMethod == null)
				{
					throw new MissingMethodException("Could not find GetSavesBasePath method");
				}
				logger.LogInfo((object)"MuckSaveGame integration successfully initialized via reflection.");
			}
			catch (Exception ex)
			{
				reflectionFailed = true;
				logger.LogError((object)("Failed to initialize MuckSaveGame integration: " + ex));
			}
		}

		public static string GetSelectedSavePath()
		{
			if (!IsInstalled || reflectionFailed || selectedSavePathField == null)
			{
				return null;
			}
			try
			{
				return selectedSavePathField.GetValue(null) as string;
			}
			catch
			{
				return null;
			}
		}

		public static string GetPathForSeed(int seed)
		{
			if (!IsInstalled || reflectionFailed || getPathForSeedMethod == null)
			{
				return null;
			}
			try
			{
				return getPathForSeedMethod.Invoke(null, new object[1] { seed }) as string;
			}
			catch
			{
				return null;
			}
		}

		public static string GetSavesBasePath()
		{
			if (!IsInstalled || reflectionFailed || getSavesBasePathMethod == null)
			{
				return null;
			}
			try
			{
				return getSavesBasePathMethod.Invoke(null, null) as string;
			}
			catch
			{
				return null;
			}
		}

		public static bool IsSaveFileValid(string path, int expectedSeed)
		{
			//IL_001d: 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 (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				return false;
			}
			try
			{
				using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
				XmlDocument val = new XmlDocument();
				val.Load((Stream)fileStream);
				XmlNode val2 = ((XmlNode)val).SelectSingleNode("/MuckSaveGame/Data[@type='main']/WorldData/Seed");
				if (val2 != null && int.TryParse(val2.InnerText, out var result))
				{
					return result == expectedSeed;
				}
			}
			catch
			{
			}
			return false;
		}

		public static HashSet<int> GetExistingSaveSeeds(ManualLogSource logger)
		{
			HashSet<int> hashSet = new HashSet<int>();
			if (!IsInstalled || reflectionFailed)
			{
				return hashSet;
			}
			try
			{
				string savesBasePath = GetSavesBasePath();
				if (string.IsNullOrEmpty(savesBasePath) || !Directory.Exists(savesBasePath))
				{
					return hashSet;
				}
				string[] files = Directory.GetFiles(savesBasePath, "*.mucksave");
				for (int i = 0; i < files.Length; i++)
				{
					if (int.TryParse(Path.GetFileNameWithoutExtension(files[i]), out var result))
					{
						hashSet.Add(result);
					}
				}
			}
			catch (Exception ex)
			{
				logger.LogWarning((object)("Failed to read MuckSaveGame saves folder: " + ex.Message));
			}
			return hashSet;
		}
	}
}