Decompiled source of Keyring v0.2.1

Keyring.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Adam Robichaud")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © 2026 Adam Robichaud")]
[assembly: AssemblyDescription("Remembers Valheim server passwords in the operating system's credential store, logs you in automatically, and adds a Continue button to the main menu.")]
[assembly: AssemblyFileVersion("0.2.1.0")]
[assembly: AssemblyInformationalVersion("0.2.1+d9fa889d799d55e57262f035e0ce5e38001e0ec0")]
[assembly: AssemblyProduct("Keyring")]
[assembly: AssemblyTitle("Keyring")]
[assembly: AssemblyVersion("0.2.1.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 Keyring
{
	[HarmonyPatch]
	internal static class ContinueMenu
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static PopupButtonCallback <0>__Pop;
		}

		private const string ButtonName = "Keyring_Continue";

		private static readonly FieldInfo F_menuButtons = AccessTools.Field(typeof(FejdStartup), "m_menuButtons");

		private static readonly FieldInfo F_queuedJoinServer = AccessTools.Field(typeof(FejdStartup), "m_queuedJoinServer");

		private static readonly FieldInfo F_joinServer = AccessTools.Field(typeof(FejdStartup), "m_joinServer");

		private static readonly FieldInfo F_startingWorld = AccessTools.Field(typeof(FejdStartup), "m_startingWorld");

		private static readonly FieldInfo F_world = AccessTools.Field(typeof(FejdStartup), "m_world");

		private static readonly FieldInfo F_profiles = AccessTools.Field(typeof(FejdStartup), "m_profiles");

		private static readonly FieldInfo F_profileIndex = AccessTools.Field(typeof(FejdStartup), "m_profileIndex");

		private static readonly MethodInfo M_setSelectedProfile = AccessTools.Method(typeof(FejdStartup), "SetSelectedProfile", (Type[])null, (Type[])null);

		private static readonly MethodInfo M_findWorld = AccessTools.Method(typeof(FejdStartup), "FindWorld", (Type[])null, (Type[])null);

		private static readonly MethodInfo M_hideAll = AccessTools.Method(typeof(FejdStartup), "HideAll", (Type[])null, (Type[])null);

		private static Button _button;

		private static HoverTooltip _tooltip;

		[HarmonyPatch(typeof(FejdStartup), "TransitionToMainScene")]
		[HarmonyPrefix]
		private static void BeforeTransition(FejdStartup __instance)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				PlayerProfile profile = SelectedProfile(__instance);
				if ((bool)F_startingWorld.GetValue(__instance))
				{
					World val = (World)F_world.GetValue(__instance);
					if (val != null)
					{
						LastSession.CaptureWorld(val.m_name, profile);
					}
				}
				else
				{
					ServerJoinData server = (ServerJoinData)F_joinServer.GetValue(__instance);
					if (((ServerJoinData)(ref server)).IsValid)
					{
						LastSession.CaptureServer(server, profile);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not record the session for Continue: " + ex.Message));
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		[HarmonyPostfix]
		private static void AfterSpawned()
		{
			LastSession.CommitCandidate();
		}

		private static PlayerProfile SelectedProfile(FejdStartup fs)
		{
			List<PlayerProfile> list = (List<PlayerProfile>)F_profiles.GetValue(fs);
			int num = (int)F_profileIndex.GetValue(fs);
			if (list == null || num < 0 || num >= list.Count)
			{
				return null;
			}
			return list[num];
		}

		[HarmonyPatch(typeof(FejdStartup), "Awake")]
		[HarmonyPostfix]
		private static void AfterAwake(FejdStartup __instance)
		{
			if (!Plugin.ShowContinue.Value || (Object)(object)__instance.m_menuList == (Object)null)
			{
				return;
			}
			try
			{
				AddButton(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Could not add the Continue button: " + ex));
			}
		}

		private static void AddButton(FejdStartup fs)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_017f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Expected O, but got Unknown
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Expected O, but got Unknown
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			Button[] componentsInChildren = fs.m_menuList.GetComponentsInChildren<Button>(true);
			if (componentsInChildren.Length == 0)
			{
				return;
			}
			Button val = FindStartButton(componentsInChildren) ?? componentsInChildren[0];
			if (((Object)val).name == "Keyring_Continue")
			{
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
			((Object)val2).name = "Keyring_Continue";
			val2.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex());
			val2.SetActive(true);
			if ((Object)(object)((Component)((Component)val).transform.parent).GetComponent<LayoutGroup>() == (Object)null)
			{
				RectTransform val3 = (RectTransform)val2.transform;
				RectTransform val4 = (RectTransform)((Component)val).transform;
				Rect rect = val4.rect;
				Vector2 val5 = default(Vector2);
				((Vector2)(ref val5))..ctor(0f, ((Rect)(ref rect)).height + 4f);
				int num = ((Component)val).transform.GetSiblingIndex() + 1;
				if (num < ((Component)val).transform.parent.childCount)
				{
					Transform child = ((Component)val).transform.parent.GetChild(num);
					RectTransform val6 = (RectTransform)(object)((child is RectTransform) ? child : null);
					if (val6 != null && (Object)(object)((Component)val6).GetComponent<Button>() != (Object)null)
					{
						val5 = val4.anchoredPosition - val6.anchoredPosition;
					}
				}
				val3.anchoredPosition = val4.anchoredPosition + val5;
			}
			TMP_Text componentInChildren = val2.GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.text = "Continue";
			}
			_button = val2.GetComponent<Button>();
			_button.onClick = new ButtonClickedEvent();
			((UnityEvent)_button.onClick).AddListener((UnityAction)delegate
			{
				OnContinue(fs);
			});
			_tooltip = val2.AddComponent<HoverTooltip>();
			_tooltip.Build(componentInChildren);
			Refresh();
			F_menuButtons.SetValue(fs, fs.m_menuList.GetComponentsInChildren<Button>());
		}

		private static Button FindStartButton(Button[] buttons)
		{
			Button[] array = buttons;
			foreach (Button val in array)
			{
				for (int j = 0; j < ((UnityEventBase)val.onClick).GetPersistentEventCount(); j++)
				{
					if (((UnityEventBase)val.onClick).GetPersistentMethodName(j) == "OnStartGame")
					{
						return val;
					}
				}
			}
			array = buttons;
			foreach (Button val2 in array)
			{
				if (((Object)val2).name.ToLowerInvariant().Contains("start"))
				{
					return val2;
				}
			}
			return null;
		}

		internal static void Refresh()
		{
			if (!((Object)(object)_button == (Object)null))
			{
				bool interactable = LastSession.Current != LastSession.Kind.None;
				((Selectable)_button).interactable = interactable;
				if ((Object)(object)_tooltip != (Object)null)
				{
					_tooltip.Text = LastSession.Describe();
				}
			}
		}

		private static void OnContinue(FejdStartup fs)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Expected O, but got Unknown
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			LastSession.Kind current = LastSession.Current;
			if (current == LastSession.Kind.None)
			{
				return;
			}
			fs.m_mainMenu.SetActive(false);
			M_setSelectedProfile.Invoke(fs, new object[1] { LastSession.CharacterFile });
			PlayerProfile val = SelectedProfile(fs);
			if (val == null || val.GetFilename() != LastSession.CharacterFile)
			{
				Fail(fs, "Character \"" + LastSession.CharacterName + "\" was not found. Pick one from Start Game.");
				return;
			}
			if (current == LastSession.Kind.Server)
			{
				if (!LastSession.TryGetServer(out var server))
				{
					Fail(fs, "The remembered server could not be read. Join it once more from the server list.");
					return;
				}
				F_queuedJoinServer.SetValue(fs, server);
				fs.OnCharacterStart();
				return;
			}
			F_queuedJoinServer.SetValue(fs, ServerJoinData.None);
			fs.OnCharacterStart();
			World val2 = (World)M_findWorld.Invoke(fs, new object[1] { LastSession.WorldName });
			if (val2 == null)
			{
				Fail(fs, "World \"" + LastSession.WorldName + "\" was not found. Pick one from Start Game.");
				return;
			}
			F_world.SetValue(fs, val2);
			fs.OnWorldStart();
		}

		private static void Fail(FejdStartup fs, string message)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			Plugin.Log.LogWarning((object)("Continue: " + message));
			LastSession.Clear();
			Refresh();
			M_hideAll?.Invoke(fs, null);
			fs.m_mainMenu.SetActive(true);
			object obj = <>O.<0>__Pop;
			if (obj == null)
			{
				PopupButtonCallback val = UnifiedPopup.Pop;
				<>O.<0>__Pop = val;
				obj = (object)val;
			}
			UnifiedPopup.Push((PopupBase)new WarningPopup("Continue", message, (PopupButtonCallback)obj, false));
		}
	}
	internal sealed class HoverTooltip : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler
	{
		private const float GapPixels = 16f;

		private GameObject _panel;

		private RectTransform _panelRt;

		private TMP_Text _label;

		private string _text = "";

		private readonly Vector3[] _corners = (Vector3[])(object)new Vector3[4];

		public string Text
		{
			get
			{
				return _text;
			}
			set
			{
				_text = value ?? "";
				if ((Object)(object)_label != (Object)null)
				{
					_label.text = _text;
				}
			}
		}

		public void Build(TMP_Text styleSource)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Expected O, but got Unknown
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Expected O, but got Unknown
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			_panel = new GameObject("Keyring_ContinueTooltip", new Type[5]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(VerticalLayoutGroup),
				typeof(ContentSizeFitter),
				typeof(LayoutElement)
			});
			_panel.GetComponent<LayoutElement>().ignoreLayout = true;
			Canvas componentInParent = ((Component)this).GetComponentInParent<Canvas>();
			Transform val = (((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent.rootCanvas).transform : ((Component)this).transform.parent);
			_panel.transform.SetParent(val, false);
			_panel.transform.SetAsLastSibling();
			_panelRt = (RectTransform)_panel.transform;
			_panelRt.anchorMin = new Vector2(0f, 0f);
			_panelRt.anchorMax = new Vector2(0f, 0f);
			_panelRt.pivot = new Vector2(0f, 0.5f);
			Image component = _panel.GetComponent<Image>();
			((Graphic)component).color = new Color(0.05f, 0.05f, 0.05f, 0.9f);
			((Graphic)component).raycastTarget = false;
			VerticalLayoutGroup component2 = _panel.GetComponent<VerticalLayoutGroup>();
			((LayoutGroup)component2).padding = new RectOffset(12, 12, 8, 8);
			((HorizontalOrVerticalLayoutGroup)component2).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)component2).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)component2).childForceExpandWidth = false;
			((HorizontalOrVerticalLayoutGroup)component2).childForceExpandHeight = false;
			ContentSizeFitter component3 = _panel.GetComponent<ContentSizeFitter>();
			component3.horizontalFit = (FitMode)2;
			component3.verticalFit = (FitMode)2;
			GameObject val2 = new GameObject("Text", new Type[2]
			{
				typeof(RectTransform),
				typeof(LayoutElement)
			});
			val2.transform.SetParent(_panel.transform, false);
			val2.GetComponent<LayoutElement>().preferredWidth = 380f;
			_label = (TMP_Text)(object)val2.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)styleSource != (Object)null)
			{
				_label.font = styleSource.font;
				_label.fontSharedMaterial = styleSource.fontSharedMaterial;
				_label.fontSize = Mathf.Max(14f, styleSource.fontSize * 0.7f);
			}
			else
			{
				_label.fontSize = 18f;
			}
			((Graphic)_label).color = new Color(0.95f, 0.9f, 0.75f);
			_label.alignment = (TextAlignmentOptions)513;
			_label.textWrappingMode = (TextWrappingModes)1;
			((Graphic)_label).raycastTarget = false;
			_label.text = _text;
			_panel.SetActive(false);
		}

		public void OnPointerEnter(PointerEventData eventData)
		{
			Show(on: true);
		}

		public void OnPointerExit(PointerEventData eventData)
		{
			Show(on: false);
		}

		public void OnSelect(BaseEventData eventData)
		{
			Show(on: true);
		}

		public void OnDeselect(BaseEventData eventData)
		{
			Show(on: false);
		}

		private void OnDisable()
		{
			Show(on: false);
		}

		private void OnDestroy()
		{
			if ((Object)(object)_panel != (Object)null)
			{
				Object.Destroy((Object)(object)_panel);
			}
		}

		private void Show(bool on)
		{
			if (!((Object)(object)_panel == (Object)null))
			{
				if (on)
				{
					Place();
				}
				_panel.SetActive(on);
			}
		}

		private void Place()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			((RectTransform)((Component)this).transform).GetWorldCorners(_corners);
			Vector3 val = (_corners[2] + _corners[3]) * 0.5f;
			float x = ((Transform)_panelRt).lossyScale.x;
			((Transform)_panelRt).position = val + new Vector3(16f * x, 0f, 0f);
		}

		private void LateUpdate()
		{
			if ((Object)(object)_panel != (Object)null && _panel.activeSelf)
			{
				Place();
			}
		}
	}
	internal static class CredentialStoreFactory
	{
		public static ICredentialStore Create()
		{
			if (Environment.OSVersion.Platform != PlatformID.Win32NT)
			{
				return new NullCredentialStore("no credential store backend for " + Environment.OSVersion.Platform.ToString() + "; passwords are not saved on this platform");
			}
			if (IsWine())
			{
				if (!Plugin.AllowWineStore.Value)
				{
					return new NullCredentialStore("running under Wine/Proton, whose credential store is not encrypted; set AllowWineStore=true to use it anyway");
				}
				Plugin.Log.LogWarning((object)"Running under Wine/Proton: the emulated Windows credential store is only obfuscated, not encrypted.");
			}
			WindowsCredentialStore windowsCredentialStore = new WindowsCredentialStore();
			if (!windowsCredentialStore.SelfTest(out var error))
			{
				return new NullCredentialStore("Windows Credential Manager unavailable: " + error);
			}
			return windowsCredentialStore;
		}

		[DllImport("ntdll.dll", CallingConvention = CallingConvention.Cdecl)]
		private static extern IntPtr wine_get_version();

		private static bool IsWine()
		{
			try
			{
				wine_get_version();
				return true;
			}
			catch (EntryPointNotFoundException)
			{
				return false;
			}
			catch (DllNotFoundException)
			{
				return false;
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogDebug((object)("Wine check failed: " + ex3.Message));
				return false;
			}
		}
	}
	internal struct StoredEntry
	{
		public string Key;

		public string Comment;

		public DateTime LastWritten;
	}
	internal interface ICredentialStore
	{
		string Description { get; }

		bool IsAvailable { get; }

		bool TryRead(string key, out string secret);

		bool Write(string key, string secret, string comment);

		bool Delete(string key);

		List<StoredEntry> List();
	}
	internal sealed class NullCredentialStore : ICredentialStore
	{
		private readonly string _reason;

		public string Description => "none (" + _reason + ")";

		public bool IsAvailable => false;

		public NullCredentialStore(string reason)
		{
			_reason = reason;
		}

		public bool TryRead(string key, out string secret)
		{
			secret = null;
			return false;
		}

		public bool Write(string key, string secret, string comment)
		{
			return false;
		}

		public bool Delete(string key)
		{
			return false;
		}

		public List<StoredEntry> List()
		{
			return new List<StoredEntry>();
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class KeyringCommands
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Run;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleOptionsFetcher <>9__1_0;

			internal List<string> <Postfix>b__1_0()
			{
				return new List<string> { "list", "forget", "status" };
			}
		}

		private static List<StoredEntry> _lastList = new List<StoredEntry>();

		private static void Postfix()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Expected O, but got Unknown
			object obj = <>O.<0>__Run;
			if (obj == null)
			{
				ConsoleEvent val = Run;
				<>O.<0>__Run = val;
				obj = (object)val;
			}
			object obj2 = <>c.<>9__1_0;
			if (obj2 == null)
			{
				ConsoleOptionsFetcher val2 = () => new List<string> { "list", "forget", "status" };
				<>c.<>9__1_0 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("keyring", "[list|forget <n|all>|status] Manage server passwords saved by Keyring", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false);
		}

		private static void Run(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			switch ((args.Length > 1) ? args[1].ToLowerInvariant() : "list")
			{
			case "list":
				ListEntries(context);
				return;
			case "forget":
				Forget(context, (args.Length > 2) ? args[2] : "");
				return;
			case "status":
				Status(context);
				return;
			}
			if (context != null)
			{
				context.AddString("Usage: keyring [list|forget <n|all>|status]");
			}
		}

		private static void ListEntries(Terminal t)
		{
			if (!Plugin.Store.IsAvailable)
			{
				if (t != null)
				{
					t.AddString("Keyring: no credential store available (" + Plugin.Store.Description + ").");
				}
				return;
			}
			_lastList = Plugin.Store.List();
			if (_lastList.Count == 0)
			{
				if (t != null)
				{
					t.AddString("Keyring: no saved server passwords.");
				}
				return;
			}
			if (t != null)
			{
				t.AddString("Keyring: " + _lastList.Count + " saved server password(s) in " + Plugin.Store.Description + ":");
			}
			for (int i = 0; i < _lastList.Count; i++)
			{
				StoredEntry storedEntry = _lastList[i];
				string text = ((storedEntry.LastWritten == DateTime.MinValue) ? "" : ("  (saved " + storedEntry.LastWritten.ToString("yyyy-MM-dd HH:mm") + ")"));
				string text2 = (string.IsNullOrEmpty(storedEntry.Comment) ? storedEntry.Key : (storedEntry.Comment + "  [" + storedEntry.Key + "]"));
				if (t != null)
				{
					t.AddString("  " + (i + 1) + ". " + text2 + text);
				}
			}
			if (t != null)
			{
				t.AddString("Use 'keyring forget <n>' to delete one, or 'keyring forget all'.");
			}
		}

		private static void Forget(Terminal t, string which)
		{
			int result;
			if (!Plugin.Store.IsAvailable)
			{
				if (t != null)
				{
					t.AddString("Keyring: no credential store available.");
				}
			}
			else if (which == "all")
			{
				int num = 0;
				foreach (StoredEntry item in Plugin.Store.List())
				{
					if (Plugin.Store.Delete(item.Key))
					{
						num++;
					}
				}
				_lastList.Clear();
				if (t != null)
				{
					t.AddString("Keyring: forgot " + num + " saved password(s).");
				}
			}
			else if (int.TryParse(which, out result))
			{
				if (_lastList.Count == 0)
				{
					_lastList = Plugin.Store.List();
				}
				if (result < 1 || result > _lastList.Count)
				{
					if (t != null)
					{
						t.AddString("Keyring: no entry " + result + ". Run 'keyring list' first.");
					}
					return;
				}
				StoredEntry storedEntry = _lastList[result - 1];
				string text = (string.IsNullOrEmpty(storedEntry.Comment) ? storedEntry.Key : storedEntry.Comment);
				if (t != null)
				{
					t.AddString(Plugin.Store.Delete(storedEntry.Key) ? ("Keyring: forgot the password for " + text + ".") : ("Keyring: could not delete the entry for " + text + "."));
				}
				_lastList.Clear();
			}
			else if (t != null)
			{
				t.AddString("Usage: keyring forget <n|all>   (n from 'keyring list')");
			}
		}

		private static void Status(Terminal t)
		{
			if (t != null)
			{
				t.AddString("Keyring 0.2.1");
			}
			if (t != null)
			{
				t.AddString("  Store:             " + Plugin.Store.Description);
			}
			if (t != null)
			{
				t.AddString("  RememberPasswords: " + Plugin.RememberPasswords.Value);
			}
			if (t != null)
			{
				t.AddString("  AutoLogin:         " + Plugin.AutoLogin.Value);
			}
			if (t != null)
			{
				t.AddString("  ForgetRejected:    " + Plugin.ForgetRejected.Value);
			}
			if (Plugin.Store.IsAvailable && t != null)
			{
				t.AddString("  Saved servers:     " + Plugin.Store.List().Count);
			}
		}
	}
	internal static class LastSession
	{
		public enum Kind
		{
			None,
			World,
			Server
		}

		private sealed class Candidate
		{
			public Kind Kind;

			public string CharacterFile;

			public string CharacterName;

			public string WorldName;

			public ServerJoinData Server;

			public string ServerName;
		}

		private const string Section = "9 - Last session (written by the mod)";

		private static ConfigEntry<Kind> _kind;

		private static ConfigEntry<string> _characterFile;

		private static ConfigEntry<string> _characterName;

		private static ConfigEntry<string> _worldName;

		private static ConfigEntry<ServerJoinDataType> _serverType;

		private static ConfigEntry<string> _serverAddress;

		private static ConfigEntry<string> _serverOwner;

		private static ConfigEntry<string> _serverName;

		private static Candidate _candidate;

		public static Kind Current => _kind.Value;

		public static string CharacterFile => _characterFile.Value;

		public static string CharacterName => _characterName.Value;

		public static string WorldName => _worldName.Value;

		public static string ServerName => _serverName.Value;

		public static void Bind(ConfigFile config)
		{
			_kind = config.Bind<Kind>("9 - Last session (written by the mod)", "LastKind", Kind.None, "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_characterFile = config.Bind<string>("9 - Last session (written by the mod)", "LastCharacterFile", "", "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_characterName = config.Bind<string>("9 - Last session (written by the mod)", "LastCharacterName", "", "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_worldName = config.Bind<string>("9 - Last session (written by the mod)", "LastWorldName", "", "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_serverType = config.Bind<ServerJoinDataType>("9 - Last session (written by the mod)", "LastServerType", (ServerJoinDataType)0, "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_serverAddress = config.Bind<string>("9 - Last session (written by the mod)", "LastServerAddress", "", "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_serverOwner = config.Bind<string>("9 - Last session (written by the mod)", "LastServerOwner", "", "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
			_serverName = config.Bind<string>("9 - Last session (written by the mod)", "LastServerName", "", "Filled in automatically by the Continue button. Clear LastKind to None to forget it.");
		}

		public static void Clear()
		{
			_kind.Value = Kind.None;
		}

		public static void CaptureWorld(string worldName, PlayerProfile profile)
		{
			_candidate = new Candidate
			{
				Kind = Kind.World,
				WorldName = worldName,
				CharacterFile = (((profile != null) ? profile.GetFilename() : null) ?? ""),
				CharacterName = (((profile != null) ? profile.GetName() : null) ?? "")
			};
			Plugin.Log.LogDebug((object)("Continue candidate: world " + worldName));
		}

		public unsafe static void CaptureServer(ServerJoinData server, PlayerProfile profile)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			string text;
			try
			{
				text = MultiBackendMatchmaking.GetServerName(server);
			}
			catch
			{
				text = ((object)(*(ServerJoinData*)(&server))/*cast due to .constrained prefix*/).ToString();
			}
			_candidate = new Candidate
			{
				Kind = Kind.Server,
				Server = server,
				ServerName = (text ?? ""),
				CharacterFile = (((profile != null) ? profile.GetFilename() : null) ?? ""),
				CharacterName = (((profile != null) ? profile.GetName() : null) ?? "")
			};
			Plugin.Log.LogDebug((object)("Continue candidate: server " + text));
		}

		public static void CommitCandidate()
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			Candidate candidate = _candidate;
			_candidate = null;
			if (candidate != null)
			{
				_characterFile.Value = candidate.CharacterFile;
				_characterName.Value = candidate.CharacterName;
				if (candidate.Kind == Kind.World)
				{
					_worldName.Value = candidate.WorldName;
					_serverType.Value = (ServerJoinDataType)0;
					_serverAddress.Value = "";
					_serverOwner.Value = "";
					_serverName.Value = "";
				}
				else
				{
					_worldName.Value = "";
					_serverType.Value = candidate.Server.m_type;
					_serverAddress.Value = AddressOf(candidate.Server);
					_serverOwner.Value = ((object)Unsafe.As<PlatformUserID, PlatformUserID>(ref candidate.Server.m_owner)/*cast due to .constrained prefix*/).ToString() ?? "";
					_serverName.Value = candidate.ServerName;
				}
				_kind.Value = candidate.Kind;
				Plugin.Log.LogInfo((object)("Continue will rejoin " + Describe()));
			}
		}

		private static string AddressOf(ServerJoinData server)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected I4, but got Unknown
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			ServerJoinDataType type = server.m_type;
			switch (type - 1)
			{
			case 0:
			{
				ulong steamID = ((ServerJoinData)(ref server)).SteamUser.m_joinUserID.m_SteamID;
				return steamID.ToString();
			}
			case 1:
				return ((ServerJoinData)(ref server)).PlayFabUser.m_remotePlayerId ?? "";
			case 2:
			{
				ServerJoinDataDedicated dedicated = ((ServerJoinData)(ref server)).Dedicated;
				string host = ((ServerJoinDataDedicated)(ref dedicated)).m_host;
				ushort port = ((ServerJoinData)(ref server)).Dedicated.m_port;
				return host + ":" + port;
			}
			default:
				return "";
			}
		}

		public static bool TryGetServer(out ServerJoinData server)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Expected I4, but got Unknown
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			server = ServerJoinData.None;
			if (_kind.Value != Kind.Server || string.IsNullOrEmpty(_serverAddress.Value))
			{
				return false;
			}
			try
			{
				ServerJoinDataType value = _serverType.Value;
				switch (value - 1)
				{
				case 0:
				{
					if (!ulong.TryParse(_serverAddress.Value, out var result) || result == 0L)
					{
						return false;
					}
					server = new ServerJoinData(new ServerJoinDataSteamUser(result));
					return true;
				}
				case 1:
				{
					ServerJoinDataPlayFabUser val = default(ServerJoinDataPlayFabUser);
					((ServerJoinDataPlayFabUser)(ref val))..ctor(_serverAddress.Value);
					server = (string.IsNullOrEmpty(_serverOwner.Value) ? new ServerJoinData(val) : new ServerJoinData(val, new PlatformUserID(_serverOwner.Value)));
					return true;
				}
				case 2:
					server = new ServerJoinData(new ServerJoinDataDedicated(_serverAddress.Value));
					return ((ServerJoinData)(ref server)).IsValid;
				default:
					return false;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not rebuild the remembered server: " + ex.Message));
				return false;
			}
		}

		public static string Describe()
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Invalid comparison between Unknown and I4
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Invalid comparison between Unknown and I4
			string text = (string.IsNullOrEmpty(_characterName.Value) ? "" : (" as " + _characterName.Value));
			switch (_kind.Value)
			{
			case Kind.World:
				return "Start world \"" + _worldName.Value + "\" (local)" + text;
			case Kind.Server:
			{
				string value = _serverAddress.Value;
				string text2 = (((int)_serverType.Value == 3) ? "dedicated" : (((int)_serverType.Value == 1) ? "Steam host" : (((int)_serverType.Value == 2) ? "crossplay host" : "server")));
				string text3 = (string.IsNullOrEmpty(_serverName.Value) ? value : _serverName.Value);
				return "Rejoin \"" + text3 + "\"\n" + text2 + ": " + value + text;
			}
			default:
				return "Nothing to continue yet. Join a world or server and it will be remembered here.";
			}
		}
	}
	[HarmonyPatch]
	internal static class LoginPatches
	{
		private static readonly MethodInfo OnPasswordEntered = AccessTools.Method(typeof(ZNet), "OnPasswordEntered", (Type[])null, (Type[])null);

		private static string _pendingKey;

		private static string _pendingPassword;

		private static string _pendingName;

		private static string _autoKey;

		private static string _autoName;

		private static void ClearPending()
		{
			_pendingKey = null;
			_pendingPassword = null;
			_pendingName = null;
		}

		[HarmonyPatch(typeof(ZNet), "RPC_ClientHandshake")]
		[HarmonyPostfix]
		private static void AfterClientHandshake(ZNet __instance, bool needPassword)
		{
			ClearPending();
			_autoKey = null;
			_autoName = null;
			if (!needPassword || !Plugin.AutoLogin.Value || !Plugin.Store.IsAvailable || FejdStartup.ServerPassword != null)
			{
				return;
			}
			if (OnPasswordEntered == null)
			{
				Plugin.Log.LogError((object)"ZNet.OnPasswordEntered not found; the game may have changed. Auto-login disabled.");
				return;
			}
			string text = ServerIdentity.CurrentKey();
			if (Plugin.Store.TryRead(text, out var secret))
			{
				_autoKey = text;
				_autoName = ServerIdentity.CurrentDisplayName();
				Plugin.Log.LogInfo((object)("Sending saved password for " + _autoName));
				OnPasswordEntered.Invoke(__instance, new object[1] { secret });
			}
		}

		[HarmonyPatch(typeof(ZNet), "OnPasswordEntered")]
		[HarmonyPrefix]
		private static void BeforePasswordEntered(string pwd)
		{
			if (_autoKey == null && Plugin.RememberPasswords.Value && Plugin.Store.IsAvailable && !string.IsNullOrEmpty(pwd) && (FejdStartup.ServerPassword == null || !(pwd == FejdStartup.ServerPassword)))
			{
				_pendingKey = ServerIdentity.CurrentKey();
				_pendingPassword = pwd;
				_pendingName = ServerIdentity.CurrentDisplayName();
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
		[HarmonyPostfix]
		private static void AfterPeerInfo(ZNet __instance)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			if (__instance.IsServer() || (int)ZNet.GetConnectionStatus() != 2)
			{
				return;
			}
			if (_autoKey != null)
			{
				Notices.Queue("Keyring: logged in to " + _autoName + " with the saved password.");
				_autoKey = null;
				_autoName = null;
			}
			if (_pendingKey != null)
			{
				if (Plugin.Store.Write(_pendingKey, _pendingPassword, _pendingName))
				{
					Plugin.Log.LogInfo((object)("Saved password for " + _pendingName + " to " + Plugin.Store.Description));
					Notices.Queue("Keyring: password for " + _pendingName + " saved to " + Plugin.Store.Description + ".");
				}
				else
				{
					Plugin.Log.LogWarning((object)("Could not save the password for " + _pendingName));
				}
				ClearPending();
			}
		}

		[HarmonyPatch(typeof(ZNet), "RPC_Error")]
		[HarmonyPostfix]
		private static void AfterError(int error)
		{
			if (error == 6 && _autoKey != null)
			{
				if (Plugin.ForgetRejected.Value)
				{
					Plugin.Store.Delete(_autoKey);
					Plugin.Log.LogWarning((object)("The saved password for " + _autoName + " was rejected and has been forgotten. Connect again to enter the new one."));
				}
				else
				{
					Plugin.Log.LogWarning((object)("The saved password for " + _autoName + " was rejected. Use 'keyring forget' in the console to remove it."));
				}
			}
			ClearPending();
			_autoKey = null;
			_autoName = null;
		}
	}
	[HarmonyPatch]
	internal static class Notices
	{
		private static readonly Queue<string> Pending = new Queue<string>();

		public static void Queue(string text)
		{
			if (Plugin.ShowMessages.Value)
			{
				Pending.Enqueue(text);
			}
		}

		[HarmonyPatch(typeof(Player), "OnSpawned")]
		[HarmonyPostfix]
		private static void AfterSpawned()
		{
			if (Pending.Count == 0)
			{
				return;
			}
			MessageHud instance = MessageHud.instance;
			while (Pending.Count > 0)
			{
				string text = Pending.Dequeue();
				if ((Object)(object)instance != (Object)null)
				{
					instance.ShowMessage((MessageType)1, text, 0, (Sprite)null, false, true);
				}
			}
		}
	}
	[BepInPlugin("com.puhpuhburr.keyring", "Keyring", "0.2.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "com.puhpuhburr.keyring";

		public const string NAME = "Keyring";

		public const string VERSION = "0.2.1";

		internal static ManualLogSource Log;

		internal static ICredentialStore Store;

		internal static ConfigEntry<bool> RememberPasswords;

		internal static ConfigEntry<bool> AutoLogin;

		internal static ConfigEntry<bool> ForgetRejected;

		internal static ConfigEntry<bool> ShowMessages;

		internal static ConfigEntry<bool> AllowWineStore;

		internal static ConfigEntry<bool> ShowContinue;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			RememberPasswords = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "RememberPasswords", true, "Save the password after a server accepts it. Passwords go to the operating system's credential store, never to this config file.");
			AutoLogin = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "AutoLogin", true, "When a server asks for a password and one is saved for it, send it instead of showing the password dialog.");
			ForgetRejected = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "ForgetRejected", true, "If a saved password is rejected by the server (it was changed), delete it so the dialog is shown on the next attempt.");
			ShowMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "ShowMessages", true, "Show a short on-screen message after spawning when a password was saved or used.");
			AllowWineStore = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Advanced", "AllowWineStore", false, "Under Wine/Proton the Windows credential API is emulated and stores secrets with weak obfuscation. Keyring refuses to save there unless this is true. Has no effect on real Windows.");
			ShowContinue = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Continue", "ShowContinueButton", true, "Add a Continue entry at the top of the main menu that rejoins the last world or server you played on. Hover it to see what it would join.");
			LastSession.Bind(((BaseUnityPlugin)this).Config);
			Store = CredentialStoreFactory.Create();
			Log.LogInfo((object)("Credential store: " + Store.Description));
			_harmony = new Harmony("com.puhpuhburr.keyring");
			_harmony.PatchAll(typeof(Plugin).Assembly);
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class ServerIdentity
	{
		public static string CurrentKey()
		{
			return ZNet.GetServerString(true);
		}

		public static string CurrentDisplayName()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				FejdStartup instance = FejdStartup.instance;
				if ((Object)(object)instance != (Object)null && instance.HasServerToJoin())
				{
					ServerJoinData serverToJoin = instance.GetServerToJoin();
					string text = default(string);
					if (((ServerJoinData)(ref serverToJoin)).IsValid && MultiBackendMatchmaking.TryGetServerName(serverToJoin, ref text) && !string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			catch
			{
			}
			return ZNet.GetServerString(false);
		}
	}
	internal sealed class WindowsCredentialStore : ICredentialStore
	{
		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
		private struct CREDENTIAL
		{
			public uint Flags;

			public uint Type;

			public IntPtr TargetName;

			public IntPtr Comment;

			public FILETIME LastWritten;

			public uint CredentialBlobSize;

			public IntPtr CredentialBlob;

			public uint Persist;

			public uint AttributeCount;

			public IntPtr Attributes;

			public IntPtr TargetAlias;

			public IntPtr UserName;
		}

		private const string TargetPrefix = "Valheim/Keyring/";

		private const string UserName = "valheim";

		private const uint CRED_TYPE_GENERIC = 1u;

		private const uint CRED_PERSIST_LOCAL_MACHINE = 2u;

		private const uint CRED_MAX_CREDENTIAL_BLOB_SIZE = 2560u;

		private const int ERROR_NOT_FOUND = 1168;

		public string Description => "Windows Credential Manager (DPAPI, current user)";

		public bool IsAvailable => true;

		public bool SelfTest(out string error)
		{
			try
			{
				if (!CredRead("Valheim/Keyring/\u0001selftest", 1u, 0u, out var credential))
				{
					int lastWin32Error = Marshal.GetLastWin32Error();
					if (lastWin32Error != 1168)
					{
						error = new Win32Exception(lastWin32Error).Message;
						return false;
					}
				}
				else
				{
					CredFree(credential);
				}
				error = null;
				return true;
			}
			catch (Exception ex)
			{
				error = ex.GetType().Name + ": " + ex.Message;
				return false;
			}
		}

		public bool TryRead(string key, out string secret)
		{
			secret = null;
			IntPtr credential = IntPtr.Zero;
			try
			{
				if (!CredRead("Valheim/Keyring/" + key, 1u, 0u, out credential))
				{
					int lastWin32Error = Marshal.GetLastWin32Error();
					if (lastWin32Error != 1168)
					{
						Plugin.Log.LogWarning((object)("CredRead failed: " + new Win32Exception(lastWin32Error).Message));
					}
					return false;
				}
				CREDENTIAL cREDENTIAL = Marshal.PtrToStructure<CREDENTIAL>(credential);
				if (cREDENTIAL.CredentialBlob == IntPtr.Zero || cREDENTIAL.CredentialBlobSize == 0)
				{
					return false;
				}
				byte[] array = new byte[cREDENTIAL.CredentialBlobSize];
				Marshal.Copy(cREDENTIAL.CredentialBlob, array, 0, array.Length);
				secret = Encoding.Unicode.GetString(array);
				Array.Clear(array, 0, array.Length);
				return secret.Length > 0;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Reading credential failed: " + ex.Message));
				return false;
			}
			finally
			{
				if (credential != IntPtr.Zero)
				{
					CredFree(credential);
				}
			}
		}

		public bool Write(string key, string secret, string comment)
		{
			byte[] bytes = Encoding.Unicode.GetBytes(secret ?? "");
			if (bytes.Length == 0 || (long)bytes.Length > 2560L)
			{
				Plugin.Log.LogWarning((object)"Password is empty or too long to store.");
				return false;
			}
			IntPtr intPtr = IntPtr.Zero;
			IntPtr intPtr2 = IntPtr.Zero;
			IntPtr intPtr3 = IntPtr.Zero;
			IntPtr intPtr4 = IntPtr.Zero;
			try
			{
				intPtr = Marshal.StringToCoTaskMemUni("Valheim/Keyring/" + key);
				intPtr2 = Marshal.StringToCoTaskMemUni("valheim");
				intPtr3 = Marshal.StringToCoTaskMemUni(Truncate(comment ?? "", 256));
				intPtr4 = Marshal.AllocCoTaskMem(bytes.Length);
				Marshal.Copy(bytes, 0, intPtr4, bytes.Length);
				CREDENTIAL credential = new CREDENTIAL
				{
					Flags = 0u,
					Type = 1u,
					TargetName = intPtr,
					Comment = intPtr3,
					CredentialBlobSize = (uint)bytes.Length,
					CredentialBlob = intPtr4,
					Persist = 2u,
					AttributeCount = 0u,
					Attributes = IntPtr.Zero,
					TargetAlias = IntPtr.Zero,
					UserName = intPtr2
				};
				if (!CredWrite(ref credential, 0u))
				{
					int lastWin32Error = Marshal.GetLastWin32Error();
					Plugin.Log.LogWarning((object)("CredWrite failed: " + new Win32Exception(lastWin32Error).Message));
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Writing credential failed: " + ex.Message));
				return false;
			}
			finally
			{
				if (intPtr4 != IntPtr.Zero)
				{
					Marshal.Copy(new byte[bytes.Length], 0, intPtr4, bytes.Length);
					Marshal.FreeCoTaskMem(intPtr4);
				}
				Array.Clear(bytes, 0, bytes.Length);
				if (intPtr != IntPtr.Zero)
				{
					Marshal.FreeCoTaskMem(intPtr);
				}
				if (intPtr2 != IntPtr.Zero)
				{
					Marshal.FreeCoTaskMem(intPtr2);
				}
				if (intPtr3 != IntPtr.Zero)
				{
					Marshal.FreeCoTaskMem(intPtr3);
				}
			}
		}

		public bool Delete(string key)
		{
			try
			{
				if (CredDelete("Valheim/Keyring/" + key, 1u, 0u))
				{
					return true;
				}
				int lastWin32Error = Marshal.GetLastWin32Error();
				if (lastWin32Error != 1168)
				{
					Plugin.Log.LogWarning((object)("CredDelete failed: " + new Win32Exception(lastWin32Error).Message));
				}
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Deleting credential failed: " + ex.Message));
				return false;
			}
		}

		public List<StoredEntry> List()
		{
			List<StoredEntry> list = new List<StoredEntry>();
			IntPtr credentials = IntPtr.Zero;
			try
			{
				if (!CredEnumerate("Valheim/Keyring/*", 0u, out var count, out credentials))
				{
					int lastWin32Error = Marshal.GetLastWin32Error();
					if (lastWin32Error != 1168)
					{
						Plugin.Log.LogWarning((object)("CredEnumerate failed: " + new Win32Exception(lastWin32Error).Message));
					}
					return list;
				}
				for (int i = 0; i < count; i++)
				{
					CREDENTIAL cREDENTIAL = Marshal.PtrToStructure<CREDENTIAL>(Marshal.ReadIntPtr(credentials, i * IntPtr.Size));
					if (cREDENTIAL.Type == 1)
					{
						string text = Marshal.PtrToStringUni(cREDENTIAL.TargetName) ?? "";
						if (text.StartsWith("Valheim/Keyring/", StringComparison.Ordinal))
						{
							long num = ((long)cREDENTIAL.LastWritten.dwHighDateTime << 32) | (uint)cREDENTIAL.LastWritten.dwLowDateTime;
							list.Add(new StoredEntry
							{
								Key = text.Substring("Valheim/Keyring/".Length),
								Comment = ((cREDENTIAL.Comment == IntPtr.Zero) ? "" : Marshal.PtrToStringUni(cREDENTIAL.Comment)),
								LastWritten = ((num > 0) ? DateTime.FromFileTimeUtc(num).ToLocalTime() : DateTime.MinValue)
							});
						}
					}
				}
				list.Sort((StoredEntry a, StoredEntry b) => string.Compare(a.Comment, b.Comment, StringComparison.OrdinalIgnoreCase));
				return list;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Listing credentials failed: " + ex.Message));
				return list;
			}
			finally
			{
				if (credentials != IntPtr.Zero)
				{
					CredFree(credentials);
				}
			}
		}

		private static string Truncate(string s, int max)
		{
			if (s.Length > max)
			{
				return s.Substring(0, max);
			}
			return s;
		}

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CredReadW", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CredWriteW", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool CredWrite(ref CREDENTIAL credential, uint flags);

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CredDeleteW", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool CredDelete(string target, uint type, uint flags);

		[DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "CredEnumerateW", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool CredEnumerate(string filter, uint flags, out uint count, out IntPtr credentials);

		[DllImport("advapi32.dll")]
		private static extern void CredFree(IntPtr buffer);
	}
}