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 markers = new List(); private readonly List markerRecords = new List(); 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 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(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(); 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(); 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(); 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(); ((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(); 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(); ((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(); 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(); if ((Object)(object)component5 == (Object)null) { if ((Object)(object)gameObject.GetComponent() != (Object)null) { gameObject.GetComponent(); _ = ((Object)gameObject).name; val3 = val5; val4 = gameObject; break; } } else if ((int)component5.renderMode == 0 && (Object)(object)gameObject.GetComponent() != (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(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(); 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(); 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 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 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 Records { get; private set; } public Candidate(StorageSlot slot, bool isTemporary, long revision, IList 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 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(); } 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 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 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 BindSession(string newWorldKey) { Unbind(); worldKey = newWorldKey; diskless = true; return new List(); } 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 MigrateFromV1(string v1Directory, string v1WorldKey, string newWorldKey, int seed) { IList 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 list = new List(); HashSet hashSet = new HashSet(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 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 ValidateAndCloneRecords(IList records) { if (records == null || records.Count > 500) { throw new InvalidDataException("Marker count is outside the supported range."); } List list = new List(records.Count); HashSet hashSet = new HashSet(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 CloneRecords(IList records) { List list = new List(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 left, IList 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 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 GetExistingSaveSeeds(ManualLogSource logger) { HashSet hashSet = new HashSet(); 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; } } }