Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PlayerMapLayers v0.2.13
BepInEx/plugins/PlayerMapLayers/PlayerMapLayers.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
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