Decompiled source of PlayerMapLayers v0.2.13

BepInEx/plugins/PlayerMapLayers/PlayerMapLayers.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("PlayerMapLayers")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("PlayerMapLayers")]
[assembly: AssemblyTitle("PlayerMapLayers")]
[assembly: AssemblyVersion("1.0.0.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 PlayerMapLayers
{
	internal static class AccountIdentity
	{
		internal static string ResolveAccountKey(long sender)
		{
			try
			{
				ZNetPeer val = FindPeer(sender);
				if (val != null && val.m_socket != null)
				{
					string hostName = val.m_socket.GetHostName();
					if (!string.IsNullOrWhiteSpace(hostName))
					{
						return hostName.Trim();
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Debug("Account key lookup failed: " + ex.Message);
			}
			return "peer:" + sender;
		}

		internal static string ResolveCharacterName(long sender, string fallback)
		{
			try
			{
				ZNetPeer val = FindPeer(sender);
				if (val != null && !string.IsNullOrWhiteSpace(val.m_playerName))
				{
					return val.m_playerName.Trim();
				}
			}
			catch (Exception ex)
			{
				Plugin.Debug("Character name lookup failed: " + ex.Message);
			}
			if (!string.IsNullOrWhiteSpace(fallback))
			{
				return fallback.Trim();
			}
			return "Unknown";
		}

		internal static string LocalCharacterName()
		{
			try
			{
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					return Player.m_localPlayer.GetPlayerName();
				}
			}
			catch
			{
			}
			return "Unknown";
		}

		private static ZNetPeer FindPeer(long uid)
		{
			if ((Object)(object)ZNet.instance == (Object)null)
			{
				return null;
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "GetPeer", new Type[1] { typeof(long) }, (Type[])null);
			if (methodInfo != null)
			{
				object? obj = methodInfo.Invoke(ZNet.instance, new object[1] { uid });
				ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null);
				if (val != null)
				{
					return val;
				}
			}
			FieldInfo fieldInfo = AccessTools.Field(typeof(ZNet), "m_peers");
			IEnumerable enumerable = ((fieldInfo != null) ? (fieldInfo.GetValue(ZNet.instance) as IEnumerable) : null);
			if (enumerable == null)
			{
				return null;
			}
			foreach (object item in enumerable)
			{
				ZNetPeer val2 = (ZNetPeer)((item is ZNetPeer) ? item : null);
				if (val2 != null && val2.m_uid == uid)
				{
					return val2;
				}
			}
			return null;
		}
	}
	internal static class BinaryProtocol
	{
		internal const int SchemaVersion = 4;

		private const string FileMagic = "PML4";

		internal static string Fingerprint(byte[] data)
		{
			if (data == null)
			{
				return "";
			}
			using SHA256 sHA = SHA256.Create();
			return ToHex(sHA.ComputeHash(data));
		}

		internal static string StableHash(string value)
		{
			using SHA256 sHA = SHA256.Create();
			return ToHex(sHA.ComputeHash(Encoding.UTF8.GetBytes(value ?? "")));
		}

		internal static string ShortUuid(string accountKey, string salt)
		{
			string text = StableHash(salt + "|" + accountKey);
			return text.Substring(0, 8) + "-" + text.Substring(8, 4) + "-" + text.Substring(12, 4) + "-" + text.Substring(16, 4) + "-" + text.Substring(20, 12);
		}

		internal static byte[] Compress(byte[] raw)
		{
			if (raw == null || raw.Length == 0)
			{
				return Array.Empty<byte>();
			}
			using MemoryStream memoryStream = new MemoryStream();
			using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true))
			{
				gZipStream.Write(raw, 0, raw.Length);
			}
			return memoryStream.ToArray();
		}

		internal static byte[] Decompress(byte[] compressed, int maximumBytes)
		{
			if (compressed == null || compressed.Length == 0)
			{
				return Array.Empty<byte>();
			}
			using MemoryStream stream = new MemoryStream(compressed);
			using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress);
			using MemoryStream memoryStream = new MemoryStream();
			byte[] array = new byte[8192];
			int count;
			while ((count = gZipStream.Read(array, 0, array.Length)) > 0)
			{
				memoryStream.Write(array, 0, count);
				if (memoryStream.Length > maximumBytes)
				{
					throw new InvalidDataException("Layer exceeds configured maximum size.");
				}
			}
			return memoryStream.ToArray();
		}

		internal static void WriteDescriptor(BinaryWriter writer, LayerDescriptor descriptor)
		{
			writer.Write(descriptor.AccountUuid ?? "");
			writer.Write(descriptor.CharacterName ?? "Unknown");
			writer.Write(descriptor.ColorHex ?? "#56B4E9");
			writer.Write(descriptor.Emblem ?? "Compass");
			writer.Write(descriptor.Revision);
			writer.Write(descriptor.UpdatedUtcTicks);
			writer.Write(descriptor.Fingerprint ?? "");
			writer.Write(descriptor.PayloadBytes);
			writer.Write(descriptor.KnownCharacterNames.Count);
			foreach (string knownCharacterName in descriptor.KnownCharacterNames)
			{
				writer.Write(knownCharacterName ?? "");
			}
		}

		internal static LayerDescriptor ReadDescriptor(BinaryReader reader)
		{
			LayerDescriptor layerDescriptor = new LayerDescriptor
			{
				AccountUuid = reader.ReadString(),
				CharacterName = reader.ReadString(),
				ColorHex = reader.ReadString(),
				Emblem = reader.ReadString(),
				Revision = reader.ReadInt32(),
				UpdatedUtcTicks = reader.ReadInt64(),
				Fingerprint = reader.ReadString(),
				PayloadBytes = reader.ReadInt32()
			};
			int num = Math.Max(0, Math.Min(100, reader.ReadInt32()));
			for (int i = 0; i < num; i++)
			{
				layerDescriptor.KnownCharacterNames.Add(reader.ReadString());
			}
			return layerDescriptor;
		}

		internal static void SaveLayer(string path, StoredLayer layer)
		{
			string text = path + ".tmp";
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			using (FileStream output = File.Create(text))
			{
				using BinaryWriter binaryWriter = new BinaryWriter(output, Encoding.UTF8);
				binaryWriter.Write("PML4");
				binaryWriter.Write(4);
				WriteDescriptor(binaryWriter, layer.Descriptor);
				binaryWriter.Write(layer.MapData.Length);
				binaryWriter.Write(layer.MapData);
			}
			if (File.Exists(path))
			{
				string destinationBackupFileName = path + ".bak";
				try
				{
					File.Replace(text, path, destinationBackupFileName, ignoreMetadataErrors: true);
					return;
				}
				catch
				{
					File.Delete(path);
					File.Move(text, path);
					return;
				}
			}
			File.Move(text, path);
		}

		internal static StoredLayer LoadLayer(string path, int maximumBytes)
		{
			using FileStream input = File.OpenRead(path);
			using BinaryReader binaryReader = new BinaryReader(input, Encoding.UTF8);
			if (binaryReader.ReadString() != "PML4")
			{
				throw new InvalidDataException("Invalid PlayerMapLayers file.");
			}
			int num = binaryReader.ReadInt32();
			if (num != 4)
			{
				throw new InvalidDataException("Unsupported PlayerMapLayers schema " + num + ".");
			}
			LayerDescriptor descriptor = ReadDescriptor(binaryReader);
			int num2 = binaryReader.ReadInt32();
			if (num2 < 0 || num2 > maximumBytes)
			{
				throw new InvalidDataException("Invalid layer length " + num2 + ".");
			}
			byte[] array = binaryReader.ReadBytes(num2);
			if (array.Length != num2)
			{
				throw new EndOfStreamException("Truncated PlayerMapLayers file.");
			}
			return new StoredLayer
			{
				Descriptor = descriptor,
				MapData = array
			};
		}

		private static string ToHex(byte[] data)
		{
			StringBuilder stringBuilder = new StringBuilder(data.Length * 2);
			foreach (byte b in data)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}
	}
	internal static class ClientState
	{
		internal const string TablePreviewUuid = "00000000-0000-0000-0000-000000000001";

		private const string LegacyLocalPreviewUuid = "00000000-0000-0000-0000-000000000002";

		private static readonly List<LayerDescriptor> DescriptorsInternal = new List<LayerDescriptor>();

		private static readonly Dictionary<string, StoredLayer> Payloads = new Dictionary<string, StoredLayer>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> Selected = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, ImportRecord> Imports = new Dictionary<string, ImportRecord>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<SharedPin> LocalPinsInternal = new List<SharedPin>();

		private static readonly List<SharedPin> RemotePinsInternal = new List<SharedPin>();

		private static readonly HashSet<string> SharedLocalPinIds = new HashSet<string>(StringComparer.Ordinal);

		private static readonly HashSet<string> SelectedPinOwners = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> KnownPinOwners = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static byte[] _explorationBeforePanel;

		private static byte[] _undoExploration;

		private static PendingAction _pendingAction;

		private static string _status = "";

		private static bool _importsLoaded;

		private static bool _largeMapSeen;

		private static float _mapOpenDeadline;

		private static float _nextPinOverlayRefresh;

		private static bool _pinOwnersInitialized;

		private static string _localAccountUuid = "";

		private static byte[] _localPersonalMapData;

		internal static bool IsOpen { get; private set; }

		internal static bool IsPreviewing { get; private set; }

		internal static IReadOnlyList<LayerDescriptor> Descriptors => DescriptorsInternal;

		internal static int SelectedLayerCount => Selected.Count;

		internal static string Status => _status;

		internal static bool CanUndo
		{
			get
			{
				if (_undoExploration != null)
				{
					return _undoExploration.Length != 0;
				}
				return false;
			}
		}

		internal static bool CanSelectPersonalAndTable
		{
			get
			{
				if (!string.IsNullOrEmpty(_localAccountUuid) && _localPersonalMapData != null)
				{
					return Payloads.ContainsKey("00000000-0000-0000-0000-000000000001");
				}
				return false;
			}
		}

		internal static IReadOnlyList<SharedPin> LocalPins => LocalPinsInternal;

		internal static IReadOnlyList<SharedPin> RemotePins => RemotePinsInternal;

		internal static RemotePinVisibility PinVisibility
		{
			get
			{
				if (!Enum.TryParse<RemotePinVisibility>((Plugin.RemotePinFilter != null) ? Plugin.RemotePinFilter.Value : "All", ignoreCase: true, out var result))
				{
					return RemotePinVisibility.All;
				}
				return result;
			}
			set
			{
				if (Plugin.RemotePinFilter != null)
				{
					Plugin.RemotePinFilter.Value = value.ToString();
				}
				_nextPinOverlayRefresh = 0f;
			}
		}

		internal static bool ShowAuthorPinHalos
		{
			get
			{
				if (Plugin.AuthorPinHalos != null)
				{
					return Plugin.AuthorPinHalos.Value;
				}
				return true;
			}
			set
			{
				if (Plugin.AuthorPinHalos != null)
				{
					Plugin.AuthorPinHalos.Value = value;
				}
				PinBridge.RefreshAuthorHalos();
			}
		}

		internal static void Tick()
		{
			ExplorationTintOverlay.Tick();
			if (IsOpen && Time.realtimeSinceStartup >= _nextPinOverlayRefresh)
			{
				_nextPinOverlayRefresh = Time.realtimeSinceStartup + 0.25f;
				PinBridge.SyncRemoteOverlay(FilteredRemotePins());
			}
			if (IsOpen)
			{
				if (MapBridge.IsLargeMapOpen())
				{
					_largeMapSeen = true;
				}
				else if (_largeMapSeen || Time.realtimeSinceStartup > _mapOpenDeadline)
				{
					Plugin.Log.LogInfo((object)(_largeMapSeen ? "Large map closed; closing the Player Map Layers panel." : "Large map did not become visible before the timeout; closing the Player Map Layers panel."));
					CloseAndRestore();
					return;
				}
			}
			if (!IsOpen || _pendingAction == PendingAction.None)
			{
				return;
			}
			if (Selected.Count == 0)
			{
				_pendingAction = PendingAction.None;
			}
			else if (Selected.All(HasLayerPayload))
			{
				PendingAction pendingAction = _pendingAction;
				_pendingAction = PendingAction.None;
				switch (pendingAction)
				{
				case PendingAction.Preview:
					PreviewNow();
					break;
				case PendingAction.Apply:
					ApplyNow();
					break;
				}
			}
		}

		internal static void OpenFromCartographyTable(byte[] explorationBeforeRead, byte[] afterRead)
		{
			if (explorationBeforeRead != null && afterRead != null)
			{
				CloseAndRestore();
				LocalClientStorage.MigrateLegacyClientState();
				LoadImports();
				_explorationBeforePanel = (byte[])explorationBeforeRead.Clone();
				IsOpen = true;
				_largeMapSeen = false;
				_mapOpenDeadline = Time.realtimeSinceStartup + 8f;
				UpsertTableLayer(afterRead, DateTime.UtcNow.Ticks);
				SaveTableSnapshot(afterRead);
				RefreshLocalPersonalLayer();
				RestoreSelection("00000000-0000-0000-0000-000000000001");
				NetworkHub.RequestIndex();
				RefreshPins();
				PreviewSelected();
				bool flag = MapBridge.OpenLargeMap();
				PlayerMapLayersUi.ShowPanel();
				_largeMapSeen = MapBridge.IsLargeMapOpen();
				Plugin.Log.LogInfo((object)("Player Map Layers panel opened from the cartography table; map request=" + flag + ", large map visible=" + _largeMapSeen + "."));
			}
		}

		internal static void OpenBrowser()
		{
			if (MapBridge.Available)
			{
				LocalClientStorage.MigrateLegacyClientState();
				if (!IsOpen)
				{
					_explorationBeforePanel = MapBridge.CaptureExplorationState();
				}
				byte[] array = LoadTableSnapshot();
				byte[] array2 = MapBridge.CaptureSharedLayerFromState(_explorationBeforePanel);
				if (array == null)
				{
					array = array2;
				}
				byte[] array3 = array;
				if (array3 != null)
				{
					UpsertTableLayer(array3, TableSnapshotTimestamp());
				}
				RefreshLocalPersonalLayer();
				IsOpen = true;
				_largeMapSeen = false;
				_mapOpenDeadline = Time.realtimeSinceStartup + 8f;
				LoadImports();
				RestoreSelection("");
				NetworkHub.RequestIndex();
				RefreshPins();
				MapBridge.OpenLargeMap();
				if (Selected.Count > 0)
				{
					PreviewSelected();
				}
			}
		}

		internal static void CloseAndRestore()
		{
			if (IsPreviewing && _explorationBeforePanel != null)
			{
				MapBridge.RestoreExplorationState(_explorationBeforePanel);
			}
			IsPreviewing = false;
			IsOpen = false;
			PlayerMapLayersUi.HidePanel();
			_pendingAction = PendingAction.None;
			Selected.Clear();
			_explorationBeforePanel = null;
			_localPersonalMapData = null;
			_status = "";
			_largeMapSeen = false;
			PinBridge.ClearRemoteOverlay();
			ExplorationTintOverlay.Clear();
		}

		internal static void SetDescriptors(IEnumerable<LayerDescriptor> descriptors, string localAccountUuid)
		{
			List<LayerDescriptor> list = (descriptors ?? Enumerable.Empty<LayerDescriptor>()).ToList();
			DescriptorsInternal.RemoveAll((LayerDescriptor x) => string.Equals(x.AccountUuid, "00000000-0000-0000-0000-000000000002", StringComparison.OrdinalIgnoreCase));
			Payloads.Remove("00000000-0000-0000-0000-000000000002");
			foreach (LayerDescriptor item in list)
			{
				MergeDescriptor(item);
			}
			DescriptorsInternal.Sort((LayerDescriptor a, LayerDescriptor b) => b.UpdatedUtcTicks.CompareTo(a.UpdatedUtcTicks));
			LayerDescriptor layerDescriptor = ResolveLocalAccount(localAccountUuid);
			if (layerDescriptor != null)
			{
				LocalClientStorage.AcceptServerIdentity(layerDescriptor);
			}
			RefreshLocalPersonalLayer();
			HashSet<string> available = new HashSet<string>(list.Select((LayerDescriptor x) => x.AccountUuid), StringComparer.OrdinalIgnoreCase);
			if (Payloads.ContainsKey("00000000-0000-0000-0000-000000000001"))
			{
				available.Add("00000000-0000-0000-0000-000000000001");
			}
			if (Selected.RemoveWhere((string x) => !available.Contains(x)) > 0)
			{
				SaveSelection();
			}
			SetStatus(L10n.T("layers_refreshed"));
			if (Selected.Count > 0 && Selected.Any((string x) => !HasLayerPayload(x)))
			{
				PreviewSelected();
			}
			else if (IsPreviewing)
			{
				RefreshExplorationTint();
			}
		}

		internal static void ReceiveLayer(StoredLayer layer)
		{
			if (layer != null && layer.Descriptor != null)
			{
				Payloads[layer.Descriptor.AccountUuid] = layer;
				MergeDescriptor(layer.Descriptor);
				SetStatus(L10n.T("layer_received") + " " + layer.Descriptor.CharacterName);
			}
		}

		internal static bool IsSelected(string uuid)
		{
			return Selected.Contains(uuid);
		}

		internal static void ToggleSelected(string uuid)
		{
			if (Selected.Contains(uuid))
			{
				Selected.Remove(uuid);
			}
			else
			{
				Selected.Add(uuid);
			}
			SaveSelection();
			if (Selected.Count > 0)
			{
				PreviewSelected();
			}
			else
			{
				RestoreNormalView();
			}
		}

		internal static void Isolate(string uuid)
		{
			Selected.Clear();
			Selected.Add(uuid);
			SaveSelection();
			LayerDescriptor layerDescriptor = DescriptorsInternal.FirstOrDefault((LayerDescriptor x) => string.Equals(x.AccountUuid, uuid, StringComparison.OrdinalIgnoreCase));
			Plugin.Log.LogInfo((object)("Isolating layer " + ((layerDescriptor != null) ? layerDescriptor.CharacterName : uuid) + "."));
			PreviewSelected();
		}

		internal static byte[] CapturePersonalLayerForUpload()
		{
			if (IsPreviewing && _explorationBeforePanel != null)
			{
				byte[] array = MapBridge.CapturePersonalLayerFromState(_explorationBeforePanel);
				if (array != null)
				{
					Plugin.Log.LogInfo((object)"Uploading the saved personal baseline instead of the active preview.");
					return array;
				}
			}
			return MapBridge.CapturePersonalLayer();
		}

		internal static void PreviewSelected()
		{
			QueueOrRun(PendingAction.Preview);
		}

		internal static void SelectPersonalAndTable()
		{
			if (CanSelectPersonalAndTable)
			{
				Selected.Clear();
				Selected.Add(_localAccountUuid);
				Selected.Add("00000000-0000-0000-0000-000000000001");
				SaveSelection();
				PreviewSelected();
			}
		}

		internal static void SelectAllLayers()
		{
			Selected.Clear();
			foreach (LayerDescriptor item in DescriptorsInternal)
			{
				Selected.Add(item.AccountUuid);
			}
			SaveSelection();
			if (Selected.Count > 0)
			{
				PreviewSelected();
			}
			else
			{
				RestoreNormalView();
			}
		}

		internal static void ClearLayerSelection()
		{
			Selected.Clear();
			SaveSelection();
			RestoreNormalView();
		}

		internal static void ApplySelected()
		{
			QueueOrRun(PendingAction.Apply);
		}

		internal static void CancelPreview()
		{
			if (IsPreviewing && _explorationBeforePanel != null)
			{
				MapBridge.RestoreExplorationState(_explorationBeforePanel);
			}
			IsPreviewing = false;
			ExplorationTintOverlay.Clear();
			SetStatus(L10n.T("preview_cancelled"));
		}

		internal static bool IsLocalLayer(string uuid)
		{
			if (!string.IsNullOrEmpty(_localAccountUuid))
			{
				return string.Equals(uuid, _localAccountUuid, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		internal static void UndoLastImport()
		{
			if (CanUndo && MapBridge.RestoreExplorationState(_undoExploration))
			{
				_undoExploration = null;
				IsPreviewing = false;
				ExplorationTintOverlay.Clear();
				DeleteUndoFile();
				SetStatus(L10n.T("import_undone"));
			}
		}

		internal static bool IsAlreadyApplied(LayerDescriptor descriptor)
		{
			if (descriptor == null || IsSyntheticLayer(descriptor.AccountUuid))
			{
				return false;
			}
			if (Imports.TryGetValue(descriptor.AccountUuid, out var value) && value.Revision >= descriptor.Revision)
			{
				return string.Equals(value.Fingerprint, descriptor.Fingerprint, StringComparison.Ordinal);
			}
			return false;
		}

		internal static void SetStatus(string value)
		{
			_status = value ?? "";
		}

		internal static void RefreshPins()
		{
			Dictionary<string, SharedPin> dictionary = (from x in LocalPinsInternal
				group x by x.PinId).ToDictionary<IGrouping<string, SharedPin>, string, SharedPin>((IGrouping<string, SharedPin> x) => x.Key, (IGrouping<string, SharedPin> x) => x.First(), StringComparer.Ordinal);
			LocalPinsInternal.Clear();
			LocalPinsInternal.AddRange(PinBridge.CaptureShareableLocalPins());
			foreach (SharedPin item in LocalPinsInternal)
			{
				if (dictionary.TryGetValue(item.PinId, out var value))
				{
					item.DisplayTypeName = value.DisplayTypeName;
					item.DisplayTypeValue = value.DisplayTypeValue;
				}
			}
			NetworkHub.RequestPins();
		}

		internal static void ReceivePins(IEnumerable<SharedPin> pins)
		{
			RemotePinsInternal.Clear();
			RemotePinsInternal.AddRange((pins ?? Enumerable.Empty<SharedPin>()).Where((SharedPin x) => x != null));
			SharedLocalPinIds.Clear();
			Dictionary<string, SharedPin> dictionary = (from x in RemotePinsInternal
				where x.IsMine
				group x by x.PinId).ToDictionary<IGrouping<string, SharedPin>, string, SharedPin>((IGrouping<string, SharedPin> x) => x.Key, (IGrouping<string, SharedPin> x) => x.First(), StringComparer.Ordinal);
			foreach (SharedPin value2 in dictionary.Values)
			{
				SharedLocalPinIds.Add(value2.PinId);
			}
			foreach (SharedPin item in LocalPinsInternal)
			{
				if (dictionary.TryGetValue(item.PinId, out var value))
				{
					item.DisplayTypeName = value.DisplayTypeName;
					item.DisplayTypeValue = value.DisplayTypeValue;
				}
			}
			HashSet<string> owners = new HashSet<string>(from x in RemotePinsInternal
				where !x.IsMine
				select x.OwnerUuid, StringComparer.OrdinalIgnoreCase);
			if (!_pinOwnersInitialized)
			{
				SelectedPinOwners.Clear();
				foreach (string item2 in owners)
				{
					SelectedPinOwners.Add(item2);
				}
				_pinOwnersInitialized = true;
			}
			else
			{
				SelectedPinOwners.RemoveWhere((string owner) => !owners.Contains(owner));
				foreach (string item3 in owners)
				{
					if (!KnownPinOwners.Contains(item3))
					{
						SelectedPinOwners.Add(item3);
					}
				}
			}
			KnownPinOwners.Clear();
			foreach (string item4 in owners)
			{
				KnownPinOwners.Add(item4);
			}
			_nextPinOverlayRefresh = 0f;
			SetStatus(L10n.T("pins_refreshed"));
		}

		internal static bool IsLocalPinShared(string pinId)
		{
			return SharedLocalPinIds.Contains(pinId ?? "");
		}

		internal static void ToggleLocalPinShared(string pinId)
		{
			if (!string.IsNullOrEmpty(pinId) && !SharedLocalPinIds.Add(pinId))
			{
				SharedLocalPinIds.Remove(pinId);
			}
		}

		internal static void CycleLocalPinIcon(string pinId, int direction)
		{
			SharedPin sharedPin = LocalPinsInternal.FirstOrDefault((SharedPin x) => string.Equals(x.PinId, pinId, StringComparison.Ordinal));
			if (sharedPin == null)
			{
				return;
			}
			List<PinIconChoice> list = PinBridge.AvailableIconChoices(sharedPin);
			if (list.Count != 0)
			{
				int currentValue = (string.IsNullOrWhiteSpace(sharedPin.DisplayTypeName) ? sharedPin.TypeValue : sharedPin.DisplayTypeValue);
				int num = list.FindIndex((PinIconChoice x) => x.TypeValue == currentValue);
				if (num < 0)
				{
					num = 0;
				}
				PinIconChoice pinIconChoice = list[(num + direction + list.Count) % list.Count];
				sharedPin.DisplayTypeName = pinIconChoice.TypeName;
				sharedPin.DisplayTypeValue = pinIconChoice.TypeValue;
			}
		}

		internal static string LocalPinIconLabel(SharedPin pin)
		{
			return PinBridge.SelectedIconLabel(pin);
		}

		internal static void SubmitSharedPins()
		{
			NetworkHub.SubmitPins(LocalPinsInternal.Where((SharedPin x) => SharedLocalPinIds.Contains(x.PinId)).ToList());
		}

		internal static bool IsPinOwnerSelected(string ownerUuid)
		{
			return SelectedPinOwners.Contains(ownerUuid ?? "");
		}

		internal static void TogglePinOwner(string ownerUuid)
		{
			if (!string.IsNullOrEmpty(ownerUuid))
			{
				if (!SelectedPinOwners.Add(ownerUuid))
				{
					SelectedPinOwners.Remove(ownerUuid);
				}
				_nextPinOverlayRefresh = 0f;
			}
		}

		internal static bool SharedPinsMasterEnabled()
		{
			return PinBridge.SharedPinsMasterEnabled();
		}

		internal static void RefreshExplorationTint()
		{
			if (!IsPreviewing)
			{
				ExplorationTintOverlay.Clear();
			}
			else
			{
				ExplorationTintOverlay.SetLayers(OrderedSelectedLayers());
			}
		}

		private static IEnumerable<SharedPin> FilteredRemotePins()
		{
			if (PinVisibility == RemotePinVisibility.None)
			{
				return Enumerable.Empty<SharedPin>();
			}
			IEnumerable<SharedPin> enumerable = RemotePinsInternal.Where((SharedPin x) => !x.IsMine);
			if (PinVisibility == RemotePinVisibility.SelectedPlayers)
			{
				enumerable = enumerable.Where((SharedPin x) => SelectedPinOwners.Contains(x.OwnerUuid));
			}
			return enumerable;
		}

		private static void QueueOrRun(PendingAction action)
		{
			if (Selected.Count == 0)
			{
				SetStatus(L10n.T("select_layer"));
				return;
			}
			List<string> list = Selected.Where((string id) => !HasLayerPayload(id)).ToList();
			if (list.Count > 0)
			{
				_pendingAction = action;
				foreach (string item in list)
				{
					NetworkHub.RequestLayer(item);
				}
				SetStatus(L10n.T("downloading_layers"));
			}
			else if (action == PendingAction.Preview)
			{
				PreviewNow();
			}
			else
			{
				ApplyNow();
			}
		}

		private static void PreviewNow(bool updateStatus = true)
		{
			List<StoredLayer> list = OrderedSelectedLayers();
			if (list.Count == 0)
			{
				return;
			}
			if (!IsPreviewing)
			{
				_explorationBeforePanel = MapBridge.CaptureExplorationState();
				if (_explorationBeforePanel == null)
				{
					return;
				}
			}
			bool flag = MapBridge.RestoreExplorationState(_explorationBeforePanel) && MapBridge.PreviewLayers(list.Select((StoredLayer x) => x.MapData));
			if (!flag)
			{
				MapBridge.RestoreExplorationState(_explorationBeforePanel);
			}
			IsPreviewing = flag;
			if (flag)
			{
				ExplorationTintOverlay.SetLayers(list);
			}
			else
			{
				ExplorationTintOverlay.Clear();
			}
			if (updateStatus)
			{
				SetStatus(flag ? L10n.T("preview_active") : L10n.T("preview_failed"));
			}
		}

		private static void ApplyNow()
		{
			List<StoredLayer> list = OrderedSelectedLayers();
			if (list.Count == 0)
			{
				return;
			}
			byte[] array = ((IsPreviewing && _explorationBeforePanel != null) ? _explorationBeforePanel : MapBridge.CaptureExplorationState());
			if (array == null)
			{
				return;
			}
			MapBridge.RestoreExplorationState(array);
			List<StoredLayer> list2 = new List<StoredLayer>();
			foreach (StoredLayer item in list)
			{
				if (!IsLocalLayer(item.Descriptor.AccountUuid) && (IsSyntheticLayer(item.Descriptor.AccountUuid) || !IsAlreadyApplied(item.Descriptor)))
				{
					list2.Add(item);
				}
			}
			bool flag = MapBridge.MergeLayers(list2.Select((StoredLayer x) => x.MapData));
			byte[] array2 = (flag ? MapBridge.CaptureExplorationState() : null);
			bool flag2 = flag && array2 != null && !array.SequenceEqual(array2);
			if (flag)
			{
				if (flag2)
				{
					_undoExploration = (byte[])array.Clone();
					SaveUndoFile(_undoExploration);
				}
				foreach (StoredLayer item2 in list2)
				{
					if (!IsSyntheticLayer(item2.Descriptor.AccountUuid))
					{
						RecordImport(item2.Descriptor);
					}
				}
			}
			else
			{
				MapBridge.RestoreExplorationState(array);
			}
			IsPreviewing = false;
			ExplorationTintOverlay.Clear();
			_explorationBeforePanel = ((flag && array2 != null) ? array2 : array);
			SaveImports();
			if (flag)
			{
				PreviewNow(updateStatus: false);
				SetStatus(flag2 ? L10n.T("import_applied") : L10n.T("import_no_change"));
			}
			else
			{
				SetStatus(L10n.T("import_failed"));
			}
		}

		private static void RestoreNormalView()
		{
			if (IsPreviewing && _explorationBeforePanel != null)
			{
				MapBridge.RestoreExplorationState(_explorationBeforePanel);
			}
			IsPreviewing = false;
			_pendingAction = PendingAction.None;
			ExplorationTintOverlay.Clear();
			SetStatus(L10n.T("normal_view"));
		}

		private static List<StoredLayer> OrderedSelectedLayers()
		{
			return (from layer in Selected.Select(GetLayerPayload)
				where layer != null
				orderby (!(layer.Descriptor.AccountUuid == "00000000-0000-0000-0000-000000000001")) ? 1 : 0, (!string.Equals(layer.Descriptor.AccountUuid, _localAccountUuid, StringComparison.OrdinalIgnoreCase)) ? 1 : 0, layer.Descriptor.CharacterName
				select layer).ToList();
		}

		private static bool IsSyntheticLayer(string uuid)
		{
			return string.Equals(uuid, "00000000-0000-0000-0000-000000000001", StringComparison.OrdinalIgnoreCase);
		}

		private static bool HasLayerPayload(string uuid)
		{
			if (!string.IsNullOrEmpty(uuid))
			{
				if (!string.Equals(uuid, _localAccountUuid, StringComparison.OrdinalIgnoreCase) || _localPersonalMapData == null)
				{
					return Payloads.ContainsKey(uuid);
				}
				return true;
			}
			return false;
		}

		private static StoredLayer GetLayerPayload(string uuid)
		{
			if (string.Equals(uuid, _localAccountUuid, StringComparison.OrdinalIgnoreCase) && _localPersonalMapData != null)
			{
				LayerDescriptor layerDescriptor = DescriptorsInternal.FirstOrDefault((LayerDescriptor x) => string.Equals(x.AccountUuid, uuid, StringComparison.OrdinalIgnoreCase));
				if (layerDescriptor != null)
				{
					LayerDescriptor layerDescriptor2 = layerDescriptor.CloneWithoutPayload();
					layerDescriptor2.Fingerprint = BinaryProtocol.Fingerprint(_localPersonalMapData);
					layerDescriptor2.PayloadBytes = _localPersonalMapData.Length;
					return new StoredLayer
					{
						Descriptor = layerDescriptor2,
						MapData = _localPersonalMapData
					};
				}
			}
			if (!Payloads.TryGetValue(uuid ?? "", out var value))
			{
				return null;
			}
			return value;
		}

		private static LayerDescriptor ResolveLocalAccount(string serverAccountUuid)
		{
			if (IdentityOptions.IsSafeUuid(serverAccountUuid))
			{
				LayerDescriptor layerDescriptor = DescriptorsInternal.FirstOrDefault((LayerDescriptor x) => string.Equals(x.AccountUuid, serverAccountUuid, StringComparison.OrdinalIgnoreCase));
				_localAccountUuid = serverAccountUuid;
				if (layerDescriptor != null)
				{
					return layerDescriptor;
				}
			}
			string localName = AccountIdentity.LocalCharacterName();
			LayerDescriptor layerDescriptor2 = (from x in DescriptorsInternal
				where !IsSyntheticLayer(x.AccountUuid)
				where string.Equals(x.CharacterName, localName, StringComparison.OrdinalIgnoreCase) || x.KnownCharacterNames.Any((string alias) => string.Equals(alias, localName, StringComparison.OrdinalIgnoreCase))
				orderby string.Equals(x.CharacterName, localName, StringComparison.OrdinalIgnoreCase) descending, x.UpdatedUtcTicks descending
				select x).FirstOrDefault();
			if (layerDescriptor2 != null)
			{
				_localAccountUuid = layerDescriptor2.AccountUuid;
			}
			return layerDescriptor2;
		}

		private static void RefreshLocalPersonalLayer()
		{
			if (string.IsNullOrEmpty(_localAccountUuid) || _explorationBeforePanel == null)
			{
				_localPersonalMapData = null;
				return;
			}
			byte[] array = MapBridge.CapturePersonalLayerFromState(_explorationBeforePanel);
			_localPersonalMapData = ((array != null && array.Length != 0) ? array : null);
		}

		private static void UpsertTableLayer(byte[] tableData, long updatedUtcTicks)
		{
			if (tableData != null && tableData.Length != 0)
			{
				LayerDescriptor descriptor = new LayerDescriptor
				{
					AccountUuid = "00000000-0000-0000-0000-000000000001",
					CharacterName = L10n.T("shared_table"),
					ColorHex = "#B0BEC5",
					Emblem = "Compass",
					Revision = 1,
					UpdatedUtcTicks = ((updatedUtcTicks > 0) ? updatedUtcTicks : DateTime.UtcNow.Ticks),
					Fingerprint = BinaryProtocol.Fingerprint(tableData),
					PayloadBytes = tableData.Length
				};
				MergeDescriptor(descriptor);
				Payloads["00000000-0000-0000-0000-000000000001"] = new StoredLayer
				{
					Descriptor = descriptor,
					MapData = (byte[])tableData.Clone()
				};
			}
		}

		private static void MergeDescriptor(LayerDescriptor descriptor)
		{
			int num = DescriptorsInternal.FindIndex((LayerDescriptor x) => string.Equals(x.AccountUuid, descriptor.AccountUuid, StringComparison.OrdinalIgnoreCase));
			if (num >= 0)
			{
				StoredLayer value;
				if (!string.Equals(DescriptorsInternal[num].Fingerprint, descriptor.Fingerprint, StringComparison.Ordinal))
				{
					Payloads.Remove(descriptor.AccountUuid);
				}
				else if (Payloads.TryGetValue(descriptor.AccountUuid, out value) && value != null)
				{
					value.Descriptor = descriptor.CloneWithoutPayload();
				}
				DescriptorsInternal[num] = descriptor.CloneWithoutPayload();
			}
			else
			{
				DescriptorsInternal.Add(descriptor.CloneWithoutPayload());
			}
		}

		private static void RecordImport(LayerDescriptor descriptor)
		{
			Imports[descriptor.AccountUuid] = new ImportRecord
			{
				AccountUuid = descriptor.AccountUuid,
				Revision = descriptor.Revision,
				Fingerprint = descriptor.Fingerprint,
				ImportedUtcTicks = DateTime.UtcNow.Ticks
			};
		}

		private static string ClientDirectory()
		{
			return LocalClientStorage.ClientWorldDirectory();
		}

		private static string ImportsPath()
		{
			long num = 0L;
			try
			{
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					num = Player.m_localPlayer.GetPlayerID();
				}
			}
			catch
			{
			}
			return Path.Combine(ClientDirectory(), "imports_v4_" + num + ".txt");
		}

		private static string UndoPath()
		{
			return Path.Combine(ClientDirectory(), "last-import-v4.undo");
		}

		private static string SelectionPath()
		{
			long num = 0L;
			try
			{
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					num = Player.m_localPlayer.GetPlayerID();
				}
			}
			catch
			{
			}
			return Path.Combine(ClientDirectory(), "last-selection-v1_" + num + ".txt");
		}

		private static string TableSnapshotPath()
		{
			long num = 0L;
			try
			{
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					num = Player.m_localPlayer.GetPlayerID();
				}
			}
			catch
			{
			}
			return Path.Combine(ClientDirectory(), "last-shared-table-v1_" + num + ".pmlx");
		}

		private static void SaveTableSnapshot(byte[] data)
		{
			try
			{
				if (data != null && ExplorationLayerData.Validate(data, out var _))
				{
					Directory.CreateDirectory(ClientDirectory());
					File.WriteAllBytes(TableSnapshotPath(), data);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not save the local shared-table snapshot: " + ex.Message));
			}
		}

		private static byte[] LoadTableSnapshot()
		{
			try
			{
				string path = TableSnapshotPath();
				if (!File.Exists(path))
				{
					return null;
				}
				byte[] array = File.ReadAllBytes(path);
				if (ExplorationLayerData.Validate(array, out var error))
				{
					return array;
				}
				Plugin.Log.LogWarning((object)("Ignored invalid local shared-table snapshot: " + error));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not load the local shared-table snapshot: " + ex.Message));
			}
			return null;
		}

		private static long TableSnapshotTimestamp()
		{
			try
			{
				string path = TableSnapshotPath();
				return File.Exists(path) ? File.GetLastWriteTimeUtc(path).Ticks : DateTime.UtcNow.Ticks;
			}
			catch
			{
				return DateTime.UtcNow.Ticks;
			}
		}

		private static void RestoreSelection(string fallbackUuid)
		{
			Selected.Clear();
			try
			{
				string path = SelectionPath();
				if (File.Exists(path))
				{
					string[] array = File.ReadAllLines(path);
					for (int i = 0; i < array.Length; i++)
					{
						string text = (array[i] ?? "").Trim();
						if (IdentityOptions.IsSafeUuid(text) && !string.Equals(text, "00000000-0000-0000-0000-000000000002", StringComparison.OrdinalIgnoreCase) && (!string.Equals(text, "00000000-0000-0000-0000-000000000001", StringComparison.OrdinalIgnoreCase) || Payloads.ContainsKey("00000000-0000-0000-0000-000000000001")))
						{
							Selected.Add(text);
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not restore the last layer selection: " + ex.Message));
			}
			if (Selected.Count == 0 && !string.IsNullOrWhiteSpace(fallbackUuid))
			{
				Selected.Add(fallbackUuid);
			}
		}

		private static void SaveSelection()
		{
			try
			{
				Directory.CreateDirectory(ClientDirectory());
				string text = SelectionPath();
				string text2 = text + ".tmp";
				File.WriteAllLines(text2, Selected.OrderBy((string x) => x).ToArray(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				if (File.Exists(text))
				{
					try
					{
						File.Replace(text2, text, null);
						return;
					}
					catch
					{
						File.Delete(text);
						File.Move(text2, text);
						return;
					}
				}
				File.Move(text2, text);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not save the layer selection: " + ex.Message));
			}
		}

		private static void LoadImports()
		{
			if (_importsLoaded)
			{
				return;
			}
			_importsLoaded = true;
			Imports.Clear();
			string path = ImportsPath();
			if (!File.Exists(path))
			{
				return;
			}
			string[] array = File.ReadAllLines(path);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { '|' });
				if (array2.Length == 4 && int.TryParse(array2[1], out var result) && long.TryParse(array2[3], out var result2))
				{
					Imports[array2[0]] = new ImportRecord
					{
						AccountUuid = array2[0],
						Revision = result,
						Fingerprint = array2[2],
						ImportedUtcTicks = result2
					};
				}
			}
			try
			{
				if (File.Exists(UndoPath()))
				{
					byte[] array3 = File.ReadAllBytes(UndoPath());
					if (ExplorationLayerData.ValidateState(array3, out var error))
					{
						_undoExploration = array3;
					}
					else
					{
						Plugin.Log.LogWarning((object)("Ignored invalid exploration-only undo file: " + error));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not load exploration-only undo file: " + ex.Message));
			}
		}

		private static void SaveImports()
		{
			Directory.CreateDirectory(ClientDirectory());
			string[] contents = (from x in Imports.Values
				orderby x.AccountUuid
				select x.AccountUuid + "|" + x.Revision + "|" + x.Fingerprint + "|" + x.ImportedUtcTicks).ToArray();
			File.WriteAllLines(ImportsPath(), contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
		}

		private static void SaveUndoFile(byte[] data)
		{
			try
			{
				Directory.CreateDirectory(ClientDirectory());
				File.WriteAllBytes(UndoPath(), data);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not save import undo point: " + ex.Message));
			}
		}

		private static void DeleteUndoFile()
		{
			try
			{
				if (File.Exists(UndoPath()))
				{
					File.Delete(UndoPath());
				}
			}
			catch
			{
			}
		}
	}
	internal static class Compatibility
	{
		internal static bool BetterMapInstalled { get; private set; }

		internal static bool TargetPortalInstalled { get; private set; }

		internal static bool SimpleAccessControlInstalled { get; private set; }

		internal static void Detect()
		{
			BetterMapInstalled = HasPlugin("xtav1m.BetterMap", "BetterMap");
			TargetPortalInstalled = HasPlugin(null, "TargetPortal");
			SimpleAccessControlInstalled = HasPlugin("zephyrfit.simpleaccesscontrol", "Simple Access Control");
			Plugin.Log.LogInfo((object)("Compatibility: BetterMap=" + BetterMapInstalled + ", TargetPortal=" + TargetPortalInstalled + ", SimpleAccessControl=" + SimpleAccessControlInstalled + "."));
		}

		private static bool HasPlugin(string guid, string displayOrAssemblyName)
		{
			try
			{
				if (!string.IsNullOrEmpty(guid) && Chainloader.PluginInfos.ContainsKey(guid))
				{
					return true;
				}
				return Chainloader.PluginInfos.Values.Any((PluginInfo info) => string.Equals(info.Metadata.Name, displayOrAssemblyName, StringComparison.OrdinalIgnoreCase) || ((object)info.Instance).GetType().Assembly.GetName().Name.IndexOf(displayOrAssemblyName.Replace(" ", ""), StringComparison.OrdinalIgnoreCase) >= 0);
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class ExplorationLayerData
	{
		private const string Magic = "PMLX";

		private const byte FormatVersion = 1;

		private const string StateMagic = "PMLS";

		private const byte StateFormatVersion = 1;

		private const int MaximumCells = 16777216;

		private const int MaximumEncodedLayerBytes = 2097216;

		internal static byte[] Encode(BitArray explored)
		{
			if (explored == null || explored.Length == 0)
			{
				throw new InvalidDataException("The exploration mask is empty.");
			}
			byte[] array = new byte[(explored.Length + 7) / 8];
			explored.CopyTo(array, 0);
			using MemoryStream memoryStream = new MemoryStream(13 + array.Length);
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
			binaryWriter.Write("PMLX");
			binaryWriter.Write((byte)1);
			binaryWriter.Write(explored.Length);
			binaryWriter.Write(array.Length);
			binaryWriter.Write(array);
			return memoryStream.ToArray();
		}

		internal static BitArray Decode(byte[] data)
		{
			if (!TryDecode(data, out var explored, out var error))
			{
				throw new InvalidDataException(error);
			}
			return explored;
		}

		internal static bool Validate(byte[] data, out string error)
		{
			BitArray explored;
			return TryDecode(data, out explored, out error);
		}

		internal static int CountExplored(byte[] data)
		{
			return CountExplored(Decode(data));
		}

		internal static int CountExplored(BitArray explored)
		{
			if (explored == null)
			{
				return 0;
			}
			int num = 0;
			foreach (bool item in explored)
			{
				if (item)
				{
					num++;
				}
			}
			return num;
		}

		internal static byte[] EncodeState(BitArray personal, BitArray shared)
		{
			if (personal == null || personal.Length == 0)
			{
				throw new InvalidDataException("The personal exploration mask is empty.");
			}
			if (shared != null && shared.Length != personal.Length)
			{
				throw new InvalidDataException("The shared exploration mask has unexpected dimensions.");
			}
			byte[] array = Encode(personal);
			byte[] array2 = ((shared != null) ? Encode(shared) : Array.Empty<byte>());
			using MemoryStream memoryStream = new MemoryStream(array.Length + array2.Length + 24);
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
			binaryWriter.Write("PMLS");
			binaryWriter.Write((byte)1);
			binaryWriter.Write(array.Length);
			binaryWriter.Write(array);
			binaryWriter.Write(array2.Length);
			binaryWriter.Write(array2);
			return memoryStream.ToArray();
		}

		internal static void DecodeState(byte[] data, out BitArray personal, out BitArray shared)
		{
			personal = null;
			shared = null;
			if (data == null || data.Length < 14)
			{
				throw new InvalidDataException("Invalid exploration-state snapshot.");
			}
			using MemoryStream memoryStream = new MemoryStream(data, writable: false);
			using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
			if (binaryReader.ReadString() != "PMLS")
			{
				throw new InvalidDataException("Legacy full-map snapshot rejected to protect map pins.");
			}
			if (binaryReader.ReadByte() != 1)
			{
				throw new InvalidDataException("Unsupported exploration-state snapshot format.");
			}
			personal = Decode(ReadStateLayer(binaryReader, memoryStream, "personal"));
			byte[] array = ReadStateLayer(binaryReader, memoryStream, "shared", allowEmpty: true);
			shared = ((array.Length != 0) ? Decode(array) : null);
			if (shared != null && shared.Length != personal.Length)
			{
				throw new InvalidDataException("Exploration-state dimensions do not match.");
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new InvalidDataException("Exploration-state snapshot contains trailing data.");
			}
		}

		internal static bool ValidateState(byte[] data, out string error)
		{
			try
			{
				DecodeState(data, out var _, out var _);
				error = "";
				return true;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		private static byte[] ReadStateLayer(BinaryReader reader, Stream stream, string name, bool allowEmpty = false)
		{
			if (stream.Length - stream.Position < 4)
			{
				throw new InvalidDataException("Truncated " + name + " exploration-state length.");
			}
			int num = reader.ReadInt32();
			if (num < 0 || num > 2097216 || (!allowEmpty && num == 0) || stream.Length - stream.Position < num)
			{
				throw new InvalidDataException("Invalid " + name + " exploration-state layer.");
			}
			byte[] array = reader.ReadBytes(num);
			if (array.Length != num)
			{
				throw new InvalidDataException("Truncated " + name + " exploration-state layer.");
			}
			return array;
		}

		private static bool TryDecode(byte[] data, out BitArray explored, out string error)
		{
			explored = null;
			error = "Invalid exploration layer.";
			if (data == null || data.Length < 13)
			{
				return false;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(data, writable: false);
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
				if (binaryReader.ReadString() != "PMLX")
				{
					error = "Legacy full-map payload rejected; record discoveries again with Player Map Layers 0.1.7.";
					return false;
				}
				if (binaryReader.ReadByte() != 1)
				{
					error = "Unsupported exploration layer format.";
					return false;
				}
				int num = binaryReader.ReadInt32();
				int num2 = binaryReader.ReadInt32();
				int num3 = (num + 7) / 8;
				if (num < 1 || num > 16777216 || num2 != num3 || memoryStream.Length - memoryStream.Position != num2)
				{
					error = "Invalid exploration layer dimensions.";
					return false;
				}
				byte[] array = binaryReader.ReadBytes(num2);
				if (array.Length != num2)
				{
					error = "Truncated exploration layer.";
					return false;
				}
				explored = new BitArray(array)
				{
					Length = num
				};
				error = "";
				return true;
			}
			catch (Exception ex)
			{
				error = "Invalid exploration layer: " + ex.Message;
				return false;
			}
		}
	}
	internal static class ExplorationTintOverlay
	{
		private const int MaximumColoredLayers = 16;

		private static Texture2D _texture;

		private static GameObject _overlayObject;

		private static Component _sourceImage;

		private static Component _overlayImage;

		private static FieldInfo _largeMapImageField;

		private static string _signature = "";

		private static bool _resolutionWarningLogged;

		private static bool _renderWarningLogged;

		internal static void SetLayers(IEnumerable<StoredLayer> sourceLayers)
		{
			//IL_0433: Unknown result type (might be due to invalid IL or missing references)
			//IL_0438: Unknown result type (might be due to invalid IL or missing references)
			//IL_0443: Unknown result type (might be due to invalid IL or missing references)
			//IL_044a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Expected O, but got Unknown
			//IL_0409: Unknown result type (might be due to invalid IL or missing references)
			//IL_040e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			//IL_0376: Unknown result type (might be due to invalid IL or missing references)
			//IL_037c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0383: Unknown result type (might be due to invalid IL or missing references)
			//IL_038a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0393: Unknown result type (might be due to invalid IL or missing references)
			//IL_0398: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.ExplorationTintEnabled == null || !Plugin.ExplorationTintEnabled.Value)
			{
				Clear();
				return;
			}
			List<StoredLayer> list = (sourceLayers ?? Enumerable.Empty<StoredLayer>()).Where((StoredLayer x) => x != null && x.Descriptor != null && x.MapData != null && x.MapData.Length != 0).Take(17).ToList();
			if (list.Count == 0)
			{
				Clear();
				return;
			}
			if (list.Count > 16)
			{
				Plugin.Log.LogWarning((object)("Exploration tint skipped because more than " + 16 + " layers are selected."));
				Clear();
				return;
			}
			string text = string.Join(";", list.Select((StoredLayer x) => x.Descriptor.AccountUuid + ":" + x.Descriptor.Fingerprint + ":" + x.Descriptor.ColorHex)) + "|" + SingleOpacity() + "|" + SharedTableOpacity() + "|" + OverlapOpacity() + "|" + Saturation();
			if (text == _signature && (Object)(object)_texture != (Object)null)
			{
				return;
			}
			try
			{
				List<BitArray> list2 = list.Select((StoredLayer x) => ExplorationLayerData.Decode(x.MapData)).ToList();
				int cells = list2[0].Length;
				if (list2.Any((BitArray x) => x.Length != cells))
				{
					throw new InvalidOperationException("Tint layers use different map dimensions.");
				}
				if (!MapBridge.TryGetFogDimensions(out var width, out var height) || width * height != cells)
				{
					int num = (int)Math.Round(Math.Sqrt(cells));
					if (num * num != cells)
					{
						throw new InvalidOperationException("Tint texture dimensions could not be resolved.");
					}
					width = num;
					height = num;
					if (!_resolutionWarningLogged)
					{
						_resolutionWarningLogged = true;
						Plugin.Log.LogWarning((object)"Exploration tint is using square-map dimensions because Valheim's fog texture was not available yet.");
					}
				}
				byte[] array = new byte[cells];
				sbyte[] array2 = new sbyte[cells];
				int num2 = list.FindIndex((StoredLayer x) => string.Equals(x.Descriptor.AccountUuid, "00000000-0000-0000-0000-000000000001", StringComparison.Ordinal));
				for (int num3 = 0; num3 < list2.Count; num3++)
				{
					if (num3 == num2)
					{
						continue;
					}
					BitArray bitArray = list2[num3];
					for (int num4 = 0; num4 < cells; num4++)
					{
						if (bitArray[num4])
						{
							if (array[num4] == 0)
							{
								array2[num4] = (sbyte)num3;
							}
							if (array[num4] < byte.MaxValue)
							{
								array[num4]++;
							}
						}
					}
				}
				Color32[] array3 = list.Select((StoredLayer x) => ParseColor(x.Descriptor.ColorHex, Saturation())).ToArray();
				byte b = (byte)Math.Round(SingleOpacity() * 255f);
				byte b2 = (byte)Math.Round(SharedTableOpacity() * 255f);
				byte b3 = (byte)Math.Round(OverlapOpacity() * 255f);
				Color32[] array4 = (Color32[])(object)new Color32[cells];
				for (int num5 = 0; num5 < cells; num5++)
				{
					if (array[num5] == 0)
					{
						if (num2 >= 0 && list2[num2][num5])
						{
							Color32 val = array3[num2];
							array4[num5] = new Color32(val.r, val.g, val.b, b2);
						}
						else
						{
							array4[num5] = new Color32((byte)0, (byte)0, (byte)0, (byte)0);
						}
					}
					else if (array[num5] == 1)
					{
						int num6 = array2[num5];
						Color32 val2 = array3[num6];
						array4[num5] = new Color32(val2.r, val2.g, val2.b, b);
					}
					else
					{
						array4[num5] = new Color32((byte)220, (byte)225, (byte)230, b3);
					}
				}
				DestroyTexture();
				_texture = new Texture2D(width, height, (TextureFormat)4, false, true)
				{
					name = "PlayerMapLayers_ExplorationTint",
					filterMode = (FilterMode)1,
					wrapMode = (TextureWrapMode)1
				};
				_texture.SetPixels32(array4);
				_texture.Apply(false, false);
				_signature = text;
				ApplyTextureToOverlay();
				Plugin.Log.LogInfo((object)("Prepared exploration tint for " + list.Count + " layer(s); the shared table stays behind player colors and player overlaps remain neutral."));
			}
			catch (Exception exception)
			{
				Plugin.Log.LogWarning((object)("Could not prepare the exploration tint: " + Unwrap(exception).Message));
				Clear();
			}
		}

		internal static void Tick()
		{
			if ((Object)(object)_texture == (Object)null || Plugin.ExplorationTintEnabled == null || !Plugin.ExplorationTintEnabled.Value)
			{
				SetOverlayActive(active: false);
				return;
			}
			if (!MapBridge.IsLargeMapOpen())
			{
				SetOverlayActive(active: false);
				return;
			}
			try
			{
				if (EnsureOverlay())
				{
					CopyProperty(_sourceImage, _overlayImage, "uvRect");
					SetOverlayActive(active: true);
				}
			}
			catch (Exception exception)
			{
				if (!_renderWarningLogged)
				{
					_renderWarningLogged = true;
					Plugin.Log.LogWarning((object)("Exploration tint display disabled for this map session: " + Unwrap(exception).Message));
				}
				DestroyOverlay();
			}
		}

		internal static void Clear()
		{
			DestroyOverlay();
			DestroyTexture();
			_signature = "";
		}

		private static bool EnsureOverlay()
		{
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_overlayObject != (Object)null && (Object)(object)_overlayImage != (Object)null)
			{
				ApplyTextureToOverlay();
				return true;
			}
			_sourceImage = FindLargeMapImage();
			if ((Object)(object)_sourceImage == (Object)null)
			{
				if (!_renderWarningLogged)
				{
					_renderWarningLogged = true;
					Plugin.Log.LogWarning((object)"Exploration tint could not find Valheim's large-map RawImage; the normal map remains unchanged.");
				}
				return false;
			}
			GameObject gameObject = _sourceImage.gameObject;
			_overlayObject = Object.Instantiate<GameObject>(gameObject);
			((Object)_overlayObject).name = "PlayerMapLayers_ExplorationTintOverlay";
			_overlayObject.transform.SetParent(gameObject.transform.parent, false);
			_overlayObject.transform.SetSiblingIndex(gameObject.transform.GetSiblingIndex() + 1);
			_overlayImage = _overlayObject.GetComponent(((object)_sourceImage).GetType());
			if ((Object)(object)_overlayImage == (Object)null)
			{
				throw new InvalidOperationException("The cloned large-map image has no RawImage component.");
			}
			for (int num = _overlayObject.transform.childCount - 1; num >= 0; num--)
			{
				Object.Destroy((Object)(object)((Component)_overlayObject.transform.GetChild(num)).gameObject);
			}
			Component[] components = _overlayObject.GetComponents<Component>();
			foreach (Component val in components)
			{
				if (!((Object)(object)val == (Object)null) && val != _overlayImage)
				{
					string name = ((object)val).GetType().Name;
					if (!(name == "RectTransform") && !(name == "CanvasRenderer"))
					{
						Object.Destroy((Object)(object)val);
					}
				}
			}
			SetProperty(_overlayImage, "material", null);
			SetProperty(_overlayImage, "raycastTarget", false);
			SetProperty(_overlayImage, "color", Color.white);
			CopyProperty(_sourceImage, _overlayImage, "uvRect");
			ApplyTextureToOverlay();
			return true;
		}

		private static Component FindLargeMapImage()
		{
			if ((Object)(object)Minimap.instance == (Object)null)
			{
				return null;
			}
			if (_largeMapImageField == null)
			{
				_largeMapImageField = AccessTools.Field(typeof(Minimap), "m_mapImageLarge") ?? AccessTools.Field(typeof(Minimap), "m_largeMapImage");
				if (_largeMapImageField == null)
				{
					_largeMapImageField = typeof(Minimap).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((FieldInfo field) => field.Name.IndexOf("large", StringComparison.OrdinalIgnoreCase) >= 0 && field.Name.IndexOf("image", StringComparison.OrdinalIgnoreCase) >= 0 && field.FieldType.Name.IndexOf("RawImage", StringComparison.OrdinalIgnoreCase) >= 0 && typeof(Component).IsAssignableFrom(field.FieldType));
				}
			}
			Component val = (Component)((_largeMapImageField != null) ? /*isinst with value type is only supported in some contexts*/: null);
			if ((Object)(object)val == (Object)null || ((object)val).GetType().GetProperty("texture", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) == null)
			{
				return null;
			}
			return val;
		}

		private static void ApplyTextureToOverlay()
		{
			if ((Object)(object)_overlayImage != (Object)null && (Object)(object)_texture != (Object)null)
			{
				SetProperty(_overlayImage, "texture", _texture);
			}
		}

		private static void SetOverlayActive(bool active)
		{
			if ((Object)(object)_overlayObject != (Object)null && _overlayObject.activeSelf != active)
			{
				_overlayObject.SetActive(active);
			}
		}

		private static void DestroyOverlay()
		{
			if ((Object)(object)_overlayObject != (Object)null)
			{
				Object.Destroy((Object)(object)_overlayObject);
			}
			_overlayObject = null;
			_overlayImage = null;
			_sourceImage = null;
		}

		private static void DestroyTexture()
		{
			if ((Object)(object)_texture != (Object)null)
			{
				Object.Destroy((Object)(object)_texture);
			}
			_texture = null;
		}

		private static void SetProperty(object target, string propertyName, object value)
		{
			if (target != null)
			{
				PropertyInfo property = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null && property.CanWrite)
				{
					property.SetValue(target, value, null);
				}
			}
		}

		private static void CopyProperty(object source, object target, string propertyName)
		{
			if (source != null && target != null)
			{
				PropertyInfo property = source.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				PropertyInfo property2 = target.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null && property.CanRead && property2 != null && property2.CanWrite)
				{
					property2.SetValue(target, property.GetValue(source, null), null);
				}
			}
		}

		private static Color32 ParseColor(string html, float saturation)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: 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_000f: Unknown result type (might be due to invalid IL or missing references)
			Color white = default(Color);
			if (!ColorUtility.TryParseHtmlString(html, ref white))
			{
				white = Color.white;
			}
			float num = white.r * 0.299f + white.g * 0.587f + white.b * 0.114f;
			white.r = Mathf.Clamp01(num + (white.r - num) * saturation);
			white.g = Mathf.Clamp01(num + (white.g - num) * saturation);
			white.b = Mathf.Clamp01(num + (white.b - num) * saturation);
			return Color32.op_Implicit(white);
		}

		private static float SingleOpacity()
		{
			return Mathf.Clamp((Plugin.SinglePlayerTintOpacity != null) ? Plugin.SinglePlayerTintOpacity.Value : 0.08f, 0.02f, 0.25f);
		}

		private static float SharedTableOpacity()
		{
			return Mathf.Clamp((Plugin.SharedTableTintOpacity != null) ? Plugin.SharedTableTintOpacity.Value : 0.03f, 0.01f, 0.15f);
		}

		private static float OverlapOpacity()
		{
			return Mathf.Clamp((Plugin.OverlapTintOpacity != null) ? Plugin.OverlapTintOpacity.Value : 0.015f, 0f, 0.12f);
		}

		private static float Saturation()
		{
			return Mathf.Clamp((Plugin.ExplorationTintSaturation != null) ? Plugin.ExplorationTintSaturation.Value : 1.8f, 1f, 2.5f);
		}

		private static Exception Unwrap(Exception exception)
		{
			if (!(exception is TargetInvocationException { InnerException: not null } ex))
			{
				return exception;
			}
			return ex.InnerException;
		}
	}
	internal static class LayerStore
	{
		private static readonly object Sync = new object();

		internal static StoredLayer Upsert(long sender, string submittedName, string requestedColor, string requestedEmblem, byte[] mapData)
		{
			if (mapData == null || mapData.Length == 0)
			{
				throw new InvalidDataException("Empty map layer.");
			}
			if (mapData.Length > Plugin.MaxLayerBytes)
			{
				throw new InvalidDataException("Map layer is too large.");
			}
			lock (Sync)
			{
				string text = ResolveUuid(sender);
				string path = LayerPath(text);
				StoredLayer storedLayer = null;
				if (File.Exists(path))
				{
					try
					{
						storedLayer = BinaryProtocol.LoadLayer(path, Plugin.MaxLayerBytes);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Could not read existing layer " + text + ": " + ex.Message));
					}
				}
				string text2 = BinaryProtocol.Fingerprint(mapData);
				string text3 = SanitizeCharacterName(AccountIdentity.ResolveCharacterName(sender, submittedName));
				LayerDescriptor layerDescriptor = ((storedLayer != null) ? storedLayer.Descriptor : new LayerDescriptor());
				layerDescriptor.AccountUuid = text;
				layerDescriptor.CharacterName = text3;
				if (!layerDescriptor.KnownCharacterNames.Contains<string>(text3, StringComparer.OrdinalIgnoreCase))
				{
					layerDescriptor.KnownCharacterNames.Add(text3);
				}
				if (storedLayer == null || Plugin.AllowIdentityChanges.Value)
				{
					layerDescriptor.ColorHex = IdentityOptions.NormalizeColor(requestedColor, layerDescriptor.ColorHex, text);
					layerDescriptor.Emblem = IdentityOptions.NormalizeEmblem(requestedEmblem);
				}
				if (storedLayer == null || !string.Equals(storedLayer.Descriptor.Fingerprint, text2, StringComparison.Ordinal))
				{
					layerDescriptor.Revision = Math.Max(0, layerDescriptor.Revision) + 1;
				}
				layerDescriptor.Fingerprint = text2;
				layerDescriptor.UpdatedUtcTicks = DateTime.UtcNow.Ticks;
				layerDescriptor.PayloadBytes = mapData.Length;
				StoredLayer storedLayer2 = new StoredLayer
				{
					Descriptor = layerDescriptor,
					MapData = mapData
				};
				BinaryProtocol.SaveLayer(path, storedLayer2);
				Plugin.Debug("Stored layer " + text + " revision " + layerDescriptor.Revision + " for " + text3 + ".");
				return storedLayer2;
			}
		}

		internal static List<LayerDescriptor> List()
		{
			lock (Sync)
			{
				List<LayerDescriptor> list = new List<LayerDescriptor>();
				string path = LayersDirectory();
				if (!Directory.Exists(path))
				{
					return list;
				}
				string[] files = Directory.GetFiles(path, "*.pml");
				foreach (string path2 in files)
				{
					try
					{
						list.Add(BinaryProtocol.LoadLayer(path2, Plugin.MaxLayerBytes).Descriptor.CloneWithoutPayload());
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Skipped invalid layer " + Path.GetFileName(path2) + ": " + ex.Message));
					}
				}
				return (from x in list
					orderby x.UpdatedUtcTicks descending, x.CharacterName
					select x).ToList();
			}
		}

		internal static LayerDescriptor UpdateIdentity(long sender, string submittedName, string requestedColor, string requestedEmblem)
		{
			lock (Sync)
			{
				string text = ResolveUuid(sender);
				string path = LayerPath(text);
				if (!File.Exists(path))
				{
					return null;
				}
				StoredLayer storedLayer = BinaryProtocol.LoadLayer(path, Plugin.MaxLayerBytes);
				string text2 = SanitizeCharacterName(AccountIdentity.ResolveCharacterName(sender, submittedName));
				storedLayer.Descriptor.CharacterName = text2;
				if (!storedLayer.Descriptor.KnownCharacterNames.Contains<string>(text2, StringComparer.OrdinalIgnoreCase))
				{
					storedLayer.Descriptor.KnownCharacterNames.Add(text2);
				}
				if (Plugin.AllowIdentityChanges.Value)
				{
					storedLayer.Descriptor.ColorHex = IdentityOptions.NormalizeColor(requestedColor, storedLayer.Descriptor.ColorHex, text);
					storedLayer.Descriptor.Emblem = IdentityOptions.NormalizeEmblem(requestedEmblem);
				}
				storedLayer.Descriptor.UpdatedUtcTicks = DateTime.UtcNow.Ticks;
				BinaryProtocol.SaveLayer(path, storedLayer);
				return storedLayer.Descriptor.CloneWithoutPayload();
			}
		}

		internal static StoredLayer Get(string uuid)
		{
			if (!IdentityOptions.IsSafeUuid(uuid))
			{
				return null;
			}
			lock (Sync)
			{
				string path = LayerPath(uuid);
				return File.Exists(path) ? BinaryProtocol.LoadLayer(path, Plugin.MaxLayerBytes) : null;
			}
		}

		internal static long CurrentWorldUid()
		{
			try
			{
				return ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetWorldUID() : 0;
			}
			catch
			{
				return 0L;
			}
		}

		internal static string ResolveUuid(long sender)
		{
			return BinaryProtocol.ShortUuid(AccountIdentity.ResolveAccountKey(sender), GetOrCreateSalt());
		}

		private static string RootDirectory()
		{
			return Path.Combine(Paths.ConfigPath, "PlayerMapLayers", "world_" + CurrentWorldUid());
		}

		private static string LayersDirectory()
		{
			return Path.Combine(RootDirectory(), "layers");
		}

		private static string LayerPath(string uuid)
		{
			return Path.Combine(LayersDirectory(), uuid + ".pml");
		}

		private static string GetOrCreateSalt()
		{
			string text = RootDirectory();
			Directory.CreateDirectory(text);
			string path = Path.Combine(text, "server-salt.txt");
			if (File.Exists(path))
			{
				return File.ReadAllText(path).Trim();
			}
			byte[] array = new byte[32];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
			}
			string text2 = Convert.ToBase64String(array);
			File.WriteAllText(path, text2, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			return text2;
		}

		private static string SanitizeCharacterName(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "Unknown";
			}
			string text = new string(value.Where((char character) => !char.IsControl(character)).Take(48).ToArray()).Trim();
			if (text.Length != 0)
			{
				return text;
			}
			return "Unknown";
		}
	}
	internal static class IdentityOptions
	{
		internal static readonly string[] Emblems = new string[15]
		{
			"Compass", "Raven", "Wolf", "Bear", "Hammer", "Axe", "Shield", "Rune", "Tree", "Mountain",
			"Wave", "Star", "Skull", "Ship", "Flame"
		};

		private static readonly string[] Palette = new string[11]
		{
			"#56B4E9", "#E69F00", "#009E73", "#CC79A7", "#D55E00", "#0072B2", "#F0E442", "#8E5BD9", "#00B8A9", "#7CB342",
			"#E0E0E0"
		};

		internal static string NormalizeColor(string requested, string existing, string seed)
		{
			string text = (string.IsNullOrWhiteSpace(requested) ? existing : requested.Trim());
			Color val = default(Color);
			if (ColorUtility.TryParseHtmlString(text, ref val))
			{
				if (!text.StartsWith("#", StringComparison.Ordinal))
				{
					text = "#" + text;
				}
				if (text.Length == 9)
				{
					text = text.Substring(0, 7);
				}
				return text.ToUpperInvariant();
			}
			int num = Math.Abs((seed ?? "").GetHashCode()) % Palette.Length;
			return Palette[num];
		}

		internal static string NormalizeEmblem(string requested)
		{
			return Emblems.FirstOrDefault((string x) => string.Equals(x, requested, StringComparison.OrdinalIgnoreCase)) ?? "Compass";
		}

		internal static bool IsSafeUuid(string value)
		{
			Guid result;
			return Guid.TryParse(value, out result);
		}
	}
	internal static class LocalClientStorage
	{
		private sealed class LegacyPreferences
		{
			internal bool PreviewBeforeImport;

			internal bool UploadOnCartographyWrite;

			internal string RemotePinFilter;

			internal bool AuthorPinHalos;

			internal bool ExplorationTintEnabled;

			internal float SinglePlayerTintOpacity;

			internal float SharedTableTintOpacity;

			internal float OverlapTintOpacity;

			internal float ExplorationTintSaturation;

			internal bool UseParchmentTheme;

			internal float WindowScale;
		}

		private static readonly HashSet<string> MigratedWorldPlayers = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static LegacyPreferences _legacyPreferences;

		internal static ConfigFile PreferencesFile { get; private set; }

		internal static string RootDirectory
		{
			get
			{
				string text = Application.persistentDataPath;
				if (string.IsNullOrWhiteSpace(text))
				{
					text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "IronGate", "Valheim");
				}
				return Path.Combine(text, "PlayerMapLayers");
			}
		}

		internal static void BindClientPreferences()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Expected O, but got Unknown
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Expected O, but got Unknown
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Expected O, but got Unknown
			//IL_01eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Expected O, but got Unknown
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Expected O, but got Unknown
			Directory.CreateDirectory(RootDirectory);
			PreferencesFile = new ConfigFile(Path.Combine(RootDirectory, "client-preferences.cfg"), true);
			Plugin.PreviewBeforeImport = PreferencesFile.Bind<bool>("Synchronization", "PreviewBeforeImport", true, "Restore the player's map after reading a cartography table and require explicit confirmation before applying the received map.");
			Plugin.UploadOnCartographyWrite = PreferencesFile.Bind<bool>("Synchronization", "UploadOnCartographyWrite", true, "Upload the player's current map layer to the server after successfully recording discoveries at a cartography table.");
			Plugin.PreferredColor = PreferencesFile.Bind<string>("Identity", "PreferredColor", "#56B4E9", "Preferred player color in #RRGGBB format. The server remains authoritative and returns the accepted value.");
			Plugin.PreferredEmblem = PreferencesFile.Bind<string>("Identity", "PreferredEmblem", "Compass", "Preferred built-in emblem: Compass, Raven, Wolf, Bear, Hammer, Axe, Shield, Rune, Tree, Mountain, Wave, Star, Skull, Ship or Flame.");
			Plugin.RemotePinFilter = PreferencesFile.Bind<string>("Pins", "RemotePinFilter", "All", "Pins shared through Player Map Layers: All, SelectedPlayers or None. Valheim's native cartography-table switch remains the master on/off control.");
			Plugin.AuthorPinHalos = PreferencesFile.Bind<bool>("Pins", "AuthorColorHalos", true, "Draw a colored relief halo behind temporary shared pins to identify their author without recoloring the original icon.");
			Plugin.ExplorationTintEnabled = PreferencesFile.Bind<bool>("Exploration overlay", "Enabled", true, "Display the selected players' accepted profile colors over an active exploration preview. This is local and never changes map data.");
			Plugin.SinglePlayerTintOpacity = PreferencesFile.Bind<float>("Exploration overlay", "SinglePlayerOpacity", 0.08f, new ConfigDescription("Opacity used where exactly one selected player explored.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.25f), Array.Empty<object>()));
			Plugin.SharedTableTintOpacity = PreferencesFile.Bind<float>("Exploration overlay", "SharedTableOpacity", 0.03f, new ConfigDescription("Lower opacity used for the shared cartography-table exploration layer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.15f), Array.Empty<object>()));
			Plugin.OverlapTintOpacity = PreferencesFile.Bind<float>("Exploration overlay", "OverlapOpacity", 0.015f, new ConfigDescription("Neutral opacity used where several selected players overlap. Colors never stack.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.12f), Array.Empty<object>()));
			Plugin.ExplorationTintSaturation = PreferencesFile.Bind<float>("Exploration overlay", "ColorSaturation", 1.8f, new ConfigDescription("Saturation multiplier for player exploration colors. Opacity remains controlled separately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 2.5f), Array.Empty<object>()));
			Plugin.UseParchmentTheme = PreferencesFile.Bind<bool>("Interface", "ParchmentTheme", true, "Use the compact Nordic parchment theme. Disable this setting to restore Valheim's classic IMGUI appearance.");
			Plugin.WindowScale = PreferencesFile.Bind<float>("Interface", "WindowScale", 1f, new ConfigDescription("Scale of the cartography layer window.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.75f, 1.5f), Array.Empty<object>()));
		}

		internal static void RemoveLegacySyncedPreferences(ConfigFile syncedConfig)
		{
			if (syncedConfig == null)
			{
				return;
			}
			bool saveOnConfigSet = syncedConfig.SaveOnConfigSet;
			try
			{
				syncedConfig.SaveOnConfigSet = false;
				_legacyPreferences = new LegacyPreferences
				{
					PreviewBeforeImport = CaptureAndRemove(syncedConfig, "Synchronization", "PreviewBeforeImport", defaultValue: true),
					UploadOnCartographyWrite = CaptureAndRemove(syncedConfig, "Synchronization", "UploadOnCartographyWrite", defaultValue: true),
					RemotePinFilter = CaptureAndRemove(syncedConfig, "Pins", "RemotePinFilter", "All"),
					AuthorPinHalos = CaptureAndRemove(syncedConfig, "Pins", "AuthorColorHalos", defaultValue: true),
					ExplorationTintEnabled = CaptureAndRemove(syncedConfig, "Exploration overlay", "Enabled", defaultValue: true),
					SinglePlayerTintOpacity = CaptureAndRemove(syncedConfig, "Exploration overlay", "SinglePlayerOpacity", 0.08f),
					SharedTableTintOpacity = CaptureAndRemove(syncedConfig, "Exploration overlay", "SharedTableOpacity", 0.03f),
					OverlapTintOpacity = CaptureAndRemove(syncedConfig, "Exploration overlay", "OverlapOpacity", 0.015f),
					ExplorationTintSaturation = CaptureAndRemove(syncedConfig, "Exploration overlay", "ColorSaturation", 1.8f),
					UseParchmentTheme = CaptureAndRemove(syncedConfig, "Interface", "ParchmentTheme", defaultValue: true),
					WindowScale = CaptureAndRemove(syncedConfig, "Interface", "WindowScale", 1f)
				};
				BindAndRemove(syncedConfig, "Identity", "PreferredColor", "#56B4E9");
				BindAndRemove(syncedConfig, "Identity", "PreferredEmblem", "Compass");
				syncedConfig.Save();
			}
			catch (Exception ex)
			{
				if (Plugin.Log != null)
				{
					Plugin.Log.LogWarning((object)("Could not remove legacy client preferences from the Gale-synchronized config: " + ex.Message));
				}
			}
			finally
			{
				syncedConfig.SaveOnConfigSet = saveOnConfigSet;
			}
		}

		internal static void AcceptServerIdentity(LayerDescriptor descriptor)
		{
			if (descriptor != null && Plugin.PreferredColor != null && Plugin.PreferredEmblem != null)
			{
				string text = IdentityOptions.NormalizeColor(descriptor.ColorHex, Plugin.PreferredColor.Value, descriptor.AccountUuid);
				string text2 = IdentityOptions.NormalizeEmblem(descriptor.Emblem);
				if (!string.Equals(Plugin.PreferredColor.Value, text, StringComparison.OrdinalIgnoreCase))
				{
					Plugin.PreferredColor.Value = text;
				}
				if (!string.Equals(Plugin.PreferredEmblem.Value, text2, StringComparison.OrdinalIgnoreCase))
				{
					Plugin.PreferredEmblem.Value = text2;
				}
			}
		}

		internal static string ClientWorldDirectory()
		{
			return Path.Combine(RootDirectory, "client", "world_" + LayerStore.CurrentWorldUid());
		}

		internal static void MigrateLegacyClientState()
		{
			long num = LayerStore.CurrentWorldUid();
			long num2 = 0L;
			try
			{
				if ((Object)(object)Player.m_localPlayer != (Object)null)
				{
					num2 = Player.m_localPlayer.GetPlayerID();
				}
			}
			catch
			{
			}
			if (num == 0L || num2 == 0L)
			{
				return;
			}
			string item = num + "|" + num2;
			if (!MigratedWorldPlayers.Add(item))
			{
				return;
			}
			string text = Path.Combine(Paths.ConfigPath, "PlayerMapLayers", "client", "world_" + num);
			if (!Directory.Exists(text))
			{
				return;
			}
			string path = ClientWorldDirectory();
			string[] obj2 = new string[3]
			{
				"imports_v4_" + num2 + ".txt",
				"last-selection-v1_" + num2 + ".txt",
				"last-shared-table-v1_" + num2 + ".pmlx"
			};
			bool flag = false;
			string[] array = obj2;
			foreach (string path2 in array)
			{
				string text2 = Path.Combine(text, path2);
				if (File.Exists(text2))
				{
					flag = true;
					MoveNewest(text2, Path.Combine(path, path2));
				}
			}
			if (flag)
			{
				ApplyCapturedPreferences();
				string path3 = "last-import-v4.undo";
				string text3 = Path.Combine(text, path3);
				if (File.Exists(text3))
				{
					MoveNewest(text3, Path.Combine(path, path3));
				}
				if (Plugin.Log != null)
				{
					Plugin.Log.LogInfo((object)"Migrated this player's local Player Map Layers state outside the Gale profile.");
				}
			}
		}

		private static void BindAndRemove<T>(ConfigFile config, string section, string key, T defaultValue)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			ConfigDefinition val = new ConfigDefinition(section, key);
			config.Bind<T>(val, defaultValue, new ConfigDescription("Migrated to the per-user LocalLow settings file.", (AcceptableValueBase)null, Array.Empty<object>()));
			config.Remove(val);
		}

		private static T CaptureAndRemove<T>(ConfigFile config, string section, string key, T defaultValue)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			ConfigDefinition val = new ConfigDefinition(section, key);
			T value = config.Bind<T>(val, defaultValue, new ConfigDescription("Migrated to the per-user LocalLow settings file.", (AcceptableValueBase)null, Array.Empty<object>())).Value;
			config.Remove(val);
			return value;
		}

		private static void ApplyCapturedPreferences()
		{
			LegacyPreferences legacyPreferences = _legacyPreferences;
			_legacyPreferences = null;
			if (legacyPreferences != null && PreferencesFile != null)
			{
				Plugin.PreviewBeforeImport.Value = legacyPreferences.PreviewBeforeImport;
				Plugin.UploadOnCartographyWrite.Value = legacyPreferences.UploadOnCartographyWrite;
				Plugin.RemotePinFilter.Value = legacyPreferences.RemotePinFilter;
				Plugin.AuthorPinHalos.Value = legacyPreferences.AuthorPinHalos;
				Plugin.ExplorationTintEnabled.Value = legacyPreferences.ExplorationTintEnabled;
				Plugin.SinglePlayerTintOpacity.Value = ((Math.Abs(legacyPreferences.SinglePlayerTintOpacity - 0.16f) < 0.0001f) ? 0.08f : legacyPreferences.SinglePlayerTintOpacity);
				Plugin.SharedTableTintOpacity.Value = legacyPreferences.SharedTableTintOpacity;
				Plugin.OverlapTintOpacity.Value = ((Math.Abs(legacyPreferences.OverlapTintOpacity - 0.04f) < 0.0001f) ? 0.015f : legacyPreferences.OverlapTintOpacity);
				Plugin.ExplorationTintSaturation.Value = legacyPreferences.ExplorationTintSaturation;
				Plugin.UseParchmentTheme.Value = legacyPreferences.UseParchmentTheme;
				Plugin.WindowScale.Value = legacyPreferences.WindowScale;
				PreferencesFile.Save();
			}
		}

		private static void MoveNewest(string source, string destination)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(destination));
			if (!File.Exists(destination) || File.GetLastWriteTimeUtc(source) > File.GetLastWriteTimeUtc(destination))
			{
				File.Copy(source, destination, overwrite: true);
			}
			File.Delete(source);
		}
	}
	internal static class L10n
	{
		private static readonly Dictionary<string, string> English = new Dictionary<string, string>
		{
			["title"] = "PLAYER MAP LAYERS",
			["open_layers"] = "Filter",
			["refresh"] = "Refresh",
			["close"] = "Close",
			["apply"] = "Import selected discoveries",
			["undo"] = "Undo last import",
			["isolate"] = "Solo",
			["selected"] = "Selected",
			["already_imported"] = "Imported",
			["revision"] = "Revision",
			["updated"] = "Updated",
			["identity"] = "My identity",
			["color"] = "Color",
			["color_sky"] = "Sky",
			["color_orange"] = "Orange",
			["color_green"] = "Green",
			["color_pink"] = "Pink",
			["color_red"] = "Red",
			["color_blue"] = "Blue",
			["color_yellow"] = "Yellow",
			["color_purple"] = "Purple",
			["color_turquoise"] = "Turquoise",
			["color_lime"] = "Lime",
			["color_white"] = "White",
			["emblem"] = "Emblem",
			["sync_profile"] = "Sync my personal exploration",
			["shared_table"] = "Shared table exploration",
			["exploration_tab"] = "Exploration",
			["color_configuration"] = "Individual color configuration",
			["enable_exploration_tint"] = "Enable player colors",
			["exploration_tint_help"] = "One color per exclusive area; overlaps stay neutral and very light. No initials are added by default.",
			["player_tint_opacity"] = "Player colors",
			["table_tint_opacity"] = "Shared table",
			["tint_saturation"] = "Color saturation",
			["pins_tab"] = "Shared pins",
			["shared_pins"] = "Shared-pin display",
			["native_master_on"] = "Cartography table: ON — shared pins are allowed.",
			["native_master_off"] = "Cartography table: OFF — native and mod shared pins are hidden.",
			["pins_filter_help"] = "All / Players / None filters only pins synchronized through Player Map Layers. Native, BetterMap and TargetPortal pins remain owned by their original mods.",
			["author_pin_halos"] = "Colored author halos on mod pins",
			["pins_all"] = "All",
			["pins_selected_players"] = "Players",
			["pins_none"] = "None",
			["refresh_pins"] = "Refresh pins",
			["visible_players"] = "Visible players",
			["legacy_table_pins"] = "Legacy cartography-table pins (owner unknown)",
			["no_remote_pins"] = "No remote Player Map Layers pin is available.",
			["my_shareable_pins"] = "My shareable pins",
			["private_by_default"] = "Private by default. Only checked pins are sent to the server.",
			["no_shareable_pins"] = "No saved personal pin can be shared.",
			["unnamed_pin"] = "Unnamed pin",
			["pin_icon_original"] = "Original",
			["pin_icon_unknown"] = "Unknown",
			["pin_icon_custom"] = "Custom icon",
			["pin_icon_ore"] = "Pickaxe / ore",
			["pin_icon_loot"] = "Chest / loot",
			["pin_icon_portal"] = "Portal",
			["pin_icon_forage"] = "Forage",
			["pin_icon_dungeon"] = "Dungeon",
			["pin_icon_spawner"] = "Spawner",
			["pin_icon_vegvisir"] = "Vegvisir",
			["pin_icon_beehive"] = "Beehive",
			["pin_icon_tar"] = "Tar",
			["pin_icon_sap"] = "Sap",
			["sync_shared_pins"] = "Synchronize checked pins",
			["pins_refreshed"] = "Shared pins refreshed.",
			["uploading_pins"] = "Uploading explicitly shared pins...",
			["too_many_pins"] = "Too many pins are selected for sharing.",
			["select_layer"] = "Select at least one layer.",
			["downloading_layers"] = "Downloading selected layers...",
			["preview_active"] = "Temporary preview active. Your map has not been changed.",
			["preview_failed"] = "Preview failed; the original map was preserved.",
			["preview_cancelled"] = "Preview cancelled; original map restored.",
			["import_applied"] = "Selected layers applied. An undo point was created.",
			["import_no_change"] = "Nothing new was imported: these discoveries were already present. The selected view remains active.",
			["import_failed"] = "Import incomplete. Use Undo last import.",
			["import_undone"] = "Last import undone.",
			["layers_refreshed"] = "Layer list refreshed.",
			["layer_received"] = "Layer received:",
			["layer_too_large"] = "Layer exceeds the server size limit.",
			["uploading_layer"] = "Uploading your map layer...",
			["network_error"] = "Network or layer data error.",
			["server_rejected"] = "Server rejected the request",
			["empty"] = "No player layer has been recorded on this server yet.",
			["multi_selection_help"] = "Check any combination; the map updates immediately. Solo replaces it with one layer. Selected: {0}.",
			["selection_presets"] = "Quick selection",
			["preset_personal_table"] = "Me + table",
			["preset_all"] = "All",
			["preset_none"] = "None",
			["selection_saved_help"] = "This selection is saved for this world and automatically restored whenever the map opens.",
			["permanent_import"] = "Permanent import",
			["permanent_import_help"] = "Optional: copies the selected remote discoveries into your saved shared map. This is not required to remember or display the selected view.",
			["normal_view"] = "Normal map restored. No layer is selected."
		};

		private static readonly Dictionary<string, string> French = new Dictionary<string, string>
		{
			["title"] = "COUCHES CARTOGRAPHIQUES",
			["open_layers"] = "Filtre",
			["refresh"] = "Actualiser",
			["close"] = "Fermer",
			["apply"] = "Importer les découvertes sélectionnées",
			["undo"] = "Annuler le dernier import",
			["isolate"] = "Isoler",
			["selected"] = "Sélectionné",
			["already_imported"] = "Importé",
			["revision"] = "Révision",
			["updated"] = "Actualisé",
			["identity"] = "Mon identité",
			["color"] = "Couleur",
			["color_sky"] = "Ciel",
			["color_orange"] = "Orange",
			["color_green"] = "Vert",
			["color_pink"] = "Rose",
			["color_red"] = "Rouge",
			["color_blue"] = "Bleu",
			["color_yellow"] = "Jaune",
			["color_purple"] = "Violet",
			["color_turquoise"] = "Turquoise",
			["color_lime"] = "Citron vert",
			["color_white"] = "Blanc",
			["emblem"] = "Emblème",
			["sync_profile"] = "Synchroniser mon exploration personnelle",
			["shared_table"] = "Exploration partagée de la table",
			["exploration_tab"] = "Exploration",
			["color_configuration"] = "Configuration des couleurs individuelles",
			["enable_exploration_tint"] = "Activer les couleurs des joueurs",
			["exploration_tint_help"] = "Une couleur par zone exclusive ; les chevauchements restent neutres et très légers. Aucune initiale n’est ajoutée par défaut.",
			["player_tint_opacity"] = "Couleurs des joueurs",
			["table_tint_opacity"] = "Table partagée",
			["tint_saturation"] = "Saturation des couleurs",
			["pins_tab"] = "Pins partagés",
			["shared_pins"] = "Affichage des pins partagés",
			["native_master_on"] = "Table de cartographie : ACTIVÉE — les pins partagés sont autorisés.",
			["native_master_off"] = "Table de cartographie : DÉSACTIVÉE — les pins partagés natifs et du mod sont masqués.",
			["pins_filter_help"] = "Tous / Joueurs / Aucun filtre uniquement les pins synchronisés par Player Map Layers. Les pins natifs, BetterMap et TargetPortal restent gérés par leur mod d’origine.",
			["author_pin_halos"] = "Contours colorés sur les pins du mod",
			["pins_all"] = "Tous",
			["pins_selected_players"] = "Joueurs",
			["pins_none"] = "Aucun",
			["refresh_pins"] = "Actualiser les pins",
			["visible_players"] = "Joueurs visibles",
			["legacy_table_pins"] = "Pins hérités de la table (propriétaire inconnu)",
			["no_remote_pins"] = "Aucun pin distant Player Map Layers n’est disponible.",
			["my_shareable_pins"] = "Mes pins partageables",
			["private_by_default"] = "Privés par défaut. Seuls les pins cochés sont envoyés au serveur.",
			["no_shareable_pins"] = "Aucun pin personnel enregistré n’est partageable.",
			["unnamed_pin"] = "Pin sans nom",
			["pin_icon_original"] = "Original",
			["pin_icon_unknown"] = "Inconnu",
			["pin_icon_custom"] = "Icône personnalisée",
			["pin_icon_ore"] = "Pioche / minerai",
			["pin_icon_loot"] = "Coffre / butin",
			["pin_icon_portal"] = "Portail",
			["pin_icon_forage"] = "Cueillette",
			["pin_icon_dungeon"] = "Donjon",
			["pin_icon_spawner"] = "Générateur",
			["pin_icon_vegvisir"] = "Vegvisir",
			["pin_icon_beehive"] = "Ruche",
			["pin_icon_tar"] = "Goudron",
			["pin_icon_sap"] = "Sève",
			["sync_shared_pins"] = "Synchroniser les pins cochés",
			["pins_refreshed"] = "Pins partagés actualisés.",
			["uploading_pins"] = "Envoi des pins explicitement partagés...",
			["too_many_pins"] = "Trop de pins sont sélectionnés pour le partage.",
			["select_layer"] = "Sélectionne au moins une couche.",
			["downloading_layers"] = "Téléchargement des couches sélectionnées...",
			["preview_active"] = "Aperçu temporaire actif. Ta carte n’a pas été modifiée.",
			["preview_failed"] = "Échec de l’aperçu ; la carte originale est conservée.",
			["preview_cancelled"] = "Aperçu annulé ; carte originale restaurée.",
			["import_applied"] = "Couches appliquées. Un point d’annulation a été créé.",
			["import_no_change"] = "Aucune nouvelle découverte à importer : ces zones étaient déjà présentes. La vue sélectionnée reste active.",
			["import_failed"] = "Import incomplet. Utilise Annuler le dernier import.",
			["import_undone"] = "Dernier import annulé.",
			["layers_refreshed"] = "Liste des couches actualisée.",
			["layer_received"] = "Couche reçue :",
			["layer_too_large"] = "La couche dépasse la limite du serveur.",
			["uploading_layer"] = "Envoi de ta couche cartographique...",
			["network_error"] = "Erreur réseau ou données cartographiques invalides.",
			["server_rejected"] = "Le serveur a refusé la requête",
			["empty"] = "Aucune couche de joueur n’a encore été enregistrée sur ce serveur.",
			["multi_selection_help"] = "Coche la combinaison voulue : la carte s’actualise immédiatement. Isoler la remplace par une seule couche. Sélection : {0}.",
			["selection_presets"] = "Sélection rapide",
			["preset_personal_table"] = "Moi + table",
			["preset_all"] = "Tout",
			["preset_none"] = "Aucun",
			["selection_saved_help"] = "Cette sélection est mémorisée pour ce monde et réaffichée automatiquement à chaque ouverture de la carte.",
			["permanent_import"] = "Import permanent",
			["permanent_import_help"] = "Optionnel : copie définitivement les découvertes distantes sélectionnées dans la partie partagée de ta carte. Ce n’est pas nécessaire pour mémoriser ou afficher la vue choisie.",
			["normal_view"] = "Carte normale restaurée. Aucune couche n’est sélectionnée."
		};

		private static readonly Dictionary<string, string> EmblemsFrench = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["Compass"] = "Boussole",
			["Raven"] = "Corbeau",
			["Wolf"] = "Loup",
			["Bear"] = "Ours",
			["Hammer"] = "Marteau",
			["Axe"] = "Hache",
			["Shield"] = "Bouclier",
			["Rune"] = "Rune",
			["Tree"] = "Arbre",
			["Mountain"] = "Montagne",
			["Wave"] = "Vague",
			["Star"] = "Étoile",
			["Skull"] = "Crâne",
			["Ship"] = "Navire",
			["Flame"] = "Flamme"
		};

		internal static string T(string key)
		{
			if (!(IsFrench() ? French : English).TryGetValue(key, out var value))
			{
				return key;
			}
			return value;
		}

		internal static string Emblem(string canonicalName)
		{
			if (!IsFrench())
			{
				return canonicalName ?? "";
			}
			string text;
			if (!EmblemsFrench.TryGetValue(canonicalName ?? "", out var value))
			{
				text = canonicalName;
				if (text == null)
				{
					return "";
				}
			}
			else
			{
				text = value;
			}
			return text;
		}

		internal static bool IsFrench()
		{
			try
			{
				Type type = FindGameType("Localization");
				if (type == null)
				{
					return false;
				}
				object obj = null;
				FieldInfo field = type.GetField("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					obj = field.GetValue(null);
				}
				if (obj == null)
				{
					PropertyInfo property = type.GetProperty("instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					if (property != null)
					{
						obj = property.GetValue(null, null);
					}
				}
				MethodInfo method = type.GetMethod("GetSelectedLanguage", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				return ((method != null) ? method.Invoke(method.IsStatic ? null : obj, null) : null)?.ToString().StartsWith("French", StringComparison.OrdinalIgnoreCase) ?? false;
			}
			catch
			{
				return false;
			}
		}

		private static Type FindGameType(string name)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				Type type = assemblies[i].GetType(name, throwOnError: false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}
	}
	internal static class MapBridge
	{
		internal sealed class PinSnapshot
		{
			internal readonly object[] Pins;

			internal PinSnapshot(object[] pins)
			{
				Pins = pins;
			}
		}

		private static MethodInfo _setMapMode;

		private static FieldInfo _exploredField;

		private static FieldInfo _exploredOthersField;

		private static FieldInfo _fogTextureField;

		private static FieldInfo _pinsField;

		private static MethodInfo _updatePinsMethod;

		private static bool _explorationStorageLogged;

		internal static bool Available => (Object)(object)Minimap.instance != (Object)null;

		internal static byte[] CaptureExplorationState()
		{
			if (!TryGetExplorationArrays(out var personal, out var shared))
			{
				return null;
			}
			try
			{
				return ExplorationLayerData.EncodeState(personal, shared);
			}
			catch (Exception exception)
			{
				Plugin.Log.LogWarning((object)("Could not capture the exploration-only map state: " + Unwrap(exception).Message));
				return null;
			}
		}

		internal static bool RestoreExplorationState(byte[] data)
		{
			if (data == null || data.Length == 0)
			{
				return false;
			}
			try
			{
				ExplorationLayerData.DecodeState(data, out var personal, out var shared);
				if (!TryGetExplorationArrays(out var personal2, out var shared2))
				{
					return false;
				}
				if (personal.Length != personal2.Length || (shared != null && (shared2 == null || shared.Length != shared2.Length)))
				{
					throw new InvalidOperationException("Exploration-state dimensions do not match this world map.");
				}
				personal2.SetAll(value: false);
				personal2.Or(personal);
				if (shared2 != null)
				{
					shared2.SetAll(value: false);
					if (shared != null)
					{
						shared2.Or(shared);
					}
				}
				return RefreshFog(personal2, shared2);
			}
			catch (Exception exception)
			{
				Plugin.Log.LogWarning((object)("Could not restore the exploration-only map state: " + Unwrap(exception).Message));
				return false;
			}
		}

		internal static PinSnapshot CapturePinState()
		{
			if ((Object)(object)Minimap.instance == (Object)null)
			{
				return null;
			}
			try
			{
				if (_pinsField == null)
				{
					_pinsField = AccessTools.Field(typeof(Minimap), "m_pins");
				}
				IList list = ((_pinsField != null) ? (_pinsField.GetValue(Minimap.instance) as IList) : null);
				if (list == null)
				{
					Plugin.Log.LogWarning((object)"Valheim's live pin collection was not found.");
					return null;
				}
				object[] array = new object[list.Count];
				for (int i = 0; i < list.Count; i++)
				{
					array[i] = list[i];
				}
				return new PinSnapshot(array);
			}
			catch (Exception exception)
			{
				Plugin.Log.LogWarning((object)("Could not capture Valheim's live pin collection: " + Unwrap(exception).Message));
				return null;
			}
		}

		internal static bool RestorePinState(PinSnapshot snapshot)
		{
			if (snapshot == null || (Object)(object)Minimap.instance == (Object)null)
			{
				return false;
			}
			try
			{
				if (_pinsField == null)
				{
					_pinsField = AccessTools.Field(typeof(Minimap), "m_pins");
				}
				IList list = ((_pinsField != null) ? (_pinsField.GetValue(Minimap.instance) as IList) : null);
				if (list == null)
				{
					return false;
				}
				List<object> list2 = new List<object>();
				object[] array = list.Cast<object>().ToArray();
				foreach (object current in array)
				{
					if (!snapshot.Pins.Any((object obj) => obj == current))
					{
						list2.Add(current);
					}
				}
				int num = 0;
				foreach (object item in list2)
				{
					if (RemoveLivePin(item, list))
					{
						num++;
					}
				}
				int num2 = 0;
				for (int num3 = 0; num3 < snapshot.Pins.Length; num3++)
				{
					object original = snapshot.Pins[num3];
					if (!list.Cast<object>().Any((object obj) => obj == original))
					{
						list.Insert(Math.Min(num3, list.Count), original);
						num2++;
					}
				}
				RefreshPinLayout();
				Plugin.Log.LogInfo((object)("Reconciled the live pin collection without rebuilding it (kept=" + snapshot.Pins.Length + ", removed read additions=" + num + ", restored missing=" + num2 + ")."));
				return true;
			}
			catch (Exception exception)
			{
				Plugin.Log.LogWarning((object)("Could not restore Valheim's live pin collection: " + Unwrap(exception).Message));
				return false;
			}
		}

		private static bool RemoveLivePin(object pin, IList pins)
		{
			if (pin == null)
			{
				return true;
			}
			try
			{
				MethodInfo methodInfo = (from method in typeof(Minimap).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
					where method.Name == "RemovePin"
					select method).FirstOrDefault(delegate(MethodInfo method)
				{
					ParameterInfo[] parameters = method.GetParameters();
					return parameters.Length == 1 && parameters[0].ParameterType.IsInstanceOfType(pin);
				});
				if (methodInfo != null)
				{
					methodInfo.Invoke(Minimap.instance, new object[1] { pin });
					return !pins.Cast<object>().Any((object current) => current == pin);
				}
			}
			catch (Exception exception)
			{
				Plugin.Debug("Valheim RemovePin rejected a cartography-read addition: " + Unwrap(exception).Message);
			}
			DestroyPinVisual(pin);
			for (int num = pins.Count - 1; num >= 0; num--)
			{
				if (pins[num] == pin)
				{
					pins.RemoveAt(num);
					return true;
				}
			}
			return true;
		}

		private static void DestroyPinVisual(object pin)
		{
			Type type = pin.GetType();
			string[] array = new string[3] { "m_uiElement", "uiElement", "UIElement" };
			foreach (string name in array)
			{
				FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				object? obj = ((field != null) ? field.GetValue(pin) : null);
				GameObject val = (GameObject)((obj is GameObject) ? obj : null);
				Component val2 = (Component)((obj is Component) ? obj : null);
				if ((Object)(object)val == (Object)null && (Object)(object)val2 != (Object)null)
				{
					val = val2.gameObject;
				}
				if (!((Object)(object)val == (Object)null))
				{
					Object.Destroy((Object)(object)val);
					break;
				}
			}
		}

		private static void RefreshPinLayout()
		{
			try
			{
				if (_updatePinsMethod == null)
				{
					_updatePinsMethod = typeof(Minimap).GetMethod("UpdatePins", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
				}
				if (_updatePinsMethod != null)
				{
					_updatePinsMethod.Invoke(Minimap.instance, null);
				}
			}
			catch (Exception exception)
			{
				Plugin.Debug("Could not refresh pins after cartography reconciliation: " + Unwrap(exception).Message);
			}
		}

		internal static byte[] CapturePersonalLayer()
		{
			if (!TryGetExplorationArrays(out var personal, out var _))
			{
				return null;
			}
			try
			{
				return ExplorationLayerData.Encode(personal);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not encode the personal exploration layer: " + ex.Message));
				return null;
			}
		}

		internal static byte[] CapturePersonalLayerFromState(byte[] state)
		{
			if (state == null || state.Length == 0)
			{
				return null;
			}
			try
			{
				ExplorationLayerData.DecodeState(state, out var personal, out var _);
				return ExplorationLayerData.Encode(personal);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not extract the personal layer from the saved exploration state: " + ex.Message));
				return null;
			}
		}

		internal static byte[] CaptureVisibleLayer()
		{
			if (!TryGetExplorationArrays(out var personal, out var shared))
			{
				return null;
			}
			BitArray bitArray = new BitArray(p